]> source.dussan.org Git - vaadin-framework.git/commitdiff
Merge branch 'master' into feature/mavenize
authorelmot <elmot@vaadin.com>
Wed, 13 Apr 2016 12:01:50 +0000 (15:01 +0300)
committerelmot <elmot@vaadin.com>
Wed, 13 Apr 2016 12:32:47 +0000 (15:32 +0300)
Change-Id: Id7db526d07a14ac259cbb50415bbafd2a7c2ab94

37 files changed:
1  2 
all/src/main/templates/release-notes.html
client-compiler/src/main/java/com/vaadin/server/widgetsetutils/metadata/RendererVisitor.java
client/src/main/java/com/vaadin/client/ApplicationConnection.java
client/src/main/java/com/vaadin/client/connectors/JavaScriptRendererConnector.java
client/src/main/java/com/vaadin/client/ui/VDragAndDropWrapper.java
client/src/main/java/com/vaadin/client/ui/VFilterSelect.java
client/src/main/java/com/vaadin/client/ui/draganddropwrapper/DragAndDropWrapperConnector.java
client/src/main/java/com/vaadin/client/ui/table/TableConnector.java
client/src/main/java/com/vaadin/client/widget/escalator/ScrollbarBundle.java
client/src/main/java/com/vaadin/client/widgets/Escalator.java
client/src/main/java/com/vaadin/client/widgets/Grid.java
server/src/main/java/com/vaadin/annotations/PreserveOnRefresh.java
server/src/main/java/com/vaadin/annotations/Push.java
server/src/main/java/com/vaadin/annotations/Theme.java
server/src/main/java/com/vaadin/annotations/Title.java
server/src/main/java/com/vaadin/annotations/VaadinServletConfiguration.java
server/src/main/java/com/vaadin/annotations/Widgetset.java
server/src/main/java/com/vaadin/server/UIProvider.java
server/src/main/java/com/vaadin/server/VaadinPortlet.java
server/src/main/java/com/vaadin/server/communication/ServerRpcHandler.java
server/src/main/java/com/vaadin/server/communication/UidlRequestHandler.java
server/src/main/java/com/vaadin/server/communication/UidlWriter.java
server/src/main/java/com/vaadin/server/widgetsetutils/ClassPathExplorer.java
server/src/main/java/com/vaadin/server/widgetsetutils/WidgetSetBuilder.java
server/src/main/java/com/vaadin/ui/AbstractOrderedLayout.java
server/src/main/java/com/vaadin/ui/AbstractSelect.java
server/src/main/java/com/vaadin/ui/DragAndDropWrapper.java
server/src/main/java/com/vaadin/ui/FormLayout.java
server/src/main/java/com/vaadin/ui/Grid.java
server/src/main/java/com/vaadin/ui/GridLayout.java
server/src/main/java/com/vaadin/ui/Window.java
server/src/main/java/com/vaadin/ui/declarative/DesignAttributeHandler.java
server/src/test/java/com/vaadin/server/UIProviderTest.java
server/src/test/java/com/vaadin/tests/server/component/gridlayout/GridLayoutDeclarativeTest.java
shared/src/main/java/com/vaadin/shared/ui/MarginInfo.java
shared/src/main/java/com/vaadin/shared/ui/draganddropwrapper/DragAndDropWrapperServerRpc.java
uitest/build.xml

index 4286b3ace0f5ae91fd3e05b913250e97e566eea6,0000000000000000000000000000000000000000..fc1d69fd245bb21c70a059529928c46682f5bb7d
mode 100644,000000..100644
--- /dev/null
@@@ -1,549 -1,0 +1,553 @@@
 +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 +<html>
 +<head>
 +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 +<title>Vaadin Framework @version@</title>
 +<link rel="stylesheet" type="text/css" href="css/styles.css" />
 +
 +<!--[if lte IE 6]>
 +              <link rel="stylesheet" type="text/css" href="css/ie.css" />
 +              <![endif]-->
 +<style type="text/css">
 +.nested-list ol {
 +      counter-reset: item
 +}
 +
 +.nested-list li {
 +      display: block
 +}
 +
 +.nested-list li:before {
 +      content: counters(item, ".") ". ";
 +      counter-increment: item
 +}
 +</style>
 +</head>
 +
 +<body>
 +    <div id="header">
 +        <h1>Vaadin &ndash; thinking of U and I</h1>
 +        <div id="version">
 +            <strong>Version @version@</strong>
 +        </div>
 +    </div>
 +    <!-- /header -->
 +
 +    <div id="content">
 +        <p>Version @version@ built on @builddate@.</p>
 +
 +        <h2 id="tableofcontents">Release Notes for Vaadin Framework
 +            @version@</h2>
 +        <ul>
 +            <li><a href="#overview">Overview of Vaadin
 +                    @version@ Release</a></li>
 +            <li><a href="#changelog">Change Log for Vaadin
 +                    @version@</a></li>
 +            <li><a href="#enhancements">Enhancements in Vaadin
 +                    @version-minor@</a></li>
 +            <li><a href="#incompatible">Incompatible or Behavior-altering Changes in
 +                    @version-minor@</a></li>
 +            <li><a href="#knownissues">Known Issues and Limitations</a></li>
 +            <li><a href="#vaadin">Vaadin Installation</a></li>
 +            <li><a href="#package">Package Contents</a></li>
 +            <li><a href="#migrating">Migrating from Vaadin 6</a></li>
 +            <li><a href="#dependencies">Vaadin @version@
 +                    dependencies</a></li>
 +            <li><a href="#upgrading">Upgrading to Vaadin
 +                    @version-minor@</a></li>
 +            <li><a href="#supportedversions">Supported
 +                    Technologies</a></li>
 +            <li><a href="#vaadinontheweb">Vaadin on the Web</a></li>
 +        </ul>
 +
 +        <h2 id="overview">Overview of Vaadin @version@ Release</h2>
 +
 +        <p>
 +            Vaadin @version@ is a
 +<!--            feature release that includes --> 
 +            pre-release for evaluating
 +            a number of new features and bug fixes, as listed in the <a
 +                href="#enhancements">list of enhancements</a> and <a
 +                href="#changelog">change log</a> below.
 +        </p>
 +
 +        <!-- ================================================================ -->
 +        <h3 id="changelog">Change Log for Vaadin @version@</h3>
 +
 +        <p>This release includes the following closed issues:</p>
 +
 +        <table>
 +        @release-notes-tickets@
 +        <tr><td>&nbsp;</td><td></td></tr>
 +        <tr><td class="fv"><span class="vote">Vote</span></td><td colspan="2" class="pad">Enhancements <a href=" https://vaadin.com/support">Vaadin support</a> users have voted for</td></tr>
 +        <tr><td class="bfp"><span class="bfp">Priority</span></td><td colspan="2" class="pad">Defects <a href=" https://vaadin.com/support">Vaadin support</a> users have prioritized</td></tr>
 +        </table>
 +        <br/>
 +        <p>
 +            You can also view the <a
 +                href="http://dev.vaadin.com/query?status=pending-release&status=released&resolution=fixed&milestone=Vaadin+@version@&order=id">list
 +                of the closed issues</a> at the Vaadin developer's site.
 +        </p>
 +
 +        <h2 id="enhancements">Enhancements in Vaadin @version-minor@</h2>
 +
 +        <p>Vaadin @version-minor@ includes many major and minor
 +            enhancements. Below is a list of the most notable changes:</p>
 +
 +        <ul>
 +            <li>New configurable client-server automatic reconnect handling and warning dialogs</li>
 +            <li>Grid columns can be resized by the user</li>
 +            <li>Grid non-modal editor mode and unbuffered datasource binding</li>
 +            <li>More fluent Grid scrolling for touch devices</li>
 +            <li>Better customizability of Grid (data generators for communication, customizable selection models etc.)</li>
 +            <li>Configurable keyboard, touch and mouse navigation support for Grid editor</li>
 +            <li>Declarative HTML serialization enhancements for Vaadin Designer 1.0</li>
 +            <li>Better handling of closing a window with keyboard shortcuts</li>
 +            <li>Column collapsing events for Table</li>
 +            <li>Most Components fire a ContextClickEvent when the user tries to open a context menu.
 +                This event is specialised to contain some extra context for Grid, Table, TreeTable and Tree,
 +                and used by the <a href="https://vaadin.com/addon/vaadin-contextmenu">ContextMenu add-on</a>.</li>
 +            <li>New push transport mode WEBSOCKET_XHR (WebSockets from server to client, XHR from client to
 +                server) which allows setting of cookies, using request scopes with CDI/Spring etc.</li>
 +            <li>Navigator is more extensible and can be used with dependency injection</li>
 +            <li>It is possible to customize the push URL using PushConfiguration.setPushUrl().
 +                This enables implementing some push related add-ons such as push on portals.</li>
 +        </ul>
 +
 +        <p>
 +            For enhancements introduced in Vaadin 7.5, see the <a
 +                href="http://vaadin.com/download/release/7.5/7.5.0/release-notes.html">Release
 +                Notes for Vaadin 7.5.0</a>.
 +        </p>
 +
 +        <h3 id="incompatible">Incompatible or Behavior-altering Changes in @version-minor@</h3>
 +        <ul>
 +            <li>Window.setCloseShortcut() is now deprecated. ESCAPE is no longer a hard-coded default, but rather a soft one, and
 +                can be removed. If the close-shortcut attribute of the v-windowelement is present, it must list all close
 +                shortcuts, including ESCAPE, separated by whitespace. Existing, unchanged code should behave as before.
 +                See ticket <a href="https://dev.vaadin.com/ticket/17383">#17383</a> for more information on the reasoning
 +                behind the change.</li>
 +            <li>Grid SelectionModels are now Extensions. This update removes all selection related variables and API from 
 +                GridConnector, GridState, GridServerRpc and GridClientRpc</li>
 +            <li>StringToEnumConverter now explicitly supports Enum types with custom toString() implementations.
 +                This may affect applications that relied on the undefined behavior in previous versions.</li>
 +            <li>The Section enumeration from client-side of the Grid has been moved to GridConstants</li>
 +            <li>The order in which AttachEvents are fired has been changed. When attaching a component with child components,
 +                events are now fired first for the children and last for the root component. The order is thus the same as
 +                the order in which custom code in overridden Component.attach methods would be executed. Please refer to
 +                ticket <a href="https://dev.vaadin.com/ticket/16348">#16348</a> for more information on this change.</li>
 +            <li>Jsoup library version 1.8.3 is incompatible with the version 1.8.1 used in early 7.6 alphas and 7.5. This might 
 +                cause problems with external libraries compiled against said versions.</li>
 +            <li>Declarative format is now using "vaadin-" as a default prefix instead of the "v-" prefix used in 7.5. 
 +                This default can be changed in deployment configuration.</li>
++            <li>The annotations @PreserveOnRefresh, @Push, @Theme, @Title, @VaadinServletConfiguration and @Widgetset now use
++                @Inherited. The annotation is also looked up in extended interfaces for backwards compatibility.</li>
++            <li>Server-side timings of request processing are only sent to the client when not in production mode. Using the
++                timings in TestBench tests requires the server not to be in production mode.</li>
 +        </ul>
 +        <h3 id="knownissues">Known Issues and Limitations</h3>
 +        <ul>
 +            <li>Vaadin TouchKit versions prior to 4.1.0 do not work with 7.6. Please use version 4.1.0 or above.</li>
 +            <li>Context click events are not generated on iOS devices
 +                (<a href="http://dev.vaadin.com/ticket/19367">#19367</a>)</li>
 +            <li>Drag'n'drop in a Table doesn't work on touch devices running
 +                Internet Explorer (Windows Phone, Surface)
 +                (<a href="http://dev.vaadin.com/ticket/13737">#13737</a>)
 +            </li>
 +            <li><p>It is currently not possible to specify <tt>font-size</tt>
 +                as <tt>em</tt> or <tt>%</tt>, or layout component sizes
 +                with <tt>em</tt> (<a
 +                href="http://dev.vaadin.com/ticket/10634">#10634</a>).</p><p>This 
 +                does not apply to Valo, but using em sizes to size layouts is discouraged, 
 +                because it results in fractional component sizes in many cases, which 
 +                might cause unwanted 1px gaps between components.</p>
 +            </li>
 +            <li>Up-to-date information about configuration needed for push and compatibility issues are available in
 +            <a href="https://vaadin.com/wiki/-/wiki/Main/Configuring+push+for+your+environment">this wiki page.</a>
 +            <br />
 +            The most prominent limitations are:
 +            <ul>
 +                <li>Push is currently not supported in portals (See <a
 +                    href="http://dev.vaadin.com/ticket/11493">#11493</a>)
 +                </li>
 +                <li>>Transport mode WEBSOCKET does not support invalidating the session on Tomcat 7,
 +                     nor setting cookies for the session. The new transport mode WEBSOCKET_XHR can be 
 +                     used to bypass these limitations.
 +                     <br />
 +                     See tickets <a href="https://dev.vaadin.com/ticket/11808">#11808</a> and
 +                     <a href="https://dev.vaadin.com/ticket/11721">#11721</a>
 +                </li>
 +            </ul>
 +            </li>
 +            <li>Google App Engine has some limitations. Please consult Vaadin Wiki:
 +                <a href="https://vaadin.com/wiki/-/wiki/Main/Vaadin+support+for+Google+App+Engine">Vaadin support for Google App Engine</a>
 +            </li>
 +            <li>Grid does not support adding components to cells. Instead light-weight Renderers can be
 +                used to present and edit data.</li>
 +        </ul>
 +
 +        <h2 id="vaadin">Vaadin Installation</h2>
 +
 +        <p>
 +            <b>Vaadin</b> is a Java framework for building modern web
 +            applications that look great, perform well and make you and
 +            your users happy. <b>Vaadin</b> is available under the
 +            Apache License, Version 2.0 (see the
 +            <tt>license.html</tt>
 +            in the Vaadin ZIP or JAR package).
 +        </p>
 +
 +        <p>
 +            The easiest ways to install <b>Vaadin</b> are:
 +        </p>
 +
 +        <ul>
 +            <li><strong>Maven dependency.</strong>If using Maven, define it as a dependency or use
 +                any of the available archetypes to create a new project. More information at 
 +                <a href="https://vaadin.com/maven">https://vaadin.com/maven</a>
 +            </li>
 +
 +            <li><strong>IDE Plugins.</strong>If using Eclipse, use the 
 +                <a href="http://vaadin.com/eclipse">Vaadin Plugin for Eclipse</a>, which automatically 
 +                downloads the Vaadin libraries. For NetBeans 8.0 and 7.4, use the 
 +                <a href="http://plugins.netbeans.org/plugin/50531/vaadin-plug-in-for-netbeans">official Vaadin plugin</a>
 +                that provides Maven based wizards and code completion support for Vaadin development.
 +            </li>
 +        </ul>
 +
 +        <p>
 +            Vaadin Framework is also available as a ZIP package downloadable from <a
 +                href="http://vaadin.com/download">Vaadin Download
 +                page</a>.
 +        </p>
 +
 +        <h3 id="package">Package Contents</h3>
 +
 +        <p>Inside the ZIP installation package you will find:</p>
 +
 +        <ul>
 +            <li>Separate server-side (<tt>vaadin-server</tt>) and
 +                client-side (<tt>vaadin-client</tt>, <tt>vaadin-client-compiler</tt>)
 +                development libraries
 +            </li>
 +            <li>Precompiled widget set (<tt>vaadin-client-compiled</tt>)
 +                for server-side development
 +            </li>
 +            <li>Shared library (<tt>vaadin-shared</tt>) for both
 +                server- and client-side libraries
 +            </li>
 +            <li>Built-in themes (<tt>vaadin-themes</tt>)
 +            </li>
 +            <li>Dependency libraries provided under the <tt>lib/</tt>
 +                folder
 +            </li>
 +        </ul>
 +
 +        <p>
 +            See the
 +            <tt>README.TXT</tt>
 +            in the installation package for detailed information about
 +            the package contents. <a href="http://vaadin.com/book">Book
 +                of Vaadin</a> (for Vaadin 7) gives more detailed
 +            instructions.
 +        </p>
 +
 +        <p>
 +            For server-side development, copy the
 +            <tt>vaadin-server</tt>
 +            ,
 +            <tt>vaadin-client-compiled</tt>
 +            ,
 +            <tt>vaadin-shared</tt>
 +            , and
 +            <tt>vaadin-themes</tt>
 +            from the main folder and the dependencies from the
 +            <tt>lib</tt>
 +            folder to the
 +            <tt>WEB-INF/lib</tt>
 +            folder of your Vaadin project. (The
 +            <tt>vaadin-client-compiled</tt>
 +            is necessary if you do not wish to compile the widget set by
 +            your own, which you need to do if you use almost any add-on
 +            components.)
 +        </p>
 +
 +        <h4 id="package.updates">Updates to the Packaging</h4>
 +        <p>
 +            Since Vaadin 7.2.0, the old vaadin-theme-compiler has been moved into
 +            a separate project and renamed to vaadin-sass-compiler. It is now included
 +            along with the other 3rd party dependencies in the ZIP package.
 +        </p>
 +
 +        <p>
 +            For pure client-side development, you only need the
 +            <tt>vaadin-client</tt>
 +            and
 +            <tt>vaadin-client-compiler</tt>
 +            JARs, which should be put to a non-deployed project library
 +            folder, such as
 +            <tt>lib</tt>
 +            . You also need them if you compile the widget set for any
 +            reason, such as using Vaadin add-ons, or create new
 +            server-side components integrated with client-side widgets.
 +        </p>
 +
 +        <h2 id="migrating">Migrating from Vaadin 6</h2>
 +
 +        <p>
 +            All Vaadin 6 applications need some changes when migrating
 +            to Vaadin 7. The most obvious changes are in the
 +            application/window API and require extending either <b>UI</b>
 +            or <b>UI.LegacyApplication</b> instead of <b>Application</b>.
 +            A detailed list of migration changes are given in the <a
 +                href="https://vaadin.com/wiki/-/wiki/Main/Migrating+from+Vaadin+6+to+Vaadin+7">Vaadin
 +                7 Migration Guide</a>.
 +        </p>
 +
 +        <p>Any custom client-side widgets need to be ported to use
 +            the new client-server communication API, or the Vaadin 6
 +            compatibility API.</p>
 +
 +        <p>
 +            Vaadin 6 add-ons (ones that contain widgets) do not work in
 +            Vaadin 7 - please check the add-ons in <a
 +                href="http://vaadin.com/directory/">Vaadin Directory</a>
 +            for Vaadin 7 support.
 +        </p>
 +
 +        <h2 id="dependencies">Vaadin @version@ Dependencies</h2>
 +
 +        <p>When using Maven, Ivy, Gradle, or other dependency
 +            management system, all Vaadin dependencies are downloaded
 +            automatically. This is also the case when using the Vaadin
 +            Plugin for Eclipse.</p>
 +
 +        <p>
 +            The Vaadin ZIP installation package includes the
 +            dependencies in the
 +            <tt>lib</tt>
 +            subfolder. These need to be copied to the
 +            <tt>WEB-INF/lib</tt>
 +            folder of the web application that uses Vaadin.
 +        </p>
 +
 +        <p>
 +            The dependencies are listed in the <a href="license.html">Licensing
 +                description</a>. Some are explicit dependencies packaged and
 +            distributed as separate JARs, while some are included inside
 +            other libraries.
 +        </p>
 +
 +        <h3>Bean Validation</h3>
 +
 +        <p>
 +            If you use the bean validation feature in Vaadin 7, you need
 +            a Bean Validation API implementation. You need to install
 +            the implementation JAR in the
 +            <tt>WEB-INF/lib</tt>
 +            directory of the web application that uses validation.
 +        </p>
 +
 +        <h2 id="upgrading">Upgrading to Vaadin @version-minor@</h2>
 +
 +        <p>When upgrading from an earlier Vaadin version, you must:
 +        </p>
 +
 +        <ul>
 +            <li>Recompile your classes using the new Vaadin
 +                version. Binary compatibility is only guaranteed for
 +                maintenance releases of Vaadin.</li>
 +
 +            <li>Unless using the precompiled widget set, recompile
 +                your widget set using the new Vaadin version.</li>
 +        </ul>
 +
 +        <p>Remember also to refresh the project in your IDE to
 +            ensure that the new version of everything is in use.</p>
 +
 +        <p>
 +            By using the "
 +            <tt>?debug</tt>
 +            " URL parameter, you can verify that the version of the
 +            servlet, the theme, and the widget set all match.
 +        </p>
 +
 +        <p>
 +            <b>Eclipse</b> users should always check if there is a new
 +            version of the Eclipse Plug-in available. The Eclipse
 +            Plug-in can be used to update the Vaadin version in the
 +            project (Project properties &raquo; Vaadin).
 +        </p>
 +
 +        <p>
 +            <b>Maven</b> users should update the Vaadin dependency
 +            version in the
 +            <tt>pom.xml</tt>
 +            unless it is defined as
 +            <tt>LATEST</tt>
 +            . You must also ensure that the GWT dependency uses the
 +            correct version and recompile your project and your widget
 +            set.
 +        </p>
 +
 +        <p>
 +            <b>Liferay and other portal</b> users must install the
 +            Vaadin libraries in
 +            <t>ROOT/WEB-INF/lib/</b> in the portal (and remove a
 +            possibly obsolete older <tt>vaadin.jar</tt>). Additionally,
 +            the contents of the <tt>vaadin-client-compiled</tt> and <tt>vaadin-themes</tt>
 +            must be extracted to the <tt>ROOT/html/VAADIN</tt> directory
 +            in the Liferay installation. If your portal uses custom
 +            widgets, you can use <a
 +                href="http://vaadin.com/directory#addon/liferay-control-panel-plugin-for-vaadin:vaadin">
 +                    Liferay Control Panel for Vaadin</a> for easy widget set compilation.</t>
 +        </p>
 +
 +        <h2 id="supportedversions">Supported Technologies</h2>
 +
 +        <p>
 +            Vaadin 7 is compatible with <b>Java 6</b> and newer. Vaadin
 +            7 is especially supported on the following <b>operating
 +                systems</b>:
 +        </p>
 +
 +        <ul>
 +            <li>Windows</li>
 +            <li>Linux</li>
 +            <li>Mac OS X</li>
 +        </ul>
 +
 +        <p>
 +            Vaadin 7 requires <b>Java Servlet API 2.4</b> but also
 +            supports later versions and should work with any Java
 +            application server that conforms to the standard. The
 +            following <b>application servers</b> are supported:
 +        </p>
 +
 +        <ul>
 +            <li>Apache Tomcat 5-8</li>
 +            <li>Apache TomEE 1</li>
 +            <li>Oracle WebLogic Server 10.3-12</li>
 +            <li>IBM WebSphere Application Server 7-8</li>
 +            <li>JBoss Application Server 4-7</li>
 +            <li>Wildfly 8-9</li>
 +            <li>Jetty 5-9</li>
 +            <li>Glassfish 2-4</li>
 +        </ul>
 +
 +        <p>
 +            Vaadin 7 supports the JSR-286 Portlet specification and all
 +            portals that implement the specification should work. The
 +            following <b>portals</b> are supported:
 +        </p>
 +
 +        <ul>
 +            <li>Liferay Portal 5.2-6</li>
 +            <li>GateIn Portal 3</li>
 +            <li>eXo Platform 3</li>
 +            <li>IBM WebSphere Portal 8</li>
 +        </ul>
 +
 +        <p>
 +            Vaadin also supports <b>Google App Engine</b>.
 +        </p>
 +
 +        <p>
 +            Vaadin @version@ supports the following <b>desktop browsers</b>:
 +        </p>
 +
 +        <ul>
 +            <li>Mozilla Firefox 18-43</li>
 +            <li>Mozilla Firefox 17 ESR, 24 ESR, 31 ESR, 38 ESR</li>
 +            <li>Internet Explorer 8-11, Edge</li>
 +            <li>Safari 6-8</li>
 +            <li>Opera 16-34</li>
 +            <li>Google Chrome 23-47</li>
 +        </ul>
 +
 +        <p>
 +            Additionally, Vaadin supports the built-in browsers in the
 +            following <b>mobile operating systems</b>:
 +        </p>
 +
 +        <ul>
 +            <li>iOS 5-9</li>
 +            <li>Android 2.3-5</li>
 +            <li>Windows Phone 8</li>
 +        </ul>
 +
 +        <p>Vaadin SQL Container supports the following databases:</p>
 +        <ul>
 +            <li>HSQLDB</li>
 +            <li>MySQL</li>
 +            <li>MSSQL</li>
 +            <li>Oracle</li>
 +            <li>PostgreSQL</li>
 +        </ul>
 +
 +        <h2 id="vaadinontheweb">Vaadin on the Web</h2>
 +
 +        <ul>
 +            <li><a href="http://vaadin.com">vaadin.com - The
 +                    developer portal containing everything you need to
 +                    know about Vaadin</a></li>
 +            <li><a href="http://vaadin.com/demo">vaadin.com/demo
 +                    - A collection of demos for Vaadin</a></li>
 +            <li><a href="http://vaadin.com/learn">vaadin.com/learn
 +                    - Getting started with Vaadin</a></li>
 +            <li><a href="http://vaadin.com/forum">vaadin.com/forum
 +                    - Forums for Vaadin related discussions</a></li>
 +            <li><a href="http://vaadin.com/book">vaadin.com/book
 +                    - Book of Vaadin - everything you need to know about
 +                    Vaadin</a></li>
 +            <li><a href="http://vaadin.com/api">vaadin.com/api
 +                    - Online javadocs</a></li>
 +            <li><a href="http://vaadin.com/directory">vaadin.com/directory
 +                    - Add-ons for Vaadin</a></li>
 +
 +            <li><a href="http://vaadin.com/pro-tools">vaadin.com/pro-tools
 +                    - Commercial tools for Vaadin development</a></li>
 +            <li><a href="http://vaadin.com/support">vaadin.com/support
 +                    - Commercial support for Vaadin development </a></li>
 +            <li><a href="http://vaadin.com/services">vaadin.com/services
 +                    - Expert services for Vaadin</a></li>
 +            <li><a href="http://vaadin.com/company">vaadin.com/company
 +                    - Information about the company behind Vaadin</a></li>
 +
 +            <li><a href="http://dev.vaadin.com">dev.vaadin.com
 +                    - Bug tracker</a></li>
 +
 +            <li><a
 +                href="http://dev.vaadin.com/wiki/Vaadin/Development/StartingVaadin7Development">How
 +                    to get the source code of Vaadin</a></li>
 +        </ul>
 +
 +    </div>
 +    <!-- /content-->
 +
 +    <div id="footer">
 +        <span class="slogan"><strong>vaadin <em>}></em>
 +        </strong> thinking of U and I<span> <a href="#top">&uarr; Back
 +                    to top</a>
 +    </div>
 +    <!-- /footer -->
 +</body>
 +</html>
 +
 +<!-- Keep this comment at the end of the file
 +Local variables:
 +mode: xml
 +sgml-omittag:nil
 +sgml-shorttag:nil
 +sgml-namecase-general:nil
 +sgml-general-insert-case:lower
 +sgml-minimize-attributes:nil
 +sgml-always-quote-attributes:t
 +sgml-indent-step:2
 +sgml-indent-data:t
 +sgml-parent-document:nil
 +sgml-exposed-tags:nil
 +sgml-local-catalogs:("/etc/sgml/catalog" "/usr/share/xemacs21/xemacs-packages/etc/psgml-dtds/CATALOG")
 +sgml-local-ecat-files:("ECAT" "~/sgml/ECAT" "/usr/share/sgml/ECAT" "/usr/local/share/sgml/ECAT" "/usr/local/lib/sgml/ECAT")
 +End:
 +-->
index 2e54d00aabf64e288f4ccbefa4ac46806466cd10,0000000000000000000000000000000000000000..8b645aa492e26f6146171510110613c1117da932
mode 100644,000000..100644
--- /dev/null
@@@ -1,119 -1,0 +1,143 @@@
-         bundle.setNeedsSerialize(presentationType);
 +/*
 + * Copyright 2000-2014 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.server.widgetsetutils.metadata;
 +
 +import com.google.gwt.core.ext.TreeLogger;
 +import com.google.gwt.core.ext.TreeLogger.Type;
 +import com.google.gwt.core.ext.UnableToCompleteException;
 +import com.google.gwt.core.ext.typeinfo.JClassType;
 +import com.google.gwt.core.ext.typeinfo.JMethod;
 +import com.google.gwt.core.ext.typeinfo.JParameterizedType;
 +import com.google.gwt.core.ext.typeinfo.JType;
++import com.google.gwt.core.ext.typeinfo.NotFoundException;
 +import com.vaadin.client.connectors.AbstractRendererConnector;
 +
++import elemental.json.JsonValue;
++
 +/**
 + * Generates type data for renderer connectors.
 + * <ul>
 + * <li>Stores the return type of the overridden
 + * {@link AbstractRendererConnector#getRenderer() getRenderer} method to enable
 + * automatic creation of an instance of the proper renderer type.
 + * <li>Stores the presentation type of the connector to enable the
 + * {@link AbstractRendererConnector#decode(elemental.json.JsonValue) decode}
 + * method to work without having to implement a "getPresentationType" method.
 + * </ul>
 + * 
 + * @see WidgetInitVisitor
 + * 
 + * @since 7.4
 + * @author Vaadin Ltd
 + */
 +public class RendererVisitor extends TypeVisitor {
 +
 +    @Override
 +    public void visitConnector(TreeLogger logger, JClassType type,
 +            ConnectorBundle bundle) throws UnableToCompleteException {
 +        if (ConnectorBundle.isConnectedRendererConnector(type)) {
 +            doRendererType(logger, type, bundle);
 +            doPresentationType(logger, type, bundle);
 +        }
 +    }
 +
 +    private static void doRendererType(TreeLogger logger, JClassType type,
 +            ConnectorBundle bundle) throws UnableToCompleteException {
 +        // The class in which createRenderer is implemented
 +        JClassType createRendererClass = ConnectorBundle.findInheritedMethod(
 +                type, "createRenderer").getEnclosingType();
 +
 +        // Needs GWT constructor if createRenderer is not overridden
 +        if (createRendererClass.getQualifiedSourceName().equals(
 +                AbstractRendererConnector.class.getCanonicalName())) {
 +
 +            JMethod getRenderer = ConnectorBundle.findInheritedMethod(type,
 +                    "getRenderer");
 +            if (getRenderer.getEnclosingType().getQualifiedSourceName()
 +                    .equals(AbstractRendererConnector.class.getCanonicalName())) {
 +                logger.log(Type.ERROR, type.getQualifiedSourceName()
 +                        + " must override either createRenderer or getRenderer");
 +                throw new UnableToCompleteException();
 +            }
 +            JClassType rendererType = getRenderer.getReturnType().isClass();
 +
 +            bundle.setNeedsGwtConstructor(rendererType);
 +
 +            // Also needs renderer type to find the right GWT constructor
 +            bundle.setNeedsReturnType(type, getRenderer);
 +
 +            logger.log(Type.DEBUG, "Renderer type of " + type + " is "
 +                    + rendererType);
 +        }
 +    }
 +
 +    private void doPresentationType(TreeLogger logger, JClassType type,
 +            ConnectorBundle bundle) throws UnableToCompleteException {
 +        JType presentationType = getPresentationType(type, logger);
 +        bundle.setPresentationType(type, presentationType);
 +
++        if (!hasCustomDecodeMethod(type, logger)) {
++            bundle.setNeedsSerialize(presentationType);
++        }
 +
 +        logger.log(Type.DEBUG, "Presentation type of " + type + " is "
 +                + presentationType);
 +    }
 +
++    private static boolean hasCustomDecodeMethod(JClassType type,
++            TreeLogger logger) throws UnableToCompleteException {
++        try {
++            JMethod decodeMethod = ConnectorBundle.findInheritedMethod(type,
++                    "decode",
++                    type.getOracle().getType(JsonValue.class.getName()));
++            if (decodeMethod == null) {
++                throw new NotFoundException();
++            }
++
++            return !decodeMethod.getEnclosingType().getQualifiedSourceName()
++                    .equals(AbstractRendererConnector.class.getName());
++        } catch (NotFoundException e) {
++            logger.log(Type.ERROR, "Can't find decode method for renderer "
++                    + type, e);
++            throw new UnableToCompleteException();
++        }
++    }
++
 +    private static JType getPresentationType(JClassType type, TreeLogger logger)
 +            throws UnableToCompleteException {
 +        JClassType originalType = type;
 +        while (type != null) {
 +            if (type.getQualifiedBinaryName().equals(
 +                    AbstractRendererConnector.class.getName())) {
 +                JParameterizedType parameterized = type.isParameterized();
 +                if (parameterized == null) {
 +                    logger.log(
 +                            Type.ERROR,
 +                            type.getQualifiedSourceName()
 +                                    + " must define the generic parameter of the inherited "
 +                                    + AbstractRendererConnector.class
 +                                            .getSimpleName());
 +                    throw new UnableToCompleteException();
 +                }
 +                return parameterized.getTypeArgs()[0];
 +            }
 +            type = type.getSuperclass();
 +        }
 +        throw new IllegalArgumentException("The type "
 +                + originalType.getQualifiedSourceName() + " does not extend "
 +                + AbstractRendererConnector.class.getName());
 +    }
 +}
index ce55c13ce5fe370e99df234641c7da3897a57c81,0000000000000000000000000000000000000000..97ddd147642474deda077a58d67052df31e4a074
mode 100644,000000..100644
--- /dev/null
@@@ -1,1642 -1,0 +1,1645 @@@
- import com.vaadin.client.ApplicationConnection.ApplicationStoppedEvent;
 +/*
 + * Copyright 2000-2014 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 java.util.HashMap;
 +import java.util.Map;
 +import java.util.logging.Logger;
 +
 +import com.google.gwt.aria.client.LiveValue;
 +import com.google.gwt.aria.client.RelevantValue;
 +import com.google.gwt.aria.client.Roles;
 +import com.google.gwt.core.client.Duration;
 +import com.google.gwt.core.client.GWT;
 +import com.google.gwt.core.client.JavaScriptObject;
 +import com.google.gwt.core.client.JsArrayString;
 +import com.google.gwt.core.client.Scheduler;
 +import com.google.gwt.dom.client.Element;
 +import com.google.gwt.event.shared.EventBus;
 +import com.google.gwt.event.shared.EventHandler;
 +import com.google.gwt.event.shared.GwtEvent;
 +import com.google.gwt.event.shared.HandlerRegistration;
 +import com.google.gwt.event.shared.HasHandlers;
 +import com.google.gwt.event.shared.SimpleEventBus;
 +import com.google.gwt.http.client.URL;
 +import com.google.gwt.user.client.Command;
 +import com.google.gwt.user.client.DOM;
 +import com.google.gwt.user.client.Timer;
 +import com.google.gwt.user.client.ui.HasWidgets;
 +import com.google.gwt.user.client.ui.Widget;
 +import com.vaadin.client.ApplicationConfiguration.ErrorMessage;
-             var smh = ap.@com.vaadin.client.ApplicationConnection::getMessageHandler();
 +import com.vaadin.client.ResourceLoader.ResourceLoadEvent;
 +import com.vaadin.client.ResourceLoader.ResourceLoadListener;
 +import com.vaadin.client.communication.ConnectionStateHandler;
 +import com.vaadin.client.communication.Heartbeat;
 +import com.vaadin.client.communication.MessageHandler;
 +import com.vaadin.client.communication.MessageSender;
 +import com.vaadin.client.communication.RpcManager;
 +import com.vaadin.client.communication.ServerRpcQueue;
 +import com.vaadin.client.componentlocator.ComponentLocator;
 +import com.vaadin.client.metadata.ConnectorBundleLoader;
 +import com.vaadin.client.ui.AbstractComponentConnector;
 +import com.vaadin.client.ui.FontIcon;
 +import com.vaadin.client.ui.Icon;
 +import com.vaadin.client.ui.ImageIcon;
 +import com.vaadin.client.ui.VContextMenu;
 +import com.vaadin.client.ui.VNotification;
 +import com.vaadin.client.ui.VOverlay;
 +import com.vaadin.client.ui.ui.UIConnector;
 +import com.vaadin.shared.VaadinUriResolver;
 +import com.vaadin.shared.Version;
 +import com.vaadin.shared.communication.LegacyChangeVariablesInvocation;
 +import com.vaadin.shared.util.SharedUtil;
 +
 +/**
 + * This is the client side communication "engine", managing client-server
 + * communication with its server side counterpart
 + * com.vaadin.server.VaadinService.
 + * 
 + * Client-side connectors receive updates from the corresponding server-side
 + * connector (typically component) as state updates or RPC calls. The connector
 + * has the possibility to communicate back with its server side counter part
 + * through RPC calls.
 + * 
 + * TODO document better
 + * 
 + * Entry point classes (widgetsets) define <code>onModuleLoad()</code>.
 + */
 +public class ApplicationConnection implements HasHandlers {
 +
 +    @Deprecated
 +    public static final String MODIFIED_CLASSNAME = StyleConstants.MODIFIED;
 +
 +    @Deprecated
 +    public static final String DISABLED_CLASSNAME = StyleConstants.DISABLED;
 +
 +    @Deprecated
 +    public static final String REQUIRED_CLASSNAME = StyleConstants.REQUIRED;
 +
 +    @Deprecated
 +    public static final String REQUIRED_CLASSNAME_EXT = StyleConstants.REQUIRED_EXT;
 +
 +    @Deprecated
 +    public static final String ERROR_CLASSNAME_EXT = StyleConstants.ERROR_EXT;
 +
 +    /**
 +     * A string that, if found in a non-JSON response to a UIDL request, will
 +     * cause the browser to refresh the page. If followed by a colon, optional
 +     * whitespace, and a URI, causes the browser to synchronously load the URI.
 +     * 
 +     * <p>
 +     * This allows, for instance, a servlet filter to redirect the application
 +     * to a custom login page when the session expires. For example:
 +     * </p>
 +     * 
 +     * <pre>
 +     * if (sessionExpired) {
 +     *     response.setHeader(&quot;Content-Type&quot;, &quot;text/html&quot;);
 +     *     response.getWriter().write(
 +     *             myLoginPageHtml + &quot;&lt;!-- Vaadin-Refresh: &quot;
 +     *                     + request.getContextPath() + &quot; --&gt;&quot;);
 +     * }
 +     * </pre>
 +     */
 +    public static final String UIDL_REFRESH_TOKEN = "Vaadin-Refresh";
 +
 +    private final HashMap<String, String> resourcesMap = new HashMap<String, String>();
 +
 +    private WidgetSet widgetSet;
 +
 +    private VContextMenu contextMenu = null;
 +
 +    private final UIConnector uIConnector;
 +
 +    protected boolean cssLoaded = false;
 +
 +    /** Parameters for this application connection loaded from the web-page */
 +    private ApplicationConfiguration configuration;
 +
 +    private final LayoutManager layoutManager;
 +
 +    private final RpcManager rpcManager;
 +
 +    /** Event bus for communication events */
 +    private EventBus eventBus = GWT.create(SimpleEventBus.class);
 +
 +    public enum ApplicationState {
 +        INITIALIZING, RUNNING, TERMINATED;
 +    }
 +
 +    private ApplicationState applicationState = ApplicationState.INITIALIZING;
 +
 +    /**
 +     * The communication handler methods are called at certain points during
 +     * communication with the server. This allows for making add-ons that keep
 +     * track of different aspects of the communication.
 +     */
 +    public interface CommunicationHandler extends EventHandler {
 +        void onRequestStarting(RequestStartingEvent e);
 +
 +        void onResponseHandlingStarted(ResponseHandlingStartedEvent e);
 +
 +        void onResponseHandlingEnded(ResponseHandlingEndedEvent e);
 +    }
 +
 +    public static class RequestStartingEvent extends ApplicationConnectionEvent {
 +
 +        public static Type<CommunicationHandler> TYPE = new Type<CommunicationHandler>();
 +
 +        public RequestStartingEvent(ApplicationConnection connection) {
 +            super(connection);
 +        }
 +
 +        @Override
 +        public Type<CommunicationHandler> getAssociatedType() {
 +            return TYPE;
 +        }
 +
 +        @Override
 +        protected void dispatch(CommunicationHandler handler) {
 +            handler.onRequestStarting(this);
 +        }
 +    }
 +
 +    public static class ResponseHandlingEndedEvent extends
 +            ApplicationConnectionEvent {
 +
 +        public static Type<CommunicationHandler> TYPE = new Type<CommunicationHandler>();
 +
 +        public ResponseHandlingEndedEvent(ApplicationConnection connection) {
 +            super(connection);
 +        }
 +
 +        @Override
 +        public Type<CommunicationHandler> getAssociatedType() {
 +            return TYPE;
 +        }
 +
 +        @Override
 +        protected void dispatch(CommunicationHandler handler) {
 +            handler.onResponseHandlingEnded(this);
 +        }
 +    }
 +
 +    public static abstract class ApplicationConnectionEvent extends
 +            GwtEvent<CommunicationHandler> {
 +
 +        private ApplicationConnection connection;
 +
 +        protected ApplicationConnectionEvent(ApplicationConnection connection) {
 +            this.connection = connection;
 +        }
 +
 +        public ApplicationConnection getConnection() {
 +            return connection;
 +        }
 +
 +    }
 +
 +    public static class ResponseHandlingStartedEvent extends
 +            ApplicationConnectionEvent {
 +
 +        public ResponseHandlingStartedEvent(ApplicationConnection connection) {
 +            super(connection);
 +        }
 +
 +        public static Type<CommunicationHandler> TYPE = new Type<CommunicationHandler>();
 +
 +        @Override
 +        public Type<CommunicationHandler> getAssociatedType() {
 +            return TYPE;
 +        }
 +
 +        @Override
 +        protected void dispatch(CommunicationHandler handler) {
 +            handler.onResponseHandlingStarted(this);
 +        }
 +    }
 +
 +    /**
 +     * Event triggered when a application is stopped by calling
 +     * {@link ApplicationConnection#setApplicationRunning(false)}.
 +     * 
 +     * To listen for the event add a {@link ApplicationStoppedHandler} by
 +     * invoking
 +     * {@link ApplicationConnection#addHandler(ApplicationConnection.ApplicationStoppedEvent.Type, ApplicationStoppedHandler)}
 +     * to the {@link ApplicationConnection}
 +     * 
 +     * @since 7.1.8
 +     * @author Vaadin Ltd
 +     */
 +    public static class ApplicationStoppedEvent extends
 +            GwtEvent<ApplicationStoppedHandler> {
 +
 +        public static Type<ApplicationStoppedHandler> TYPE = new Type<ApplicationStoppedHandler>();
 +
 +        @Override
 +        public Type<ApplicationStoppedHandler> getAssociatedType() {
 +            return TYPE;
 +        }
 +
 +        @Override
 +        protected void dispatch(ApplicationStoppedHandler listener) {
 +            listener.onApplicationStopped(this);
 +        }
 +    }
 +
 +    /**
 +     * Allows custom handling of communication errors.
 +     */
 +    public interface CommunicationErrorHandler {
 +        /**
 +         * Called when a communication error has occurred. Returning
 +         * <code>true</code> from this method suppresses error handling.
 +         * 
 +         * @param details
 +         *            A string describing the error.
 +         * @param statusCode
 +         *            The HTTP status code (e.g. 404, etc).
 +         * @return true if the error reporting should be suppressed, false to
 +         *         perform normal error reporting.
 +         */
 +        public boolean onError(String details, int statusCode);
 +    }
 +
 +    /**
 +     * A listener for listening to application stopped events. The listener can
 +     * be added to a {@link ApplicationConnection} by invoking
 +     * {@link ApplicationConnection#addHandler(ApplicationStoppedEvent.Type, ApplicationStoppedHandler)}
 +     * 
 +     * @since 7.1.8
 +     * @author Vaadin Ltd
 +     */
 +    public interface ApplicationStoppedHandler extends EventHandler {
 +
 +        /**
 +         * Triggered when the {@link ApplicationConnection} marks a previously
 +         * running application as stopped by invoking
 +         * {@link ApplicationConnection#setApplicationRunning(false)}
 +         * 
 +         * @param event
 +         *            the event triggered by the {@link ApplicationConnection}
 +         */
 +        void onApplicationStopped(ApplicationStoppedEvent event);
 +    }
 +
 +    private CommunicationErrorHandler communicationErrorDelegate = null;
 +
 +    private VLoadingIndicator loadingIndicator;
 +
 +    private Heartbeat heartbeat = GWT.create(Heartbeat.class);
 +
 +    private boolean tooltipInitialized = false;
 +
 +    private final VaadinUriResolver uriResolver = new VaadinUriResolver() {
 +        @Override
 +        protected String getVaadinDirUrl() {
 +            return getConfiguration().getVaadinDirUrl();
 +        }
 +
 +        @Override
 +        protected String getServiceUrlParameterName() {
 +            return getConfiguration().getServiceUrlParameterName();
 +        }
 +
 +        @Override
 +        protected String getServiceUrl() {
 +            return getConfiguration().getServiceUrl();
 +        }
 +
 +        @Override
 +        protected String getThemeUri() {
 +            return ApplicationConnection.this.getThemeUri();
 +        }
 +
 +        @Override
 +        protected String encodeQueryStringParameterValue(String queryString) {
 +            return URL.encodeQueryString(queryString);
 +        }
 +    };
 +
 +    public static class MultiStepDuration extends Duration {
 +        private int previousStep = elapsedMillis();
 +
 +        public void logDuration(String message) {
 +            logDuration(message, 0);
 +        }
 +
 +        public void logDuration(String message, int minDuration) {
 +            int currentTime = elapsedMillis();
 +            int stepDuration = currentTime - previousStep;
 +            if (stepDuration >= minDuration) {
 +                getLogger().info(message + ": " + stepDuration + " ms");
 +            }
 +            previousStep = currentTime;
 +        }
 +    }
 +
 +    public ApplicationConnection() {
 +        // Assuming UI data is eagerly loaded
 +        ConnectorBundleLoader.get().loadBundle(
 +                ConnectorBundleLoader.EAGER_BUNDLE_NAME, null);
 +        uIConnector = GWT.create(UIConnector.class);
 +        rpcManager = GWT.create(RpcManager.class);
 +        layoutManager = GWT.create(LayoutManager.class);
 +        tooltip = GWT.create(VTooltip.class);
 +        loadingIndicator = GWT.create(VLoadingIndicator.class);
 +        serverRpcQueue = GWT.create(ServerRpcQueue.class);
 +        connectionStateHandler = GWT.create(ConnectionStateHandler.class);
 +        messageHandler = GWT.create(MessageHandler.class);
 +        messageSender = GWT.create(MessageSender.class);
 +    }
 +
 +    public void init(WidgetSet widgetSet, ApplicationConfiguration cnf) {
 +        getLogger().info("Starting application " + cnf.getRootPanelId());
 +        getLogger().info("Using theme: " + cnf.getThemeName());
 +
 +        getLogger().info(
 +                "Vaadin application servlet version: "
 +                        + cnf.getServletVersion());
 +
 +        if (!cnf.getServletVersion().equals(Version.getFullVersion())) {
 +            getLogger()
 +                    .severe("Warning: your widget set seems to be built with a different "
 +                            + "version than the one used on server. Unexpected "
 +                            + "behavior may occur.");
 +        }
 +
 +        this.widgetSet = widgetSet;
 +        configuration = cnf;
 +
 +        layoutManager.setConnection(this);
 +        loadingIndicator.setConnection(this);
 +        serverRpcQueue.setConnection(this);
 +        messageHandler.setConnection(this);
 +        messageSender.setConnection(this);
 +
 +        ComponentLocator componentLocator = new ComponentLocator(this);
 +
 +        String appRootPanelName = cnf.getRootPanelId();
 +        // remove the end (window name) of autogenerated rootpanel id
 +        appRootPanelName = appRootPanelName.replaceFirst("-\\d+$", "");
 +
 +        initializeTestbenchHooks(componentLocator, appRootPanelName);
 +
 +        initializeClientHooks();
 +
 +        uIConnector.init(cnf.getRootPanelId(), this);
 +
 +        // Connection state handler preloads the reconnect dialog, which uses
 +        // overlay container. This in turn depends on VUI being attached
 +        // (done in uiConnector.init)
 +        connectionStateHandler.setConnection(this);
 +
 +        tooltip.setOwner(uIConnector.getWidget());
 +
 +        getLoadingIndicator().show();
 +
 +        heartbeat.init(this);
 +
 +        // Ensure the overlay container is added to the dom and set as a live
 +        // area for assistive devices
 +        Element overlayContainer = VOverlay.getOverlayContainer(this);
 +        Roles.getAlertRole().setAriaLiveProperty(overlayContainer,
 +                LiveValue.ASSERTIVE);
 +        VOverlay.setOverlayContainerLabel(this,
 +                getUIConnector().getState().overlayContainerLabel);
 +        Roles.getAlertRole().setAriaRelevantProperty(overlayContainer,
 +                RelevantValue.ADDITIONS);
 +    }
 +
 +    /**
 +     * Starts this application. Don't call this method directly - it's called by
 +     * {@link ApplicationConfiguration#startNextApplication()}, which should be
 +     * called once this application has started (first response received) or
 +     * failed to start. This ensures that the applications are started in order,
 +     * to avoid session-id problems.
 +     * 
 +     */
 +    public void start() {
 +        String jsonText = configuration.getUIDL();
 +        if (jsonText == null) {
 +            // initial UIDL not in DOM, request from server
 +            getMessageSender().resynchronize();
 +        } else {
 +            // initial UIDL provided in DOM, continue as if returned by request
 +
 +            // Hack to avoid logging an error in endRequest()
 +            getMessageSender().startRequest();
 +            getMessageHandler().handleMessage(
 +                    MessageHandler.parseJson(jsonText));
 +        }
 +
 +        // Tooltip can't be created earlier because the
 +        // necessary fields are not setup to add it in the
 +        // correct place in the DOM
 +        if (!tooltipInitialized) {
 +            tooltipInitialized = true;
 +            ApplicationConfiguration.runWhenDependenciesLoaded(new Command() {
 +                @Override
 +                public void execute() {
 +                    getVTooltip().initializeAssistiveTooltips();
 +                }
 +            });
 +        }
 +    }
 +
 +    /**
 +     * Checks if there is some work to be done on the client side
 +     * 
 +     * @return true if the client has some work to be done, false otherwise
 +     */
 +    private boolean isActive() {
 +        return !getMessageHandler().isInitialUidlHandled() || isWorkPending()
 +                || getMessageSender().hasActiveRequest()
 +                || isExecutingDeferredCommands();
 +    }
 +
 +    private native void initializeTestbenchHooks(
 +            ComponentLocator componentLocator, String TTAppId)
 +    /*-{
 +        var ap = this;
 +        var client = {};
 +        client.isActive = $entry(function() {
 +            return ap.@com.vaadin.client.ApplicationConnection::isActive()();
 +        });
 +        var vi = ap.@com.vaadin.client.ApplicationConnection::getVersionInfo()();
 +        if (vi) {
 +            client.getVersionInfo = function() {
 +                return vi;
 +            }
 +        }
 +
 +        client.getProfilingData = $entry(function() {
-             pd = pd.concat(smh.@com.vaadin.client.communication.MessageHandler::serverTimingInfo);
++            var smh = ap.@com.vaadin.client.ApplicationConnection::getMessageHandler()();
 +            var pd = [
 +                smh.@com.vaadin.client.communication.MessageHandler::lastProcessingTime,
 +                    smh.@com.vaadin.client.communication.MessageHandler::totalProcessingTime
 +                ];
++            if (null != smh.@com.vaadin.client.communication.MessageHandler::serverTimingInfo) {
++                pd = pd.concat(smh.@com.vaadin.client.communication.MessageHandler::serverTimingInfo);
++            } else {
++                pd = pd.concat(-1, -1);
++            }
 +            pd[pd.length] = smh.@com.vaadin.client.communication.MessageHandler::bootstrapTime;
 +            return pd;
 +        });
 +
 +        client.getElementByPath = $entry(function(id) {
 +            return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getElementByPath(Ljava/lang/String;)(id);
 +        });
 +        client.getElementByPathStartingAt = $entry(function(id, element) {
 +            return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getElementByPathStartingAt(Ljava/lang/String;Lcom/google/gwt/dom/client/Element;)(id, element);
 +        });
 +        client.getElementsByPath = $entry(function(id) {
 +            return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getElementsByPath(Ljava/lang/String;)(id);
 +        });
 +        client.getElementsByPathStartingAt = $entry(function(id, element) {
 +            return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getElementsByPathStartingAt(Ljava/lang/String;Lcom/google/gwt/dom/client/Element;)(id, element);
 +        });
 +        client.getPathForElement = $entry(function(element) {
 +            return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getPathForElement(Lcom/google/gwt/dom/client/Element;)(element);
 +        });
 +        client.initializing = false;
 +
 +        $wnd.vaadin.clients[TTAppId] = client;
 +    }-*/;
 +
 +    /**
 +     * Helper for tt initialization
 +     */
 +    private JavaScriptObject getVersionInfo() {
 +        return configuration.getVersionInfoJSObject();
 +    }
 +
 +    /**
 +     * Publishes a JavaScript API for mash-up applications.
 +     * <ul>
 +     * <li><code>vaadin.forceSync()</code> sends pending variable changes, in
 +     * effect synchronizing the server and client state. This is done for all
 +     * applications on host page.</li>
 +     * <li><code>vaadin.postRequestHooks</code> is a map of functions which gets
 +     * called after each XHR made by vaadin application. Note, that it is
 +     * attaching js functions responsibility to create the variable like this:
 +     * 
 +     * <code><pre>
 +     * if(!vaadin.postRequestHooks) {vaadin.postRequestHooks = new Object();}
 +     * postRequestHooks.myHook = function(appId) {
 +     *          if(appId == "MyAppOfInterest") {
 +     *                  // do the staff you need on xhr activity
 +     *          }
 +     * }
 +     * </pre></code> First parameter passed to these functions is the identifier
 +     * of Vaadin application that made the request.
 +     * </ul>
 +     * 
 +     * TODO make this multi-app aware
 +     */
 +    private native void initializeClientHooks()
 +    /*-{
 +        var app = this;
 +        var oldSync;
 +        if ($wnd.vaadin.forceSync) {
 +                oldSync = $wnd.vaadin.forceSync;
 +        }
 +        $wnd.vaadin.forceSync = $entry(function() {
 +                if (oldSync) {
 +                        oldSync();
 +                }
 +                app.@com.vaadin.client.ApplicationConnection::sendPendingVariableChanges()();
 +        });
 +        var oldForceLayout;
 +        if ($wnd.vaadin.forceLayout) {
 +                oldForceLayout = $wnd.vaadin.forceLayout;
 +        }
 +        $wnd.vaadin.forceLayout = $entry(function() {
 +                if (oldForceLayout) {
 +                        oldForceLayout();
 +                }
 +                app.@com.vaadin.client.ApplicationConnection::forceLayout()();
 +        });
 +    }-*/;
 +
 +    /**
 +     * Requests an analyze of layouts, to find inconsistencies. Exclusively used
 +     * for debugging during development.
 +     * 
 +     * @deprecated as of 7.1. Replaced by {@link UIConnector#analyzeLayouts()}
 +     */
 +    @Deprecated
 +    public void analyzeLayouts() {
 +        getUIConnector().analyzeLayouts();
 +    }
 +
 +    /**
 +     * Sends a request to the server to print details to console that will help
 +     * the developer to locate the corresponding server-side connector in the
 +     * source code.
 +     * 
 +     * @param serverConnector
 +     * @deprecated as of 7.1. Replaced by
 +     *             {@link UIConnector#showServerDebugInfo(ServerConnector)}
 +     */
 +    @Deprecated
 +    void highlightConnector(ServerConnector serverConnector) {
 +        getUIConnector().showServerDebugInfo(serverConnector);
 +    }
 +
 +    int cssWaits = 0;
 +
 +    protected ServerRpcQueue serverRpcQueue;
 +    protected ConnectionStateHandler connectionStateHandler;
 +    protected MessageHandler messageHandler;
 +    protected MessageSender messageSender;
 +
 +    static final int MAX_CSS_WAITS = 100;
 +
 +    public void executeWhenCSSLoaded(final Command c) {
 +        if (!isCSSLoaded() && cssWaits < MAX_CSS_WAITS) {
 +            (new Timer() {
 +                @Override
 +                public void run() {
 +                    executeWhenCSSLoaded(c);
 +                }
 +            }).schedule(50);
 +
 +            // Show this message just once
 +            if (cssWaits++ == 0) {
 +                getLogger().warning(
 +                        "Assuming CSS loading is not complete, "
 +                                + "postponing render phase. "
 +                                + "(.v-loading-indicator height == 0)");
 +            }
 +        } else {
 +            cssLoaded = true;
 +            if (cssWaits >= MAX_CSS_WAITS) {
 +                getLogger().severe("CSS files may have not loaded properly.");
 +            }
 +
 +            c.execute();
 +        }
 +    }
 +
 +    /**
 +     * Checks whether or not the CSS is loaded. By default checks the size of
 +     * the loading indicator element.
 +     * 
 +     * @return
 +     */
 +    protected boolean isCSSLoaded() {
 +        return cssLoaded
 +                || getLoadingIndicator().getElement().getOffsetHeight() != 0;
 +    }
 +
 +    /**
 +     * Shows the communication error notification.
 +     * 
 +     * @param details
 +     *            Optional details.
 +     * @param statusCode
 +     *            The status code returned for the request
 +     * 
 +     */
 +    public void showCommunicationError(String details, int statusCode) {
 +        getLogger().severe("Communication error: " + details);
 +        showError(details, configuration.getCommunicationError());
 +    }
 +
 +    /**
 +     * Shows the authentication error notification.
 +     * 
 +     * @param details
 +     *            Optional details.
 +     */
 +    public void showAuthenticationError(String details) {
 +        getLogger().severe("Authentication error: " + details);
 +        showError(details, configuration.getAuthorizationError());
 +    }
 +
 +    /**
 +     * Shows the session expiration notification.
 +     * 
 +     * @param details
 +     *            Optional details.
 +     */
 +    public void showSessionExpiredError(String details) {
 +        getLogger().severe("Session expired: " + details);
 +        showError(details, configuration.getSessionExpiredError());
 +    }
 +
 +    /**
 +     * Shows an error notification.
 +     * 
 +     * @param details
 +     *            Optional details.
 +     * @param message
 +     *            An ErrorMessage describing the error.
 +     */
 +    protected void showError(String details, ErrorMessage message) {
 +        VNotification.showError(this, message.getCaption(),
 +                message.getMessage(), details, message.getUrl());
 +    }
 +
 +    /**
 +     * Checks if the client has running or scheduled commands
 +     */
 +    private boolean isWorkPending() {
 +        ConnectorMap connectorMap = getConnectorMap();
 +        JsArrayObject<ServerConnector> connectors = connectorMap
 +                .getConnectorsAsJsArray();
 +        int size = connectors.size();
 +        for (int i = 0; i < size; i++) {
 +            ServerConnector conn = connectors.get(i);
 +            if (isWorkPending(conn)) {
 +                return true;
 +            }
 +
 +            if (conn instanceof ComponentConnector) {
 +                ComponentConnector compConn = (ComponentConnector) conn;
 +                if (isWorkPending(compConn.getWidget())) {
 +                    return true;
 +                }
 +            }
 +        }
 +        return false;
 +    }
 +
 +    private static boolean isWorkPending(Object object) {
 +        return object instanceof DeferredWorker
 +                && ((DeferredWorker) object).isWorkPending();
 +    }
 +
 +    /**
 +     * Checks if deferred commands are (potentially) still being executed as a
 +     * result of an update from the server. Returns true if a deferred command
 +     * might still be executing, false otherwise. This will not work correctly
 +     * if a deferred command is added in another deferred command.
 +     * <p>
 +     * Used by the native "client.isActive" function.
 +     * </p>
 +     * 
 +     * @return true if deferred commands are (potentially) being executed, false
 +     *         otherwise
 +     */
 +    private boolean isExecutingDeferredCommands() {
 +        Scheduler s = Scheduler.get();
 +        if (s instanceof VSchedulerImpl) {
 +            return ((VSchedulerImpl) s).hasWorkQueued();
 +        } else {
 +            return false;
 +        }
 +    }
 +
 +    /**
 +     * Returns the loading indicator used by this ApplicationConnection
 +     * 
 +     * @return The loading indicator for this ApplicationConnection
 +     */
 +    public VLoadingIndicator getLoadingIndicator() {
 +        return loadingIndicator;
 +    }
 +
 +    /**
 +     * Determines whether or not the loading indicator is showing.
 +     * 
 +     * @return true if the loading indicator is visible
 +     * @deprecated As of 7.1. Use {@link #getLoadingIndicator()} and
 +     *             {@link VLoadingIndicator#isVisible()}.isVisible() instead.
 +     */
 +    @Deprecated
 +    public boolean isLoadingIndicatorVisible() {
 +        return getLoadingIndicator().isVisible();
 +    }
 +
 +    public void loadStyleDependencies(JsArrayString dependencies) {
 +        // Assuming no reason to interpret in a defined order
 +        ResourceLoadListener resourceLoadListener = new ResourceLoadListener() {
 +            @Override
 +            public void onLoad(ResourceLoadEvent event) {
 +                ApplicationConfiguration.endDependencyLoading();
 +            }
 +
 +            @Override
 +            public void onError(ResourceLoadEvent event) {
 +                getLogger()
 +                        .severe(event.getResourceUrl()
 +                                + " could not be loaded, or the load detection failed because the stylesheet is empty.");
 +                // The show must go on
 +                onLoad(event);
 +            }
 +        };
 +        ResourceLoader loader = ResourceLoader.get();
 +        for (int i = 0; i < dependencies.length(); i++) {
 +            String url = translateVaadinUri(dependencies.get(i));
 +            ApplicationConfiguration.startDependencyLoading();
 +            loader.loadStylesheet(url, resourceLoadListener);
 +        }
 +    }
 +
 +    public void loadScriptDependencies(final JsArrayString dependencies) {
 +        if (dependencies.length() == 0) {
 +            return;
 +        }
 +
 +        // Listener that loads the next when one is completed
 +        ResourceLoadListener resourceLoadListener = new ResourceLoadListener() {
 +            @Override
 +            public void onLoad(ResourceLoadEvent event) {
 +                if (dependencies.length() != 0) {
 +                    String url = translateVaadinUri(dependencies.shift());
 +                    ApplicationConfiguration.startDependencyLoading();
 +                    // Load next in chain (hopefully already preloaded)
 +                    event.getResourceLoader().loadScript(url, this);
 +                }
 +                // Call start for next before calling end for current
 +                ApplicationConfiguration.endDependencyLoading();
 +            }
 +
 +            @Override
 +            public void onError(ResourceLoadEvent event) {
 +                getLogger().severe(
 +                        event.getResourceUrl() + " could not be loaded.");
 +                // The show must go on
 +                onLoad(event);
 +            }
 +        };
 +
 +        ResourceLoader loader = ResourceLoader.get();
 +
 +        // Start chain by loading first
 +        String url = translateVaadinUri(dependencies.shift());
 +        ApplicationConfiguration.startDependencyLoading();
 +        loader.loadScript(url, resourceLoadListener);
 +
 +        if (ResourceLoader.supportsInOrderScriptExecution()) {
 +            for (int i = 0; i < dependencies.length(); i++) {
 +                String preloadUrl = translateVaadinUri(dependencies.get(i));
 +                loader.loadScript(preloadUrl, null);
 +            }
 +        } else {
 +            // Preload all remaining
 +            for (int i = 0; i < dependencies.length(); i++) {
 +                String preloadUrl = translateVaadinUri(dependencies.get(i));
 +                loader.preloadResource(preloadUrl, null);
 +            }
 +        }
 +    }
 +
 +    private void addVariableToQueue(String connectorId, String variableName,
 +            Object value, boolean immediate) {
 +        boolean lastOnly = !immediate;
 +        // note that type is now deduced from value
 +        serverRpcQueue.add(new LegacyChangeVariablesInvocation(connectorId,
 +                variableName, value), lastOnly);
 +        if (immediate) {
 +            serverRpcQueue.flush();
 +        }
 +    }
 +
 +    /**
 +     * @deprecated as of 7.6, use {@link ServerRpcQueue#flush()}
 +     */
 +    @Deprecated
 +    public void sendPendingVariableChanges() {
 +        serverRpcQueue.flush();
 +    }
 +
 +    /**
 +     * Sends a new value for the given paintables given variable to the server.
 +     * <p>
 +     * The update is actually queued to be sent at a suitable time. If immediate
 +     * is true, the update is sent as soon as possible. If immediate is false,
 +     * the update will be sent along with the next immediate update.
 +     * </p>
 +     * 
 +     * @param paintableId
 +     *            the id of the paintable that owns the variable
 +     * @param variableName
 +     *            the name of the variable
 +     * @param newValue
 +     *            the new value to be sent
 +     * @param immediate
 +     *            true if the update is to be sent as soon as possible
 +     */
 +    public void updateVariable(String paintableId, String variableName,
 +            ServerConnector newValue, boolean immediate) {
 +        addVariableToQueue(paintableId, variableName, newValue, immediate);
 +    }
 +
 +    /**
 +     * Sends a new value for the given paintables given variable to the server.
 +     * <p>
 +     * The update is actually queued to be sent at a suitable time. If immediate
 +     * is true, the update is sent as soon as possible. If immediate is false,
 +     * the update will be sent along with the next immediate update.
 +     * </p>
 +     * 
 +     * @param paintableId
 +     *            the id of the paintable that owns the variable
 +     * @param variableName
 +     *            the name of the variable
 +     * @param newValue
 +     *            the new value to be sent
 +     * @param immediate
 +     *            true if the update is to be sent as soon as possible
 +     */
 +
 +    public void updateVariable(String paintableId, String variableName,
 +            String newValue, boolean immediate) {
 +        addVariableToQueue(paintableId, variableName, newValue, immediate);
 +    }
 +
 +    /**
 +     * Sends a new value for the given paintables given variable to the server.
 +     * <p>
 +     * The update is actually queued to be sent at a suitable time. If immediate
 +     * is true, the update is sent as soon as possible. If immediate is false,
 +     * the update will be sent along with the next immediate update.
 +     * </p>
 +     * 
 +     * @param paintableId
 +     *            the id of the paintable that owns the variable
 +     * @param variableName
 +     *            the name of the variable
 +     * @param newValue
 +     *            the new value to be sent
 +     * @param immediate
 +     *            true if the update is to be sent as soon as possible
 +     */
 +
 +    public void updateVariable(String paintableId, String variableName,
 +            int newValue, boolean immediate) {
 +        addVariableToQueue(paintableId, variableName, newValue, immediate);
 +    }
 +
 +    /**
 +     * Sends a new value for the given paintables given variable to the server.
 +     * <p>
 +     * The update is actually queued to be sent at a suitable time. If immediate
 +     * is true, the update is sent as soon as possible. If immediate is false,
 +     * the update will be sent along with the next immediate update.
 +     * </p>
 +     * 
 +     * @param paintableId
 +     *            the id of the paintable that owns the variable
 +     * @param variableName
 +     *            the name of the variable
 +     * @param newValue
 +     *            the new value to be sent
 +     * @param immediate
 +     *            true if the update is to be sent as soon as possible
 +     */
 +
 +    public void updateVariable(String paintableId, String variableName,
 +            long newValue, boolean immediate) {
 +        addVariableToQueue(paintableId, variableName, newValue, immediate);
 +    }
 +
 +    /**
 +     * Sends a new value for the given paintables given variable to the server.
 +     * <p>
 +     * The update is actually queued to be sent at a suitable time. If immediate
 +     * is true, the update is sent as soon as possible. If immediate is false,
 +     * the update will be sent along with the next immediate update.
 +     * </p>
 +     * 
 +     * @param paintableId
 +     *            the id of the paintable that owns the variable
 +     * @param variableName
 +     *            the name of the variable
 +     * @param newValue
 +     *            the new value to be sent
 +     * @param immediate
 +     *            true if the update is to be sent as soon as possible
 +     */
 +
 +    public void updateVariable(String paintableId, String variableName,
 +            float newValue, boolean immediate) {
 +        addVariableToQueue(paintableId, variableName, newValue, immediate);
 +    }
 +
 +    /**
 +     * Sends a new value for the given paintables given variable to the server.
 +     * <p>
 +     * The update is actually queued to be sent at a suitable time. If immediate
 +     * is true, the update is sent as soon as possible. If immediate is false,
 +     * the update will be sent along with the next immediate update.
 +     * </p>
 +     * 
 +     * @param paintableId
 +     *            the id of the paintable that owns the variable
 +     * @param variableName
 +     *            the name of the variable
 +     * @param newValue
 +     *            the new value to be sent
 +     * @param immediate
 +     *            true if the update is to be sent as soon as possible
 +     */
 +
 +    public void updateVariable(String paintableId, String variableName,
 +            double newValue, boolean immediate) {
 +        addVariableToQueue(paintableId, variableName, newValue, immediate);
 +    }
 +
 +    /**
 +     * Sends a new value for the given paintables given variable to the server.
 +     * <p>
 +     * The update is actually queued to be sent at a suitable time. If immediate
 +     * is true, the update is sent as soon as possible. If immediate is false,
 +     * the update will be sent along with the next immediate update.
 +     * </p>
 +     * 
 +     * @param paintableId
 +     *            the id of the paintable that owns the variable
 +     * @param variableName
 +     *            the name of the variable
 +     * @param newValue
 +     *            the new value to be sent
 +     * @param immediate
 +     *            true if the update is to be sent as soon as possible
 +     */
 +
 +    public void updateVariable(String paintableId, String variableName,
 +            boolean newValue, boolean immediate) {
 +        addVariableToQueue(paintableId, variableName, newValue, immediate);
 +    }
 +
 +    /**
 +     * Sends a new value for the given paintables given variable to the server.
 +     * <p>
 +     * The update is actually queued to be sent at a suitable time. If immediate
 +     * is true, the update is sent as soon as possible. If immediate is false,
 +     * the update will be sent along with the next immediate update.
 +     * </p>
 +     * 
 +     * @param paintableId
 +     *            the id of the paintable that owns the variable
 +     * @param variableName
 +     *            the name of the variable
 +     * @param map
 +     *            the new values to be sent
 +     * @param immediate
 +     *            true if the update is to be sent as soon as possible
 +     */
 +    public void updateVariable(String paintableId, String variableName,
 +            Map<String, Object> map, boolean immediate) {
 +        addVariableToQueue(paintableId, variableName, map, immediate);
 +    }
 +
 +    /**
 +     * Sends a new value for the given paintables given variable to the server.
 +     * <p>
 +     * The update is actually queued to be sent at a suitable time. If immediate
 +     * is true, the update is sent as soon as possible. If immediate is false,
 +     * the update will be sent along with the next immediate update.
 +     * <p>
 +     * A null array is sent as an empty array.
 +     * 
 +     * @param paintableId
 +     *            the id of the paintable that owns the variable
 +     * @param variableName
 +     *            the name of the variable
 +     * @param values
 +     *            the new value to be sent
 +     * @param immediate
 +     *            true if the update is to be sent as soon as possible
 +     */
 +    public void updateVariable(String paintableId, String variableName,
 +            String[] values, boolean immediate) {
 +        addVariableToQueue(paintableId, variableName, values, immediate);
 +    }
 +
 +    /**
 +     * Sends a new value for the given paintables given variable to the server.
 +     * <p>
 +     * The update is actually queued to be sent at a suitable time. If immediate
 +     * is true, the update is sent as soon as possible. If immediate is false,
 +     * the update will be sent along with the next immediate update.
 +     * <p>
 +     * A null array is sent as an empty array.
 +     * 
 +     * @param paintableId
 +     *            the id of the paintable that owns the variable
 +     * @param variableName
 +     *            the name of the variable
 +     * @param values
 +     *            the new value to be sent
 +     * @param immediate
 +     *            true if the update is to be sent as soon as possible
 +     */
 +    public void updateVariable(String paintableId, String variableName,
 +            Object[] values, boolean immediate) {
 +        addVariableToQueue(paintableId, variableName, values, immediate);
 +    }
 +
 +    /**
 +     * Does absolutely nothing. Replaced by {@link LayoutManager}.
 +     * 
 +     * @param container
 +     * @deprecated As of 7.0, serves no purpose
 +     */
 +    @Deprecated
 +    public void runDescendentsLayout(HasWidgets container) {
 +    }
 +
 +    /**
 +     * This will cause re-layouting of all components. Mainly used for
 +     * development. Published to JavaScript.
 +     */
 +    public void forceLayout() {
 +        Duration duration = new Duration();
 +
 +        layoutManager.forceLayout();
 +
 +        getLogger().info("forceLayout in " + duration.elapsedMillis() + " ms");
 +    }
 +
 +    /**
 +     * Returns false
 +     * 
 +     * @param paintable
 +     * @return false, always
 +     * @deprecated As of 7.0, serves no purpose
 +     */
 +    @Deprecated
 +    private boolean handleComponentRelativeSize(ComponentConnector paintable) {
 +        return false;
 +    }
 +
 +    /**
 +     * Returns false
 +     * 
 +     * @param paintable
 +     * @return false, always
 +     * @deprecated As of 7.0, serves no purpose
 +     */
 +    @Deprecated
 +    public boolean handleComponentRelativeSize(Widget widget) {
 +        return handleComponentRelativeSize(connectorMap.getConnector(widget));
 +
 +    }
 +
 +    @Deprecated
 +    public ComponentConnector getPaintable(UIDL uidl) {
 +        // Non-component connectors shouldn't be painted from legacy connectors
 +        return (ComponentConnector) getConnector(uidl.getId(),
 +                Integer.parseInt(uidl.getTag()));
 +    }
 +
 +    /**
 +     * Get either an existing ComponentConnector or create a new
 +     * ComponentConnector with the given type and id.
 +     * 
 +     * If a ComponentConnector with the given id already exists, returns it.
 +     * Otherwise creates and registers a new ComponentConnector of the given
 +     * type.
 +     * 
 +     * @param connectorId
 +     *            Id of the paintable
 +     * @param connectorType
 +     *            Type of the connector, as passed from the server side
 +     * 
 +     * @return Either an existing ComponentConnector or a new ComponentConnector
 +     *         of the given type
 +     */
 +    public ServerConnector getConnector(String connectorId, int connectorType) {
 +        if (!connectorMap.hasConnector(connectorId)) {
 +            return createAndRegisterConnector(connectorId, connectorType);
 +        }
 +        return connectorMap.getConnector(connectorId);
 +    }
 +
 +    /**
 +     * Creates a new ServerConnector with the given type and id.
 +     * 
 +     * Creates and registers a new ServerConnector of the given type. Should
 +     * never be called with the connector id of an existing connector.
 +     * 
 +     * @param connectorId
 +     *            Id of the new connector
 +     * @param connectorType
 +     *            Type of the connector, as passed from the server side
 +     * 
 +     * @return A new ServerConnector of the given type
 +     */
 +    private ServerConnector createAndRegisterConnector(String connectorId,
 +            int connectorType) {
 +        Profiler.enter("ApplicationConnection.createAndRegisterConnector");
 +
 +        // Create and register a new connector with the given type
 +        ServerConnector p = widgetSet.createConnector(connectorType,
 +                configuration);
 +        connectorMap.registerConnector(connectorId, p);
 +        p.doInit(connectorId, this);
 +
 +        Profiler.leave("ApplicationConnection.createAndRegisterConnector");
 +        return p;
 +    }
 +
 +    /**
 +     * Gets a resource that has been pre-loaded via UIDL, such as custom
 +     * layouts.
 +     * 
 +     * @param name
 +     *            identifier of the resource to get
 +     * @return the resource
 +     */
 +    public String getResource(String name) {
 +        return resourcesMap.get(name);
 +    }
 +
 +    /**
 +     * Sets a resource that has been pre-loaded via UIDL, such as custom
 +     * layouts.
 +     * 
 +     * @since 7.6
 +     * @param name
 +     *            identifier of the resource to Set
 +     * @param resource
 +     *            the resource
 +     */
 +    public void setResource(String name, String resource) {
 +        resourcesMap.put(name, resource);
 +    }
 +
 +    /**
 +     * Singleton method to get instance of app's context menu.
 +     * 
 +     * @return VContextMenu object
 +     */
 +    public VContextMenu getContextMenu() {
 +        if (contextMenu == null) {
 +            contextMenu = new VContextMenu();
 +            contextMenu.setOwner(uIConnector.getWidget());
 +            DOM.setElementProperty(contextMenu.getElement(), "id",
 +                    "PID_VAADIN_CM");
 +        }
 +        return contextMenu;
 +    }
 +
 +    /**
 +     * Gets an {@link Icon} instance corresponding to a URI.
 +     * 
 +     * @since 7.2
 +     * @param uri
 +     * @return Icon object
 +     */
 +    public Icon getIcon(String uri) {
 +        Icon icon;
 +        if (uri == null) {
 +            return null;
 +        } else if (FontIcon.isFontIconUri(uri)) {
 +            icon = GWT.create(FontIcon.class);
 +        } else {
 +            icon = GWT.create(ImageIcon.class);
 +        }
 +        icon.setUri(translateVaadinUri(uri));
 +        return icon;
 +    }
 +
 +    /**
 +     * Translates custom protocols in UIDL URI's to be recognizable by browser.
 +     * All uri's from UIDL should be routed via this method before giving them
 +     * to browser due URI's in UIDL may contain custom protocols like theme://.
 +     * 
 +     * @param uidlUri
 +     *            Vaadin URI from uidl
 +     * @return translated URI ready for browser
 +     */
 +    public String translateVaadinUri(String uidlUri) {
 +        return uriResolver.resolveVaadinUri(uidlUri);
 +    }
 +
 +    /**
 +     * Gets the URI for the current theme. Can be used to reference theme
 +     * resources.
 +     * 
 +     * @return URI to the current theme
 +     */
 +    public String getThemeUri() {
 +        return configuration.getVaadinDirUrl() + "themes/"
 +                + getUIConnector().getActiveTheme();
 +    }
 +
 +    /* Extended title handling */
 +
 +    private final VTooltip tooltip;
 +
 +    private ConnectorMap connectorMap = GWT.create(ConnectorMap.class);
 +
 +    /**
 +     * Use to notify that the given component's caption has changed; layouts may
 +     * have to be recalculated.
 +     * 
 +     * @param component
 +     *            the Paintable whose caption has changed
 +     * @deprecated As of 7.0.2, has not had any effect for a long time
 +     */
 +    @Deprecated
 +    public void captionSizeUpdated(Widget widget) {
 +        // This doesn't do anything, it's just kept here for compatibility
 +    }
 +
 +    /**
 +     * Gets the main view
 +     * 
 +     * @return the main view
 +     */
 +    public UIConnector getUIConnector() {
 +        return uIConnector;
 +    }
 +
 +    /**
 +     * Gets the {@link ApplicationConfiguration} for the current application.
 +     * 
 +     * @see ApplicationConfiguration
 +     * @return the configuration for this application
 +     */
 +    public ApplicationConfiguration getConfiguration() {
 +        return configuration;
 +    }
 +
 +    /**
 +     * Checks if there is a registered server side listener for the event. The
 +     * list of events which has server side listeners is updated automatically
 +     * before the component is updated so the value is correct if called from
 +     * updatedFromUIDL.
 +     * 
 +     * @param connector
 +     *            The connector to register event listeners for
 +     * @param eventIdentifier
 +     *            The identifier for the event
 +     * @return true if at least one listener has been registered on server side
 +     *         for the event identified by eventIdentifier.
 +     * @deprecated As of 7.0. Use
 +     *             {@link AbstractConnector#hasEventListener(String)} instead
 +     */
 +    @Deprecated
 +    public boolean hasEventListeners(ComponentConnector connector,
 +            String eventIdentifier) {
 +        return connector.hasEventListener(eventIdentifier);
 +    }
 +
 +    /**
 +     * Adds the get parameters to the uri and returns the new uri that contains
 +     * the parameters.
 +     * 
 +     * @param uri
 +     *            The uri to which the parameters should be added.
 +     * @param extraParams
 +     *            One or more parameters in the format "a=b" or "c=d&e=f". An
 +     *            empty string is allowed but will not modify the url.
 +     * @return The modified URI with the get parameters in extraParams added.
 +     * @deprecated Use {@link SharedUtil#addGetParameters(String,String)}
 +     *             instead
 +     */
 +    @Deprecated
 +    public static String addGetParameters(String uri, String extraParams) {
 +        return SharedUtil.addGetParameters(uri, extraParams);
 +    }
 +
 +    ConnectorMap getConnectorMap() {
 +        return connectorMap;
 +    }
 +
 +    /**
 +     * @deprecated As of 7.0. No longer serves any purpose.
 +     */
 +    @Deprecated
 +    public void unregisterPaintable(ServerConnector p) {
 +        getLogger().info(
 +                "unregisterPaintable (unnecessarily) called for "
 +                        + Util.getConnectorString(p));
 +    }
 +
 +    /**
 +     * Get VTooltip instance related to application connection
 +     * 
 +     * @return VTooltip instance
 +     */
 +    public VTooltip getVTooltip() {
 +        return tooltip;
 +    }
 +
 +    /**
 +     * Method provided for backwards compatibility. Duties previously done by
 +     * this method is now handled by the state change event handler in
 +     * AbstractComponentConnector. The only function this method has is to
 +     * return true if the UIDL is a "cached" update.
 +     * 
 +     * @param component
 +     * @param uidl
 +     * @param manageCaption
 +     * @deprecated As of 7.0, no longer serves any purpose
 +     * @return
 +     */
 +    @Deprecated
 +    public boolean updateComponent(Widget component, UIDL uidl,
 +            boolean manageCaption) {
 +        ComponentConnector connector = getConnectorMap()
 +                .getConnector(component);
 +        if (!AbstractComponentConnector.isRealUpdate(uidl)) {
 +            return true;
 +        }
 +
 +        if (!manageCaption) {
 +            getLogger()
 +                    .warning(
 +                            Util.getConnectorString(connector)
 +                                    + " called updateComponent with manageCaption=false. The parameter was ignored - override delegateCaption() to return false instead. It is however not recommended to use caption this way at all.");
 +        }
 +        return false;
 +    }
 +
 +    /**
 +     * @deprecated As of 7.0. Use
 +     *             {@link AbstractComponentConnector#hasEventListener(String)}
 +     *             instead
 +     */
 +    @Deprecated
 +    public boolean hasEventListeners(Widget widget, String eventIdentifier) {
 +        ComponentConnector connector = getConnectorMap().getConnector(widget);
 +        if (connector == null) {
 +            /*
 +             * No connector will exist in cases where Vaadin widgets have been
 +             * re-used without implementing server<->client communication.
 +             */
 +            return false;
 +        }
 +
 +        return hasEventListeners(getConnectorMap().getConnector(widget),
 +                eventIdentifier);
 +    }
 +
 +    LayoutManager getLayoutManager() {
 +        return layoutManager;
 +    }
 +
 +    /**
 +     * Schedules a heartbeat request to occur after the configured heartbeat
 +     * interval elapses if the interval is a positive number. Otherwise, does
 +     * nothing.
 +     * 
 +     * @deprecated as of 7.2, use {@link Heartbeat#schedule()} instead
 +     */
 +    @Deprecated
 +    protected void scheduleHeartbeat() {
 +        heartbeat.schedule();
 +    }
 +
 +    /**
 +     * Sends a heartbeat request to the server.
 +     * <p>
 +     * Heartbeat requests are used to inform the server that the client-side is
 +     * still alive. If the client page is closed or the connection lost, the
 +     * server will eventually close the inactive UI.
 +     * 
 +     * @deprecated as of 7.2, use {@link Heartbeat#send()} instead
 +     */
 +    @Deprecated
 +    protected void sendHeartbeat() {
 +        heartbeat.send();
 +    }
 +
 +    public void handleCommunicationError(String details, int statusCode) {
 +        boolean handled = false;
 +        if (communicationErrorDelegate != null) {
 +            handled = communicationErrorDelegate.onError(details, statusCode);
 +
 +        }
 +
 +        if (!handled) {
 +            showCommunicationError(details, statusCode);
 +        }
 +
 +    }
 +
 +    /**
 +     * Sets the delegate that is called whenever a communication error occurrs.
 +     * 
 +     * @param delegate
 +     *            the delegate.
 +     */
 +    public void setCommunicationErrorDelegate(CommunicationErrorHandler delegate) {
 +        communicationErrorDelegate = delegate;
 +    }
 +
 +    public void setApplicationRunning(boolean applicationRunning) {
 +        if (getApplicationState() == ApplicationState.TERMINATED) {
 +            if (applicationRunning) {
 +                getLogger()
 +                        .severe("Tried to restart a terminated application. This is not supported");
 +            } else {
 +                getLogger()
 +                        .warning(
 +                                "Tried to stop a terminated application. This should not be done");
 +            }
 +            return;
 +        } else if (getApplicationState() == ApplicationState.INITIALIZING) {
 +            if (applicationRunning) {
 +                applicationState = ApplicationState.RUNNING;
 +            } else {
 +                getLogger()
 +                        .warning(
 +                                "Tried to stop the application before it has started. This should not be done");
 +            }
 +        } else if (getApplicationState() == ApplicationState.RUNNING) {
 +            if (!applicationRunning) {
 +                applicationState = ApplicationState.TERMINATED;
 +                eventBus.fireEvent(new ApplicationStoppedEvent());
 +            } else {
 +                getLogger()
 +                        .warning(
 +                                "Tried to start an already running application. This should not be done");
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Checks if the application is in the {@link ApplicationState#RUNNING}
 +     * state.
 +     * 
 +     * @since 7.6
 +     * @return true if the application is in the running state, false otherwise
 +     */
 +    public boolean isApplicationRunning() {
 +        return applicationState == ApplicationState.RUNNING;
 +    }
 +
 +    public <H extends EventHandler> HandlerRegistration addHandler(
 +            GwtEvent.Type<H> type, H handler) {
 +        return eventBus.addHandler(type, handler);
 +    }
 +
 +    @Override
 +    public void fireEvent(GwtEvent<?> event) {
 +        eventBus.fireEvent(event);
 +    }
 +
 +    /**
 +     * Calls {@link ComponentConnector#flush()} on the active connector. Does
 +     * nothing if there is no active (focused) connector.
 +     */
 +    public void flushActiveConnector() {
 +        ComponentConnector activeConnector = getActiveConnector();
 +        if (activeConnector == null) {
 +            return;
 +        }
 +        activeConnector.flush();
 +    }
 +
 +    /**
 +     * Gets the active connector for focused element in browser.
 +     * 
 +     * @return Connector for focused element or null.
 +     */
 +    private ComponentConnector getActiveConnector() {
 +        Element focusedElement = WidgetUtil.getFocusedElement();
 +        if (focusedElement == null) {
 +            return null;
 +        }
 +        return Util.getConnectorForElement(this, getUIConnector().getWidget(),
 +                focusedElement);
 +    }
 +
 +    private static Logger getLogger() {
 +        return Logger.getLogger(ApplicationConnection.class.getName());
 +    }
 +
 +    /**
 +     * Returns the hearbeat instance.
 +     */
 +    public Heartbeat getHeartbeat() {
 +        return heartbeat;
 +    }
 +
 +    /**
 +     * Returns the state of this application. An application state goes from
 +     * "initializing" to "running" to "stopped". There is no way for an
 +     * application to go back to a previous state, i.e. a stopped application
 +     * can never be re-started
 +     * 
 +     * @since 7.6
 +     * @return the current state of this application
 +     */
 +    public ApplicationState getApplicationState() {
 +        return applicationState;
 +    }
 +
 +    /**
 +     * Gets the server RPC queue for this application
 +     * 
 +     * @since 7.6
 +     * @return the server RPC queue
 +     */
 +    public ServerRpcQueue getServerRpcQueue() {
 +        return serverRpcQueue;
 +    }
 +
 +    /**
 +     * Gets the communication error handler for this application
 +     * 
 +     * @since 7.6
 +     * @return the server RPC queue
 +     */
 +    public ConnectionStateHandler getConnectionStateHandler() {
 +        return connectionStateHandler;
 +    }
 +
 +    /**
 +     * Gets the (server to client) message handler for this application
 +     * 
 +     * @since 7.6
 +     * @return the message handler
 +     */
 +    public MessageHandler getMessageHandler() {
 +        return messageHandler;
 +    }
 +
 +    /**
 +     * Gets the server rpc manager for this application
 +     * 
 +     * @since 7.6
 +     * @return the server rpc manager
 +     */
 +    public RpcManager getRpcManager() {
 +        return rpcManager;
 +    }
 +
 +    /**
 +     * Gets the (client to server) message sender for this application
 +     * 
 +     * @since 7.6
 +     * @return the message sender
 +     */
 +    public MessageSender getMessageSender() {
 +        return messageSender;
 +    }
 +
 +    /**
 +     * @since 7.6
 +     * @return the widget set
 +     */
 +    public WidgetSet getWidgetSet() {
 +        return widgetSet;
 +    }
 +
 +    public int getLastSeenServerSyncId() {
 +        return getMessageHandler().getLastSeenServerSyncId();
 +    }
 +
 +}
index 112623d1fe8c6a757c4b70fa15f40670c453477a,0000000000000000000000000000000000000000..1229922a9c33f8012e2b41d63cc8a11721a00929
mode 100644,000000..100644
--- /dev/null
@@@ -1,280 -1,0 +1,284 @@@
-         AbstractRendererConnector<JsonValue> implements
 +/*
 + * Copyright 2000-2014 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.connectors;
 +
 +import java.util.ArrayList;
 +import java.util.Collection;
 +
 +import com.google.gwt.core.client.JavaScriptObject;
 +import com.google.gwt.core.client.JsArrayString;
 +import com.google.gwt.dom.client.NativeEvent;
 +import com.vaadin.client.BrowserInfo;
 +import com.vaadin.client.JavaScriptConnectorHelper;
 +import com.vaadin.client.Util;
 +import com.vaadin.client.communication.HasJavaScriptConnectorHelper;
 +import com.vaadin.client.renderers.ComplexRenderer;
 +import com.vaadin.client.renderers.Renderer;
 +import com.vaadin.client.widget.grid.CellReference;
 +import com.vaadin.client.widget.grid.RendererCellReference;
 +import com.vaadin.shared.JavaScriptExtensionState;
 +import com.vaadin.shared.ui.Connect;
 +import com.vaadin.ui.renderers.AbstractJavaScriptRenderer;
 +
 +import elemental.json.JsonObject;
 +import elemental.json.JsonValue;
 +
 +/**
 + * Connector for server-side renderer implemented using JavaScript.
 + * 
 + * @since 7.4
 + * @author Vaadin Ltd
 + */
++// This is really typed to <JsonValue>, but because of the way native strings
++// are not always instanceof JsonValue, we need to accept Object
 +@Connect(AbstractJavaScriptRenderer.class)
 +public class JavaScriptRendererConnector extends
-     protected Renderer<JsonValue> createRenderer() {
++        AbstractRendererConnector<Object> implements
 +        HasJavaScriptConnectorHelper {
 +    private final JavaScriptConnectorHelper helper = new JavaScriptConnectorHelper(
 +            this);
 +
 +    private final JavaScriptObject cellReferenceWrapper = createCellReferenceWrapper(BrowserInfo
 +            .get().isIE8());
 +
 +    @Override
 +    protected void init() {
 +        super.init();
 +        helper.init();
 +
 +        addGetRowKey(helper.getConnectorWrapper());
 +    }
 +
 +    private static native JavaScriptObject createCellReferenceWrapper(
 +            boolean isIE8)
 +    /*-{
 +        var reference = {};
 +        if (isIE8) {
 +          // IE8 only supports defineProperty for DOM objects
 +          reference = $doc.createElement('div');
 +        }
 +        
 +        var setProperty = function(name, getter, setter) {
 +            var descriptor = {
 +                get: getter
 +            }
 +            if (setter) {
 +                descriptor.set = setter;
 +            }
 +            Object.defineProperty(reference, name, descriptor);
 +        };
 +        
 +        setProperty("element", function() {
 +            return reference.target.@CellReference::getElement()();
 +        }, null);
 +        
 +        setProperty("rowIndex", function() {
 +            return reference.target.@CellReference::getRowIndex()();
 +        }, null);
 +        
 +        setProperty("columnIndex", function() {
 +            return reference.target.@CellReference::getColumnIndex()();
 +        }, null);
 +        
 +        setProperty("colSpan", function() {
 +            return reference.target.@RendererCellReference::getColSpan()();
 +        }, function(colSpan) {
 +            reference.target.@RendererCellReference::setColSpan(*)(colSpan);
 +        });
 +        
 +        return reference;
 +    }-*/;
 +
 +    @Override
 +    public JavaScriptExtensionState getState() {
 +        return (JavaScriptExtensionState) super.getState();
 +    }
 +
 +    private native void addGetRowKey(JavaScriptObject wrapper)
 +    /*-{
 +        var self = this;
 +        wrapper.getRowKey = $entry(function(rowIndex) {
 +            return @JavaScriptRendererConnector::findRowKey(*)(self, rowIndex);
 +        });
 +    }-*/;
 +
 +    private static String findRowKey(JavaScriptRendererConnector connector,
 +            int rowIndex) {
 +        GridConnector gc = (GridConnector) connector.getParent();
 +        JsonObject row = gc.getWidget().getDataSource().getRow(rowIndex);
 +        return connector.getRowKey(row);
 +    }
 +
 +    private boolean hasFunction(String name) {
 +        return hasFunction(helper.getConnectorWrapper(), name);
 +    }
 +
 +    private static native boolean hasFunction(JavaScriptObject wrapper,
 +            String name)
 +    /*-{
 +        return typeof wrapper[name] === 'function';
 +    }-*/;
 +
 +    @Override
-         return new ComplexRenderer<JsonValue>() {
++    protected Renderer<Object> createRenderer() {
 +        helper.ensureJavascriptInited();
 +
 +        if (!hasFunction("render")) {
 +            throw new RuntimeException("JavaScriptRenderer "
 +                    + helper.getInitFunctionName()
 +                    + " must have a function named 'render'");
 +        }
 +
 +        final boolean hasInit = hasFunction("init");
 +        final boolean hasDestroy = hasFunction("destroy");
 +        final boolean hasOnActivate = hasFunction("onActivate");
 +        final boolean hasGetConsumedEvents = hasFunction("getConsumedEvents");
 +        final boolean hasOnBrowserEvent = hasFunction("onBrowserEvent");
 +
-             public void render(RendererCellReference cell, JsonValue data) {
-                 render(helper.getConnectorWrapper(), getJsCell(cell),
-                         Util.json2jso(data));
++        return new ComplexRenderer<Object>() {
 +            @Override
-                     JavaScriptObject cell, JavaScriptObject data)
++            public void render(RendererCellReference cell, Object data) {
++                if (data instanceof JsonValue) {
++                    data = Util.json2jso((JsonValue) data);
++                }
++                render(helper.getConnectorWrapper(), getJsCell(cell), data);
 +            }
 +
 +            private JavaScriptObject getJsCell(CellReference<?> cell) {
 +                updateCellReference(cellReferenceWrapper, cell);
 +                return cellReferenceWrapper;
 +            }
 +
 +            public native void render(JavaScriptObject wrapper,
-     public JsonValue decode(JsonValue value) {
++                    JavaScriptObject cell, Object data)
 +            /*-{
 +                wrapper.render(cell, data);
 +            }-*/;
 +
 +            @Override
 +            public void init(RendererCellReference cell) {
 +                if (hasInit) {
 +                    init(helper.getConnectorWrapper(), getJsCell(cell));
 +                }
 +            }
 +
 +            private native void init(JavaScriptObject wrapper,
 +                    JavaScriptObject cell)
 +            /*-{
 +                wrapper.init(cell);
 +            }-*/;
 +
 +            private native void updateCellReference(
 +                    JavaScriptObject cellWrapper, CellReference<?> target)
 +            /*-{
 +                cellWrapper.target = target;
 +            }-*/;
 +
 +            @Override
 +            public void destroy(RendererCellReference cell) {
 +                if (hasDestroy) {
 +                    destory(helper.getConnectorWrapper(), getJsCell(cell));
 +                } else {
 +                    super.destroy(cell);
 +                }
 +            }
 +
 +            private native void destory(JavaScriptObject wrapper,
 +                    JavaScriptObject cell)
 +            /*-{
 +                wrapper.destory(cell);
 +            }-*/;
 +
 +            @Override
 +            public boolean onActivate(CellReference<?> cell) {
 +                if (hasOnActivate) {
 +                    return onActivate(helper.getConnectorWrapper(),
 +                            getJsCell(cell));
 +                } else {
 +                    return super.onActivate(cell);
 +                }
 +            }
 +
 +            private native boolean onActivate(JavaScriptObject wrapper,
 +                    JavaScriptObject cell)
 +            /*-{
 +                return !!wrapper.onActivate(cell);
 +            }-*/;
 +
 +            @Override
 +            public Collection<String> getConsumedEvents() {
 +                if (hasGetConsumedEvents) {
 +                    JsArrayString events = getConsumedEvents(helper
 +                            .getConnectorWrapper());
 +
 +                    ArrayList<String> list = new ArrayList<String>(
 +                            events.length());
 +                    for (int i = 0; i < events.length(); i++) {
 +                        list.add(events.get(i));
 +                    }
 +                    return list;
 +                } else {
 +                    return super.getConsumedEvents();
 +                }
 +            }
 +
 +            private native JsArrayString getConsumedEvents(
 +                    JavaScriptObject wrapper)
 +            /*-{
 +                var rawEvents = wrapper.getConsumedEvents();
 +                var events = [];
 +                for(var i = 0; i < rawEvents.length; i++) {
 +                  events[i] = ""+rawEvents[i];
 +                }
 +                return events;
 +            }-*/;
 +
 +            @Override
 +            public boolean onBrowserEvent(CellReference<?> cell,
 +                    NativeEvent event) {
 +                if (hasOnBrowserEvent) {
 +                    return onBrowserEvent(helper.getConnectorWrapper(),
 +                            getJsCell(cell), event);
 +                } else {
 +                    return super.onBrowserEvent(cell, event);
 +                }
 +            }
 +
 +            private native boolean onBrowserEvent(JavaScriptObject wrapper,
 +                    JavaScriptObject cell, NativeEvent event)
 +            /*-{
 +                return !!wrapper.onBrowserEvent(cell, event);
 +            }-*/;
 +        };
 +    }
 +
 +    @Override
++    public Object decode(JsonValue value) {
 +        // Let the js logic decode the raw json that the server sent
 +        return value;
 +    }
 +
 +    @Override
 +    public void onUnregister() {
 +        super.onUnregister();
 +        helper.onUnregister();
 +    }
 +
 +    @Override
 +    public JavaScriptConnectorHelper getJavascriptConnectorHelper() {
 +        return helper;
 +    }
 +}
index f3905f9e46f959efcea11bb46005ae26a26f3676,0000000000000000000000000000000000000000..15ab9a6ce0dc43cf0bbbcfeadb11b92a82ce78e2
mode 100644,000000..100644
--- /dev/null
@@@ -1,730 -1,0 +1,734 @@@
-                     setFocusOnLastElement(event);
 +/*
 + * Copyright 2000-2014 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;
 +
 +import java.util.ArrayList;
 +import java.util.List;
 +import java.util.Map;
 +
 +import com.google.gwt.core.client.GWT;
 +import com.google.gwt.core.client.JsArrayString;
 +import com.google.gwt.core.client.Scheduler;
 +import com.google.gwt.dom.client.Element;
 +import com.google.gwt.dom.client.NativeEvent;
 +import com.google.gwt.event.dom.client.MouseDownEvent;
 +import com.google.gwt.event.dom.client.MouseDownHandler;
 +import com.google.gwt.event.dom.client.MouseUpEvent;
 +import com.google.gwt.event.dom.client.MouseUpHandler;
 +import com.google.gwt.event.dom.client.TouchStartEvent;
 +import com.google.gwt.event.dom.client.TouchStartHandler;
 +import com.google.gwt.user.client.Command;
 +import com.google.gwt.user.client.DOM;
 +import com.google.gwt.user.client.Event;
 +import com.google.gwt.user.client.Timer;
 +import com.google.gwt.user.client.ui.Widget;
 +import com.google.gwt.xhr.client.ReadyStateChangeHandler;
 +import com.google.gwt.xhr.client.XMLHttpRequest;
 +import com.vaadin.client.ApplicationConnection;
 +import com.vaadin.client.ComponentConnector;
 +import com.vaadin.client.ConnectorMap;
 +import com.vaadin.client.LayoutManager;
 +import com.vaadin.client.MouseEventDetailsBuilder;
 +import com.vaadin.client.Util;
 +import com.vaadin.client.VConsole;
 +import com.vaadin.client.ValueMap;
++import com.vaadin.client.WidgetUtil;
 +import com.vaadin.client.ui.dd.DDUtil;
 +import com.vaadin.client.ui.dd.VAbstractDropHandler;
 +import com.vaadin.client.ui.dd.VAcceptCallback;
 +import com.vaadin.client.ui.dd.VDragAndDropManager;
 +import com.vaadin.client.ui.dd.VDragEvent;
 +import com.vaadin.client.ui.dd.VDropHandler;
 +import com.vaadin.client.ui.dd.VHasDropHandler;
 +import com.vaadin.client.ui.dd.VHtml5DragEvent;
 +import com.vaadin.client.ui.dd.VHtml5File;
 +import com.vaadin.client.ui.dd.VTransferable;
 +import com.vaadin.shared.ui.dd.HorizontalDropLocation;
 +import com.vaadin.shared.ui.dd.VerticalDropLocation;
 +
 +/**
 + * 
 + * Must have features pending:
 + * 
 + * drop details: locations + sizes in document hierarchy up to wrapper
 + * 
 + */
 +public class VDragAndDropWrapper extends VCustomComponent implements
 +        VHasDropHandler {
 +
 +    /**
 +     * Minimum pixel delta is used to detect click from drag. #12838
 +     */
 +    private static final int MIN_PX_DELTA = 4;
 +    private static final String CLASSNAME = "v-ddwrapper";
 +    protected static final String DRAGGABLE = "draggable";
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public boolean hasTooltip = false;
 +    private int startX = 0;
 +    private int startY = 0;
 +
 +    public VDragAndDropWrapper() {
 +        super();
 +        hookHtml5Events(getElement());
 +        setStyleName(CLASSNAME);
 +
 +        addDomHandler(new MouseDownHandler() {
 +
 +            @Override
 +            public void onMouseDown(final MouseDownEvent event) {
 +                if (getConnector().isEnabled()
 +                        && event.getNativeEvent().getButton() == Event.BUTTON_LEFT
 +                        && startDrag(event.getNativeEvent())) {
 +                    event.preventDefault(); // prevent text selection
 +                    startX = event.getClientX();
 +                    startY = event.getClientY();
 +                }
 +            }
 +        }, MouseDownEvent.getType());
 +
 +        addDomHandler(new MouseUpHandler() {
 +
 +            @Override
 +            public void onMouseUp(final MouseUpEvent event) {
 +                final int deltaX = Math.abs(event.getClientX() - startX);
 +                final int deltaY = Math.abs(event.getClientY() - startY);
 +                if ((deltaX + deltaY) < MIN_PX_DELTA) {
-             private void setFocusOnLastElement(final MouseUpEvent event) {
-                 Element el = event.getRelativeElement();
-                 getLastChildElement(el).focus();
-             }
-             private Element getLastChildElement(Element el) {
-                 do {
-                     if (el == null) {
-                         break;
-                     }
-                     el = el.getFirstChildElement();
-                 } while (el.getFirstChildElement() != null);
-                 return el;
-             }
++                    Element clickedElement = WidgetUtil.getElementFromPoint(
++                            event.getClientX(), event.getClientY());
++                    clickedElement.focus();
 +                }
 +            }
 +
-                 // visit server for possible
-                 // variable changes
-                 client.sendPendingVariableChanges();
 +        }, MouseUpEvent.getType());
 +
 +        addDomHandler(new TouchStartHandler() {
 +
 +            @Override
 +            public void onTouchStart(TouchStartEvent event) {
 +                if (getConnector().isEnabled()
 +                        && startDrag(event.getNativeEvent())) {
 +                    /*
 +                     * Dont let eg. panel start scrolling.
 +                     */
 +                    event.stopPropagation();
 +                }
 +            }
 +        }, TouchStartEvent.getType());
 +
 +        sinkEvents(Event.TOUCHEVENTS);
 +    }
 +
 +    /**
 +     * Starts a drag and drop operation from mousedown or touchstart event if
 +     * required conditions are met.
 +     * 
 +     * @param event
 +     * @return true if the event was handled as a drag start event
 +     */
 +    private boolean startDrag(NativeEvent event) {
 +        if (dragStartMode == WRAPPER || dragStartMode == COMPONENT
 +                || dragStartMode == COMPONENT_OTHER) {
 +            VTransferable transferable = new VTransferable();
 +            transferable.setDragSource(getConnector());
 +
 +            ComponentConnector paintable = Util.findPaintable(client,
 +                    Element.as(event.getEventTarget()));
 +            Widget widget = paintable.getWidget();
 +            transferable.setData("component", paintable);
 +            VDragEvent dragEvent = VDragAndDropManager.get().startDrag(
 +                    transferable, event, true);
 +
 +            transferable.setData("mouseDown", MouseEventDetailsBuilder
 +                    .buildMouseEventDetails(event).serialize());
 +
 +            if (dragStartMode == WRAPPER) {
 +                dragEvent.createDragImage(getElement(), true);
 +            } else if (dragStartMode == COMPONENT_OTHER
 +                    && getDragImageWidget() != null) {
 +                dragEvent.createDragImage(getDragImageWidget().getElement(),
 +                        true);
 +            } else {
 +                dragEvent.createDragImage(widget.getElement(), true);
 +            }
 +            return true;
 +        }
 +        return false;
 +    }
 +
 +    protected final static int NONE = 0;
 +    protected final static int COMPONENT = 1;
 +    protected final static int WRAPPER = 2;
 +    protected final static int HTML5 = 3;
 +    protected final static int COMPONENT_OTHER = 4;
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public int dragStartMode;
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public ApplicationConnection client;
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public VAbstractDropHandler dropHandler;
 +
++    /** For internal use only. May be removed or replaced in the future. */
++    public UploadHandler uploadHandler;
++
 +    private VDragEvent vaadinDragEvent;
 +
 +    int filecounter = 0;
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public Map<String, String> fileIdToReceiver;
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public ValueMap html5DataFlavors;
 +
 +    private Element dragStartElement;
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public void initDragStartMode() {
 +        Element div = getElement();
 +        if (dragStartMode == HTML5) {
 +            if (dragStartElement == null) {
 +                dragStartElement = getDragStartElement();
 +                dragStartElement.setPropertyBoolean(DRAGGABLE, true);
 +                VConsole.log("draggable = "
 +                        + dragStartElement.getPropertyBoolean(DRAGGABLE));
 +                hookHtml5DragStart(dragStartElement);
 +                VConsole.log("drag start listeners hooked.");
 +            }
 +        } else {
 +            dragStartElement = null;
 +            if (div.hasAttribute(DRAGGABLE)) {
 +                div.removeAttribute(DRAGGABLE);
 +            }
 +        }
 +    }
 +
 +    protected com.google.gwt.user.client.Element getDragStartElement() {
 +        return getElement();
 +    }
 +
 +    private boolean uploading;
 +
 +    private final ReadyStateChangeHandler readyStateChangeHandler = new ReadyStateChangeHandler() {
 +
 +        @Override
 +        public void onReadyStateChange(XMLHttpRequest xhr) {
 +            if (xhr.getReadyState() == XMLHttpRequest.DONE) {
++                // #19616 Notify the upload handler that the request is complete
++                // and let it poll the server for changes.
++                uploadHandler.uploadDone();
 +                uploading = false;
 +                startNextUpload();
 +                xhr.clearOnReadyStateChange();
 +            }
 +        }
 +    };
 +    private Timer dragleavetimer;
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public void startNextUpload() {
 +        Scheduler.get().scheduleDeferred(new Command() {
 +
 +            @Override
 +            public void execute() {
 +                if (!uploading) {
 +                    if (fileIds.size() > 0) {
 +
 +                        uploading = true;
 +                        final Integer fileId = fileIds.remove(0);
 +                        VHtml5File file = files.remove(0);
 +                        final String receiverUrl = client
 +                                .translateVaadinUri(fileIdToReceiver
 +                                        .remove(fileId.toString()));
 +                        ExtendedXHR extendedXHR = (ExtendedXHR) ExtendedXHR
 +                                .create();
 +                        extendedXHR
 +                                .setOnReadyStateChange(readyStateChangeHandler);
 +                        extendedXHR.open("POST", receiverUrl);
 +                        extendedXHR.postFile(file);
 +                    }
 +                }
 +
 +            }
 +        });
 +
 +    }
 +
 +    public boolean html5DragStart(VHtml5DragEvent event) {
 +        if (dragStartMode == HTML5) {
 +            /*
 +             * Populate html5 payload with dataflavors from the serverside
 +             */
 +            JsArrayString flavors = html5DataFlavors.getKeyArray();
 +            for (int i = 0; i < flavors.length(); i++) {
 +                String flavor = flavors.get(i);
 +                event.setHtml5DataFlavor(flavor,
 +                        html5DataFlavors.getString(flavor));
 +            }
 +            event.setEffectAllowed("copy");
 +            return true;
 +        }
 +        return false;
 +    }
 +
 +    public boolean html5DragEnter(VHtml5DragEvent event) {
 +        if (dropHandler == null) {
 +            return true;
 +        }
 +        try {
 +            if (dragleavetimer != null) {
 +                // returned quickly back to wrapper
 +                dragleavetimer.cancel();
 +                dragleavetimer = null;
 +            }
 +            if (VDragAndDropManager.get().getCurrentDropHandler() != getDropHandler()) {
 +                VTransferable transferable = new VTransferable();
 +                transferable.setDragSource(getConnector());
 +
 +                vaadinDragEvent = VDragAndDropManager.get().startDrag(
 +                        transferable, event, false);
 +                VDragAndDropManager.get().setCurrentDropHandler(
 +                        getDropHandler());
 +            }
 +            try {
 +                event.preventDefault();
 +                event.stopPropagation();
 +            } catch (Exception e) {
 +                // VConsole.log("IE9 fails");
 +            }
 +            return false;
 +        } catch (Exception e) {
 +            GWT.getUncaughtExceptionHandler().onUncaughtException(e);
 +            return true;
 +        }
 +    }
 +
 +    public boolean html5DragLeave(VHtml5DragEvent event) {
 +        if (dropHandler == null) {
 +            return true;
 +        }
 +
 +        try {
 +            dragleavetimer = new Timer() {
 +
 +                @Override
 +                public void run() {
 +                    // Yes, dragleave happens before drop. Makes no sense to me.
 +                    // IMO shouldn't fire leave at all if drop happens (I guess
 +                    // this
 +                    // is what IE does).
 +                    // In Vaadin we fire it only if drop did not happen.
 +                    if (vaadinDragEvent != null
 +                            && VDragAndDropManager.get()
 +                                    .getCurrentDropHandler() == getDropHandler()) {
 +                        VDragAndDropManager.get().interruptDrag();
 +                    }
 +                }
 +            };
 +            dragleavetimer.schedule(350);
 +            try {
 +                event.preventDefault();
 +                event.stopPropagation();
 +            } catch (Exception e) {
 +                // VConsole.log("IE9 fails");
 +            }
 +            return false;
 +        } catch (Exception e) {
 +            GWT.getUncaughtExceptionHandler().onUncaughtException(e);
 +            return true;
 +        }
 +    }
 +
 +    public boolean html5DragOver(VHtml5DragEvent event) {
 +        if (dropHandler == null) {
 +            return true;
 +        }
 +
 +        if (dragleavetimer != null) {
 +            // returned quickly back to wrapper
 +            dragleavetimer.cancel();
 +            dragleavetimer = null;
 +        }
 +
 +        vaadinDragEvent.setCurrentGwtEvent(event);
 +        getDropHandler().dragOver(vaadinDragEvent);
 +
 +        try {
 +            String s = event.getEffectAllowed();
 +            if ("all".equals(s) || s.contains("opy")) {
 +                event.setDropEffect("copy");
 +            } else {
 +                event.setDropEffect(s);
 +            }
 +        } catch (Exception e) {
 +            // IE10 throws exception here in getEffectAllowed, ignore it, let
 +            // drop effect be whatever it is
 +        }
 +
 +        try {
 +            event.preventDefault();
 +            event.stopPropagation();
 +        } catch (Exception e) {
 +            // VConsole.log("IE9 fails");
 +        }
 +        return false;
 +    }
 +
 +    public boolean html5DragDrop(VHtml5DragEvent event) {
 +        if (dropHandler == null || !currentlyValid) {
 +            return true;
 +        }
 +        try {
 +
 +            VTransferable transferable = vaadinDragEvent.getTransferable();
 +
 +            JsArrayString types = event.getTypes();
 +            for (int i = 0; i < types.length(); i++) {
 +                String type = types.get(i);
 +                if (isAcceptedType(type)) {
 +                    String data = event.getDataAsText(type);
 +                    if (data != null) {
 +                        transferable.setData(type, data);
 +                    }
 +                }
 +            }
 +
 +            int fileCount = event.getFileCount();
 +            if (fileCount > 0) {
 +                transferable.setData("filecount", fileCount);
 +                for (int i = 0; i < fileCount; i++) {
 +                    final int fileId = filecounter++;
 +                    final VHtml5File file = event.getFile(i);
 +                    VConsole.log("Preparing to upload file " + file.getName()
 +                            + " with id " + fileId);
 +                    transferable.setData("fi" + i, "" + fileId);
 +                    transferable.setData("fn" + i, file.getName());
 +                    transferable.setData("ft" + i, file.getType());
 +                    transferable.setData("fs" + i, file.getSize());
 +                    queueFilePost(fileId, file);
 +                }
 +
 +            }
 +
 +            VDragAndDropManager.get().endDrag();
 +            vaadinDragEvent = null;
 +            try {
 +                event.preventDefault();
 +                event.stopPropagation();
 +            } catch (Exception e) {
 +                // VConsole.log("IE9 fails");
 +            }
 +            return false;
 +        } catch (Exception e) {
 +            GWT.getUncaughtExceptionHandler().onUncaughtException(e);
 +            return true;
 +        }
 +
 +    }
 +
 +    protected String[] acceptedTypes = new String[] { "Text", "Url",
 +            "text/html", "text/plain", "text/rtf" };
 +
 +    private boolean isAcceptedType(String type) {
 +        for (String t : acceptedTypes) {
 +            if (t.equals(type)) {
 +                return true;
 +            }
 +        }
 +        return false;
 +    }
 +
 +    static class ExtendedXHR extends XMLHttpRequest {
 +
 +        protected ExtendedXHR() {
 +        }
 +
 +        public final native void postFile(VHtml5File file)
 +        /*-{
 +
 +            this.setRequestHeader('Content-Type', 'multipart/form-data');
 +            // Seems like IE10 will loose the file if we don't keep a reference to it...
 +            this.fileBeingUploaded = file;
 +
 +            this.send(file);
 +        }-*/;
 +
 +    }
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public List<Integer> fileIds = new ArrayList<Integer>();
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public List<VHtml5File> files = new ArrayList<VHtml5File>();
 +
 +    private void queueFilePost(final int fileId, final VHtml5File file) {
 +        fileIds.add(fileId);
 +        files.add(file);
 +    }
 +
 +    @Override
 +    public VDropHandler getDropHandler() {
 +        return dropHandler;
 +    }
 +
 +    protected VerticalDropLocation verticalDropLocation;
 +    protected HorizontalDropLocation horizontalDropLocation;
 +    private VerticalDropLocation emphasizedVDrop;
 +    private HorizontalDropLocation emphasizedHDrop;
 +
 +    /**
 +     * Flag used by html5 dd
 +     */
 +    private boolean currentlyValid;
 +    private Widget dragImageWidget;
 +
 +    private static final String OVER_STYLE = "v-ddwrapper-over";
 +
 +    public class CustomDropHandler extends VAbstractDropHandler {
 +
 +        @Override
 +        public void dragEnter(VDragEvent drag) {
 +            if (!getConnector().isEnabled()) {
 +                return;
 +            }
 +            updateDropDetails(drag);
 +            currentlyValid = false;
 +            super.dragEnter(drag);
 +        }
 +
 +        @Override
 +        public void dragLeave(VDragEvent drag) {
 +            deEmphasis(true);
 +            dragleavetimer = null;
 +        }
 +
 +        @Override
 +        public void dragOver(final VDragEvent drag) {
 +            if (!getConnector().isEnabled()) {
 +                return;
 +            }
 +            boolean detailsChanged = updateDropDetails(drag);
 +            if (detailsChanged) {
 +                currentlyValid = false;
 +                validate(new VAcceptCallback() {
 +
 +                    @Override
 +                    public void accepted(VDragEvent event) {
 +                        dragAccepted(drag);
 +                    }
 +                }, drag);
 +            }
 +        }
 +
 +        @Override
 +        public boolean drop(VDragEvent drag) {
 +            if (!getConnector().isEnabled()) {
 +                return false;
 +            }
 +            deEmphasis(true);
 +
 +            Map<String, Object> dd = drag.getDropDetails();
 +
 +            // this is absolute layout based, and we may want to set
 +            // component
 +            // relatively to where the drag ended.
 +            // need to add current location of the drop area
 +
 +            int absoluteLeft = getAbsoluteLeft();
 +            int absoluteTop = getAbsoluteTop();
 +
 +            dd.put("absoluteLeft", absoluteLeft);
 +            dd.put("absoluteTop", absoluteTop);
 +
 +            if (verticalDropLocation != null) {
 +                dd.put("verticalLocation", verticalDropLocation.toString());
 +                dd.put("horizontalLocation", horizontalDropLocation.toString());
 +            }
 +
 +            return super.drop(drag);
 +        }
 +
 +        @Override
 +        protected void dragAccepted(VDragEvent drag) {
 +            if (!getConnector().isEnabled()) {
 +                return;
 +            }
 +            currentlyValid = true;
 +            emphasis(drag);
 +        }
 +
 +        @Override
 +        public ComponentConnector getConnector() {
 +            return VDragAndDropWrapper.this.getConnector();
 +        }
 +
 +        @Override
 +        public ApplicationConnection getApplicationConnection() {
 +            return client;
 +        }
 +
 +    }
 +
 +    public ComponentConnector getConnector() {
 +        return ConnectorMap.get(client).getConnector(this);
 +    }
 +
 +    /**
 +     * @deprecated As of 7.2, call or override
 +     *             {@link #hookHtml5DragStart(Element)} instead
 +     */
 +    @Deprecated
 +    protected native void hookHtml5DragStart(
 +            com.google.gwt.user.client.Element el)
 +    /*-{
 +        var me = this;
 +        el.addEventListener("dragstart",  $entry(function(ev) {
 +            return me.@com.vaadin.client.ui.VDragAndDropWrapper::html5DragStart(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
 +        }), false);
 +    }-*/;
 +
 +    /**
 +     * @since 7.2
 +     */
 +    protected void hookHtml5DragStart(Element el) {
 +        hookHtml5DragStart(DOM.asOld(el));
 +    }
 +
 +    /**
 +     * Prototype code, memory leak risk.
 +     * 
 +     * @param el
 +     * @deprecated As of 7.2, call or override {@link #hookHtml5Events(Element)}
 +     *             instead
 +     */
 +    @Deprecated
 +    protected native void hookHtml5Events(com.google.gwt.user.client.Element el)
 +    /*-{
 +            var me = this;
 +
 +            el.addEventListener("dragenter",  $entry(function(ev) {
 +                return me.@com.vaadin.client.ui.VDragAndDropWrapper::html5DragEnter(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
 +            }), false);
 +
 +            el.addEventListener("dragleave",  $entry(function(ev) {
 +                return me.@com.vaadin.client.ui.VDragAndDropWrapper::html5DragLeave(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
 +            }), false);
 +
 +            el.addEventListener("dragover",  $entry(function(ev) {
 +                return me.@com.vaadin.client.ui.VDragAndDropWrapper::html5DragOver(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
 +            }), false);
 +
 +            el.addEventListener("drop",  $entry(function(ev) {
 +                return me.@com.vaadin.client.ui.VDragAndDropWrapper::html5DragDrop(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
 +            }), false);
 +    }-*/;
 +
 +    /**
 +     * Prototype code, memory leak risk.
 +     * 
 +     * @param el
 +     * 
 +     * @since 7.2
 +     */
 +    protected void hookHtml5Events(Element el) {
 +        hookHtml5Events(DOM.asOld(el));
 +    }
 +
 +    public boolean updateDropDetails(VDragEvent drag) {
 +        VerticalDropLocation oldVL = verticalDropLocation;
 +        verticalDropLocation = DDUtil.getVerticalDropLocation(getElement(),
 +                drag.getCurrentGwtEvent(), 0.2);
 +        drag.getDropDetails().put("verticalLocation",
 +                verticalDropLocation.toString());
 +        HorizontalDropLocation oldHL = horizontalDropLocation;
 +        horizontalDropLocation = DDUtil.getHorizontalDropLocation(getElement(),
 +                drag.getCurrentGwtEvent(), 0.2);
 +        drag.getDropDetails().put("horizontalLocation",
 +                horizontalDropLocation.toString());
 +        if (oldHL != horizontalDropLocation || oldVL != verticalDropLocation) {
 +            return true;
 +        } else {
 +            return false;
 +        }
 +    }
 +
 +    protected void deEmphasis(boolean doLayout) {
 +        if (emphasizedVDrop != null) {
 +            VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE, false);
 +            VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-"
 +                    + emphasizedVDrop.toString().toLowerCase(), false);
 +            VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-"
 +                    + emphasizedHDrop.toString().toLowerCase(), false);
 +        }
 +        if (doLayout) {
 +            notifySizePotentiallyChanged();
 +        }
 +    }
 +
 +    private void notifySizePotentiallyChanged() {
 +        LayoutManager.get(client).setNeedsMeasure(getConnector());
 +    }
 +
 +    protected void emphasis(VDragEvent drag) {
 +        deEmphasis(false);
 +        VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE, true);
 +        VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-"
 +                + verticalDropLocation.toString().toLowerCase(), true);
 +        VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-"
 +                + horizontalDropLocation.toString().toLowerCase(), true);
 +        emphasizedVDrop = verticalDropLocation;
 +        emphasizedHDrop = horizontalDropLocation;
 +
 +        // TODO build (to be an example) an emphasis mode where drag image
 +        // is fitted before or after the content
 +        notifySizePotentiallyChanged();
 +    }
 +
 +    /**
 +     * Set the widget that will be used as the drag image when using
 +     * DragStartMode {@link COMPONENT_OTHER} .
 +     * 
 +     * @param widget
 +     */
 +    public void setDragAndDropWidget(Widget widget) {
 +        dragImageWidget = widget;
 +    }
 +
 +    /**
 +     * @return the widget used as drag image. Returns <code>null</code> if no
 +     *         widget is set.
 +     */
 +    public Widget getDragImageWidget() {
 +        return dragImageWidget;
 +    }
 +
++    /**
++     * Internal client side interface used by the connector and the widget for
++     * the drag and drop wrapper to signal the completion of an HTML5 file
++     * upload.
++     * 
++     * @since 7.6.4
++     */
++    public interface UploadHandler {
++
++        public void uploadDone();
++
++    }
++
 +}
index 9459cc14a64a7215d2893ddd5cf526577d41d3af,0000000000000000000000000000000000000000..8a6b442fb8ad2b17c1de54982e2e41e68c0c5e9a
mode 100644,000000..100644
--- /dev/null
@@@ -1,2351 -1,0 +1,2371 @@@
 +/*
 + * Copyright 2000-2014 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;
 +
 +import java.util.ArrayList;
 +import java.util.Collection;
 +import java.util.Date;
 +import java.util.HashSet;
 +import java.util.Iterator;
 +import java.util.List;
 +import java.util.Set;
 +
 +import com.google.gwt.aria.client.Roles;
 +import com.google.gwt.core.client.JavaScriptObject;
 +import com.google.gwt.core.client.Scheduler;
 +import com.google.gwt.core.client.Scheduler.ScheduledCommand;
 +import com.google.gwt.dom.client.Element;
 +import com.google.gwt.dom.client.NativeEvent;
 +import com.google.gwt.dom.client.Style;
 +import com.google.gwt.dom.client.Style.Display;
 +import com.google.gwt.dom.client.Style.Unit;
 +import com.google.gwt.event.dom.client.BlurEvent;
 +import com.google.gwt.event.dom.client.BlurHandler;
 +import com.google.gwt.event.dom.client.ClickEvent;
 +import com.google.gwt.event.dom.client.ClickHandler;
 +import com.google.gwt.event.dom.client.FocusEvent;
 +import com.google.gwt.event.dom.client.FocusHandler;
 +import com.google.gwt.event.dom.client.KeyCodes;
 +import com.google.gwt.event.dom.client.KeyDownEvent;
 +import com.google.gwt.event.dom.client.KeyDownHandler;
 +import com.google.gwt.event.dom.client.KeyUpEvent;
 +import com.google.gwt.event.dom.client.KeyUpHandler;
 +import com.google.gwt.event.dom.client.LoadEvent;
 +import com.google.gwt.event.dom.client.LoadHandler;
 +import com.google.gwt.event.logical.shared.CloseEvent;
 +import com.google.gwt.event.logical.shared.CloseHandler;
 +import com.google.gwt.i18n.client.HasDirection.Direction;
 +import com.google.gwt.user.client.Command;
 +import com.google.gwt.user.client.DOM;
 +import com.google.gwt.user.client.Event;
 +import com.google.gwt.user.client.Timer;
 +import com.google.gwt.user.client.Window;
 +import com.google.gwt.user.client.ui.Composite;
 +import com.google.gwt.user.client.ui.FlowPanel;
 +import com.google.gwt.user.client.ui.HTML;
 +import com.google.gwt.user.client.ui.PopupPanel;
 +import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
 +import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
 +import com.google.gwt.user.client.ui.TextBox;
 +import com.google.gwt.user.client.ui.Widget;
 +import com.vaadin.client.ApplicationConnection;
 +import com.vaadin.client.BrowserInfo;
 +import com.vaadin.client.ComponentConnector;
 +import com.vaadin.client.ComputedStyle;
 +import com.vaadin.client.ConnectorMap;
 +import com.vaadin.client.DeferredWorker;
 +import com.vaadin.client.Focusable;
 +import com.vaadin.client.UIDL;
 +import com.vaadin.client.VConsole;
 +import com.vaadin.client.WidgetUtil;
 +import com.vaadin.client.ui.aria.AriaHelper;
 +import com.vaadin.client.ui.aria.HandlesAriaCaption;
 +import com.vaadin.client.ui.aria.HandlesAriaInvalid;
 +import com.vaadin.client.ui.aria.HandlesAriaRequired;
 +import com.vaadin.client.ui.menubar.MenuBar;
 +import com.vaadin.client.ui.menubar.MenuItem;
 +import com.vaadin.shared.AbstractComponentState;
 +import com.vaadin.shared.EventId;
 +import com.vaadin.shared.ui.ComponentStateUtil;
 +import com.vaadin.shared.ui.combobox.FilteringMode;
 +import com.vaadin.shared.util.SharedUtil;
 +
 +/**
 + * Client side implementation of the Select component.
 + * 
 + * TODO needs major refactoring (to be extensible etc)
 + */
 +@SuppressWarnings("deprecation")
 +public class VFilterSelect extends Composite implements Field, KeyDownHandler,
 +        KeyUpHandler, ClickHandler, FocusHandler, BlurHandler, Focusable,
 +        SubPartAware, HandlesAriaCaption, HandlesAriaInvalid,
 +        HandlesAriaRequired, DeferredWorker {
 +
 +    /**
 +     * Represents a suggestion in the suggestion popup box
 +     */
 +    public class FilterSelectSuggestion implements Suggestion, Command {
 +
 +        private final String key;
 +        private final String caption;
 +        private String untranslatedIconUri;
 +        private String style;
 +
 +        /**
 +         * Constructor
 +         * 
 +         * @param uidl
 +         *            The UIDL recieved from the server
 +         */
 +        public FilterSelectSuggestion(UIDL uidl) {
 +            key = uidl.getStringAttribute("key");
 +            caption = uidl.getStringAttribute("caption");
 +            style = uidl.getStringAttribute("style");
 +
 +            if (uidl.hasAttribute("icon")) {
 +                untranslatedIconUri = uidl.getStringAttribute("icon");
 +            }
 +        }
 +
 +        /**
 +         * Gets the visible row in the popup as a HTML string. The string
 +         * contains an image tag with the rows icon (if an icon has been
 +         * specified) and the caption of the item
 +         */
 +
 +        @Override
 +        public String getDisplayString() {
 +            final StringBuffer sb = new StringBuffer();
 +            final Icon icon = client.getIcon(client
 +                    .translateVaadinUri(untranslatedIconUri));
 +            if (icon != null) {
 +                sb.append(icon.getElement().getString());
 +            }
 +            String content;
 +            if ("".equals(caption)) {
 +                // Ensure that empty options use the same height as other
 +                // options and are not collapsed (#7506)
 +                content = "&nbsp;";
 +            } else {
 +                content = WidgetUtil.escapeHTML(caption);
 +            }
 +            sb.append("<span>" + content + "</span>");
 +            return sb.toString();
 +        }
 +
 +        /**
 +         * Get a string that represents this item. This is used in the text box.
 +         */
 +
 +        @Override
 +        public String getReplacementString() {
 +            return caption;
 +        }
 +
 +        /**
 +         * Get the option key which represents the item on the server side.
 +         * 
 +         * @return The key of the item
 +         */
 +        public String getOptionKey() {
 +            return key;
 +        }
 +
 +        /**
 +         * Get the URI of the icon. Used when constructing the displayed option.
 +         * 
 +         * @return
 +         */
 +        public String getIconUri() {
 +            return client.translateVaadinUri(untranslatedIconUri);
 +        }
 +
 +        /**
 +         * Gets the style set for this suggestion item. Styles are typically set
 +         * by a server-side {@link com.vaadin.ui.ComboBox.ItemStyleGenerator}.
 +         * The returned style is prefixed by <code>v-filterselect-item-</code>.
 +         * 
 +         * @since 7.5.6
 +         * @return the style name to use, or <code>null</code> to not apply any
 +         *         custom style.
 +         */
 +        public String getStyle() {
 +            return style;
 +        }
 +
 +        /**
 +         * Executes a selection of this item.
 +         */
 +
 +        @Override
 +        public void execute() {
 +            onSuggestionSelected(this);
 +        }
 +
 +        @Override
 +        public boolean equals(Object obj) {
 +            if (!(obj instanceof FilterSelectSuggestion)) {
 +                return false;
 +            }
 +            FilterSelectSuggestion other = (FilterSelectSuggestion) obj;
 +            if ((key == null && other.key != null)
 +                    || (key != null && !key.equals(other.key))) {
 +                return false;
 +            }
 +            if ((caption == null && other.caption != null)
 +                    || (caption != null && !caption.equals(other.caption))) {
 +                return false;
 +            }
 +            if (!SharedUtil.equals(untranslatedIconUri,
 +                    other.untranslatedIconUri)) {
 +                return false;
 +            }
 +            if (!SharedUtil.equals(style, other.style)) {
 +                return false;
 +            }
 +            return true;
 +        }
 +    }
 +
 +    /** An inner class that handles all logic related to mouse wheel. */
 +    private class MouseWheeler extends JsniMousewheelHandler {
 +
 +        public MouseWheeler() {
 +            super(VFilterSelect.this);
 +        }
 +
 +        @Override
 +        protected native JavaScriptObject createMousewheelListenerFunction(
 +                Widget widget)
 +        /*-{
 +            return $entry(function(e) {
 +                var deltaX = e.deltaX ? e.deltaX : -0.5*e.wheelDeltaX;
 +                var deltaY = e.deltaY ? e.deltaY : -0.5*e.wheelDeltaY;
 +                
 +                // IE8 has only delta y
 +                if (isNaN(deltaY)) {
 +                    deltaY = -0.5*e.wheelDelta;
 +                }
 +
 +                @com.vaadin.client.ui.VFilterSelect.JsniUtil::moveScrollFromEvent(*)(widget, deltaX, deltaY, e);
 +            });
 +        }-*/;
 +
 +    }
 +
 +    /**
 +     * A utility class that contains utility methods that are usually called
 +     * from JSNI.
 +     * <p>
 +     * The methods are moved in this class to minimize the amount of JSNI code
 +     * as much as feasible.
 +     */
 +    static class JsniUtil {
 +        public static void moveScrollFromEvent(final Widget widget,
 +                final double deltaX, final double deltaY,
 +                final NativeEvent event) {
 +
 +            if (!Double.isNaN(deltaY)) {
 +                ((VFilterSelect) widget).suggestionPopup.scroll(deltaY);
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Represents the popup box with the selection options. Wraps a suggestion
 +     * menu.
 +     */
 +    public class SuggestionPopup extends VOverlay implements PositionCallback,
 +            CloseHandler<PopupPanel> {
 +
 +        private static final int Z_INDEX = 30000;
 +
 +        /** For internal use only. May be removed or replaced in the future. */
 +        public final SuggestionMenu menu;
 +
 +        private final Element up = DOM.createDiv();
 +        private final Element down = DOM.createDiv();
 +        private final Element status = DOM.createDiv();
 +
 +        private boolean isPagingEnabled = true;
 +
 +        private long lastAutoClosed;
 +
 +        private int popupOuterPadding = -1;
 +
 +        private int topPosition;
 +
 +        private final MouseWheeler mouseWheeler = new MouseWheeler();
 +
 +        /**
 +         * Default constructor
 +         */
 +        SuggestionPopup() {
 +            super(true, false, true);
 +            debug("VFS.SP: constructor()");
 +            setOwner(VFilterSelect.this);
 +            menu = new SuggestionMenu();
 +            setWidget(menu);
 +
 +            getElement().getStyle().setZIndex(Z_INDEX);
 +
 +            final Element root = getContainerElement();
 +
 +            up.setInnerHTML("<span>Prev</span>");
 +            DOM.sinkEvents(up, Event.ONCLICK);
 +
 +            down.setInnerHTML("<span>Next</span>");
 +            DOM.sinkEvents(down, Event.ONCLICK);
 +
 +            root.insertFirst(up);
 +            root.appendChild(down);
 +            root.appendChild(status);
 +
 +            DOM.sinkEvents(root, Event.ONMOUSEDOWN | Event.ONMOUSEWHEEL);
 +            addCloseHandler(this);
 +
 +            Roles.getListRole().set(getElement());
 +
 +            setPreviewingAllNativeEvents(true);
 +        }
 +
 +        @Override
 +        protected void onLoad() {
 +            super.onLoad();
 +            mouseWheeler.attachMousewheelListener(getElement());
 +        }
 +
 +        @Override
 +        protected void onUnload() {
 +            mouseWheeler.detachMousewheelListener(getElement());
 +            super.onUnload();
 +        }
 +
 +        /**
 +         * Shows the popup where the user can see the filtered options
 +         * 
 +         * @param currentSuggestions
 +         *            The filtered suggestions
 +         * @param currentPage
 +         *            The current page number
 +         * @param totalSuggestions
 +         *            The total amount of suggestions
 +         */
 +        public void showSuggestions(
 +                final Collection<FilterSelectSuggestion> currentSuggestions,
 +                final int currentPage, final int totalSuggestions) {
 +
 +            debug("VFS.SP: showSuggestions(" + currentSuggestions + ", "
 +                    + currentPage + ", " + totalSuggestions + ")");
 +
 +            /*
 +             * We need to defer the opening of the popup so that the parent DOM
 +             * has stabilized so we can calculate an absolute top and left
 +             * correctly. This issue manifests when a Combobox is placed in
 +             * another popupView which also needs to calculate the absoluteTop()
 +             * to position itself. #9768
 +             * 
 +             * After deferring the showSuggestions method, a problem with
 +             * navigating in the combo box occurs. Because of that the method
 +             * navigateItemAfterPageChange in ComboBoxConnector class, which
 +             * navigates to the exact item after page was changed also was
 +             * marked as deferred. #11333
 +             */
 +            final SuggestionPopup popup = this;
 +            Scheduler.get().scheduleDeferred(new ScheduledCommand() {
 +                @Override
 +                public void execute() {
 +                    // Add TT anchor point
 +                    getElement().setId("VAADIN_COMBOBOX_OPTIONLIST");
 +
 +                    menu.setSuggestions(currentSuggestions);
 +                    final int x = VFilterSelect.this.getAbsoluteLeft();
 +
 +                    topPosition = tb.getAbsoluteTop();
 +                    topPosition += tb.getOffsetHeight();
 +
 +                    setPopupPosition(x, topPosition);
 +
 +                    int nullOffset = (nullSelectionAllowed
 +                            && "".equals(lastFilter) ? 1 : 0);
 +                    boolean firstPage = (currentPage == 0);
 +                    final int first = currentPage * pageLength + 1
 +                            - (firstPage ? 0 : nullOffset);
 +                    final int last = first
 +                            + currentSuggestions.size()
 +                            - 1
 +                            - (firstPage && "".equals(lastFilter) ? nullOffset
 +                                    : 0);
 +                    final int matches = totalSuggestions - nullOffset;
 +                    if (last > 0) {
 +                        // nullsel not counted, as requested by user
 +                        status.setInnerText((matches == 0 ? 0 : first) + "-"
 +                                + last + "/" + matches);
 +                    } else {
 +                        status.setInnerText("");
 +                    }
 +                    // We don't need to show arrows or statusbar if there is
 +                    // only one page
 +                    if (totalSuggestions <= pageLength || pageLength == 0) {
 +                        setPagingEnabled(false);
 +                    } else {
 +                        setPagingEnabled(true);
 +                    }
 +                    setPrevButtonActive(first > 1);
 +                    setNextButtonActive(last < matches);
 +
 +                    // clear previously fixed width
 +                    menu.setWidth("");
 +                    menu.getElement().getFirstChildElement().getStyle()
 +                            .clearWidth();
 +
 +                    setPopupPositionAndShow(popup);
 +                    // Fix for #14173
 +                    // IE9 and IE10 have a bug, when resize an a element with
 +                    // box-shadow.
 +                    // IE9 and IE10 need explicit update to remove extra
 +                    // box-shadows
 +                    if (BrowserInfo.get().isIE9() || BrowserInfo.get().isIE10()) {
 +                        forceReflow();
 +                    }
 +                }
 +            });
 +        }
 +
 +        /**
 +         * Should the next page button be visible to the user?
 +         * 
 +         * @param active
 +         */
 +        private void setNextButtonActive(boolean active) {
 +            if (enableDebug) {
 +                debug("VFS.SP: setNextButtonActive(" + active + ")");
 +            }
 +            if (active) {
 +                DOM.sinkEvents(down, Event.ONCLICK);
 +                down.setClassName(VFilterSelect.this.getStylePrimaryName()
 +                        + "-nextpage");
 +            } else {
 +                DOM.sinkEvents(down, 0);
 +                down.setClassName(VFilterSelect.this.getStylePrimaryName()
 +                        + "-nextpage-off");
 +            }
 +        }
 +
 +        /**
 +         * Should the previous page button be visible to the user
 +         * 
 +         * @param active
 +         */
 +        private void setPrevButtonActive(boolean active) {
 +            if (enableDebug) {
 +                debug("VFS.SP: setPrevButtonActive(" + active + ")");
 +            }
 +
 +            if (active) {
 +                DOM.sinkEvents(up, Event.ONCLICK);
 +                up.setClassName(VFilterSelect.this.getStylePrimaryName()
 +                        + "-prevpage");
 +            } else {
 +                DOM.sinkEvents(up, 0);
 +                up.setClassName(VFilterSelect.this.getStylePrimaryName()
 +                        + "-prevpage-off");
 +            }
 +
 +        }
 +
 +        /**
 +         * Selects the next item in the filtered selections
 +         */
 +        public void selectNextItem() {
 +            debug("VFS.SP: selectNextItem()");
 +
 +            final int index = menu.getSelectedIndex() + 1;
 +            if (menu.getItems().size() > index) {
 +                selectItem(menu.getItems().get(index));
 +
 +            } else {
 +                selectNextPage();
 +            }
 +        }
 +
 +        /**
 +         * Selects the previous item in the filtered selections
 +         */
 +        public void selectPrevItem() {
 +            debug("VFS.SP: selectPrevItem()");
 +
 +            final int index = menu.getSelectedIndex() - 1;
 +            if (index > -1) {
 +                selectItem(menu.getItems().get(index));
 +
 +            } else if (index == -1) {
 +                selectPrevPage();
 +
 +            } else {
 +                if (!menu.getItems().isEmpty()) {
 +                    selectLastItem();
 +                }
 +            }
 +        }
 +
 +        /**
 +         * Select the first item of the suggestions list popup.
 +         * 
 +         * @since 7.2.6
 +         */
 +        public void selectFirstItem() {
 +            debug("VFS.SP: selectFirstItem()");
 +            selectItem(menu.getFirstItem());
 +        }
 +
 +        /**
 +         * Select the last item of the suggestions list popup.
 +         * 
 +         * @since 7.2.6
 +         */
 +        public void selectLastItem() {
 +            debug("VFS.SP: selectLastItem()");
 +            selectItem(menu.getLastItem());
 +        }
 +
 +        /*
 +         * Sets the selected item in the popup menu.
 +         */
 +        private void selectItem(final MenuItem newSelectedItem) {
 +            menu.selectItem(newSelectedItem);
 +
 +            // Set the icon.
 +            FilterSelectSuggestion suggestion = (FilterSelectSuggestion) newSelectedItem
 +                    .getCommand();
 +            setSelectedItemIcon(suggestion.getIconUri());
 +
 +            // Set the text.
 +            setText(suggestion.getReplacementString());
 +
 +        }
 +
 +        /*
 +         * Using a timer to scroll up or down the pages so when we receive lots
 +         * of consecutive mouse wheel events the pages does not flicker.
 +         */
 +        private LazyPageScroller lazyPageScroller = new LazyPageScroller();
 +
 +        private class LazyPageScroller extends Timer {
 +            private int pagesToScroll = 0;
 +
 +            @Override
 +            public void run() {
 +                debug("VFS.SP.LPS: run()");
 +                if (pagesToScroll != 0) {
 +                    if (!waitingForFilteringResponse) {
 +                        /*
 +                         * Avoid scrolling while we are waiting for a response
 +                         * because otherwise the waiting flag will be reset in
 +                         * the first response and the second response will be
 +                         * ignored, causing an empty popup...
 +                         * 
 +                         * As long as the scrolling delay is suitable
 +                         * double/triple clicks will work by scrolling two or
 +                         * three pages at a time and this should not be a
 +                         * problem.
 +                         */
 +                        filterOptions(currentPage + pagesToScroll, lastFilter);
 +                    }
 +                    pagesToScroll = 0;
 +                }
 +            }
 +
 +            public void scrollUp() {
 +                debug("VFS.SP.LPS: scrollUp()");
 +                if (pageLength > 0 && currentPage + pagesToScroll > 0) {
 +                    pagesToScroll--;
 +                    cancel();
 +                    schedule(200);
 +                }
 +            }
 +
 +            public void scrollDown() {
 +                debug("VFS.SP.LPS: scrollDown()");
 +                if (pageLength > 0
 +                        && totalMatches > (currentPage + pagesToScroll + 1)
 +                                * pageLength) {
 +                    pagesToScroll++;
 +                    cancel();
 +                    schedule(200);
 +                }
 +            }
 +        }
 +
 +        private void scroll(double deltaY) {
 +            boolean scrollActive = menu.isScrollActive();
 +
 +            debug("VFS.SP: scroll() scrollActive: " + scrollActive);
 +
 +            if (!scrollActive) {
 +                if (deltaY > 0d) {
 +                    lazyPageScroller.scrollDown();
 +                } else {
 +                    lazyPageScroller.scrollUp();
 +                }
 +            }
 +        }
 +
 +        @Override
 +        public void onBrowserEvent(Event event) {
 +            debug("VFS.SP: onBrowserEvent()");
 +
 +            if (event.getTypeInt() == Event.ONCLICK) {
 +                final Element target = DOM.eventGetTarget(event);
 +                if (target == up || target == DOM.getChild(up, 0)) {
 +                    lazyPageScroller.scrollUp();
 +                } else if (target == down || target == DOM.getChild(down, 0)) {
 +                    lazyPageScroller.scrollDown();
 +                }
 +
 +            }
 +
 +            /*
 +             * Prevent the keyboard focus from leaving the textfield by
 +             * preventing the default behaviour of the browser. Fixes #4285.
 +             */
 +            handleMouseDownEvent(event);
 +        }
 +
 +        /**
 +         * Should paging be enabled. If paging is enabled then only a certain
 +         * amount of items are visible at a time and a scrollbar or buttons are
 +         * visible to change page. If paging is turned of then all options are
 +         * rendered into the popup menu.
 +         * 
 +         * @param paging
 +         *            Should the paging be turned on?
 +         */
 +        public void setPagingEnabled(boolean paging) {
 +            debug("VFS.SP: setPagingEnabled(" + paging + ")");
 +            if (isPagingEnabled == paging) {
 +                return;
 +            }
 +            if (paging) {
 +                down.getStyle().clearDisplay();
 +                up.getStyle().clearDisplay();
 +                status.getStyle().clearDisplay();
 +            } else {
 +                down.getStyle().setDisplay(Display.NONE);
 +                up.getStyle().setDisplay(Display.NONE);
 +                status.getStyle().setDisplay(Display.NONE);
 +            }
 +            isPagingEnabled = paging;
 +        }
 +
 +        @Override
 +        public void setPosition(int offsetWidth, int offsetHeight) {
 +            debug("VFS.SP: setPosition(" + offsetWidth + ", " + offsetHeight
 +                    + ")");
 +
 +            int top = topPosition;
 +            int left = getPopupLeft();
 +
 +            // reset menu size and retrieve its "natural" size
 +            menu.setHeight("");
 +            if (currentPage > 0 && !hasNextPage()) {
 +                // fix height to avoid height change when getting to last page
 +                menu.fixHeightTo(pageLength);
 +            }
 +
 +            final int desiredHeight = offsetHeight = getOffsetHeight();
 +            final int desiredWidth = getMainWidth();
 +
 +            debug("VFS.SP:     desired[" + desiredWidth + ", " + desiredHeight
 +                    + "]");
 +
 +            Element menuFirstChild = menu.getElement().getFirstChildElement();
 +            final int naturalMenuWidth = WidgetUtil
 +                    .getRequiredWidth(menuFirstChild);
 +
 +            if (popupOuterPadding == -1) {
 +                popupOuterPadding = WidgetUtil
 +                        .measureHorizontalPaddingAndBorder(getElement(), 2);
 +            }
 +
 +            if (naturalMenuWidth < desiredWidth) {
 +                menu.setWidth((desiredWidth - popupOuterPadding) + "px");
 +                menuFirstChild.getStyle().setWidth(100, Unit.PCT);
 +            }
 +
 +            if (BrowserInfo.get().isIE()
 +                    && BrowserInfo.get().getBrowserMajorVersion() < 11) {
 +                // Must take margin,border,padding manually into account for
 +                // menu element as we measure the element child and set width to
 +                // the element parent
 +                double naturalMenuOuterWidth = WidgetUtil
 +                        .getRequiredWidthDouble(menuFirstChild)
 +                        + getMarginBorderPaddingWidth(menu.getElement());
 +
 +                /*
 +                 * IE requires us to specify the width for the container
 +                 * element. Otherwise it will be 100% wide
 +                 */
 +                double rootWidth = Math.max(desiredWidth - popupOuterPadding,
 +                        naturalMenuOuterWidth);
 +                getContainerElement().getStyle().setWidth(rootWidth, Unit.PX);
 +            }
 +
 +            final int vfsHeight = VFilterSelect.this.getOffsetHeight();
 +            final int spaceAvailableAbove = top - vfsHeight;
 +            final int spaceAvailableBelow = Window.getClientHeight() - top;
 +            if (spaceAvailableBelow < offsetHeight
 +                    && spaceAvailableBelow < spaceAvailableAbove) {
 +                // popup on top of input instead
 +                top -= offsetHeight + vfsHeight;
 +                if (top < 0) {
 +                    offsetHeight += top;
 +                    top = 0;
 +                }
 +            } else {
 +                offsetHeight = Math.min(offsetHeight, spaceAvailableBelow);
 +            }
 +
 +            // fetch real width (mac FF bugs here due GWT popups overflow:auto )
 +            offsetWidth = menuFirstChild.getOffsetWidth();
 +
 +            if (offsetHeight < desiredHeight) {
 +                int menuHeight = offsetHeight;
 +                if (isPagingEnabled) {
 +                    menuHeight -= up.getOffsetHeight() + down.getOffsetHeight()
 +                            + status.getOffsetHeight();
 +                } else {
 +                    final ComputedStyle s = new ComputedStyle(menu.getElement());
 +                    menuHeight -= s.getIntProperty("marginBottom")
 +                            + s.getIntProperty("marginTop");
 +                }
 +
 +                // If the available page height is really tiny then this will be
 +                // negative and an exception will be thrown on setHeight.
 +                int menuElementHeight = menu.getItemOffsetHeight();
 +                if (menuHeight < menuElementHeight) {
 +                    menuHeight = menuElementHeight;
 +                }
 +
 +                menu.setHeight(menuHeight + "px");
 +
 +                final int naturalMenuWidthPlusScrollBar = naturalMenuWidth
 +                        + WidgetUtil.getNativeScrollbarSize();
 +                if (offsetWidth < naturalMenuWidthPlusScrollBar) {
 +                    menu.setWidth(naturalMenuWidthPlusScrollBar + "px");
 +                }
 +            }
 +
 +            if (offsetWidth + left > Window.getClientWidth()) {
 +                left = VFilterSelect.this.getAbsoluteLeft()
 +                        + VFilterSelect.this.getOffsetWidth() - offsetWidth;
 +                if (left < 0) {
 +                    left = 0;
 +                    menu.setWidth(Window.getClientWidth() + "px");
 +                }
 +            }
 +
 +            setPopupPosition(left, top);
 +            menu.scrollSelectionIntoView();
 +        }
 +
 +        /**
 +         * Was the popup just closed?
 +         * 
 +         * @return true if popup was just closed
 +         */
 +        public boolean isJustClosed() {
 +            debug("VFS.SP: justClosed()");
 +            final long now = (new Date()).getTime();
 +            return (lastAutoClosed > 0 && (now - lastAutoClosed) < 200);
 +        }
 +
 +        /*
 +         * (non-Javadoc)
 +         * 
 +         * @see
 +         * com.google.gwt.event.logical.shared.CloseHandler#onClose(com.google
 +         * .gwt.event.logical.shared.CloseEvent)
 +         */
 +
 +        @Override
 +        public void onClose(CloseEvent<PopupPanel> event) {
 +            if (enableDebug) {
 +                debug("VFS.SP: onClose(" + event.isAutoClosed() + ")");
 +            }
 +            if (event.isAutoClosed()) {
 +                lastAutoClosed = (new Date()).getTime();
 +            }
 +        }
 +
 +        /**
 +         * Updates style names in suggestion popup to help theme building.
 +         * 
 +         * @param uidl
 +         *            UIDL for the whole combo box
 +         * @param componentState
 +         *            shared state of the combo box
 +         */
 +        public void updateStyleNames(UIDL uidl,
 +                AbstractComponentState componentState) {
 +            debug("VFS.SP: updateStyleNames()");
 +            setStyleName(VFilterSelect.this.getStylePrimaryName()
 +                    + "-suggestpopup");
 +            menu.setStyleName(VFilterSelect.this.getStylePrimaryName()
 +                    + "-suggestmenu");
 +            status.setClassName(VFilterSelect.this.getStylePrimaryName()
 +                    + "-status");
 +            if (ComponentStateUtil.hasStyles(componentState)) {
 +                for (String style : componentState.styles) {
 +                    if (!"".equals(style)) {
 +                        addStyleDependentName(style);
 +                    }
 +                }
 +            }
 +        }
 +
 +    }
 +
 +    /**
 +     * The menu where the suggestions are rendered
 +     */
 +    public class SuggestionMenu extends MenuBar implements SubPartAware,
 +            LoadHandler {
 +
 +        private VLazyExecutor delayedImageLoadExecutioner = new VLazyExecutor(
 +                100, new ScheduledCommand() {
 +
 +                    @Override
 +                    public void execute() {
 +                        debug("VFS.SM: delayedImageLoadExecutioner()");
 +                        if (suggestionPopup.isVisible()
 +                                && suggestionPopup.isAttached()) {
 +                            setWidth("");
 +                            getElement().getFirstChildElement().getStyle()
 +                                    .clearWidth();
 +                            suggestionPopup
 +                                    .setPopupPositionAndShow(suggestionPopup);
 +                        }
 +
 +                    }
 +                });
 +
 +        /**
 +         * Default constructor
 +         */
 +        SuggestionMenu() {
 +            super(true);
 +            debug("VFS.SM: constructor()");
 +            addDomHandler(this, LoadEvent.getType());
 +
 +            setScrollEnabled(true);
 +        }
 +
 +        /**
 +         * Fixes menus height to use same space as full page would use. Needed
 +         * to avoid height changes when quickly "scrolling" to last page.
 +         */
 +        public void fixHeightTo(int pageItemsCount) {
 +            setHeight(getPreferredHeight(pageItemsCount));
 +        }
 +
 +        /*
 +         * Gets the preferred height of the menu including pageItemsCount items.
 +         */
 +        String getPreferredHeight(int pageItemsCount) {
 +            if (currentSuggestions.size() > 0) {
 +                final int pixels = (getPreferredHeight() / currentSuggestions
 +                        .size()) * pageItemsCount;
 +                return pixels + "px";
 +            } else {
 +                return "";
 +            }
 +        }
 +
 +        /**
 +         * Sets the suggestions rendered in the menu
 +         * 
 +         * @param suggestions
 +         *            The suggestions to be rendered in the menu
 +         */
 +        public void setSuggestions(
 +                Collection<FilterSelectSuggestion> suggestions) {
 +            if (enableDebug) {
 +                debug("VFS.SM: setSuggestions(" + suggestions + ")");
 +            }
 +
 +            clearItems();
 +            final Iterator<FilterSelectSuggestion> it = suggestions.iterator();
 +            boolean isFirstIteration = true;
 +            while (it.hasNext()) {
 +                final FilterSelectSuggestion s = it.next();
 +                final MenuItem mi = new MenuItem(s.getDisplayString(), true, s);
 +                String style = s.getStyle();
 +                if (style != null) {
 +                    mi.addStyleName("v-filterselect-item-" + style);
 +                }
 +                Roles.getListitemRole().set(mi.getElement());
 +
 +                WidgetUtil.sinkOnloadForImages(mi.getElement());
 +
 +                this.addItem(mi);
 +
 +                // By default, first item on the list is always highlighted,
 +                // unless adding new items is allowed.
 +                if (isFirstIteration && !allowNewItem) {
 +                    selectItem(mi);
 +                }
 +
 +                // If the filter matches the current selection, highlight that
 +                // instead of the first item.
 +                if (tb.getText().equals(s.getReplacementString())
 +                        && s == currentSuggestion) {
 +                    selectItem(mi);
 +                }
 +
 +                isFirstIteration = false;
 +            }
 +        }
 +
 +        /**
 +         * Send the current selection to the server. Triggered when a selection
 +         * is made or on a blur event.
 +         */
 +        public void doSelectedItemAction() {
 +            debug("VFS.SM: doSelectedItemAction()");
 +            // do not send a value change event if null was and stays selected
 +            final String enteredItemValue = tb.getText();
 +            if (nullSelectionAllowed && "".equals(enteredItemValue)
 +                    && selectedOptionKey != null
 +                    && !"".equals(selectedOptionKey)) {
 +                if (nullSelectItem) {
 +                    reset();
 +                    return;
 +                }
 +                // null is not visible on pages != 0, and not visible when
 +                // filtering: handle separately
 +                client.updateVariable(paintableId, "filter", "", false);
 +                client.updateVariable(paintableId, "page", 0, false);
 +                client.updateVariable(paintableId, "selected", new String[] {},
 +                        immediate);
 +                afterUpdateClientVariables();
 +
 +                suggestionPopup.hide();
 +                return;
 +            }
 +
 +            updateSelectionWhenReponseIsReceived = waitingForFilteringResponse;
 +            if (!waitingForFilteringResponse) {
 +                doPostFilterSelectedItemAction();
 +            }
 +        }
 +
 +        /**
 +         * Triggered after a selection has been made
 +         */
 +        public void doPostFilterSelectedItemAction() {
 +            debug("VFS.SM: doPostFilterSelectedItemAction()");
 +            final MenuItem item = getSelectedItem();
 +            final String enteredItemValue = tb.getText();
 +
 +            updateSelectionWhenReponseIsReceived = false;
 +
 +            // check for exact match in menu
 +            int p = getItems().size();
 +            if (p > 0) {
 +                for (int i = 0; i < p; i++) {
 +                    final MenuItem potentialExactMatch = getItems().get(i);
 +                    if (potentialExactMatch.getText().equals(enteredItemValue)) {
 +                        selectItem(potentialExactMatch);
 +                        // do not send a value change event if null was and
 +                        // stays selected
 +                        if (!"".equals(enteredItemValue)
 +                                || (selectedOptionKey != null && !""
 +                                        .equals(selectedOptionKey))) {
 +                            doItemAction(potentialExactMatch, true);
 +                        }
 +                        suggestionPopup.hide();
 +                        return;
 +                    }
 +                }
 +            }
 +            if (allowNewItem) {
 +
 +                if (!prompting && !enteredItemValue.equals(lastNewItemString)) {
 +                    /*
 +                     * Store last sent new item string to avoid double sends
 +                     */
 +                    lastNewItemString = enteredItemValue;
 +                    client.updateVariable(paintableId, "newitem",
 +                            enteredItemValue, immediate);
 +                    afterUpdateClientVariables();
 +                }
 +            } else if (item != null
 +                    && !"".equals(lastFilter)
 +                    && (filteringmode == FilteringMode.CONTAINS ? item
 +                            .getText().toLowerCase()
 +                            .contains(lastFilter.toLowerCase()) : item
 +                            .getText().toLowerCase()
 +                            .startsWith(lastFilter.toLowerCase()))) {
 +                doItemAction(item, true);
 +            } else {
 +                // currentSuggestion has key="" for nullselection
 +                if (currentSuggestion != null
 +                        && !currentSuggestion.key.equals("")) {
 +                    // An item (not null) selected
 +                    String text = currentSuggestion.getReplacementString();
 +                    setText(text);
 +                    selectedOptionKey = currentSuggestion.key;
 +                } else {
 +                    // Null selected
 +                    setText("");
 +                    selectedOptionKey = null;
 +                }
 +            }
 +            suggestionPopup.hide();
 +        }
 +
 +        private static final String SUBPART_PREFIX = "item";
 +
 +        @Override
 +        public com.google.gwt.user.client.Element getSubPartElement(
 +                String subPart) {
 +            int index = Integer.parseInt(subPart.substring(SUBPART_PREFIX
 +                    .length()));
 +
 +            MenuItem item = getItems().get(index);
 +
 +            return item.getElement();
 +        }
 +
 +        @Override
 +        public String getSubPartName(
 +                com.google.gwt.user.client.Element subElement) {
 +            if (!getElement().isOrHasChild(subElement)) {
 +                return null;
 +            }
 +
 +            Element menuItemRoot = subElement;
 +            while (menuItemRoot != null
 +                    && !menuItemRoot.getTagName().equalsIgnoreCase("td")) {
 +                menuItemRoot = menuItemRoot.getParentElement().cast();
 +            }
 +            // "menuItemRoot" is now the root of the menu item
 +
 +            final int itemCount = getItems().size();
 +            for (int i = 0; i < itemCount; i++) {
 +                if (getItems().get(i).getElement() == menuItemRoot) {
 +                    String name = SUBPART_PREFIX + i;
 +                    return name;
 +                }
 +            }
 +            return null;
 +        }
 +
 +        @Override
 +        public void onLoad(LoadEvent event) {
 +            debug("VFS.SM: onLoad()");
 +            // Handle icon onload events to ensure shadow is resized
 +            // correctly
 +            delayedImageLoadExecutioner.trigger();
 +
 +        }
 +
 +        /**
 +         * @deprecated use {@link SuggestionPopup#selectFirstItem()} instead.
 +         */
 +        @Deprecated
 +        public void selectFirstItem() {
 +            debug("VFS.SM: selectFirstItem()");
 +            MenuItem firstItem = getItems().get(0);
 +            selectItem(firstItem);
 +        }
 +
 +        /**
 +         * @deprecated use {@link SuggestionPopup#selectLastItem()} instead.
 +         */
 +        @Deprecated
 +        public void selectLastItem() {
 +            debug("VFS.SM: selectLastItem()");
 +            List<MenuItem> items = getItems();
 +            MenuItem lastItem = items.get(items.size() - 1);
 +            selectItem(lastItem);
 +        }
 +
 +        /*
 +         * Gets the height of one menu item.
 +         */
 +        int getItemOffsetHeight() {
 +            List<MenuItem> items = getItems();
 +            return items != null && items.size() > 0 ? items.get(0)
 +                    .getOffsetHeight() : 0;
 +        }
 +
 +        /*
 +         * Gets the width of one menu item.
 +         */
 +        int getItemOffsetWidth() {
 +            List<MenuItem> items = getItems();
 +            return items != null && items.size() > 0 ? items.get(0)
 +                    .getOffsetWidth() : 0;
 +        }
 +
 +        /**
 +         * Returns true if the scroll is active on the menu element or if the
 +         * menu currently displays the last page with less items then the
 +         * maximum visibility (in which case the scroll is not active, but the
 +         * scroll is active for any other page in general).
 +         * 
 +         * @since 7.2.6
 +         */
 +        @Override
 +        public boolean isScrollActive() {
 +            String height = getElement().getStyle().getHeight();
 +            String preferredHeight = getPreferredHeight(pageLength);
 +
 +            return !(height == null || height.length() == 0 || height
 +                    .equals(preferredHeight));
 +        }
 +
 +    }
 +
 +    /**
 +     * TextBox variant used as input element for filter selects, which prevents
 +     * selecting text when disabled.
 +     * 
 +     * @since 7.1.5
 +     */
 +    public class FilterSelectTextBox extends TextBox {
 +
++        /**
++         * Creates a new filter select text box.
++         * 
++         * @since 7.6.4
++         */
++        public FilterSelectTextBox() {
++            /*-
++             * Stop the browser from showing its own suggestion popup.
++             * 
++             * Using an invalid value instead of "off" as suggested by
++             * https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion
++             * 
++             * Leaving the non-standard Safari options autocapitalize and
++             * autocorrect untouched since those do not interfere in the same
++             * way, and they might be useful in a combo box where new items are
++             * allowed.
++             */
++            getElement().setAttribute("autocomplete", "nope");
++        }
++
 +        /**
 +         * Overridden to avoid selecting text when text input is disabled
 +         */
 +        @Override
 +        public void setSelectionRange(int pos, int length) {
 +            if (textInputEnabled) {
 +                /*
 +                 * set selection range with a backwards direction: anchor at the
 +                 * back, focus at the front. This means that items that are too
 +                 * long to display will display from the start and not the end
 +                 * even on Firefox.
 +                 * 
 +                 * We need the JSNI function to set selection range so that we
 +                 * can use the optional direction attribute to set the anchor to
 +                 * the end and the focus to the start. This makes Firefox work
 +                 * the same way as other browsers (#13477)
 +                 */
 +                WidgetUtil.setSelectionRange(getElement(), pos, length,
 +                        "backward");
 +
 +            } else {
 +                /*
 +                 * Setting the selectionrange for an uneditable textbox leads to
 +                 * unwanted behaviour when the width of the textbox is narrower
 +                 * than the width of the entry: the end of the entry is shown
 +                 * instead of the beginning. (see #13477)
 +                 * 
 +                 * To avoid this, we set the caret to the beginning of the line.
 +                 */
 +
 +                super.setSelectionRange(0, 0);
 +            }
 +        }
 +
 +    }
 +
 +    @Deprecated
 +    public static final FilteringMode FILTERINGMODE_OFF = FilteringMode.OFF;
 +    @Deprecated
 +    public static final FilteringMode FILTERINGMODE_STARTSWITH = FilteringMode.STARTSWITH;
 +    @Deprecated
 +    public static final FilteringMode FILTERINGMODE_CONTAINS = FilteringMode.CONTAINS;
 +
 +    public static final String CLASSNAME = "v-filterselect";
 +    private static final String STYLE_NO_INPUT = "no-input";
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public int pageLength = 10;
 +
 +    private boolean enableDebug = false;
 +
 +    private final FlowPanel panel = new FlowPanel();
 +
 +    /**
 +     * The text box where the filter is written
 +     * <p>
 +     * For internal use only. May be removed or replaced in the future.
 +     */
 +    public final TextBox tb;
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public final SuggestionPopup suggestionPopup;
 +
 +    /**
 +     * Used when measuring the width of the popup
 +     */
 +    private final HTML popupOpener = new HTML("") {
 +
 +        /*
 +         * (non-Javadoc)
 +         * 
 +         * @see
 +         * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt
 +         * .user.client.Event)
 +         */
 +
 +        @Override
 +        public void onBrowserEvent(Event event) {
 +            super.onBrowserEvent(event);
 +
 +            /*
 +             * Prevent the keyboard focus from leaving the textfield by
 +             * preventing the default behaviour of the browser. Fixes #4285.
 +             */
 +            handleMouseDownEvent(event);
 +        }
 +    };
 +
 +    private class IconWidget extends Widget {
 +        IconWidget(Icon icon) {
 +            setElement(icon.getElement());
 +            addDomHandler(VFilterSelect.this, ClickEvent.getType());
 +        }
 +    }
 +
 +    private IconWidget selectedItemIcon;
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public ApplicationConnection client;
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public String paintableId;
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public int currentPage;
 +
 +    /**
 +     * A collection of available suggestions (options) as received from the
 +     * server.
 +     * <p>
 +     * For internal use only. May be removed or replaced in the future.
 +     */
 +    public final List<FilterSelectSuggestion> currentSuggestions = new ArrayList<FilterSelectSuggestion>();
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public boolean immediate;
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public String selectedOptionKey;
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public boolean waitingForFilteringResponse = false;
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public boolean updateSelectionWhenReponseIsReceived = false;
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public boolean initDone = false;
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public String lastFilter = "";
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public enum Select {
 +        NONE, FIRST, LAST
 +    }
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public Select selectPopupItemWhenResponseIsReceived = Select.NONE;
 +
 +    /**
 +     * The current suggestion selected from the dropdown. This is one of the
 +     * values in currentSuggestions except when filtering, in this case
 +     * currentSuggestion might not be in currentSuggestions.
 +     * <p>
 +     * For internal use only. May be removed or replaced in the future.
 +     */
 +    public FilterSelectSuggestion currentSuggestion;
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public boolean allowNewItem;
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public int totalMatches;
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public boolean nullSelectionAllowed;
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public boolean nullSelectItem;
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public boolean enabled;
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public boolean readonly;
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public FilteringMode filteringmode = FilteringMode.OFF;
 +
 +    // shown in unfocused empty field, disappears on focus (e.g "Search here")
 +    private static final String CLASSNAME_PROMPT = "prompt";
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public String inputPrompt = "";
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public boolean prompting = false;
 +
 +    /**
 +     * Set true when popupopened has been clicked. Cleared on each UIDL-update.
 +     * This handles the special case where are not filtering yet and the
 +     * selected value has changed on the server-side. See #2119
 +     * <p>
 +     * For internal use only. May be removed or replaced in the future.
 +     */
 +    public boolean popupOpenerClicked;
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public int suggestionPopupMinWidth = 0;
 +
 +    private int popupWidth = -1;
 +    /**
 +     * Stores the last new item string to avoid double submissions. Cleared on
 +     * uidl updates.
 +     * <p>
 +     * For internal use only. May be removed or replaced in the future.
 +     */
 +    public String lastNewItemString;
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public boolean focused = false;
 +
 +    /**
 +     * If set to false, the component should not allow entering text to the
 +     * field even for filtering.
 +     */
 +    private boolean textInputEnabled = true;
 +
 +    /**
 +     * Default constructor.
 +     */
 +    public VFilterSelect() {
 +        tb = createTextBox();
 +        suggestionPopup = createSuggestionPopup();
 +
 +        popupOpener.sinkEvents(Event.ONMOUSEDOWN);
 +        Roles.getButtonRole()
 +                .setAriaHiddenState(popupOpener.getElement(), true);
 +        Roles.getButtonRole().set(popupOpener.getElement());
 +
 +        panel.add(tb);
 +        panel.add(popupOpener);
 +        initWidget(panel);
 +        Roles.getComboboxRole().set(panel.getElement());
 +
 +        tb.addKeyDownHandler(this);
 +        tb.addKeyUpHandler(this);
 +
 +        tb.addFocusHandler(this);
 +        tb.addBlurHandler(this);
 +        tb.addClickHandler(this);
 +
 +        popupOpener.addClickHandler(this);
 +
 +        setStyleName(CLASSNAME);
 +
 +        sinkEvents(Event.ONPASTE);
 +    }
 +
 +    private static double getMarginBorderPaddingWidth(Element element) {
 +        final ComputedStyle s = new ComputedStyle(element);
 +        return s.getMarginWidth() + s.getBorderWidth() + s.getPaddingWidth();
 +
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see
 +     * com.google.gwt.user.client.ui.Composite#onBrowserEvent(com.google.gwt
 +     * .user.client.Event)
 +     */
 +    @Override
 +    public void onBrowserEvent(Event event) {
 +        super.onBrowserEvent(event);
 +
 +        if (event.getTypeInt() == Event.ONPASTE) {
 +            if (textInputEnabled) {
 +                Scheduler.get().scheduleDeferred(new ScheduledCommand() {
 +
 +                    @Override
 +                    public void execute() {
 +                        filterOptions(currentPage);
 +                    }
 +                });
 +            }
 +        }
 +    }
 +
 +    /**
 +     * This method will create the TextBox used by the VFilterSelect instance.
 +     * It is invoked during the Constructor and should only be overridden if a
 +     * custom TextBox shall be used. The overriding method cannot use any
 +     * instance variables.
 +     * 
 +     * @since 7.1.5
 +     * @return TextBox instance used by this VFilterSelect
 +     */
 +    protected TextBox createTextBox() {
 +        return new FilterSelectTextBox();
 +    }
 +
 +    /**
 +     * This method will create the SuggestionPopup used by the VFilterSelect
 +     * instance. It is invoked during the Constructor and should only be
 +     * overridden if a custom SuggestionPopup shall be used. The overriding
 +     * method cannot use any instance variables.
 +     * 
 +     * @since 7.1.5
 +     * @return SuggestionPopup instance used by this VFilterSelect
 +     */
 +    protected SuggestionPopup createSuggestionPopup() {
 +        return new SuggestionPopup();
 +    }
 +
 +    @Override
 +    public void setStyleName(String style) {
 +        super.setStyleName(style);
 +        updateStyleNames();
 +    }
 +
 +    @Override
 +    public void setStylePrimaryName(String style) {
 +        super.setStylePrimaryName(style);
 +        updateStyleNames();
 +    }
 +
 +    protected void updateStyleNames() {
 +        tb.setStyleName(getStylePrimaryName() + "-input");
 +        popupOpener.setStyleName(getStylePrimaryName() + "-button");
 +        suggestionPopup.setStyleName(getStylePrimaryName() + "-suggestpopup");
 +    }
 +
 +    /**
 +     * Does the Select have more pages?
 +     * 
 +     * @return true if a next page exists, else false if the current page is the
 +     *         last page
 +     */
 +    public boolean hasNextPage() {
 +        if (pageLength > 0 && totalMatches > (currentPage + 1) * pageLength) {
 +            return true;
 +        } else {
 +            return false;
 +        }
 +    }
 +
 +    /**
 +     * Filters the options at a certain page. Uses the text box input as a
 +     * filter
 +     * 
 +     * @param page
 +     *            The page which items are to be filtered
 +     */
 +    public void filterOptions(int page) {
 +        filterOptions(page, tb.getText());
 +    }
 +
 +    /**
 +     * Filters the options at certain page using the given filter
 +     * 
 +     * @param page
 +     *            The page to filter
 +     * @param filter
 +     *            The filter to apply to the components
 +     */
 +    public void filterOptions(int page, String filter) {
 +        filterOptions(page, filter, true);
 +    }
 +
 +    /**
 +     * Filters the options at certain page using the given filter
 +     * 
 +     * @param page
 +     *            The page to filter
 +     * @param filter
 +     *            The filter to apply to the options
 +     * @param immediate
 +     *            Whether to send the options request immediately
 +     */
 +    private void filterOptions(int page, String filter, boolean immediate) {
 +        debug("VFS: filterOptions(" + page + ", " + filter + ", " + immediate
 +                + ")");
 +
 +        if (filter.equals(lastFilter) && currentPage == page) {
 +            if (!suggestionPopup.isAttached()) {
 +                suggestionPopup.showSuggestions(currentSuggestions,
 +                        currentPage, totalMatches);
 +            }
 +            return;
 +        }
 +        if (!filter.equals(lastFilter)) {
 +            // when filtering, let the server decide the page unless we've
 +            // set the filter to empty and explicitly said that we want to see
 +            // the results starting from page 0.
 +            if ("".equals(filter) && page != 0) {
 +                // let server decide
 +                page = -1;
 +            } else {
 +                page = 0;
 +            }
 +        }
 +
 +        waitingForFilteringResponse = true;
 +        client.updateVariable(paintableId, "filter", filter, false);
 +        client.updateVariable(paintableId, "page", page, immediate);
 +        afterUpdateClientVariables();
 +
 +        lastFilter = filter;
 +        currentPage = page;
 +    }
 +
 +    /** For internal use only. May be removed or replaced in the future. */
 +    public void updateReadOnly() {
 +        debug("VFS: updateReadOnly()");
 +        tb.setReadOnly(readonly || !textInputEnabled);
 +    }
 +
 +    public void setTextInputEnabled(boolean textInputEnabled) {
 +        debug("VFS: setTextInputEnabled()");
 +        // Always update styles as they might have been overwritten
 +        if (textInputEnabled) {
 +            removeStyleDependentName(STYLE_NO_INPUT);
 +            Roles.getTextboxRole().removeAriaReadonlyProperty(tb.getElement());
 +        } else {
 +            addStyleDependentName(STYLE_NO_INPUT);
 +            Roles.getTextboxRole().setAriaReadonlyProperty(tb.getElement(),
 +                    true);
 +        }
 +
 +        if (this.textInputEnabled == textInputEnabled) {
 +            return;
 +        }
 +
 +        this.textInputEnabled = textInputEnabled;
 +        updateReadOnly();
 +    }
 +
 +    /**
 +     * Sets the text in the text box.
 +     * 
 +     * @param text
 +     *            the text to set in the text box
 +     */
 +    public void setTextboxText(final String text) {
 +        if (enableDebug) {
 +            debug("VFS: setTextboxText(" + text + ")");
 +        }
 +        setText(text);
 +    }
 +
 +    private void setText(final String text) {
 +        /**
 +         * To leave caret in the beginning of the line. SetSelectionRange
 +         * wouldn't work on IE (see #13477)
 +         */
 +        Direction previousDirection = tb.getDirection();
 +        tb.setDirection(Direction.RTL);
 +        tb.setText(text);
 +        tb.setDirection(previousDirection);
 +    }
 +
 +    /**
 +     * Turns prompting on. When prompting is turned on a command prompt is shown
 +     * in the text box if nothing has been entered.
 +     */
 +    public void setPromptingOn() {
 +        debug("VFS: setPromptingOn()");
 +        if (!prompting) {
 +            prompting = true;
 +            addStyleDependentName(CLASSNAME_PROMPT);
 +        }
 +        setTextboxText(inputPrompt);
 +    }
 +
 +    /**
 +     * Turns prompting off. When prompting is turned on a command prompt is
 +     * shown in the text box if nothing has been entered.
 +     * <p>
 +     * For internal use only. May be removed or replaced in the future.
 +     * 
 +     * @param text
 +     *            The text the text box should contain.
 +     */
 +    public void setPromptingOff(String text) {
 +        debug("VFS: setPromptingOff()");
 +        setTextboxText(text);
 +        if (prompting) {
 +            prompting = false;
 +            removeStyleDependentName(CLASSNAME_PROMPT);
 +        }
 +    }
 +
 +    /**
 +     * Triggered when a suggestion is selected
 +     * 
 +     * @param suggestion
 +     *            The suggestion that just got selected.
 +     */
 +    public void onSuggestionSelected(FilterSelectSuggestion suggestion) {
 +        if (enableDebug) {
 +            debug("VFS: onSuggestionSelected(" + suggestion.caption + ": "
 +                    + suggestion.key + ")");
 +        }
 +        updateSelectionWhenReponseIsReceived = false;
 +
 +        currentSuggestion = suggestion;
 +        String newKey;
 +        if (suggestion.key.equals("")) {
 +            // "nullselection"
 +            newKey = "";
 +        } else {
 +            // normal selection
 +            newKey = suggestion.getOptionKey();
 +        }
 +
 +        String text = suggestion.getReplacementString();
 +        if ("".equals(newKey) && !focused) {
 +            setPromptingOn();
 +        } else {
 +            setPromptingOff(text);
 +        }
 +        setSelectedItemIcon(suggestion.getIconUri());
 +
 +        if (!(newKey.equals(selectedOptionKey) || ("".equals(newKey) && selectedOptionKey == null))) {
 +            selectedOptionKey = newKey;
 +            client.updateVariable(paintableId, "selected",
 +                    new String[] { selectedOptionKey }, immediate);
 +            afterUpdateClientVariables();
 +
 +            // currentPage = -1; // forget the page
 +        }
 +        suggestionPopup.hide();
 +    }
 +
 +    /**
 +     * Sets the icon URI of the selected item. The icon is shown on the left
 +     * side of the item caption text. Set the URI to null to remove the icon.
 +     * 
 +     * @param iconUri
 +     *            The URI of the icon
 +     */
 +    public void setSelectedItemIcon(String iconUri) {
 +
 +        if (iconUri == null || iconUri.length() == 0) {
 +            if (selectedItemIcon != null) {
 +                panel.remove(selectedItemIcon);
 +                selectedItemIcon = null;
 +                afterSelectedItemIconChange();
 +            }
 +        } else {
 +            if (selectedItemIcon != null) {
 +                panel.remove(selectedItemIcon);
 +            }
 +            selectedItemIcon = new IconWidget(client.getIcon(iconUri));
 +            // Older IE versions don't scale icon correctly if DOM
 +            // contains height and width attributes.
 +            selectedItemIcon.getElement().removeAttribute("height");
 +            selectedItemIcon.getElement().removeAttribute("width");
 +            selectedItemIcon.addDomHandler(new LoadHandler() {
 +                @Override
 +                public void onLoad(LoadEvent event) {
 +                    afterSelectedItemIconChange();
 +                }
 +            }, LoadEvent.getType());
 +            panel.insert(selectedItemIcon, 0);
 +            afterSelectedItemIconChange();
 +        }
 +    }
 +
 +    private void afterSelectedItemIconChange() {
 +        if (BrowserInfo.get().isWebkit() || BrowserInfo.get().isIE8()) {
 +            // Some browsers need a nudge to reposition the text field
 +            forceReflow();
 +        }
 +        updateRootWidth();
 +        if (selectedItemIcon != null) {
 +            updateSelectedIconPosition();
 +        }
 +    }
 +
 +    private void forceReflow() {
 +        WidgetUtil.setStyleTemporarily(tb.getElement(), "zoom", "1");
 +    }
 +
 +    /**
 +     * Positions the icon vertically in the middle. Should be called after the
 +     * icon has loaded
 +     */
 +    private void updateSelectedIconPosition() {
 +        // Position icon vertically to middle
 +        int availableHeight = 0;
 +        availableHeight = getOffsetHeight();
 +
 +        int iconHeight = WidgetUtil.getRequiredHeight(selectedItemIcon);
 +        int marginTop = (availableHeight - iconHeight) / 2;
 +        selectedItemIcon.getElement().getStyle()
 +                .setMarginTop(marginTop, Unit.PX);
 +    }
 +
 +    private static Set<Integer> navigationKeyCodes = new HashSet<Integer>();
 +    static {
 +        navigationKeyCodes.add(KeyCodes.KEY_DOWN);
 +        navigationKeyCodes.add(KeyCodes.KEY_UP);
 +        navigationKeyCodes.add(KeyCodes.KEY_PAGEDOWN);
 +        navigationKeyCodes.add(KeyCodes.KEY_PAGEUP);
 +        navigationKeyCodes.add(KeyCodes.KEY_ENTER);
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see
 +     * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
 +     * .event.dom.client.KeyDownEvent)
 +     */
 +
 +    @Override
 +    public void onKeyDown(KeyDownEvent event) {
 +        if (enabled && !readonly) {
 +            int keyCode = event.getNativeKeyCode();
 +
 +            if (enableDebug) {
 +                debug("VFS: key down: " + keyCode);
 +            }
 +            if (waitingForFilteringResponse
 +                    && navigationKeyCodes.contains(keyCode)) {
 +                /*
 +                 * Keyboard navigation events should not be handled while we are
 +                 * waiting for a response. This avoids flickering, disappearing
 +                 * items, wrongly interpreted responses and more.
 +                 */
 +                if (enableDebug) {
 +                    debug("Ignoring "
 +                            + keyCode
 +                            + " because we are waiting for a filtering response");
 +                }
 +                DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
 +                event.stopPropagation();
 +                return;
 +            }
 +
 +            if (suggestionPopup.isAttached()) {
 +                if (enableDebug) {
 +                    debug("Keycode " + keyCode + " target is popup");
 +                }
 +                popupKeyDown(event);
 +            } else {
 +                if (enableDebug) {
 +                    debug("Keycode " + keyCode + " target is text field");
 +                }
 +                inputFieldKeyDown(event);
 +            }
 +        }
 +    }
 +
 +    private void debug(String string) {
 +        if (enableDebug) {
 +            VConsole.error(string);
 +        }
 +    }
 +
 +    /**
 +     * Triggered when a key is pressed in the text box
 +     * 
 +     * @param event
 +     *            The KeyDownEvent
 +     */
 +    private void inputFieldKeyDown(KeyDownEvent event) {
 +        if (enableDebug) {
 +            debug("VFS: inputFieldKeyDown(" + event.getNativeKeyCode() + ")");
 +        }
 +        switch (event.getNativeKeyCode()) {
 +        case KeyCodes.KEY_DOWN:
 +        case KeyCodes.KEY_UP:
 +        case KeyCodes.KEY_PAGEDOWN:
 +        case KeyCodes.KEY_PAGEUP:
 +            // open popup as from gadget
 +            filterOptions(-1, "");
 +            lastFilter = "";
 +            tb.selectAll();
 +            break;
 +        case KeyCodes.KEY_ENTER:
 +            /*
 +             * This only handles the case when new items is allowed, a text is
 +             * entered, the popup opener button is clicked to close the popup
 +             * and enter is then pressed (see #7560).
 +             */
 +            if (!allowNewItem) {
 +                return;
 +            }
 +
 +            if (currentSuggestion != null
 +                    && tb.getText().equals(
 +                            currentSuggestion.getReplacementString())) {
 +                // Retain behavior from #6686 by returning without stopping
 +                // propagation if there's nothing to do
 +                return;
 +            }
 +            suggestionPopup.menu.doSelectedItemAction();
 +
 +            event.stopPropagation();
 +            break;
 +        }
 +
 +    }
 +
 +    /**
 +     * Triggered when a key was pressed in the suggestion popup.
 +     * 
 +     * @param event
 +     *            The KeyDownEvent of the key
 +     */
 +    private void popupKeyDown(KeyDownEvent event) {
 +        if (enableDebug) {
 +            debug("VFS: popupKeyDown(" + event.getNativeKeyCode() + ")");
 +        }
 +        // Propagation of handled events is stopped so other handlers such as
 +        // shortcut key handlers do not also handle the same events.
 +        switch (event.getNativeKeyCode()) {
 +        case KeyCodes.KEY_DOWN:
 +            suggestionPopup.selectNextItem();
 +
 +            DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
 +            event.stopPropagation();
 +            break;
 +        case KeyCodes.KEY_UP:
 +            suggestionPopup.selectPrevItem();
 +
 +            DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
 +            event.stopPropagation();
 +            break;
 +        case KeyCodes.KEY_PAGEDOWN:
 +            selectNextPage();
 +            event.stopPropagation();
 +            break;
 +        case KeyCodes.KEY_PAGEUP:
 +            selectPrevPage();
 +            event.stopPropagation();
 +            break;
 +        case KeyCodes.KEY_ESCAPE:
 +            reset();
 +            DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
 +            event.stopPropagation();
 +            break;
 +        case KeyCodes.KEY_TAB:
 +        case KeyCodes.KEY_ENTER:
 +
 +            if (!allowNewItem) {
 +                int selected = suggestionPopup.menu.getSelectedIndex();
 +                if (selected != -1) {
 +                    onSuggestionSelected(currentSuggestions.get(selected));
 +                } else {
 +                    // The way VFilterSelect is done, it handles enter and tab
 +                    // in exactly the same way so we close the popup in both
 +                    // cases even though we could leave it open when pressing
 +                    // enter
 +                    suggestionPopup.hide();
 +                }
 +            } else {
 +                // Handle addition of new items.
 +                suggestionPopup.menu.doSelectedItemAction();
 +            }
 +
 +            event.stopPropagation();
 +            break;
 +        }
 +
 +    }
 +
 +    /*
 +     * Show the prev page.
 +     */
 +    private void selectPrevPage() {
 +        if (currentPage > 0) {
 +            filterOptions(currentPage - 1, lastFilter);
 +            selectPopupItemWhenResponseIsReceived = Select.LAST;
 +        }
 +    }
 +
 +    /*
 +     * Show the next page.
 +     */
 +    private void selectNextPage() {
 +        if (hasNextPage()) {
 +            filterOptions(currentPage + 1, lastFilter);
 +            selectPopupItemWhenResponseIsReceived = Select.FIRST;
 +        }
 +    }
 +
 +    /**
 +     * Triggered when a key was depressed
 +     * 
 +     * @param event
 +     *            The KeyUpEvent of the key depressed
 +     */
 +
 +    @Override
 +    public void onKeyUp(KeyUpEvent event) {
 +        if (enableDebug) {
 +            debug("VFS: onKeyUp(" + event.getNativeKeyCode() + ")");
 +        }
 +        if (enabled && !readonly) {
 +            switch (event.getNativeKeyCode()) {
 +            case KeyCodes.KEY_ENTER:
 +            case KeyCodes.KEY_TAB:
 +            case KeyCodes.KEY_SHIFT:
 +            case KeyCodes.KEY_CTRL:
 +            case KeyCodes.KEY_ALT:
 +            case KeyCodes.KEY_DOWN:
 +            case KeyCodes.KEY_UP:
 +            case KeyCodes.KEY_PAGEDOWN:
 +            case KeyCodes.KEY_PAGEUP:
 +            case KeyCodes.KEY_ESCAPE:
 +                // NOP
 +                break;
 +            default:
 +                if (textInputEnabled) {
 +                    // when filtering, we always want to see the results on the
 +                    // first page first.
 +                    filterOptions(0);
 +                }
 +                break;
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Resets the Select to its initial state
 +     */
 +    private void reset() {
 +        debug("VFS: reset()");
 +        if (currentSuggestion != null) {
 +            String text = currentSuggestion.getReplacementString();
 +            setPromptingOff(text);
 +            setSelectedItemIcon(currentSuggestion.getIconUri());
 +
 +            selectedOptionKey = currentSuggestion.key;
 +
 +        } else {
 +            if (focused || readonly || !enabled) {
 +                setPromptingOff("");
 +            } else {
 +                setPromptingOn();
 +            }
 +            setSelectedItemIcon(null);
 +
 +            selectedOptionKey = null;
 +        }
 +
 +        lastFilter = "";
 +        suggestionPopup.hide();
 +    }
 +
 +    /**
 +     * Listener for popupopener
 +     */
 +
 +    @Override
 +    public void onClick(ClickEvent event) {
 +        debug("VFS: onClick()");
 +        if (textInputEnabled
 +                && event.getNativeEvent().getEventTarget().cast() == tb
 +                        .getElement()) {
 +            // Don't process clicks on the text field if text input is enabled
 +            return;
 +        }
 +        if (enabled && !readonly) {
 +            // ask suggestionPopup if it was just closed, we are using GWT
 +            // Popup's auto close feature
 +            if (!suggestionPopup.isJustClosed()) {
 +                // If a focus event is not going to be sent, send the options
 +                // request immediately; otherwise queue in the same burst as the
 +                // focus event. Fixes #8321.
 +                boolean immediate = focused
 +                        || !client.hasEventListeners(this, EventId.FOCUS);
 +                filterOptions(-1, "", immediate);
 +                popupOpenerClicked = true;
 +                lastFilter = "";
 +            }
 +            DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
 +            focus();
 +            tb.selectAll();
 +        }
 +    }
 +
 +    /**
 +     * Update minimum width for FilterSelect textarea based on input prompt and
 +     * suggestions.
 +     * <p>
 +     * For internal use only. May be removed or replaced in the future.
 +     */
 +    public void updateSuggestionPopupMinWidth() {
 +        // used only to calculate minimum width
 +        String captions = WidgetUtil.escapeHTML(inputPrompt);
 +
 +        for (FilterSelectSuggestion suggestion : currentSuggestions) {
 +            // Collect captions so we can calculate minimum width for
 +            // textarea
 +            if (captions.length() > 0) {
 +                captions += "|";
 +            }
 +            captions += WidgetUtil
 +                    .escapeHTML(suggestion.getReplacementString());
 +        }
 +
 +        // Calculate minimum textarea width
 +        suggestionPopupMinWidth = minWidth(captions);
 +    }
 +
 +    /**
 +     * Calculate minimum width for FilterSelect textarea.
 +     * <p>
 +     * For internal use only. May be removed or replaced in the future.
 +     */
 +    public native int minWidth(String captions)
 +    /*-{
 +        if(!captions || captions.length <= 0)
 +                return 0;
 +        captions = captions.split("|");
 +        var d = $wnd.document.createElement("div");
 +        var html = "";
 +        for(var i=0; i < captions.length; i++) {
 +                html += "<div>" + captions[i] + "</div>";
 +                // TODO apply same CSS classname as in suggestionmenu
 +        }
 +        d.style.position = "absolute";
 +        d.style.top = "0";
 +        d.style.left = "0";
 +        d.style.visibility = "hidden";
 +        d.innerHTML = html;
 +        $wnd.document.body.appendChild(d);
 +        var w = d.offsetWidth;
 +        $wnd.document.body.removeChild(d);
 +        return w;
 +    }-*/;
 +
 +    /**
 +     * A flag which prevents a focus event from taking place
 +     */
 +    boolean iePreventNextFocus = false;
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see
 +     * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
 +     * .dom.client.FocusEvent)
 +     */
 +
 +    @Override
 +    public void onFocus(FocusEvent event) {
 +        debug("VFS: onFocus()");
 +
 +        /*
 +         * When we disable a blur event in ie we need to refocus the textfield.
 +         * This will cause a focus event we do not want to process, so in that
 +         * case we just ignore it.
 +         */
 +        if (BrowserInfo.get().isIE() && iePreventNextFocus) {
 +            iePreventNextFocus = false;
 +            return;
 +        }
 +
 +        focused = true;
 +        if (prompting && !readonly) {
 +            setPromptingOff("");
 +        }
 +        addStyleDependentName("focus");
 +
 +        if (client.hasEventListeners(this, EventId.FOCUS)) {
 +            client.updateVariable(paintableId, EventId.FOCUS, "", true);
 +            afterUpdateClientVariables();
 +        }
 +
 +        ComponentConnector connector = ConnectorMap.get(client).getConnector(
 +                this);
 +        client.getVTooltip().showAssistive(
 +                connector.getTooltipInfo(getElement()));
 +    }
 +
 +    /**
 +     * A flag which cancels the blur event and sets the focus back to the
 +     * textfield if the Browser is IE
 +     */
 +    boolean preventNextBlurEventInIE = false;
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see
 +     * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event
 +     * .dom.client.BlurEvent)
 +     */
 +
 +    @Override
 +    public void onBlur(BlurEvent event) {
 +        debug("VFS: onBlur()");
 +
 +        if (BrowserInfo.get().isIE() && preventNextBlurEventInIE) {
 +            /*
 +             * Clicking in the suggestion popup or on the popup button in IE
 +             * causes a blur event to be sent for the field. In other browsers
 +             * this is prevented by canceling/preventing default behavior for
 +             * the focus event, in IE we handle it here by refocusing the text
 +             * field and ignoring the resulting focus event for the textfield
 +             * (in onFocus).
 +             */
 +            preventNextBlurEventInIE = false;
 +
 +            Element focusedElement = WidgetUtil.getFocusedElement();
 +            if (getElement().isOrHasChild(focusedElement)
 +                    || suggestionPopup.getElement()
 +                            .isOrHasChild(focusedElement)) {
 +
 +                // IF the suggestion popup or another part of the VFilterSelect
 +                // was focused, move the focus back to the textfield and prevent
 +                // the triggered focus event (in onFocus).
 +                iePreventNextFocus = true;
 +                tb.setFocus(true);
 +                return;
 +            }
 +        }
 +
 +        focused = false;
 +        if (!readonly) {
 +            if (selectedOptionKey == null) {
 +                setPromptingOn();
 +            } else if (currentSuggestion != null) {
 +                setPromptingOff(currentSuggestion.caption);
 +            }
 +        }
 +        removeStyleDependentName("focus");
 +
 +        if (client.hasEventListeners(this, EventId.BLUR)) {
 +            client.updateVariable(paintableId, EventId.BLUR, "", true);
 +            afterUpdateClientVariables();
 +        }
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see com.vaadin.client.Focusable#focus()
 +     */
 +
 +    @Override
 +    public void focus() {
 +        debug("VFS: focus()");
 +        focused = true;
 +        if (prompting && !readonly) {
 +            setPromptingOff("");
 +        }
 +        tb.setFocus(true);
 +    }
 +
 +    /**
 +     * Calculates the width of the select if the select has undefined width.
 +     * Should be called when the width changes or when the icon changes.
 +     * <p>
 +     * For internal use only. May be removed or replaced in the future.
 +     */
 +    public void updateRootWidth() {
 +        ComponentConnector paintable = ConnectorMap.get(client).getConnector(
 +                this);
 +
 +        if (paintable.isUndefinedWidth()) {
 +
 +            /*
 +             * When the select has a undefined with we need to check that we are
 +             * only setting the text box width relative to the first page width
 +             * of the items. If this is not done the text box width will change
 +             * when the popup is used to view longer items than the text box is
 +             * wide.
 +             */
 +            int w = WidgetUtil.getRequiredWidth(this);
 +
 +            if ((!initDone || currentPage + 1 < 0)
 +                    && suggestionPopupMinWidth > w) {
 +                /*
 +                 * We want to compensate for the paddings just to preserve the
 +                 * exact size as in Vaadin 6.x, but we get here before
 +                 * MeasuredSize has been initialized.
 +                 * Util.measureHorizontalPaddingAndBorder does not work with
 +                 * border-box, so we must do this the hard way.
 +                 */
 +                Style style = getElement().getStyle();
 +                String originalPadding = style.getPadding();
 +                String originalBorder = style.getBorderWidth();
 +                style.setPaddingLeft(0, Unit.PX);
 +                style.setBorderWidth(0, Unit.PX);
 +                style.setProperty("padding", originalPadding);
 +                style.setProperty("borderWidth", originalBorder);
 +
 +                // Use util.getRequiredWidth instead of getOffsetWidth here
 +
 +                int iconWidth = selectedItemIcon == null ? 0 : WidgetUtil
 +                        .getRequiredWidth(selectedItemIcon);
 +                int buttonWidth = popupOpener == null ? 0 : WidgetUtil
 +                        .getRequiredWidth(popupOpener);
 +
 +                /*
 +                 * Instead of setting the width of the wrapper, set the width of
 +                 * the combobox. Subtract the width of the icon and the
 +                 * popupopener
 +                 */
 +
 +                tb.setWidth((suggestionPopupMinWidth - iconWidth - buttonWidth)
 +                        + "px");
 +
 +            }
 +
 +            /*
 +             * Lock the textbox width to its current value if it's not already
 +             * locked
 +             */
 +            if (!tb.getElement().getStyle().getWidth().endsWith("px")) {
 +                int iconWidth = selectedItemIcon == null ? 0 : selectedItemIcon
 +                        .getOffsetWidth();
 +                tb.setWidth((tb.getOffsetWidth() - iconWidth) + "px");
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Get the width of the select in pixels where the text area and icon has
 +     * been included.
 +     * 
 +     * @return The width in pixels
 +     */
 +    private int getMainWidth() {
 +        return getOffsetWidth();
 +    }
 +
 +    @Override
 +    public void setWidth(String width) {
 +        super.setWidth(width);
 +        if (width.length() != 0) {
 +            tb.setWidth("100%");
 +        }
 +    }
 +
 +    /**
 +     * Handles special behavior of the mouse down event
 +     * 
 +     * @param event
 +     */
 +    private void handleMouseDownEvent(Event event) {
 +        /*
 +         * Prevent the keyboard focus from leaving the textfield by preventing
 +         * the default behaviour of the browser. Fixes #4285.
 +         */
 +        if (event.getTypeInt() == Event.ONMOUSEDOWN) {
 +            event.preventDefault();
 +            event.stopPropagation();
 +
 +            /*
 +             * In IE the above wont work, the blur event will still trigger. So,
 +             * we set a flag here to prevent the next blur event from happening.
 +             * This is not needed if do not already have focus, in that case
 +             * there will not be any blur event and we should not cancel the
 +             * next blur.
 +             */
 +            if (BrowserInfo.get().isIE() && focused) {
 +                preventNextBlurEventInIE = true;
 +                debug("VFS: Going to prevent next blur event on IE");
 +            }
 +        }
 +    }
 +
 +    @Override
 +    protected void onDetach() {
 +        super.onDetach();
 +        suggestionPopup.hide();
 +    }
 +
 +    @Override
 +    public com.google.gwt.user.client.Element getSubPartElement(String subPart) {
 +        String[] parts = subPart.split("/");
 +        if ("textbox".equals(parts[0])) {
 +            return tb.getElement();
 +        } else if ("button".equals(parts[0])) {
 +            return popupOpener.getElement();
 +        } else if ("popup".equals(parts[0]) && suggestionPopup.isAttached()) {
 +            if (parts.length == 2) {
 +                return suggestionPopup.menu.getSubPartElement(parts[1]);
 +            }
 +            return suggestionPopup.getElement();
 +        }
 +        return null;
 +    }
 +
 +    @Override
 +    public String getSubPartName(com.google.gwt.user.client.Element subElement) {
 +        if (tb.getElement().isOrHasChild(subElement)) {
 +            return "textbox";
 +        } else if (popupOpener.getElement().isOrHasChild(subElement)) {
 +            return "button";
 +        } else if (suggestionPopup.getElement().isOrHasChild(subElement)) {
 +            return "popup";
 +        }
 +        return null;
 +    }
 +
 +    @Override
 +    public void setAriaRequired(boolean required) {
 +        AriaHelper.handleInputRequired(tb, required);
 +    }
 +
 +    @Override
 +    public void setAriaInvalid(boolean invalid) {
 +        AriaHelper.handleInputInvalid(tb, invalid);
 +    }
 +
 +    @Override
 +    public void bindAriaCaption(
 +            com.google.gwt.user.client.Element captionElement) {
 +        AriaHelper.bindCaption(tb, captionElement);
 +    }
 +
 +    /*
 +     * Anything that should be set after the client updates the server.
 +     */
 +    private void afterUpdateClientVariables() {
 +        // We need this here to be consistent with the all the calls.
 +        // Then set your specific selection type only after
 +        // client.updateVariable() method call.
 +        selectPopupItemWhenResponseIsReceived = Select.NONE;
 +    }
 +
 +    @Override
 +    public boolean isWorkPending() {
 +        return waitingForFilteringResponse
 +                || suggestionPopup.lazyPageScroller.isRunning();
 +    }
 +
 +}
index f222721e24723ad135e6089adae2d5fb6760b2fc,0000000000000000000000000000000000000000..7ea959680975f2f0335f4af1d065aaa0dc6cf8b8
mode 100644,000000..100644
--- /dev/null
@@@ -1,124 -1,0 +1,138 @@@
-         implements Paintable {
 +/*
 + * Copyright 2000-2014 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.draganddropwrapper;
 +
 +import java.util.HashMap;
 +import java.util.Set;
 +import java.util.logging.Level;
 +import java.util.logging.Logger;
 +
 +import com.vaadin.client.ApplicationConnection;
 +import com.vaadin.client.ComponentConnector;
 +import com.vaadin.client.ConnectorMap;
 +import com.vaadin.client.Paintable;
 +import com.vaadin.client.UIDL;
 +import com.vaadin.client.VConsole;
 +import com.vaadin.client.ui.VDragAndDropWrapper;
 +import com.vaadin.client.ui.customcomponent.CustomComponentConnector;
 +import com.vaadin.shared.ui.Connect;
 +import com.vaadin.shared.ui.draganddropwrapper.DragAndDropWrapperConstants;
++import com.vaadin.shared.ui.draganddropwrapper.DragAndDropWrapperServerRpc;
 +import com.vaadin.ui.DragAndDropWrapper;
 +
 +@Connect(DragAndDropWrapper.class)
 +public class DragAndDropWrapperConnector extends CustomComponentConnector
++        implements Paintable, VDragAndDropWrapper.UploadHandler {
++
++    @Override
++    protected void init() {
++        super.init();
++        getWidget().uploadHandler = this;
++    }
 +
 +    @Override
 +    public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
 +        getWidget().client = client;
 +        if (isRealUpdate(uidl) && !uidl.hasAttribute("hidden")) {
 +            UIDL acceptCrit = uidl.getChildByTagName("-ac");
 +            if (acceptCrit == null) {
 +                getWidget().dropHandler = null;
 +            } else {
 +                if (getWidget().dropHandler == null) {
 +                    getWidget().dropHandler = getWidget().new CustomDropHandler();
 +                }
 +                getWidget().dropHandler.updateAcceptRules(acceptCrit);
 +            }
 +
 +            Set<String> variableNames = uidl.getVariableNames();
 +            for (String fileId : variableNames) {
 +                if (fileId.startsWith("rec-")) {
 +                    String receiverUrl = uidl.getStringVariable(fileId);
 +                    fileId = fileId.substring(4);
 +                    if (getWidget().fileIdToReceiver == null) {
 +                        getWidget().fileIdToReceiver = new HashMap<String, String>();
 +                    }
 +                    if ("".equals(receiverUrl)) {
 +                        Integer id = Integer.parseInt(fileId);
 +                        int indexOf = getWidget().fileIds.indexOf(id);
 +                        if (indexOf != -1) {
 +                            getWidget().files.remove(indexOf);
 +                            getWidget().fileIds.remove(indexOf);
 +                        }
 +                    } else {
 +                        if (getWidget().fileIdToReceiver.containsKey(fileId)
 +                                && receiverUrl != null
 +                                && !receiverUrl
 +                                        .equals(getWidget().fileIdToReceiver
 +                                                .get(fileId))) {
 +                            VConsole.error("Overwriting file receiver mapping for fileId "
 +                                    + fileId
 +                                    + " . Old receiver URL: "
 +                                    + getWidget().fileIdToReceiver.get(fileId)
 +                                    + " New receiver URL: " + receiverUrl);
 +                        }
 +                        getWidget().fileIdToReceiver.put(fileId, receiverUrl);
 +                    }
 +                }
 +            }
 +            getWidget().startNextUpload();
 +
 +            getWidget().dragStartMode = uidl
 +                    .getIntAttribute(DragAndDropWrapperConstants.DRAG_START_MODE);
 +
 +            String dragImageComponentConnectorId = uidl
 +                    .getStringAttribute(DragAndDropWrapperConstants.DRAG_START_COMPONENT_ATTRIBUTE);
 +
 +            ComponentConnector connector = null;
 +            if (dragImageComponentConnectorId != null) {
 +                connector = (ComponentConnector) ConnectorMap.get(client)
 +                        .getConnector(dragImageComponentConnectorId);
 +
 +                if (connector == null) {
 +                    getLogger().log(
 +                            Level.WARNING,
 +                            "DragAndDropWrapper drag image component"
 +                                    + " connector now found. Make sure the"
 +                                    + " component is attached.");
 +                } else {
 +                    getWidget().setDragAndDropWidget(connector.getWidget());
 +                }
 +            }
 +            getWidget().initDragStartMode();
 +            getWidget().html5DataFlavors = uidl
 +                    .getMapAttribute(DragAndDropWrapperConstants.HTML5_DATA_FLAVORS);
 +
 +            // Used to prevent wrapper from stealing tooltips when not defined
 +            getWidget().hasTooltip = getState().description != null;
 +        }
 +    }
 +
 +    @Override
 +    public VDragAndDropWrapper getWidget() {
 +        return (VDragAndDropWrapper) super.getWidget();
 +    }
 +
 +    private static Logger getLogger() {
 +        return Logger.getLogger(DragAndDropWrapperConnector.class.getName());
 +    }
++
++    @Override
++    public void uploadDone() {
++        // #19616 RPC to poll the server for changes
++        getRpcProxy(DragAndDropWrapperServerRpc.class).poll();
++    }
++
 +}
index a554b9335cae29616d46392b181cffe1127d9ae0,0000000000000000000000000000000000000000..cfe93b6641345ed79787853d75c4ad860f422381
mode 100644,000000..100644
--- /dev/null
@@@ -1,556 -1,0 +1,561 @@@
-         getWidget().enabled = isEnabled();
-         if (BrowserInfo.get().isIE8() && !getWidget().enabled) {
-             /*
-              * The disabled shim will not cover the table body if it is relative
-              * in IE8. See #7324
-              */
-             getWidget().scrollBodyPanel.getElement().getStyle()
-                     .setPosition(Position.STATIC);
-         } else if (BrowserInfo.get().isIE8()) {
-             getWidget().scrollBodyPanel.getElement().getStyle()
-                     .setPosition(Position.RELATIVE);
-         }
 +/*
 + * Copyright 2000-2014 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.table;
 +
 +import java.util.Collections;
 +import java.util.Iterator;
 +import java.util.List;
 +
 +import com.google.gwt.core.client.Scheduler;
 +import com.google.gwt.core.client.Scheduler.ScheduledCommand;
 +import com.google.gwt.dom.client.Element;
 +import com.google.gwt.dom.client.EventTarget;
 +import com.google.gwt.dom.client.Style.Position;
 +import com.google.gwt.event.shared.HandlerRegistration;
 +import com.google.gwt.user.client.ui.Widget;
 +import com.vaadin.client.ApplicationConnection;
 +import com.vaadin.client.BrowserInfo;
 +import com.vaadin.client.ComponentConnector;
 +import com.vaadin.client.ConnectorHierarchyChangeEvent;
 +import com.vaadin.client.ConnectorHierarchyChangeEvent.ConnectorHierarchyChangeHandler;
 +import com.vaadin.client.DirectionalManagedLayout;
 +import com.vaadin.client.HasChildMeasurementHintConnector;
 +import com.vaadin.client.HasComponentsConnector;
 +import com.vaadin.client.Paintable;
 +import com.vaadin.client.ServerConnector;
 +import com.vaadin.client.TooltipInfo;
 +import com.vaadin.client.UIDL;
 +import com.vaadin.client.WidgetUtil;
 +import com.vaadin.client.ui.AbstractFieldConnector;
 +import com.vaadin.client.ui.PostLayoutListener;
 +import com.vaadin.client.ui.VScrollTable;
 +import com.vaadin.client.ui.VScrollTable.ContextMenuDetails;
 +import com.vaadin.client.ui.VScrollTable.FooterCell;
 +import com.vaadin.client.ui.VScrollTable.HeaderCell;
 +import com.vaadin.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow;
 +import com.vaadin.shared.MouseEventDetails;
 +import com.vaadin.shared.ui.Connect;
 +import com.vaadin.shared.ui.table.TableConstants;
 +import com.vaadin.shared.ui.table.TableConstants.Section;
 +import com.vaadin.shared.ui.table.TableServerRpc;
 +import com.vaadin.shared.ui.table.TableState;
 +
 +@Connect(com.vaadin.ui.Table.class)
 +public class TableConnector extends AbstractFieldConnector implements
 +        HasComponentsConnector, ConnectorHierarchyChangeHandler, Paintable,
 +        DirectionalManagedLayout, PostLayoutListener,
 +        HasChildMeasurementHintConnector {
 +
 +    private List<ComponentConnector> childComponents;
 +
 +    public TableConnector() {
 +        addConnectorHierarchyChangeHandler(this);
 +    }
 +
 +    @Override
 +    protected void init() {
 +        super.init();
 +        getWidget().init(getConnection());
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see com.vaadin.client.ui.AbstractComponentConnector#onUnregister()
 +     */
 +    @Override
 +    public void onUnregister() {
 +        super.onUnregister();
 +        getWidget().onUnregister();
 +    }
 +
 +    @Override
 +    protected void sendContextClickEvent(MouseEventDetails details,
 +            EventTarget eventTarget) {
 +
 +        if (!Element.is(eventTarget)) {
 +            return;
 +        }
 +        Element e = Element.as(eventTarget);
 +
 +        Section section;
 +        String colKey = null;
 +        String rowKey = null;
 +        if (getWidget().tFoot.getElement().isOrHasChild(e)) {
 +            section = Section.FOOTER;
 +            FooterCell w = WidgetUtil.findWidget(e, FooterCell.class);
 +            colKey = w.getColKey();
 +        } else if (getWidget().tHead.getElement().isOrHasChild(e)) {
 +            section = Section.HEADER;
 +            HeaderCell w = WidgetUtil.findWidget(e, HeaderCell.class);
 +            colKey = w.getColKey();
 +        } else {
 +            section = Section.BODY;
 +            if (getWidget().scrollBody.getElement().isOrHasChild(e)) {
 +                VScrollTableRow w = getScrollTableRow(e);
 +                /*
 +                 * if w is null because we've clicked on an empty area, we will
 +                 * let rowKey and colKey be null too, which will then lead to
 +                 * the server side returning a null object.
 +                 */
 +                if (w != null) {
 +                    rowKey = w.getKey();
 +                    colKey = getWidget().tHead.getHeaderCell(
 +                            getElementIndex(e, w.getElement())).getColKey();
 +                }
 +            }
 +        }
 +
 +        getRpcProxy(TableServerRpc.class).contextClick(rowKey, colKey, section,
 +                details);
 +
 +        WidgetUtil.clearTextSelection();
 +    }
 +
 +    protected VScrollTableRow getScrollTableRow(Element e) {
 +        return WidgetUtil.findWidget(e, VScrollTableRow.class);
 +    }
 +
 +    private int getElementIndex(Element e,
 +            com.google.gwt.user.client.Element element) {
 +        int i = 0;
 +        Element current = element.getFirstChildElement();
 +        while (!current.isOrHasChild(e)) {
 +            current = current.getNextSiblingElement();
 +            ++i;
 +        }
 +        return i;
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see com.vaadin.client.Paintable#updateFromUIDL(com.vaadin.client.UIDL,
 +     * com.vaadin.client.ApplicationConnection)
 +     */
 +    @Override
 +    public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
 +        getWidget().rendering = true;
 +
 +        // If a row has an open context menu, it will be closed as the row is
 +        // detached. Retain a reference here so we can restore the menu if
 +        // required.
 +        ContextMenuDetails contextMenuBeforeUpdate = getWidget().contextMenu;
 +
 +        if (uidl.hasAttribute(TableConstants.ATTRIBUTE_PAGEBUFFER_FIRST)) {
 +            getWidget().serverCacheFirst = uidl
 +                    .getIntAttribute(TableConstants.ATTRIBUTE_PAGEBUFFER_FIRST);
 +            getWidget().serverCacheLast = uidl
 +                    .getIntAttribute(TableConstants.ATTRIBUTE_PAGEBUFFER_LAST);
 +        } else {
 +            getWidget().serverCacheFirst = -1;
 +            getWidget().serverCacheLast = -1;
 +        }
 +        /*
 +         * We need to do this before updateComponent since updateComponent calls
 +         * this.setHeight() which will calculate a new body height depending on
 +         * the space available.
 +         */
 +        if (uidl.hasAttribute("colfooters")) {
 +            getWidget().showColFooters = uidl.getBooleanAttribute("colfooters");
 +        }
 +
 +        getWidget().tFoot.setVisible(getWidget().showColFooters);
 +
 +        if (!isRealUpdate(uidl)) {
 +            getWidget().rendering = false;
 +            return;
 +        }
 +
 +        getWidget().paintableId = uidl.getStringAttribute("id");
 +        getWidget().immediate = getState().immediate;
 +
 +        int previousTotalRows = getWidget().totalRows;
 +        getWidget().updateTotalRows(uidl);
 +        boolean totalRowsHaveChanged = (getWidget().totalRows != previousTotalRows);
 +
 +        getWidget().updateDragMode(uidl);
 +
 +        // Update child measure hint
 +        int childMeasureHint = uidl.hasAttribute("measurehint") ? uidl
 +                .getIntAttribute("measurehint") : 0;
 +        getWidget().setChildMeasurementHint(
 +                ChildMeasurementHint.values()[childMeasureHint]);
 +
 +        getWidget().updateSelectionProperties(uidl, getState(), isReadOnly());
 +
 +        if (uidl.hasAttribute("alb")) {
 +            getWidget().bodyActionKeys = uidl.getStringArrayAttribute("alb");
 +        } else {
 +            // Need to clear the actions if the action handlers have been
 +            // removed
 +            getWidget().bodyActionKeys = null;
 +        }
 +
 +        getWidget().setCacheRateFromUIDL(uidl);
 +
 +        getWidget().recalcWidths = uidl.hasAttribute("recalcWidths");
 +        if (getWidget().recalcWidths) {
 +            getWidget().tHead.clear();
 +            getWidget().tFoot.clear();
 +        }
 +
 +        getWidget().updatePageLength(uidl);
 +
 +        getWidget().updateFirstVisibleAndScrollIfNeeded(uidl);
 +
 +        getWidget().showRowHeaders = uidl.getBooleanAttribute("rowheaders");
 +        getWidget().showColHeaders = uidl.getBooleanAttribute("colheaders");
 +
 +        getWidget().updateSortingProperties(uidl);
 +
 +        getWidget().updateActionMap(uidl);
 +
 +        getWidget().updateColumnProperties(uidl);
 +
 +        UIDL ac = uidl.getChildByTagName("-ac");
 +        if (ac == null) {
 +            if (getWidget().dropHandler != null) {
 +                // remove dropHandler if not present anymore
 +                getWidget().dropHandler = null;
 +            }
 +        } else {
 +            if (getWidget().dropHandler == null) {
 +                getWidget().dropHandler = getWidget().new VScrollTableDropHandler();
 +            }
 +            getWidget().dropHandler.updateAcceptRules(ac);
 +        }
 +
 +        UIDL partialRowAdditions = uidl.getChildByTagName("prows");
 +        UIDL partialRowUpdates = uidl.getChildByTagName("urows");
 +        if (partialRowUpdates != null || partialRowAdditions != null) {
 +            getWidget().postponeSanityCheckForLastRendered = true;
 +            // we may have pending cache row fetch, cancel it. See #2136
 +            getWidget().rowRequestHandler.cancel();
 +
 +            getWidget().updateRowsInBody(partialRowUpdates);
 +            getWidget().addAndRemoveRows(partialRowAdditions);
 +
 +            // sanity check (in case the value has slipped beyond the total
 +            // amount of rows)
 +            getWidget().scrollBody.setLastRendered(getWidget().scrollBody
 +                    .getLastRendered());
 +            getWidget().updateMaxIndent();
 +        } else {
 +            getWidget().postponeSanityCheckForLastRendered = false;
 +            UIDL rowData = uidl.getChildByTagName("rows");
 +            if (rowData != null) {
 +                // we may have pending cache row fetch, cancel it. See #2136
 +                getWidget().rowRequestHandler.cancel();
 +
 +                if (!getWidget().recalcWidths
 +                        && getWidget().initializedAndAttached) {
 +                    getWidget().updateBody(rowData,
 +                            uidl.getIntAttribute("firstrow"),
 +                            uidl.getIntAttribute("rows"));
 +                    if (getWidget().headerChangedDuringUpdate) {
 +                        getWidget().triggerLazyColumnAdjustment(true);
 +                    }
 +                } else {
 +                    getWidget().initializeRows(uidl, rowData);
 +                }
 +            }
 +        }
 +
 +        boolean keyboardSelectionOverRowFetchInProgress = getWidget()
 +                .selectSelectedRows(uidl);
 +
 +        // If a row had an open context menu before the update, and after the
 +        // update there's a row with the same key as that row, restore the
 +        // context menu. See #8526.
 +        showSavedContextMenu(contextMenuBeforeUpdate);
 +
 +        if (!getWidget().isSelectable()) {
 +            getWidget().scrollBody.addStyleName(getWidget()
 +                    .getStylePrimaryName() + "-body-noselection");
 +        } else {
 +            getWidget().scrollBody.removeStyleName(getWidget()
 +                    .getStylePrimaryName() + "-body-noselection");
 +        }
 +
 +        getWidget().hideScrollPositionAnnotation();
 +
 +        // selection is no in sync with server, avoid excessive server visits by
 +        // clearing to flag used during the normal operation
 +        if (!keyboardSelectionOverRowFetchInProgress) {
 +            getWidget().selectionChanged = false;
 +        }
 +
 +        /*
 +         * This is called when the Home or page up button has been pressed in
 +         * selectable mode and the next selected row was not yet rendered in the
 +         * client
 +         */
 +        if (getWidget().selectFirstItemInNextRender
 +                || getWidget().focusFirstItemInNextRender) {
 +            getWidget().selectFirstRenderedRowInViewPort(
 +                    getWidget().focusFirstItemInNextRender);
 +            getWidget().selectFirstItemInNextRender = getWidget().focusFirstItemInNextRender = false;
 +        }
 +
 +        /*
 +         * This is called when the page down or end button has been pressed in
 +         * selectable mode and the next selected row was not yet rendered in the
 +         * client
 +         */
 +        if (getWidget().selectLastItemInNextRender
 +                || getWidget().focusLastItemInNextRender) {
 +            getWidget().selectLastRenderedRowInViewPort(
 +                    getWidget().focusLastItemInNextRender);
 +            getWidget().selectLastItemInNextRender = getWidget().focusLastItemInNextRender = false;
 +        }
 +        getWidget().multiselectPending = false;
 +
 +        if (getWidget().focusedRow != null) {
 +            if (!getWidget().focusedRow.isAttached()
 +                    && !getWidget().rowRequestHandler.isRequestHandlerRunning()) {
 +                // focused row has been orphaned, can't focus
 +                if (getWidget().selectedRowKeys.contains(getWidget().focusedRow
 +                        .getKey())) {
 +                    // if row cache was refreshed, focused row should be
 +                    // in selection and exists with same index
 +                    getWidget().setRowFocus(
 +                            getWidget().getRenderedRowByKey(
 +                                    getWidget().focusedRow.getKey()));
 +                } else if (getWidget().selectedRowKeys.size() > 0) {
 +                    // try to focus any row in selection
 +                    getWidget().setRowFocus(
 +                            getWidget().getRenderedRowByKey(
 +                                    getWidget().selectedRowKeys.iterator()
 +                                            .next()));
 +                } else {
 +                    // try to focus any row
 +                    getWidget().focusRowFromBody();
 +                }
 +            }
 +        }
 +
 +        /*
 +         * If the server has (re)initialized the rows, our selectionRangeStart
 +         * row will point to an index that the server knows nothing about,
 +         * causing problems if doing multi selection with shift. The field will
 +         * be cleared a little later when the row focus has been restored.
 +         * (#8584)
 +         */
 +        if (uidl.hasAttribute(TableConstants.ATTRIBUTE_KEY_MAPPER_RESET)
 +                && uidl.getBooleanAttribute(TableConstants.ATTRIBUTE_KEY_MAPPER_RESET)
 +                && getWidget().selectionRangeStart != null) {
 +            assert !getWidget().selectionRangeStart.isAttached();
 +            getWidget().selectionRangeStart = getWidget().focusedRow;
 +        }
 +
 +        getWidget().tabIndex = getState().tabIndex;
 +        getWidget().setProperTabIndex();
 +
 +        Scheduler.get().scheduleFinally(new ScheduledCommand() {
 +
 +            @Override
 +            public void execute() {
 +                getWidget().resizeSortedColumnForSortIndicator();
 +            }
 +        });
 +
 +        // Remember this to detect situations where overflow hack might be
 +        // needed during scrolling
 +        getWidget().lastRenderedHeight = getWidget().scrollBody
 +                .getOffsetHeight();
 +
 +        getWidget().rendering = false;
 +        getWidget().headerChangedDuringUpdate = false;
 +
 +        getWidget().collapsibleMenuContent = getState().collapseMenuContent;
 +    }
 +
++    @Override
++    public void updateEnabledState(boolean enabledState) {
++        super.updateEnabledState(enabledState);
++        getWidget().enabled = isEnabled();
++
++        if (BrowserInfo.get().isIE8() && !getWidget().enabled) {
++            /*
++             * The disabled shim will not cover the table body if it is relative
++             * in IE8. See #7324
++             */
++            getWidget().scrollBodyPanel.getElement().getStyle()
++                    .setPosition(Position.STATIC);
++        } else if (BrowserInfo.get().isIE8()) {
++            getWidget().scrollBodyPanel.getElement().getStyle()
++                    .setPosition(Position.RELATIVE);
++        }
++
++    }
++
 +    @Override
 +    public VScrollTable getWidget() {
 +        return (VScrollTable) super.getWidget();
 +    }
 +
 +    @Override
 +    public void updateCaption(ComponentConnector component) {
 +        // NOP, not rendered
 +    }
 +
 +    @Override
 +    public void layoutVertically() {
 +        getWidget().updateHeight();
 +    }
 +
 +    @Override
 +    public void layoutHorizontally() {
 +        getWidget().updateWidth();
 +    }
 +
 +    @Override
 +    public void postLayout() {
 +        VScrollTable table = getWidget();
 +        if (table.sizeNeedsInit) {
 +            table.sizeInit();
 +            Scheduler.get().scheduleFinally(new ScheduledCommand() {
 +                @Override
 +                public void execute() {
 +                    // IE8 needs some hacks to measure sizes correctly
 +                    WidgetUtil.forceIE8Redraw(getWidget().getElement());
 +
 +                    getLayoutManager().setNeedsMeasure(TableConnector.this);
 +                    ServerConnector parent = getParent();
 +                    if (parent instanceof ComponentConnector) {
 +                        getLayoutManager().setNeedsMeasure(
 +                                (ComponentConnector) parent);
 +                    }
 +                    getLayoutManager().setNeedsVerticalLayout(
 +                            TableConnector.this);
 +                    getLayoutManager().layoutNow();
 +                }
 +            });
 +        }
 +    }
 +
 +    @Override
 +    public boolean isReadOnly() {
 +        return super.isReadOnly() || getState().propertyReadOnly;
 +    }
 +
 +    @Override
 +    public TableState getState() {
 +        return (TableState) super.getState();
 +    }
 +
 +    /**
 +     * Shows a saved row context menu if the row for the context menu is still
 +     * visible. Does nothing if a context menu has not been saved.
 +     * 
 +     * @param savedContextMenu
 +     */
 +    public void showSavedContextMenu(ContextMenuDetails savedContextMenu) {
 +        if (isEnabled() && savedContextMenu != null) {
 +            Iterator<Widget> iterator = getWidget().scrollBody.iterator();
 +            while (iterator.hasNext()) {
 +                Widget w = iterator.next();
 +                VScrollTableRow row = (VScrollTableRow) w;
 +                if (row.getKey().equals(savedContextMenu.rowKey)) {
 +                    row.showContextMenu(savedContextMenu.left,
 +                            savedContextMenu.top);
 +                }
 +            }
 +        }
 +    }
 +
 +    @Override
 +    public TooltipInfo getTooltipInfo(Element element) {
 +
 +        TooltipInfo info = null;
 +
 +        if (element != getWidget().getElement()) {
 +            Object node = WidgetUtil.findWidget(element, VScrollTableRow.class);
 +
 +            if (node != null) {
 +                VScrollTableRow row = (VScrollTableRow) node;
 +                info = row.getTooltip(element);
 +            }
 +        }
 +
 +        if (info == null) {
 +            info = super.getTooltipInfo(element);
 +        }
 +
 +        return info;
 +    }
 +
 +    @Override
 +    public boolean hasTooltip() {
 +        /*
 +         * Tooltips for individual rows and cells are not processed until
 +         * updateFromUIDL, so we can't be sure that there are no tooltips during
 +         * onStateChange when this method is used.
 +         */
 +        return true;
 +    }
 +
 +    @Override
 +    public void onConnectorHierarchyChange(
 +            ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent) {
 +        // TODO Move code from updateFromUIDL to this method
 +    }
 +
 +    @Override
 +    protected void updateComponentSize(String newWidth, String newHeight) {
 +        super.updateComponentSize(newWidth, newHeight);
 +
 +        if ("".equals(newWidth)) {
 +            getWidget().updateWidth();
 +        }
 +        if ("".equals(newHeight)) {
 +            getWidget().updateHeight();
 +        }
 +    }
 +
 +    @Override
 +    public List<ComponentConnector> getChildComponents() {
 +        if (childComponents == null) {
 +            return Collections.emptyList();
 +        }
 +
 +        return childComponents;
 +    }
 +
 +    @Override
 +    public void setChildComponents(List<ComponentConnector> childComponents) {
 +        this.childComponents = childComponents;
 +    }
 +
 +    @Override
 +    public HandlerRegistration addConnectorHierarchyChangeHandler(
 +            ConnectorHierarchyChangeHandler handler) {
 +        return ensureHandlerManager().addHandler(
 +                ConnectorHierarchyChangeEvent.TYPE, handler);
 +    }
 +
 +    @Override
 +    public void setChildMeasurementHint(ChildMeasurementHint hint) {
 +        getWidget().setChildMeasurementHint(hint);
 +    }
 +
 +    @Override
 +    public ChildMeasurementHint getChildMeasurementHint() {
 +        return getWidget().getChildMeasurementHint();
 +    }
 +
 +}
index 958029889d73106d87178361d837759a4d914065,0000000000000000000000000000000000000000..151deb87d159f79235a178969c7b166b77262824
mode 100644,000000..100644
--- /dev/null
@@@ -1,866 -1,0 +1,866 @@@
-                 + toInt32(scrollPos)
 +/*
 + * Copyright 2000-2014 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.widget.escalator;
 +
 +import com.google.gwt.core.client.Scheduler;
 +import com.google.gwt.core.client.Scheduler.ScheduledCommand;
 +import com.google.gwt.dom.client.Element;
 +import com.google.gwt.dom.client.Style.Display;
 +import com.google.gwt.dom.client.Style.Overflow;
 +import com.google.gwt.dom.client.Style.Unit;
 +import com.google.gwt.dom.client.Style.Visibility;
 +import com.google.gwt.event.shared.EventHandler;
 +import com.google.gwt.event.shared.GwtEvent;
 +import com.google.gwt.event.shared.HandlerManager;
 +import com.google.gwt.event.shared.HandlerRegistration;
 +import com.google.gwt.user.client.DOM;
 +import com.google.gwt.user.client.Event;
 +import com.google.gwt.user.client.EventListener;
 +import com.google.gwt.user.client.Timer;
 +import com.vaadin.client.DeferredWorker;
 +import com.vaadin.client.WidgetUtil;
 +import com.vaadin.client.widget.grid.events.ScrollEvent;
 +import com.vaadin.client.widget.grid.events.ScrollHandler;
 +
 +/**
 + * An element-like bundle representing a configurable and visual scrollbar in
 + * one axis.
 + * 
 + * @since 7.4
 + * @author Vaadin Ltd
 + * @see VerticalScrollbarBundle
 + * @see HorizontalScrollbarBundle
 + */
 +public abstract class ScrollbarBundle implements DeferredWorker {
 +
 +    private class ScrollEventFirer {
 +        private final ScheduledCommand fireEventCommand = new ScheduledCommand() {
 +            @Override
 +            public void execute() {
 +
 +                /*
 +                 * Some kind of native-scroll-event related asynchronous problem
 +                 * occurs here (at least on desktops) where the internal
 +                 * bookkeeping isn't up to date with the real scroll position.
 +                 * The weird thing is, that happens only once, and if you drag
 +                 * scrollbar fast enough. After it has failed once, it never
 +                 * fails again.
 +                 * 
 +                 * Theory: the user drags the scrollbar, and this command is
 +                 * executed before the browser has a chance to fire a scroll
 +                 * event (which normally would correct this situation). This
 +                 * would explain why slow scrolling doesn't trigger the problem,
 +                 * while fast scrolling does.
 +                 * 
 +                 * To make absolutely sure that we have the latest scroll
 +                 * position, let's update the internal value.
 +                 * 
 +                 * This might lead to a slight performance hit (on my computer
 +                 * it was never more than 3ms on either of Chrome 38 or Firefox
 +                 * 31). It also _slightly_ counteracts the purpose of the
 +                 * internal bookkeeping. But since getScrollPos is called 3
 +                 * times (on one direction) per scroll loop, it's still better
 +                 * to have take this small penalty than removing it altogether.
 +                 */
 +                updateScrollPosFromDom();
 +
 +                getHandlerManager().fireEvent(new ScrollEvent());
 +                isBeingFired = false;
 +            }
 +        };
 +
 +        private boolean isBeingFired;
 +
 +        public void scheduleEvent() {
 +            if (!isBeingFired) {
 +                /*
 +                 * We'll gather all the scroll events, and only fire once, once
 +                 * everything has calmed down.
 +                 */
 +                Scheduler.get().scheduleDeferred(fireEventCommand);
 +                isBeingFired = true;
 +            }
 +        }
 +    }
 +
 +    /**
 +     * The orientation of the scrollbar.
 +     */
 +    public enum Direction {
 +        VERTICAL, HORIZONTAL;
 +    }
 +
 +    private class TemporaryResizer {
 +        private static final int TEMPORARY_RESIZE_DELAY = 1000;
 +
 +        private final Timer timer = new Timer() {
 +            @Override
 +            public void run() {
 +                internalSetScrollbarThickness(1);
 +                root.getStyle().setVisibility(Visibility.HIDDEN);
 +            }
 +        };
 +
 +        public void show() {
 +            internalSetScrollbarThickness(OSX_INVISIBLE_SCROLLBAR_FAKE_SIZE_PX);
 +            root.getStyle().setVisibility(Visibility.VISIBLE);
 +            timer.schedule(TEMPORARY_RESIZE_DELAY);
 +        }
 +    }
 +
 +    /**
 +     * A means to listen to when the scrollbar handle in a
 +     * {@link ScrollbarBundle} either appears or is removed.
 +     */
 +    public interface VisibilityHandler extends EventHandler {
 +        /**
 +         * This method is called whenever the scrollbar handle's visibility is
 +         * changed in a {@link ScrollbarBundle}.
 +         * 
 +         * @param event
 +         *            the {@link VisibilityChangeEvent}
 +         */
 +        void visibilityChanged(VisibilityChangeEvent event);
 +    }
 +
 +    public static class VisibilityChangeEvent extends
 +            GwtEvent<VisibilityHandler> {
 +        public static final Type<VisibilityHandler> TYPE = new Type<ScrollbarBundle.VisibilityHandler>() {
 +            @Override
 +            public String toString() {
 +                return "VisibilityChangeEvent";
 +            }
 +        };
 +
 +        private final boolean isScrollerVisible;
 +
 +        private VisibilityChangeEvent(boolean isScrollerVisible) {
 +            this.isScrollerVisible = isScrollerVisible;
 +        }
 +
 +        /**
 +         * Checks whether the scroll handle is currently visible or not
 +         * 
 +         * @return <code>true</code> if the scroll handle is currently visible.
 +         *         <code>false</code> if not.
 +         */
 +        public boolean isScrollerVisible() {
 +            return isScrollerVisible;
 +        }
 +
 +        @Override
 +        public Type<VisibilityHandler> getAssociatedType() {
 +            return TYPE;
 +        }
 +
 +        @Override
 +        protected void dispatch(VisibilityHandler handler) {
 +            handler.visibilityChanged(this);
 +        }
 +    }
 +
 +    /**
 +     * The pixel size for OSX's invisible scrollbars.
 +     * <p>
 +     * Touch devices don't show a scrollbar at all, so the scrollbar size is
 +     * irrelevant in their case. There doesn't seem to be any other popular
 +     * platforms that has scrollbars similar to OSX. Thus, this behavior is
 +     * tailored for OSX only, until additional platforms start behaving this
 +     * way.
 +     */
 +    private static final int OSX_INVISIBLE_SCROLLBAR_FAKE_SIZE_PX = 13;
 +
 +    /**
 +     * A representation of a single vertical scrollbar.
 +     * 
 +     * @see VerticalScrollbarBundle#getElement()
 +     */
 +    public final static class VerticalScrollbarBundle extends ScrollbarBundle {
 +
 +        @Override
 +        public void setStylePrimaryName(String primaryStyleName) {
 +            super.setStylePrimaryName(primaryStyleName);
 +            root.addClassName(primaryStyleName + "-scroller-vertical");
 +        }
 +
 +        @Override
 +        protected void internalSetScrollPos(int px) {
 +            root.setScrollTop(px);
 +        }
 +
 +        @Override
 +        protected int internalGetScrollPos() {
 +            return root.getScrollTop();
 +        }
 +
 +        @Override
 +        protected void internalSetScrollSize(double px) {
 +            scrollSizeElement.getStyle().setHeight(px, Unit.PX);
 +        }
 +
 +        @Override
 +        protected String internalGetScrollSize() {
 +            return scrollSizeElement.getStyle().getHeight();
 +        }
 +
 +        @Override
 +        protected void internalSetOffsetSize(double px) {
 +            root.getStyle().setHeight(px, Unit.PX);
 +        }
 +
 +        @Override
 +        public String internalGetOffsetSize() {
 +            return root.getStyle().getHeight();
 +        }
 +
 +        @Override
 +        protected void internalSetScrollbarThickness(double px) {
 +            root.getStyle().setPaddingRight(px, Unit.PX);
 +            root.getStyle().setWidth(0, Unit.PX);
 +            scrollSizeElement.getStyle().setWidth(px, Unit.PX);
 +        }
 +
 +        @Override
 +        protected String internalGetScrollbarThickness() {
 +            return scrollSizeElement.getStyle().getWidth();
 +        }
 +
 +        @Override
 +        protected void internalForceScrollbar(boolean enable) {
 +            if (enable) {
 +                root.getStyle().setOverflowY(Overflow.SCROLL);
 +            } else {
 +                root.getStyle().clearOverflowY();
 +            }
 +        }
 +
 +        @Override
 +        public Direction getDirection() {
 +            return Direction.VERTICAL;
 +        }
 +    }
 +
 +    /**
 +     * A representation of a single horizontal scrollbar.
 +     * 
 +     * @see HorizontalScrollbarBundle#getElement()
 +     */
 +    public final static class HorizontalScrollbarBundle extends ScrollbarBundle {
 +
 +        @Override
 +        public void setStylePrimaryName(String primaryStyleName) {
 +            super.setStylePrimaryName(primaryStyleName);
 +            root.addClassName(primaryStyleName + "-scroller-horizontal");
 +        }
 +
 +        @Override
 +        protected void internalSetScrollPos(int px) {
 +            root.setScrollLeft(px);
 +        }
 +
 +        @Override
 +        protected int internalGetScrollPos() {
 +            return root.getScrollLeft();
 +        }
 +
 +        @Override
 +        protected void internalSetScrollSize(double px) {
 +            scrollSizeElement.getStyle().setWidth(px, Unit.PX);
 +        }
 +
 +        @Override
 +        protected String internalGetScrollSize() {
 +            return scrollSizeElement.getStyle().getWidth();
 +        }
 +
 +        @Override
 +        protected void internalSetOffsetSize(double px) {
 +            root.getStyle().setWidth(px, Unit.PX);
 +        }
 +
 +        @Override
 +        public String internalGetOffsetSize() {
 +            return root.getStyle().getWidth();
 +        }
 +
 +        @Override
 +        protected void internalSetScrollbarThickness(double px) {
 +            root.getStyle().setPaddingBottom(px, Unit.PX);
 +            root.getStyle().setHeight(0, Unit.PX);
 +            scrollSizeElement.getStyle().setHeight(px, Unit.PX);
 +        }
 +
 +        @Override
 +        protected String internalGetScrollbarThickness() {
 +            return scrollSizeElement.getStyle().getHeight();
 +        }
 +
 +        @Override
 +        protected void internalForceScrollbar(boolean enable) {
 +            if (enable) {
 +                root.getStyle().setOverflowX(Overflow.SCROLL);
 +            } else {
 +                root.getStyle().clearOverflowX();
 +            }
 +        }
 +
 +        @Override
 +        public Direction getDirection() {
 +            return Direction.HORIZONTAL;
 +        }
 +    }
 +
 +    protected final Element root = DOM.createDiv();
 +    protected final Element scrollSizeElement = DOM.createDiv();
 +    protected boolean isInvisibleScrollbar = false;
 +
 +    private double scrollPos = 0;
 +    private double maxScrollPos = 0;
 +
 +    private boolean scrollHandleIsVisible = false;
 +
 +    private boolean isLocked = false;
 +
 +    /** @deprecated access via {@link #getHandlerManager()} instead. */
 +    @Deprecated
 +    private HandlerManager handlerManager;
 +
 +    private TemporaryResizer invisibleScrollbarTemporaryResizer = new TemporaryResizer();
 +
 +    private final ScrollEventFirer scrollEventFirer = new ScrollEventFirer();
 +
 +    private HandlerRegistration scrollSizeTemporaryScrollHandler;
 +    private HandlerRegistration offsetSizeTemporaryScrollHandler;
 +
 +    private ScrollbarBundle() {
 +        root.appendChild(scrollSizeElement);
 +        root.getStyle().setDisplay(Display.NONE);
 +        root.setTabIndex(-1);
 +    }
 +
 +    protected abstract String internalGetScrollSize();
 +
 +    /**
 +     * Sets the primary style name
 +     * 
 +     * @param primaryStyleName
 +     *            The primary style name to use
 +     */
 +    public void setStylePrimaryName(String primaryStyleName) {
 +        root.setClassName(primaryStyleName + "-scroller");
 +    }
 +
 +    /**
 +     * Gets the root element of this scrollbar-composition.
 +     * 
 +     * @return the root element
 +     */
 +    public final Element getElement() {
 +        return root;
 +    }
 +
 +    /**
 +     * Modifies the scroll position of this scrollbar by a number of pixels.
 +     * <p>
 +     * <em>Note:</em> Even though {@code double} values are used, they are
 +     * currently only used as integers as large {@code int} (or small but fast
 +     * {@code long}). This means, all values are truncated to zero decimal
 +     * places.
 +     * 
 +     * @param delta
 +     *            the delta in pixels to change the scroll position by
 +     */
 +    public final void setScrollPosByDelta(double delta) {
 +        if (delta != 0) {
 +            setScrollPos(getScrollPos() + delta);
 +        }
 +    }
 +
 +    /**
 +     * Modifies {@link #root root's} dimensions in the axis the scrollbar is
 +     * representing.
 +     * 
 +     * @param px
 +     *            the new size of {@link #root} in the dimension this scrollbar
 +     *            is representing
 +     */
 +    protected abstract void internalSetOffsetSize(double px);
 +
 +    /**
 +     * Sets the length of the scrollbar.
 +     * 
 +     * @param px
 +     *            the length of the scrollbar in pixels
 +     */
 +    public final void setOffsetSize(final double px) {
 +
 +        /*
 +         * This needs to be made step-by-step because IE8 flat-out refuses to
 +         * fire a scroll event when the scroll size becomes smaller than the
 +         * offset size. All other browser need to suffer alongside.
 +         */
 +
 +        boolean newOffsetSizeIsGreaterThanScrollSize = px > getScrollSize();
 +        boolean offsetSizeBecomesGreaterThanScrollSize = showsScrollHandle()
 +                && newOffsetSizeIsGreaterThanScrollSize;
 +        if (offsetSizeBecomesGreaterThanScrollSize && getScrollPos() != 0) {
 +            // must be a field because Java insists.
 +            offsetSizeTemporaryScrollHandler = addScrollHandler(new ScrollHandler() {
 +                @Override
 +                public void onScroll(ScrollEvent event) {
 +                    setOffsetSizeNow(px);
 +                }
 +            });
 +            setScrollPos(0);
 +        } else {
 +            setOffsetSizeNow(px);
 +        }
 +    }
 +
 +    private void setOffsetSizeNow(double px) {
 +        internalSetOffsetSize(Math.max(0, px));
 +        recalculateMaxScrollPos();
 +        forceScrollbar(showsScrollHandle());
 +        fireVisibilityChangeIfNeeded();
 +        if (offsetSizeTemporaryScrollHandler != null) {
 +            offsetSizeTemporaryScrollHandler.removeHandler();
 +            offsetSizeTemporaryScrollHandler = null;
 +        }
 +    }
 +
 +    /**
 +     * Force the scrollbar to be visible with CSS. In practice, this means to
 +     * set either <code>overflow-x</code> or <code>overflow-y</code> to "
 +     * <code>scroll</code>" in the scrollbar's direction.
 +     * <p>
 +     * This is an IE8 workaround, since it doesn't always show scrollbars with
 +     * <code>overflow: auto</code> enabled.
 +     */
 +    protected void forceScrollbar(boolean enable) {
 +        if (enable) {
 +            root.getStyle().clearDisplay();
 +        } else {
 +            root.getStyle().setDisplay(Display.NONE);
 +        }
 +        internalForceScrollbar(enable);
 +    }
 +
 +    protected abstract void internalForceScrollbar(boolean enable);
 +
 +    /**
 +     * Gets the length of the scrollbar
 +     * 
 +     * @return the length of the scrollbar in pixels
 +     */
 +    public double getOffsetSize() {
 +        return parseCssDimensionToPixels(internalGetOffsetSize());
 +    }
 +
 +    public abstract String internalGetOffsetSize();
 +
 +    /**
 +     * Sets the scroll position of the scrollbar in the axis the scrollbar is
 +     * representing.
 +     * <p>
 +     * <em>Note:</em> Even though {@code double} values are used, they are
 +     * currently only used as integers as large {@code int} (or small but fast
 +     * {@code long}). This means, all values are truncated to zero decimal
 +     * places.
 +     * 
 +     * @param px
 +     *            the new scroll position in pixels
 +     */
 +    public final void setScrollPos(double px) {
 +        if (isLocked()) {
 +            return;
 +        }
 +
 +        double oldScrollPos = scrollPos;
 +        scrollPos = Math.max(0, Math.min(maxScrollPos, truncate(px)));
 +
 +        if (!WidgetUtil.pixelValuesEqual(oldScrollPos, scrollPos)) {
 +            if (isInvisibleScrollbar) {
 +                invisibleScrollbarTemporaryResizer.show();
 +            }
 +
 +            /*
 +             * This is where the value needs to be converted into an integer no
 +             * matter how we flip it, since GWT expects an integer value.
 +             * There's no point making a JSNI method that accepts doubles as the
 +             * scroll position, since the browsers themselves don't support such
 +             * large numbers (as of today, 25.3.2014). This double-ranged is
 +             * only facilitating future virtual scrollbars.
 +             */
 +            internalSetScrollPos(toInt32(scrollPos));
 +        }
 +    }
 +
 +    /**
 +     * Should be called whenever this bundle is attached to the DOM (typically,
 +     * from the onLoad of the containing widget). Used to ensure the DOM scroll
 +     * position is maintained when detaching and reattaching the bundle.
 +     * 
 +     * @since 7.4.1
 +     */
 +    public void onLoad() {
 +        internalSetScrollPos(toInt32(scrollPos));
 +    }
 +
 +    /**
 +     * Truncates a double such that no decimal places are retained.
 +     * <p>
 +     * E.g. {@code trunc(2.3d) == 2.0d} and {@code trunc(-2.3d) == -2.0d}.
 +     * 
 +     * @param num
 +     *            the double value to be truncated
 +     * @return the {@code num} value without any decimal digits
 +     */
 +    private static double truncate(double num) {
 +        if (num > 0) {
 +            return Math.floor(num);
 +        } else {
 +            return Math.ceil(num);
 +        }
 +    }
 +
 +    /**
 +     * Modifies the element's scroll position (scrollTop or scrollLeft).
 +     * <p>
 +     * <em>Note:</em> The parameter here is a type of integer (instead of a
 +     * double) by design. The browsers internally convert all double values into
 +     * an integer value. To make this fact explicit, this API has chosen to
 +     * force integers already at this level.
 +     * 
 +     * @param px
 +     *            integer pixel value to scroll to
 +     */
 +    protected abstract void internalSetScrollPos(int px);
 +
 +    /**
 +     * Gets the scroll position of the scrollbar in the axis the scrollbar is
 +     * representing.
 +     * 
 +     * @return the new scroll position in pixels
 +     */
 +    public final double getScrollPos() {
 +        assert internalGetScrollPos() == toInt32(scrollPos) : "calculated scroll position ("
-         return val | 0;
++                + scrollPos
 +                + ") did not match the DOM element scroll position ("
 +                + internalGetScrollPos() + ")";
 +        return scrollPos;
 +    }
 +
 +    /**
 +     * Retrieves the element's scroll position (scrollTop or scrollLeft).
 +     * <p>
 +     * <em>Note:</em> The parameter here is a type of integer (instead of a
 +     * double) by design. The browsers internally convert all double values into
 +     * an integer value. To make this fact explicit, this API has chosen to
 +     * force integers already at this level.
 +     * 
 +     * @return integer pixel value of the scroll position
 +     */
 +    protected abstract int internalGetScrollPos();
 +
 +    /**
 +     * Modifies {@link #scrollSizeElement scrollSizeElement's} dimensions in
 +     * such a way that the scrollbar is able to scroll a certain number of
 +     * pixels in the axis it is representing.
 +     * 
 +     * @param px
 +     *            the new size of {@link #scrollSizeElement} in the dimension
 +     *            this scrollbar is representing
 +     */
 +    protected abstract void internalSetScrollSize(double px);
 +
 +    /**
 +     * Sets the amount of pixels the scrollbar needs to be able to scroll
 +     * through.
 +     * 
 +     * @param px
 +     *            the number of pixels the scrollbar should be able to scroll
 +     *            through
 +     */
 +    public final void setScrollSize(final double px) {
 +
 +        /*
 +         * This needs to be made step-by-step because IE8 flat-out refuses to
 +         * fire a scroll event when the scroll size becomes smaller than the
 +         * offset size. All other browser need to suffer alongside.
 +         */
 +
 +        boolean newScrollSizeIsSmallerThanOffsetSize = px <= getOffsetSize();
 +        boolean scrollSizeBecomesSmallerThanOffsetSize = showsScrollHandle()
 +                && newScrollSizeIsSmallerThanOffsetSize;
 +        if (scrollSizeBecomesSmallerThanOffsetSize && getScrollPos() != 0) {
 +            // must be a field because Java insists.
 +            scrollSizeTemporaryScrollHandler = addScrollHandler(new ScrollHandler() {
 +                @Override
 +                public void onScroll(ScrollEvent event) {
 +                    setScrollSizeNow(px);
 +                }
 +            });
 +            setScrollPos(0);
 +        } else {
 +            setScrollSizeNow(px);
 +        }
 +    }
 +
 +    private void setScrollSizeNow(double px) {
 +        internalSetScrollSize(Math.max(0, px));
 +        recalculateMaxScrollPos();
 +        forceScrollbar(showsScrollHandle());
 +        fireVisibilityChangeIfNeeded();
 +        if (scrollSizeTemporaryScrollHandler != null) {
 +            scrollSizeTemporaryScrollHandler.removeHandler();
 +            scrollSizeTemporaryScrollHandler = null;
 +        }
 +    }
 +
 +    /**
 +     * Gets the amount of pixels the scrollbar needs to be able to scroll
 +     * through.
 +     * 
 +     * @return the number of pixels the scrollbar should be able to scroll
 +     *         through
 +     */
 +    public double getScrollSize() {
 +        return parseCssDimensionToPixels(internalGetScrollSize());
 +    }
 +
 +    /**
 +     * Modifies {@link #scrollSizeElement scrollSizeElement's} dimensions in the
 +     * opposite axis to what the scrollbar is representing.
 +     * 
 +     * @param px
 +     *            the dimension that {@link #scrollSizeElement} should take in
 +     *            the opposite axis to what the scrollbar is representing
 +     */
 +    protected abstract void internalSetScrollbarThickness(double px);
 +
 +    /**
 +     * Sets the scrollbar's thickness.
 +     * <p>
 +     * If the thickness is set to 0, the scrollbar will be treated as an
 +     * "invisible" scrollbar. This means, the DOM structure will be given a
 +     * non-zero size, but {@link #getScrollbarThickness()} will still return the
 +     * value 0.
 +     * 
 +     * @param px
 +     *            the scrollbar's thickness in pixels
 +     */
 +    public final void setScrollbarThickness(double px) {
 +        isInvisibleScrollbar = (px == 0);
 +
 +        if (isInvisibleScrollbar) {
 +            Event.sinkEvents(root, Event.ONSCROLL);
 +            Event.setEventListener(root, new EventListener() {
 +                @Override
 +                public void onBrowserEvent(Event event) {
 +                    invisibleScrollbarTemporaryResizer.show();
 +                }
 +            });
 +            root.getStyle().setVisibility(Visibility.HIDDEN);
 +        } else {
 +            Event.sinkEvents(root, 0);
 +            Event.setEventListener(root, null);
 +            root.getStyle().clearVisibility();
 +        }
 +
 +        internalSetScrollbarThickness(Math.max(1d, px));
 +    }
 +
 +    /**
 +     * Gets the scrollbar's thickness as defined in the DOM.
 +     * 
 +     * @return the scrollbar's thickness as defined in the DOM, in pixels
 +     */
 +    protected abstract String internalGetScrollbarThickness();
 +
 +    /**
 +     * Gets the scrollbar's thickness.
 +     * <p>
 +     * This value will differ from the value in the DOM, if the thickness was
 +     * set to 0 with {@link #setScrollbarThickness(double)}, as the scrollbar is
 +     * then treated as "invisible."
 +     * 
 +     * @return the scrollbar's thickness in pixels
 +     */
 +    public final double getScrollbarThickness() {
 +        if (!isInvisibleScrollbar) {
 +            return parseCssDimensionToPixels(internalGetScrollbarThickness());
 +        } else {
 +            return 0;
 +        }
 +    }
 +
 +    /**
 +     * Checks whether the scrollbar's handle is visible.
 +     * <p>
 +     * In other words, this method checks whether the contents is larger than
 +     * can visually fit in the element.
 +     * 
 +     * @return <code>true</code> iff the scrollbar's handle is visible
 +     */
 +    public boolean showsScrollHandle() {
 +        return getScrollSize() - getOffsetSize() > WidgetUtil.PIXEL_EPSILON;
 +    }
 +
 +    public void recalculateMaxScrollPos() {
 +        double scrollSize = getScrollSize();
 +        double offsetSize = getOffsetSize();
 +        maxScrollPos = Math.max(0, scrollSize - offsetSize);
 +
 +        // make sure that the correct max scroll position is maintained.
 +        setScrollPos(scrollPos);
 +    }
 +
 +    /**
 +     * This is a method that JSNI can call to synchronize the object state from
 +     * the DOM.
 +     */
 +    private final void updateScrollPosFromDom() {
 +
 +        /*
 +         * TODO: this method probably shouldn't be called from Escalator's JSNI,
 +         * but probably could be handled internally by this listening to its own
 +         * element. Would clean up the code quite a bit. Needs further
 +         * investigation.
 +         */
 +
 +        int newScrollPos = internalGetScrollPos();
 +        if (!isLocked()) {
 +            scrollPos = newScrollPos;
 +            scrollEventFirer.scheduleEvent();
 +        } else if (scrollPos != newScrollPos) {
 +            // we need to actually undo the setting of the scroll.
 +            internalSetScrollPos(toInt32(scrollPos));
 +        }
 +    }
 +
 +    protected HandlerManager getHandlerManager() {
 +        if (handlerManager == null) {
 +            handlerManager = new HandlerManager(this);
 +        }
 +        return handlerManager;
 +    }
 +
 +    /**
 +     * Adds handler for the scrollbar handle visibility.
 +     * 
 +     * @param handler
 +     *            the {@link VisibilityHandler} to add
 +     * @return {@link HandlerRegistration} used to remove the handler
 +     */
 +    public HandlerRegistration addVisibilityHandler(
 +            final VisibilityHandler handler) {
 +        return getHandlerManager().addHandler(VisibilityChangeEvent.TYPE,
 +                handler);
 +    }
 +
 +    private void fireVisibilityChangeIfNeeded() {
 +        final boolean oldHandleIsVisible = scrollHandleIsVisible;
 +        scrollHandleIsVisible = showsScrollHandle();
 +        if (oldHandleIsVisible != scrollHandleIsVisible) {
 +            final VisibilityChangeEvent event = new VisibilityChangeEvent(
 +                    scrollHandleIsVisible);
 +            getHandlerManager().fireEvent(event);
 +        }
 +    }
 +
 +    /**
 +     * Converts a double into an integer by JavaScript's terms.
 +     * <p>
 +     * Implementation copied from {@link Element#toInt32(double)}.
 +     * 
 +     * @param val
 +     *            the double value to convert into an integer
 +     * @return the double value converted to an integer
 +     */
 +    private static native int toInt32(double val)
 +    /*-{
++        return Math.round(val) | 0;
 +    }-*/;
 +
 +    /**
 +     * Locks or unlocks the scrollbar bundle.
 +     * <p>
 +     * A locked scrollbar bundle will refuse to scroll, both programmatically
 +     * and via user-triggered events.
 +     * 
 +     * @param isLocked
 +     *            <code>true</code> to lock, <code>false</code> to unlock
 +     */
 +    public void setLocked(boolean isLocked) {
 +        this.isLocked = isLocked;
 +    }
 +
 +    /**
 +     * Checks whether the scrollbar bundle is locked or not.
 +     * 
 +     * @return <code>true</code> iff the scrollbar bundle is locked
 +     */
 +    public boolean isLocked() {
 +        return isLocked;
 +    }
 +
 +    /**
 +     * Returns the scroll direction of this scrollbar bundle.
 +     * 
 +     * @return the scroll direction of this scrollbar bundle
 +     */
 +    public abstract Direction getDirection();
 +
 +    /**
 +     * Adds a scroll handler to the scrollbar bundle.
 +     * 
 +     * @param handler
 +     *            the handler to add
 +     * @return the registration object for the handler registration
 +     */
 +    public HandlerRegistration addScrollHandler(final ScrollHandler handler) {
 +        return getHandlerManager().addHandler(ScrollEvent.TYPE, handler);
 +    }
 +
 +    private static double parseCssDimensionToPixels(String size) {
 +
 +        /*
 +         * Sizes of elements are calculated from CSS rather than
 +         * element.getOffset*() because those values are 0 whenever display:
 +         * none. Because we know that all elements have populated
 +         * CSS-dimensions, it's better to do it that way.
 +         * 
 +         * Another solution would be to make the elements visible while
 +         * measuring and then re-hide them, but that would cause unnecessary
 +         * reflows that would probably kill the performance dead.
 +         */
 +
 +        if (size.isEmpty()) {
 +            return 0;
 +        } else {
 +            assert size.endsWith("px") : "Can't parse CSS dimension \"" + size
 +                    + "\"";
 +            return Double.parseDouble(size.substring(0, size.length() - 2));
 +        }
 +    }
 +
 +    @Override
 +    public boolean isWorkPending() {
 +        return scrollSizeTemporaryScrollHandler != null
 +                || offsetSizeTemporaryScrollHandler != null;
 +    }
 +}
index 3585be1d60b0c3f8b8f90c7c89b0e59bcc7a4e80,0000000000000000000000000000000000000000..29b7eb6d53a83d298caac941a83280c16dfab46b
mode 100644,000000..100644
--- /dev/null
@@@ -1,6697 -1,0 +1,6706 @@@
 +/*
 + * Copyright 2000-2014 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.widgets;
 +
 +import java.util.ArrayList;
 +import java.util.Arrays;
 +import java.util.Collection;
 +import java.util.Collections;
 +import java.util.HashMap;
 +import java.util.LinkedList;
 +import java.util.List;
 +import java.util.ListIterator;
 +import java.util.Map;
 +import java.util.Map.Entry;
 +import java.util.TreeMap;
 +import java.util.logging.Level;
 +import java.util.logging.Logger;
 +
 +import com.google.gwt.animation.client.Animation;
 +import com.google.gwt.animation.client.AnimationScheduler;
 +import com.google.gwt.animation.client.AnimationScheduler.AnimationCallback;
 +import com.google.gwt.animation.client.AnimationScheduler.AnimationHandle;
 +import com.google.gwt.core.client.Duration;
 +import com.google.gwt.core.client.JavaScriptObject;
 +import com.google.gwt.core.client.JsArray;
 +import com.google.gwt.core.client.Scheduler;
 +import com.google.gwt.core.client.Scheduler.ScheduledCommand;
 +import com.google.gwt.dom.client.DivElement;
 +import com.google.gwt.dom.client.Document;
 +import com.google.gwt.dom.client.Element;
 +import com.google.gwt.dom.client.NativeEvent;
 +import com.google.gwt.dom.client.Node;
 +import com.google.gwt.dom.client.NodeList;
 +import com.google.gwt.dom.client.Style;
 +import com.google.gwt.dom.client.Style.Display;
 +import com.google.gwt.dom.client.Style.Unit;
 +import com.google.gwt.dom.client.TableCellElement;
 +import com.google.gwt.dom.client.TableRowElement;
 +import com.google.gwt.dom.client.TableSectionElement;
 +import com.google.gwt.dom.client.Touch;
 +import com.google.gwt.event.shared.HandlerRegistration;
 +import com.google.gwt.logging.client.LogConfiguration;
 +import com.google.gwt.user.client.Command;
 +import com.google.gwt.user.client.DOM;
 +import com.google.gwt.user.client.Window;
 +import com.google.gwt.user.client.ui.RequiresResize;
 +import com.google.gwt.user.client.ui.RootPanel;
 +import com.google.gwt.user.client.ui.UIObject;
 +import com.google.gwt.user.client.ui.Widget;
 +import com.vaadin.client.BrowserInfo;
 +import com.vaadin.client.DeferredWorker;
 +import com.vaadin.client.Profiler;
 +import com.vaadin.client.WidgetUtil;
 +import com.vaadin.client.ui.SubPartAware;
 +import com.vaadin.client.widget.escalator.Cell;
 +import com.vaadin.client.widget.escalator.ColumnConfiguration;
 +import com.vaadin.client.widget.escalator.EscalatorUpdater;
 +import com.vaadin.client.widget.escalator.FlyweightCell;
 +import com.vaadin.client.widget.escalator.FlyweightRow;
 +import com.vaadin.client.widget.escalator.PositionFunction;
 +import com.vaadin.client.widget.escalator.PositionFunction.AbsolutePosition;
 +import com.vaadin.client.widget.escalator.PositionFunction.Translate3DPosition;
 +import com.vaadin.client.widget.escalator.PositionFunction.TranslatePosition;
 +import com.vaadin.client.widget.escalator.PositionFunction.WebkitTranslate3DPosition;
 +import com.vaadin.client.widget.escalator.Row;
 +import com.vaadin.client.widget.escalator.RowContainer;
 +import com.vaadin.client.widget.escalator.RowContainer.BodyRowContainer;
 +import com.vaadin.client.widget.escalator.RowVisibilityChangeEvent;
 +import com.vaadin.client.widget.escalator.RowVisibilityChangeHandler;
 +import com.vaadin.client.widget.escalator.ScrollbarBundle;
 +import com.vaadin.client.widget.escalator.ScrollbarBundle.HorizontalScrollbarBundle;
 +import com.vaadin.client.widget.escalator.ScrollbarBundle.VerticalScrollbarBundle;
 +import com.vaadin.client.widget.escalator.Spacer;
 +import com.vaadin.client.widget.escalator.SpacerUpdater;
 +import com.vaadin.client.widget.grid.events.ScrollEvent;
 +import com.vaadin.client.widget.grid.events.ScrollHandler;
 +import com.vaadin.client.widgets.Escalator.JsniUtil.TouchHandlerBundle;
 +import com.vaadin.shared.ui.grid.HeightMode;
 +import com.vaadin.shared.ui.grid.Range;
 +import com.vaadin.shared.ui.grid.ScrollDestination;
 +import com.vaadin.shared.util.SharedUtil;
 +
 +/*-
 +
 + Maintenance Notes! Reading these might save your day.
 + (note for editors: line width is 80 chars, including the
 + one-space indentation)
 +
 +
 + == Row Container Structure
 +
 + AbstractRowContainer
 + |-- AbstractStaticRowContainer
 + | |-- HeaderRowContainer
 + | `-- FooterContainer
 + `---- BodyRowContainerImpl
 +
 + AbstractRowContainer is intended to contain all common logic
 + between RowContainers. It manages the bookkeeping of row
 + count, makes sure that all individual cells are rendered
 + the same way, and so on.
 +
 + AbstractStaticRowContainer has some special logic that is
 + required by all RowContainers that don't scroll (hence the
 + word "static"). HeaderRowContainer and FooterRowContainer
 + are pretty thin special cases of a StaticRowContainer
 + (mostly relating to positioning of the root element).
 +
 + BodyRowContainerImpl could also be split into an additional
 + "AbstractScrollingRowContainer", but I felt that no more
 + inner classes were needed. So it contains both logic
 + required for making things scroll about, and equivalent
 + special cases for layouting, as are found in
 + Header/FooterRowContainers.
 +
 +
 + == The Three Indices
 +
 + Each RowContainer can be thought to have three levels of
 + indices for any given displayed row (but the distinction
 + matters primarily for the BodyRowContainerImpl, because of 
 + the way it scrolls through data):
 +
 + - Logical index
 + - Physical (or DOM) index
 + - Visual index
 +
 + LOGICAL INDEX is the index that is linked to the data
 + source. If you want your data source to represent a SQL
 + database with 10 000 rows, the 7 000:th row in the SQL has a
 + logical index of 6 999, since the index is 0-based (unless
 + that data source does some funky logic).
 +
 + PHYSICAL INDEX is the index for a row that you see in a
 + browser's DOM inspector. If your row is the second <tr>
 + element within a <tbody> tag, it has a physical index of 1
 + (because of 0-based indices). In Header and
 + FooterRowContainers, you are safe to assume that the logical
 + index is the same as the physical index. But because the
 + BodyRowContainerImpl never displays large data sources 
 + entirely in the DOM, a physical index usually has no 
 + apparent direct relationship with its logical index.
 +
 + VISUAL INDEX is the index relating to the order that you
 + see a row in, in the browser, as it is rendered. The
 + topmost row is 0, the second is 1, and so on. The visual
 + index is similar to the physical index in the sense that
 + Header and FooterRowContainers can assume a 1:1
 + relationship between visual index and logical index. And
 + again, BodyRowContainerImpl has no such relationship. The
 + body's visual index has additionally no apparent
 + relationship with its physical index. Because the <tr> tags
 + are reused in the body and visually repositioned with CSS
 + as the user scrolls, the relationship between physical
 + index and visual index is quickly broken. You can get an
 + element's visual index via the field
 + BodyRowContainerImpl.visualRowOrder.
 +
 + Currently, the physical and visual indices are kept in sync
 + _most of the time_ by a deferred rearrangement of rows.
 + They become desynced when scrolling. This is to help screen
 + readers to read the contents from the DOM in a natural
 + order. See BodyRowContainerImpl.DeferredDomSorter for more
 + about that.
 +
 + */
 +
 +/**
 + * A workaround-class for GWT and JSNI.
 + * <p>
 + * GWT is unable to handle some method calls to Java methods in inner-classes
 + * from within JSNI blocks. Having that inner class extend a non-inner-class (or
 + * implement such an interface), makes it possible for JSNI to indirectly refer
 + * to the inner class, by invoking methods and fields in the non-inner-class
 + * API.
 + * 
 + * @see Escalator.Scroller
 + */
 +abstract class JsniWorkaround {
 +    /**
 +     * A JavaScript function that handles the scroll DOM event, and passes it on
 +     * to Java code.
 +     * 
 +     * @see #createScrollListenerFunction(Escalator)
 +     * @see Escalator#onScroll()
 +     * @see Escalator.Scroller#onScroll()
 +     */
 +    protected final JavaScriptObject scrollListenerFunction;
 +
 +    /**
 +     * A JavaScript function that handles the mousewheel DOM event, and passes
 +     * it on to Java code.
 +     * 
 +     * @see #createMousewheelListenerFunction(Escalator)
 +     * @see Escalator#onScroll()
 +     * @see Escalator.Scroller#onScroll()
 +     */
 +    protected final JavaScriptObject mousewheelListenerFunction;
 +
 +    /**
 +     * A JavaScript function that handles the touch start DOM event, and passes
 +     * it on to Java code.
 +     * 
 +     * @see TouchHandlerBundle#touchStart(Escalator.JsniUtil.TouchHandlerBundle.CustomTouchEvent)
 +     */
 +    protected JavaScriptObject touchStartFunction;
 +
 +    /**
 +     * A JavaScript function that handles the touch move DOM event, and passes
 +     * it on to Java code.
 +     * 
 +     * @see TouchHandlerBundle#touchMove(Escalator.JsniUtil.TouchHandlerBundle.CustomTouchEvent)
 +     */
 +    protected JavaScriptObject touchMoveFunction;
 +
 +    /**
 +     * A JavaScript function that handles the touch end and cancel DOM events,
 +     * and passes them on to Java code.
 +     * 
 +     * @see TouchHandlerBundle#touchEnd(Escalator.JsniUtil.TouchHandlerBundle.CustomTouchEvent)
 +     */
 +    protected JavaScriptObject touchEndFunction;
 +
 +    protected TouchHandlerBundle touchHandlerBundle;
 +
 +    protected JsniWorkaround(final Escalator escalator) {
 +        scrollListenerFunction = createScrollListenerFunction(escalator);
 +        mousewheelListenerFunction = createMousewheelListenerFunction(escalator);
 +
 +        touchHandlerBundle = new TouchHandlerBundle(escalator);
 +        touchStartFunction = touchHandlerBundle.getTouchStartHandler();
 +        touchMoveFunction = touchHandlerBundle.getTouchMoveHandler();
 +        touchEndFunction = touchHandlerBundle.getTouchEndHandler();
 +    }
 +
 +    /**
 +     * A method that constructs the JavaScript function that will be stored into
 +     * {@link #scrollListenerFunction}.
 +     * 
 +     * @param esc
 +     *            a reference to the current instance of {@link Escalator}
 +     * @see Escalator#onScroll()
 +     */
 +    protected abstract JavaScriptObject createScrollListenerFunction(
 +            Escalator esc);
 +
 +    /**
 +     * A method that constructs the JavaScript function that will be stored into
 +     * {@link #mousewheelListenerFunction}.
 +     * 
 +     * @param esc
 +     *            a reference to the current instance of {@link Escalator}
 +     * @see Escalator#onScroll()
 +     */
 +    protected abstract JavaScriptObject createMousewheelListenerFunction(
 +            Escalator esc);
 +}
 +
 +/**
 + * A low-level table-like widget that features a scrolling virtual viewport and
 + * lazily generated rows.
 + * 
 + * @since 7.4
 + * @author Vaadin Ltd
 + */
 +public class Escalator extends Widget implements RequiresResize,
 +        DeferredWorker, SubPartAware {
 +
 +    // todo comments legend
 +    /*
 +     * [[optimize]]: There's an opportunity to rewrite the code in such a way
 +     * that it _might_ perform better (rememeber to measure, implement,
 +     * re-measure)
 +     */
 +    /*
 +     * [[mpixscroll]]: This code will require alterations that are relevant for
 +     * supporting the scrolling through more pixels than some browsers normally
 +     * would support. (i.e. when we support more than "a million" pixels in the
 +     * escalator DOM). NOTE: these bits can most often also be identified by
 +     * searching for code that call scrollElem.getScrollTop();.
 +     */
 +    /*
 +     * [[spacer]]: Code that is important to make spacers work.
 +     */
 +
 +    /**
 +     * A utility class that contains utility methods that are usually called
 +     * from JSNI.
 +     * <p>
 +     * The methods are moved in this class to minimize the amount of JSNI code
 +     * as much as feasible.
 +     */
 +    static class JsniUtil {
 +        public static class TouchHandlerBundle {
 +
 +            /**
 +             * A <a href=
 +             * "http://www.gwtproject.org/doc/latest/DevGuideCodingBasicsOverlay.html"
 +             * >JavaScriptObject overlay</a> for the <a
 +             * href="http://www.w3.org/TR/touch-events/">JavaScript
 +             * TouchEvent</a> object.
 +             * <p>
 +             * This needs to be used in the touch event handlers, since GWT's
 +             * {@link com.google.gwt.event.dom.client.TouchEvent TouchEvent}
 +             * can't be cast from the JSNI call, and the
 +             * {@link com.google.gwt.dom.client.NativeEvent NativeEvent} isn't
 +             * properly populated with the correct values.
 +             */
 +            private final static class CustomTouchEvent extends
 +                    JavaScriptObject {
 +                protected CustomTouchEvent() {
 +                }
 +
 +                public native NativeEvent getNativeEvent()
 +                /*-{
 +                    return this;
 +                }-*/;
 +
 +                public native int getPageX()
 +                /*-{
 +                    return this.targetTouches[0].pageX;
 +                }-*/;
 +
 +                public native int getPageY()
 +                /*-{
 +                    return this.targetTouches[0].pageY;
 +                }-*/;
 +            }
 +
 +            private final Escalator escalator;
 +
 +            public TouchHandlerBundle(final Escalator escalator) {
 +                this.escalator = escalator;
 +            }
 +
 +            public native JavaScriptObject getTouchStartHandler()
 +            /*-{
 +                // we need to store "this", since it won't be preserved on call.
 +                var self = this;
 +                return $entry(function (e) {
 +                    self.@com.vaadin.client.widgets.Escalator.JsniUtil.TouchHandlerBundle::touchStart(*)(e);
 +                });
 +            }-*/;
 +
 +            public native JavaScriptObject getTouchMoveHandler()
 +            /*-{
 +                // we need to store "this", since it won't be preserved on call.
 +                var self = this;
 +                return $entry(function (e) {
 +                    self.@com.vaadin.client.widgets.Escalator.JsniUtil.TouchHandlerBundle::touchMove(*)(e);
 +                });
 +            }-*/;
 +
 +            public native JavaScriptObject getTouchEndHandler()
 +            /*-{
 +                // we need to store "this", since it won't be preserved on call.
 +                var self = this;
 +                return $entry(function (e) {
 +                    self.@com.vaadin.client.widgets.Escalator.JsniUtil.TouchHandlerBundle::touchEnd(*)(e);
 +                });
 +            }-*/;
 +
 +            // Duration of the inertial scrolling simulation. Devices with
 +            // larger screens take longer durations.
 +            private static final int DURATION = Window.getClientHeight();
 +            // multiply scroll velocity with repeated touching
 +            private int acceleration = 1;
 +            private boolean touching = false;
 +            // Two movement objects for storing status and processing touches
 +            private Movement yMov, xMov;
 +            final double MIN_VEL = 0.6, MAX_VEL = 4, F_VEL = 1500, F_ACC = 0.7,
 +                    F_AXIS = 1;
 +
 +            // The object to deal with one direction scrolling
 +            private class Movement {
 +                final List<Double> speeds = new ArrayList<Double>();
 +                final ScrollbarBundle scroll;
 +                double position, offset, velocity, prevPos, prevTime, delta;
 +                boolean run, vertical;
 +
 +                public Movement(boolean vertical) {
 +                    this.vertical = vertical;
 +                    scroll = vertical ? escalator.verticalScrollbar
 +                            : escalator.horizontalScrollbar;
 +                }
 +
 +                public void startTouch(CustomTouchEvent event) {
 +                    speeds.clear();
 +                    prevPos = pagePosition(event);
 +                    prevTime = Duration.currentTimeMillis();
 +                }
 +
 +                public void moveTouch(CustomTouchEvent event) {
 +                    double pagePosition = pagePosition(event);
 +                    if (pagePosition > -1) {
 +                        delta = prevPos - pagePosition;
 +                        double now = Duration.currentTimeMillis();
 +                        double ellapsed = now - prevTime;
 +                        velocity = delta / ellapsed;
 +                        // if last speed was so low, reset speeds and start
 +                        // storing again
 +                        if (speeds.size() > 0 && !validSpeed(speeds.get(0))) {
 +                            speeds.clear();
 +                            run = true;
 +                        }
 +                        speeds.add(0, velocity);
 +                        prevTime = now;
 +                        prevPos = pagePosition;
 +                    }
 +                }
 +
 +                public void endTouch(CustomTouchEvent event) {
 +                    // Compute average speed
 +                    velocity = 0;
 +                    for (double s : speeds) {
 +                        velocity += s / speeds.size();
 +                    }
 +                    position = scroll.getScrollPos();
 +
 +                    // Compute offset, and adjust it with an easing curve so as
 +                    // movement is smoother.
 +                    offset = F_VEL * velocity * acceleration
 +                            * easingInOutCos(velocity, MAX_VEL);
 +
 +                    // Enable or disable inertia movement in this axis
 +                    run = validSpeed(velocity);
 +                    if (run) {
 +                        event.getNativeEvent().preventDefault();
 +                    }
 +                }
 +
 +                void validate(Movement other) {
 +                    if (!run || other.velocity > 0
 +                            && Math.abs(velocity / other.velocity) < F_AXIS) {
 +                        delta = offset = 0;
 +                        run = false;
 +                    }
 +                }
 +
 +                void stepAnimation(double progress) {
 +                    scroll.setScrollPos(position + offset * progress);
 +                }
 +
 +                int pagePosition(CustomTouchEvent event) {
 +                    JsArray<Touch> a = event.getNativeEvent().getTouches();
 +                    return vertical ? a.get(0).getPageY() : a.get(0).getPageX();
 +                }
 +
 +                boolean validSpeed(double speed) {
 +                    return Math.abs(speed) > MIN_VEL;
 +                }
 +            }
 +
 +            // Using GWT animations which take care of native animation frames.
 +            private Animation animation = new Animation() {
 +                @Override
 +                public void onUpdate(double progress) {
 +                    xMov.stepAnimation(progress);
 +                    yMov.stepAnimation(progress);
 +                }
 +
 +                @Override
 +                public double interpolate(double progress) {
 +                    return easingOutCirc(progress);
 +                };
 +
 +                @Override
 +                public void onComplete() {
 +                    touching = false;
 +                    escalator.body.domSorter.reschedule();
 +                };
 +
 +                @Override
 +                public void run(int duration) {
 +                    if (xMov.run || yMov.run) {
 +                        super.run(duration);
 +                    } else {
 +                        onComplete();
 +                    }
 +                };
 +            };
 +
 +            public void touchStart(final CustomTouchEvent event) {
 +                if (event.getNativeEvent().getTouches().length() == 1) {
 +                    if (yMov == null) {
 +                        yMov = new Movement(true);
 +                        xMov = new Movement(false);
 +                    }
 +                    if (animation.isRunning()) {
 +                        acceleration += F_ACC;
 +                        event.getNativeEvent().preventDefault();
 +                        animation.cancel();
 +                    } else {
 +                        acceleration = 1;
 +                    }
 +                    xMov.startTouch(event);
 +                    yMov.startTouch(event);
 +                    touching = true;
 +                } else {
 +                    touching = false;
 +                    animation.cancel();
 +                    acceleration = 1;
 +                }
 +            }
 +
 +            public void touchMove(final CustomTouchEvent event) {
 +                if (touching) {
 +                    xMov.moveTouch(event);
 +                    yMov.moveTouch(event);
 +                    xMov.validate(yMov);
 +                    yMov.validate(xMov);
 +                    event.getNativeEvent().preventDefault();
 +                    moveScrollFromEvent(escalator, xMov.delta, yMov.delta,
 +                            event.getNativeEvent());
 +                }
 +            }
 +
 +            public void touchEnd(final CustomTouchEvent event) {
 +                if (touching) {
 +                    xMov.endTouch(event);
 +                    yMov.endTouch(event);
 +                    xMov.validate(yMov);
 +                    yMov.validate(xMov);
 +                    // Adjust duration so as longer movements take more duration
 +                    boolean vert = !xMov.run || yMov.run
 +                            && Math.abs(yMov.offset) > Math.abs(xMov.offset);
 +                    double delta = Math.abs((vert ? yMov : xMov).offset);
 +                    animation.run((int) (3 * DURATION * easingOutExp(delta)));
 +                }
 +            }
 +
 +            private double easingInOutCos(double val, double max) {
 +                return 0.5 - 0.5 * Math.cos(Math.PI * Math.signum(val)
 +                        * Math.min(Math.abs(val), max) / max);
 +            }
 +
 +            private double easingOutExp(double delta) {
 +                return (1 - Math.pow(2, -delta / 1000));
 +            }
 +
 +            private double easingOutCirc(double progress) {
 +                return Math.sqrt(1 - (progress - 1) * (progress - 1));
 +            }
 +        }
 +
 +        public static void moveScrollFromEvent(final Escalator escalator,
 +                final double deltaX, final double deltaY,
 +                final NativeEvent event) {
 +
 +            if (!Double.isNaN(deltaX)) {
 +                escalator.horizontalScrollbar.setScrollPosByDelta(deltaX);
 +            }
 +
 +            if (!Double.isNaN(deltaY)) {
 +                escalator.verticalScrollbar.setScrollPosByDelta(deltaY);
 +            }
 +
 +            /*
 +             * TODO: only prevent if not scrolled to end/bottom. Or no? UX team
 +             * needs to decide.
 +             */
 +            final boolean warrantedYScroll = deltaY != 0
 +                    && escalator.verticalScrollbar.showsScrollHandle();
 +            final boolean warrantedXScroll = deltaX != 0
 +                    && escalator.horizontalScrollbar.showsScrollHandle();
 +            if (warrantedYScroll || warrantedXScroll) {
 +                event.preventDefault();
 +            }
 +        }
 +    }
 +
 +    /**
 +     * ScrollDestination case-specific handling logic.
 +     */
 +    private static double getScrollPos(final ScrollDestination destination,
 +            final double targetStartPx, final double targetEndPx,
 +            final double viewportStartPx, final double viewportEndPx,
 +            final double padding) {
 +
 +        final double viewportLength = viewportEndPx - viewportStartPx;
 +
 +        switch (destination) {
 +
 +        /*
 +         * Scroll as little as possible to show the target element. If the
 +         * element fits into view, this works as START or END depending on the
 +         * current scroll position. If the element does not fit into view, this
 +         * works as START.
 +         */
 +        case ANY: {
 +            final double startScrollPos = targetStartPx - padding;
 +            final double endScrollPos = targetEndPx + padding - viewportLength;
 +
 +            if (startScrollPos < viewportStartPx) {
 +                return startScrollPos;
 +            } else if (targetEndPx + padding > viewportEndPx) {
 +                return endScrollPos;
 +            } else {
 +                // NOOP, it's already visible
 +                return viewportStartPx;
 +            }
 +        }
 +
 +        /*
 +         * Scrolls so that the element is shown at the end of the viewport. The
 +         * viewport will, however, not scroll before its first element.
 +         */
 +        case END: {
 +            return targetEndPx + padding - viewportLength;
 +        }
 +
 +        /*
 +         * Scrolls so that the element is shown in the middle of the viewport.
 +         * The viewport will, however, not scroll beyond its contents, given
 +         * more elements than what the viewport is able to show at once. Under
 +         * no circumstances will the viewport scroll before its first element.
 +         */
 +        case MIDDLE: {
 +            final double targetMiddle = targetStartPx
 +                    + (targetEndPx - targetStartPx) / 2;
 +            return targetMiddle - viewportLength / 2;
 +        }
 +
 +        /*
 +         * Scrolls so that the element is shown at the start of the viewport.
 +         * The viewport will, however, not scroll beyond its contents.
 +         */
 +        case START: {
 +            return targetStartPx - padding;
 +        }
 +
 +        /*
 +         * Throw an error if we're here. This can only mean that
 +         * ScrollDestination has been carelessly amended..
 +         */
 +        default: {
 +            throw new IllegalArgumentException(
 +                    "Internal: ScrollDestination has been modified, "
 +                            + "but Escalator.getScrollPos has not been updated "
 +                            + "to match new values.");
 +        }
 +        }
 +
 +    }
 +
 +    /** An inner class that handles all logic related to scrolling. */
 +    private class Scroller extends JsniWorkaround {
 +        private double lastScrollTop = 0;
 +        private double lastScrollLeft = 0;
 +
 +        public Scroller() {
 +            super(Escalator.this);
 +        }
 +
 +        @Override
 +        protected native JavaScriptObject createScrollListenerFunction(
 +                Escalator esc)
 +        /*-{
 +            var vScroll = esc.@com.vaadin.client.widgets.Escalator::verticalScrollbar;
 +            var vScrollElem = vScroll.@com.vaadin.client.widget.escalator.ScrollbarBundle::getElement()();
 +
 +            var hScroll = esc.@com.vaadin.client.widgets.Escalator::horizontalScrollbar;
 +            var hScrollElem = hScroll.@com.vaadin.client.widget.escalator.ScrollbarBundle::getElement()();
 +
 +            return $entry(function(e) {
 +                var target = e.target || e.srcElement; // IE8 uses e.scrElement
 +
 +                // in case the scroll event was native (i.e. scrollbars were dragged, or
 +                // the scrollTop/Left was manually modified), the bundles have old cache
 +                // values. We need to make sure that the caches are kept up to date.
 +                if (target === vScrollElem) {
 +                    vScroll.@com.vaadin.client.widget.escalator.ScrollbarBundle::updateScrollPosFromDom()();
 +                } else if (target === hScrollElem) {
 +                    hScroll.@com.vaadin.client.widget.escalator.ScrollbarBundle::updateScrollPosFromDom()();
 +                } else {
 +                    $wnd.console.error("unexpected scroll target: "+target);
 +                }
 +            });
 +        }-*/;
 +
 +        @Override
 +        protected native JavaScriptObject createMousewheelListenerFunction(
 +                Escalator esc)
 +        /*-{
 +            return $entry(function(e) {
 +                var deltaX = e.deltaX ? e.deltaX : -0.5*e.wheelDeltaX;
 +                var deltaY = e.deltaY ? e.deltaY : -0.5*e.wheelDeltaY;
 +
 +                // Delta mode 0 is in pixels; we don't need to do anything...
 +
 +                // A delta mode of 1 means we're scrolling by lines instead of pixels
 +                // We need to scale the number of lines by the default line height
 +                if(e.deltaMode === 1) {
 +                    var brc = esc.@com.vaadin.client.widgets.Escalator::body;
 +                    deltaY *= brc.@com.vaadin.client.widgets.Escalator.AbstractRowContainer::getDefaultRowHeight()();
 +                }
 +
 +                // Other delta modes aren't supported
 +                if((e.deltaMode !== undefined) && (e.deltaMode >= 2 || e.deltaMode < 0)) {
 +                    var msg = "Unsupported wheel delta mode \"" + e.deltaMode + "\"";
 +
 +                    // Print warning message
 +                    esc.@com.vaadin.client.widgets.Escalator::logWarning(*)(msg);
 +                }
 +
 +                // IE8 has only delta y
 +                if (isNaN(deltaY)) {
 +                    deltaY = -0.5*e.wheelDelta;
 +                }
 +
 +                @com.vaadin.client.widgets.Escalator.JsniUtil::moveScrollFromEvent(*)(esc, deltaX, deltaY, e);
 +            });
 +        }-*/;
 +
 +        /**
 +         * Recalculates the virtual viewport represented by the scrollbars, so
 +         * that the sizes of the scroll handles appear correct in the browser
 +         */
 +        public void recalculateScrollbarsForVirtualViewport() {
 +            double scrollContentHeight = body.calculateTotalRowHeight()
 +                    + body.spacerContainer.getSpacerHeightsSum();
 +            double scrollContentWidth = columnConfiguration.calculateRowWidth();
 +            double tableWrapperHeight = heightOfEscalator;
 +            double tableWrapperWidth = widthOfEscalator;
 +
 +            boolean verticalScrollNeeded = scrollContentHeight > tableWrapperHeight
 +                    + WidgetUtil.PIXEL_EPSILON
 +                    - header.getHeightOfSection()
 +                    - footer.getHeightOfSection();
 +            boolean horizontalScrollNeeded = scrollContentWidth > tableWrapperWidth
 +                    + WidgetUtil.PIXEL_EPSILON;
 +
 +            // One dimension got scrollbars, but not the other. Recheck time!
 +            if (verticalScrollNeeded != horizontalScrollNeeded) {
 +                if (!verticalScrollNeeded && horizontalScrollNeeded) {
 +                    verticalScrollNeeded = scrollContentHeight > tableWrapperHeight
 +                            + WidgetUtil.PIXEL_EPSILON
 +                            - header.getHeightOfSection()
 +                            - footer.getHeightOfSection()
 +                            - horizontalScrollbar.getScrollbarThickness();
 +                } else {
 +                    horizontalScrollNeeded = scrollContentWidth > tableWrapperWidth
 +                            + WidgetUtil.PIXEL_EPSILON
 +                            - verticalScrollbar.getScrollbarThickness();
 +                }
 +            }
 +
 +            // let's fix the table wrapper size, since it's now stable.
 +            if (verticalScrollNeeded) {
 +                tableWrapperWidth -= verticalScrollbar.getScrollbarThickness();
 +                tableWrapperWidth = Math.max(0, tableWrapperWidth);
 +            }
 +            if (horizontalScrollNeeded) {
 +                tableWrapperHeight -= horizontalScrollbar
 +                        .getScrollbarThickness();
 +                tableWrapperHeight = Math.max(0, tableWrapperHeight);
 +            }
 +            tableWrapper.getStyle().setHeight(tableWrapperHeight, Unit.PX);
 +            tableWrapper.getStyle().setWidth(tableWrapperWidth, Unit.PX);
 +
 +            double footerHeight = footer.getHeightOfSection();
 +            double headerHeight = header.getHeightOfSection();
 +            double vScrollbarHeight = Math.max(0, tableWrapperHeight
 +                    - footerHeight - headerHeight);
 +            verticalScrollbar.setOffsetSize(vScrollbarHeight);
 +            verticalScrollbar.setScrollSize(scrollContentHeight);
 +
 +            /*
 +             * If decreasing the amount of frozen columns, and scrolled to the
 +             * right, the scroll position might reset. So we need to remember
 +             * the scroll position, and re-apply it once the scrollbar size has
 +             * been adjusted.
 +             */
 +            double prevScrollPos = horizontalScrollbar.getScrollPos();
 +
 +            double unfrozenPixels = columnConfiguration
 +                    .getCalculatedColumnsWidth(Range.between(
 +                            columnConfiguration.getFrozenColumnCount(),
 +                            columnConfiguration.getColumnCount()));
 +            double frozenPixels = scrollContentWidth - unfrozenPixels;
 +            double hScrollOffsetWidth = tableWrapperWidth - frozenPixels;
 +            horizontalScrollbar.setOffsetSize(hScrollOffsetWidth);
 +            horizontalScrollbar.setScrollSize(unfrozenPixels);
 +            horizontalScrollbar.getElement().getStyle()
 +                    .setLeft(frozenPixels, Unit.PX);
 +            horizontalScrollbar.setScrollPos(prevScrollPos);
 +
 +            /*
 +             * only show the scrollbar wrapper if the scrollbar itself is
 +             * visible.
 +             */
 +            if (horizontalScrollbar.showsScrollHandle()) {
 +                horizontalScrollbarDeco.getStyle().clearDisplay();
 +            } else {
 +                horizontalScrollbarDeco.getStyle().setDisplay(Display.NONE);
 +            }
 +
 +            /*
 +             * only show corner background divs if the vertical scrollbar is
 +             * visible.
 +             */
 +            Style hCornerStyle = headerDeco.getStyle();
 +            Style fCornerStyle = footerDeco.getStyle();
 +            if (verticalScrollbar.showsScrollHandle()) {
 +                hCornerStyle.clearDisplay();
 +                fCornerStyle.clearDisplay();
 +
 +                if (horizontalScrollbar.showsScrollHandle()) {
 +                    double offset = horizontalScrollbar.getScrollbarThickness();
 +                    fCornerStyle.setBottom(offset, Unit.PX);
 +                } else {
 +                    fCornerStyle.clearBottom();
 +                }
 +            } else {
 +                hCornerStyle.setDisplay(Display.NONE);
 +                fCornerStyle.setDisplay(Display.NONE);
 +            }
 +        }
 +
 +        /**
 +         * Logical scrolling event handler for the entire widget.
 +         */
 +        public void onScroll() {
 +
 +            final double scrollTop = verticalScrollbar.getScrollPos();
 +            final double scrollLeft = horizontalScrollbar.getScrollPos();
 +            if (lastScrollLeft != scrollLeft) {
 +                for (int i = 0; i < columnConfiguration.frozenColumns; i++) {
 +                    header.updateFreezePosition(i, scrollLeft);
 +                    body.updateFreezePosition(i, scrollLeft);
 +                    footer.updateFreezePosition(i, scrollLeft);
 +                }
 +
 +                position.set(headElem, -scrollLeft, 0);
 +
 +                /*
 +                 * TODO [[optimize]]: cache this value in case the instanceof
 +                 * check has undesirable overhead. This could also be a
 +                 * candidate for some deferred binding magic so that e.g.
 +                 * AbsolutePosition is not even considered in permutations that
 +                 * we know support something better. That would let the compiler
 +                 * completely remove the entire condition since it knows that
 +                 * the if will never be true.
 +                 */
 +                if (position instanceof AbsolutePosition) {
 +                    /*
 +                     * we don't want to put "top: 0" on the footer, since it'll
 +                     * render wrong, as we already have
 +                     * "bottom: $footer-height".
 +                     */
 +                    footElem.getStyle().setLeft(-scrollLeft, Unit.PX);
 +                } else {
 +                    position.set(footElem, -scrollLeft, 0);
 +                }
 +
 +                lastScrollLeft = scrollLeft;
 +            }
 +
 +            body.setBodyScrollPosition(scrollLeft, scrollTop);
 +
 +            lastScrollTop = scrollTop;
 +            body.updateEscalatorRowsOnScroll();
 +            body.spacerContainer.updateSpacerDecosVisibility();
 +            /*
 +             * TODO [[optimize]]: Might avoid a reflow by first calculating new
 +             * scrolltop and scrolleft, then doing the escalator magic based on
 +             * those numbers and only updating the positions after that.
 +             */
 +        }
 +
 +        public native void attachScrollListener(Element element)
 +        /*
 +         * Attaching events with JSNI instead of the GWT event mechanism because
 +         * GWT didn't provide enough details in events, or triggering the event
 +         * handlers with GWT bindings was unsuccessful. Maybe, with more time
 +         * and skill, it could be done with better success. JavaScript overlay
 +         * types might work. This might also get rid of the JsniWorkaround
 +         * class.
 +         */
 +        /*-{
 +             if (element.addEventListener) {
 +                 element.addEventListener("scroll", this.@com.vaadin.client.widgets.JsniWorkaround::scrollListenerFunction);
 +             } else {
 +                 element.attachEvent("onscroll", this.@com.vaadin.client.widgets.JsniWorkaround::scrollListenerFunction);
 +             }
 +        }-*/;
 +
 +        public native void detachScrollListener(Element element)
 +        /*
 +         * Attaching events with JSNI instead of the GWT event mechanism because
 +         * GWT didn't provide enough details in events, or triggering the event
 +         * handlers with GWT bindings was unsuccessful. Maybe, with more time
 +         * and skill, it could be done with better success. JavaScript overlay
 +         * types might work. This might also get rid of the JsniWorkaround
 +         * class.
 +         */
 +        /*-{
 +            if (element.addEventListener) {
 +                element.removeEventListener("scroll", this.@com.vaadin.client.widgets.JsniWorkaround::scrollListenerFunction);
 +            } else {
 +                element.detachEvent("onscroll", this.@com.vaadin.client.widgets.JsniWorkaround::scrollListenerFunction);
 +            }
 +        }-*/;
 +
 +        public native void attachMousewheelListener(Element element)
 +        /*
 +         * Attaching events with JSNI instead of the GWT event mechanism because
 +         * GWT didn't provide enough details in events, or triggering the event
 +         * handlers with GWT bindings was unsuccessful. Maybe, with more time
 +         * and skill, it could be done with better success. JavaScript overlay
 +         * types might work. This might also get rid of the JsniWorkaround
 +         * class.
 +         */
 +        /*-{
 +            if (element.addEventListener) {
 +                // firefox likes "wheel", while others use "mousewheel"
 +                var eventName = 'onmousewheel' in element ? 'mousewheel' : 'wheel';
 +                element.addEventListener(eventName, this.@com.vaadin.client.widgets.JsniWorkaround::mousewheelListenerFunction);
 +            } else {
 +                // IE8
 +                element.attachEvent("onmousewheel", this.@com.vaadin.client.widgets.JsniWorkaround::mousewheelListenerFunction);
 +            }
 +        }-*/;
 +
 +        public native void detachMousewheelListener(Element element)
 +        /*
 +         * Detaching events with JSNI instead of the GWT event mechanism because
 +         * GWT didn't provide enough details in events, or triggering the event
 +         * handlers with GWT bindings was unsuccessful. Maybe, with more time
 +         * and skill, it could be done with better success. JavaScript overlay
 +         * types might work. This might also get rid of the JsniWorkaround
 +         * class.
 +         */
 +        /*-{
 +            if (element.addEventListener) {
 +                // firefox likes "wheel", while others use "mousewheel"
 +                var eventName = element.onwheel===undefined?"mousewheel":"wheel";
 +                element.removeEventListener(eventName, this.@com.vaadin.client.widgets.JsniWorkaround::mousewheelListenerFunction);
 +            } else {
 +                // IE8
 +                element.detachEvent("onmousewheel", this.@com.vaadin.client.widgets.JsniWorkaround::mousewheelListenerFunction);
 +            }
 +        }-*/;
 +
 +        public native void attachTouchListeners(Element element)
 +        /*
 +         * Detaching events with JSNI instead of the GWT event mechanism because
 +         * GWT didn't provide enough details in events, or triggering the event
 +         * handlers with GWT bindings was unsuccessful. Maybe, with more time
 +         * and skill, it could be done with better success. JavaScript overlay
 +         * types might work. This might also get rid of the JsniWorkaround
 +         * class.
 +         */
 +        /*-{
 +            if (element.addEventListener) {
 +                element.addEventListener("touchstart", this.@com.vaadin.client.widgets.JsniWorkaround::touchStartFunction);
 +                element.addEventListener("touchmove", this.@com.vaadin.client.widgets.JsniWorkaround::touchMoveFunction);
 +                element.addEventListener("touchend", this.@com.vaadin.client.widgets.JsniWorkaround::touchEndFunction);
 +                element.addEventListener("touchcancel", this.@com.vaadin.client.widgets.JsniWorkaround::touchEndFunction);
 +            } else {
 +                // this would be IE8, but we don't support it with touch
 +            }
 +        }-*/;
 +
 +        public native void detachTouchListeners(Element element)
 +        /*
 +         * Detaching events with JSNI instead of the GWT event mechanism because
 +         * GWT didn't provide enough details in events, or triggering the event
 +         * handlers with GWT bindings was unsuccessful. Maybe, with more time
 +         * and skill, it could be done with better success. JavaScript overlay
 +         * types might work. This might also get rid of the JsniWorkaround
 +         * class.
 +         */
 +        /*-{
 +            if (element.removeEventListener) {
 +                element.removeEventListener("touchstart", this.@com.vaadin.client.widgets.JsniWorkaround::touchStartFunction);
 +                element.removeEventListener("touchmove", this.@com.vaadin.client.widgets.JsniWorkaround::touchMoveFunction);
 +                element.removeEventListener("touchend", this.@com.vaadin.client.widgets.JsniWorkaround::touchEndFunction);
 +                element.removeEventListener("touchcancel", this.@com.vaadin.client.widgets.JsniWorkaround::touchEndFunction);
 +            } else {
 +                // this would be IE8, but we don't support it with touch
 +            }
 +        }-*/;
 +
 +        public void scrollToColumn(final int columnIndex,
 +                final ScrollDestination destination, final int padding) {
 +            assert columnIndex >= columnConfiguration.frozenColumns : "Can't scroll to a frozen column";
 +
 +            /*
 +             * To cope with frozen columns, we just pretend those columns are
 +             * not there at all when calculating the position of the target
 +             * column and the boundaries of the viewport. The resulting
 +             * scrollLeft will be correct without compensation since the DOM
 +             * structure effectively means that scrollLeft also ignores the
 +             * frozen columns.
 +             */
 +            final double frozenPixels = columnConfiguration
 +                    .getCalculatedColumnsWidth(Range.withLength(0,
 +                            columnConfiguration.frozenColumns));
 +
 +            final double targetStartPx = columnConfiguration
 +                    .getCalculatedColumnsWidth(Range.withLength(0, columnIndex))
 +                    - frozenPixels;
 +            final double targetEndPx = targetStartPx
 +                    + columnConfiguration.getColumnWidthActual(columnIndex);
 +
 +            final double viewportStartPx = getScrollLeft();
 +            double viewportEndPx = viewportStartPx
 +                    + WidgetUtil
 +                            .getRequiredWidthBoundingClientRectDouble(getElement())
 +                    - frozenPixels;
 +            if (verticalScrollbar.showsScrollHandle()) {
 +                viewportEndPx -= WidgetUtil.getNativeScrollbarSize();
 +            }
 +
 +            final double scrollLeft = getScrollPos(destination, targetStartPx,
 +                    targetEndPx, viewportStartPx, viewportEndPx, padding);
 +
 +            /*
 +             * note that it doesn't matter if the scroll would go beyond the
 +             * content, since the browser will adjust for that, and everything
 +             * fall into line accordingly.
 +             */
 +            setScrollLeft(scrollLeft);
 +        }
 +
 +        public void scrollToRow(final int rowIndex,
 +                final ScrollDestination destination, final double padding) {
 +
 +            final double targetStartPx = (body.getDefaultRowHeight() * rowIndex)
 +                    + body.spacerContainer
 +                            .getSpacerHeightsSumUntilIndex(rowIndex);
 +            final double targetEndPx = targetStartPx
 +                    + body.getDefaultRowHeight();
 +
 +            final double viewportStartPx = getScrollTop();
 +            final double viewportEndPx = viewportStartPx
 +                    + body.getHeightOfSection();
 +
 +            final double scrollTop = getScrollPos(destination, targetStartPx,
 +                    targetEndPx, viewportStartPx, viewportEndPx, padding);
 +
 +            /*
 +             * note that it doesn't matter if the scroll would go beyond the
 +             * content, since the browser will adjust for that, and everything
 +             * falls into line accordingly.
 +             */
 +            setScrollTop(scrollTop);
 +        }
 +    }
 +
 +    protected abstract class AbstractRowContainer implements RowContainer {
 +        private EscalatorUpdater updater = EscalatorUpdater.NULL;
 +
 +        private int rows;
 +
 +        /**
 +         * The table section element ({@code <thead>}, {@code <tbody>} or
 +         * {@code <tfoot>}) the rows (i.e. {@code <tr>} tags) are contained in.
 +         */
 +        protected final TableSectionElement root;
 +
 +        /**
 +         * The primary style name of the escalator. Most commonly provided by
 +         * Escalator as "v-escalator".
 +         */
 +        private String primaryStyleName = null;
 +
 +        private boolean defaultRowHeightShouldBeAutodetected = true;
 +
 +        private double defaultRowHeight = INITIAL_DEFAULT_ROW_HEIGHT;
 +
 +        public AbstractRowContainer(
 +                final TableSectionElement rowContainerElement) {
 +            root = rowContainerElement;
 +        }
 +
 +        @Override
 +        public TableSectionElement getElement() {
 +            return root;
 +        }
 +
 +        /**
 +         * Gets the tag name of an element to represent a cell in a row.
 +         * <p>
 +         * Usually {@code "th"} or {@code "td"}.
 +         * <p>
 +         * <em>Note:</em> To actually <em>create</em> such an element, use
 +         * {@link #createCellElement(int, int)} instead.
 +         * 
 +         * @return the tag name for the element to represent cells as
 +         * @see #createCellElement(int, int)
 +         */
 +        protected abstract String getCellElementTagName();
 +
 +        @Override
 +        public EscalatorUpdater getEscalatorUpdater() {
 +            return updater;
 +        }
 +
 +        /**
 +         * {@inheritDoc}
 +         * <p>
 +         * <em>Implementation detail:</em> This method does no DOM modifications
 +         * (i.e. is very cheap to call) if there is no data for rows or columns
 +         * when this method is called.
 +         * 
 +         * @see #hasColumnAndRowData()
 +         */
 +        @Override
 +        public void setEscalatorUpdater(final EscalatorUpdater escalatorUpdater) {
 +            if (escalatorUpdater == null) {
 +                throw new IllegalArgumentException(
 +                        "escalator updater cannot be null");
 +            }
 +
 +            updater = escalatorUpdater;
 +
 +            if (hasColumnAndRowData() && getRowCount() > 0) {
 +                refreshRows(0, getRowCount());
 +            }
 +        }
 +
 +        /**
 +         * {@inheritDoc}
 +         * <p>
 +         * <em>Implementation detail:</em> This method does no DOM modifications
 +         * (i.e. is very cheap to call) if there are no rows in the DOM when
 +         * this method is called.
 +         * 
 +         * @see #hasSomethingInDom()
 +         */
 +        @Override
 +        public void removeRows(final int index, final int numberOfRows) {
 +            assertArgumentsAreValidAndWithinRange(index, numberOfRows);
 +
 +            rows -= numberOfRows;
 +
 +            if (!isAttached()) {
 +                return;
 +            }
 +
 +            if (hasSomethingInDom()) {
 +                paintRemoveRows(index, numberOfRows);
 +            }
 +        }
 +
 +        /**
 +         * Removes those row elements from the DOM that correspond to the given
 +         * range of logical indices. This may be fewer than {@code numberOfRows}
 +         * , even zero, if not all the removed rows are actually visible.
 +         * <p>
 +         * The implementation must call {@link #paintRemoveRow(Element, int)}
 +         * for each row that is removed from the DOM.
 +         * 
 +         * @param index
 +         *            the logical index of the first removed row
 +         * @param numberOfRows
 +         *            number of logical rows to remove
 +         */
 +        protected abstract void paintRemoveRows(final int index,
 +                final int numberOfRows);
 +
 +        /**
 +         * Removes a row element from the DOM, invoking
 +         * {@link #getEscalatorUpdater()}
 +         * {@link EscalatorUpdater#preDetach(Row, Iterable) preDetach} and
 +         * {@link EscalatorUpdater#postDetach(Row, Iterable) postDetach} before
 +         * and after removing the row, respectively.
 +         * <p>
 +         * This method must be called for each removed DOM row by any
 +         * {@link #paintRemoveRows(int, int)} implementation.
 +         * 
 +         * @param tr
 +         *            the row element to remove.
 +         */
 +        protected void paintRemoveRow(final TableRowElement tr,
 +                final int logicalRowIndex) {
 +
 +            flyweightRow.setup(tr, logicalRowIndex,
 +                    columnConfiguration.getCalculatedColumnWidths());
 +
 +            getEscalatorUpdater().preDetach(flyweightRow,
 +                    flyweightRow.getCells());
 +
 +            tr.removeFromParent();
 +
 +            getEscalatorUpdater().postDetach(flyweightRow,
 +                    flyweightRow.getCells());
 +
 +            /*
 +             * the "assert" guarantees that this code is run only during
 +             * development/debugging.
 +             */
 +            assert flyweightRow.teardown();
 +
 +        }
 +
 +        protected void assertArgumentsAreValidAndWithinRange(final int index,
 +                final int numberOfRows) throws IllegalArgumentException,
 +                IndexOutOfBoundsException {
 +            if (numberOfRows < 1) {
 +                throw new IllegalArgumentException(
 +                        "Number of rows must be 1 or greater (was "
 +                                + numberOfRows + ")");
 +            }
 +
 +            if (index < 0 || index + numberOfRows > getRowCount()) {
 +                throw new IndexOutOfBoundsException("The given "
 +                        + "row range (" + index + ".." + (index + numberOfRows)
 +                        + ") was outside of the current number of rows ("
 +                        + getRowCount() + ")");
 +            }
 +        }
 +
 +        @Override
 +        public int getRowCount() {
 +            return rows;
 +        }
 +
 +        /**
 +         * This method calculates the current row count directly from the DOM.
 +         * <p>
 +         * While Escalator is stable, this value should equal to
 +         * {@link #getRowCount()}, but while row counts are being updated, these
 +         * two values might differ for a short while.
 +         * <p>
 +         * Any extra content, such as spacers for the body, should not be
 +         * included in this count.
 +         * 
 +         * @since 7.5.0
 +         * 
 +         * @return the actual DOM count of rows
 +         */
 +        public abstract int getDomRowCount();
 +
 +        /**
 +         * {@inheritDoc}
 +         * <p>
 +         * <em>Implementation detail:</em> This method does no DOM modifications
 +         * (i.e. is very cheap to call) if there is no data for columns when
 +         * this method is called.
 +         * 
 +         * @see #hasColumnAndRowData()
 +         */
 +        @Override
 +        public void insertRows(final int index, final int numberOfRows) {
 +            if (index < 0 || index > getRowCount()) {
 +                throw new IndexOutOfBoundsException("The given index (" + index
 +                        + ") was outside of the current number of rows (0.."
 +                        + getRowCount() + ")");
 +            }
 +
 +            if (numberOfRows < 1) {
 +                throw new IllegalArgumentException(
 +                        "Number of rows must be 1 or greater (was "
 +                                + numberOfRows + ")");
 +            }
 +
 +            rows += numberOfRows;
 +
 +            /*
 +             * only add items in the DOM if the widget itself is attached to the
 +             * DOM. We can't calculate sizes otherwise.
 +             */
 +            if (isAttached()) {
 +                paintInsertRows(index, numberOfRows);
 +
 +                if (rows == numberOfRows) {
 +                    /*
 +                     * We are inserting the first rows in this container. We
 +                     * potentially need to set the widths for the cells for the
 +                     * first time.
 +                     */
 +                    Map<Integer, Double> colWidths = new HashMap<Integer, Double>();
 +                    for (int i = 0; i < getColumnConfiguration()
 +                            .getColumnCount(); i++) {
 +                        Double width = Double.valueOf(getColumnConfiguration()
 +                                .getColumnWidth(i));
 +                        Integer col = Integer.valueOf(i);
 +                        colWidths.put(col, width);
 +                    }
 +                    getColumnConfiguration().setColumnWidths(colWidths);
 +                }
 +            }
 +        }
 +
 +        /**
 +         * Actually add rows into the DOM, now that everything can be
 +         * calculated.
 +         * 
 +         * @param visualIndex
 +         *            the DOM index to add rows into
 +         * @param numberOfRows
 +         *            the number of rows to insert
 +         * @return a list of the added row elements
 +         */
 +        protected abstract void paintInsertRows(final int visualIndex,
 +                final int numberOfRows);
 +
 +        protected List<TableRowElement> paintInsertStaticRows(
 +                final int visualIndex, final int numberOfRows) {
 +            assert isAttached() : "Can't paint rows if Escalator is not attached";
 +
 +            final List<TableRowElement> addedRows = new ArrayList<TableRowElement>();
 +
 +            if (numberOfRows < 1) {
 +                return addedRows;
 +            }
 +
 +            Node referenceRow;
 +            if (root.getChildCount() != 0 && visualIndex != 0) {
 +                // get the row node we're inserting stuff after
 +                referenceRow = root.getChild(visualIndex - 1);
 +            } else {
 +                // index is 0, so just prepend.
 +                referenceRow = null;
 +            }
 +
 +            for (int row = visualIndex; row < visualIndex + numberOfRows; row++) {
 +                final TableRowElement tr = TableRowElement.as(DOM.createTR());
 +                addedRows.add(tr);
 +                tr.addClassName(getStylePrimaryName() + "-row");
 +
 +                for (int col = 0; col < columnConfiguration.getColumnCount(); col++) {
 +                    final double colWidth = columnConfiguration
 +                            .getColumnWidthActual(col);
 +                    final TableCellElement cellElem = createCellElement(colWidth);
 +                    tr.appendChild(cellElem);
 +
 +                    // Set stylename and position if new cell is frozen
 +                    if (col < columnConfiguration.frozenColumns) {
 +                        cellElem.addClassName("frozen");
 +                        position.set(cellElem, scroller.lastScrollLeft, 0);
 +                    }
 +                    if (columnConfiguration.frozenColumns > 0
 +                            && col == columnConfiguration.frozenColumns - 1) {
 +                        cellElem.addClassName("last-frozen");
 +                    }
 +                }
 +
 +                referenceRow = paintInsertRow(referenceRow, tr, row);
 +            }
 +            reapplyRowWidths();
 +
 +            recalculateSectionHeight();
 +
 +            return addedRows;
 +        }
 +
 +        /**
 +         * Inserts a single row into the DOM, invoking
 +         * {@link #getEscalatorUpdater()}
 +         * {@link EscalatorUpdater#preAttach(Row, Iterable) preAttach} and
 +         * {@link EscalatorUpdater#postAttach(Row, Iterable) postAttach} before
 +         * and after inserting the row, respectively. The row should have its
 +         * cells already inserted.
 +         * 
 +         * @param referenceRow
 +         *            the row after which to insert or null if insert as first
 +         * @param tr
 +         *            the row to be inserted
 +         * @param logicalRowIndex
 +         *            the logical index of the inserted row
 +         * @return the inserted row to be used as the new reference
 +         */
 +        protected Node paintInsertRow(Node referenceRow,
 +                final TableRowElement tr, int logicalRowIndex) {
 +            flyweightRow.setup(tr, logicalRowIndex,
 +                    columnConfiguration.getCalculatedColumnWidths());
 +
 +            getEscalatorUpdater().preAttach(flyweightRow,
 +                    flyweightRow.getCells());
 +
 +            referenceRow = insertAfterReferenceAndUpdateIt(root, tr,
 +                    referenceRow);
 +
 +            getEscalatorUpdater().postAttach(flyweightRow,
 +                    flyweightRow.getCells());
 +            updater.update(flyweightRow, flyweightRow.getCells());
 +
 +            /*
 +             * the "assert" guarantees that this code is run only during
 +             * development/debugging.
 +             */
 +            assert flyweightRow.teardown();
 +            return referenceRow;
 +        }
 +
 +        private Node insertAfterReferenceAndUpdateIt(final Element parent,
 +                final Element elem, final Node referenceNode) {
 +            if (referenceNode != null) {
 +                parent.insertAfter(elem, referenceNode);
 +            } else {
 +                /*
 +                 * referencenode being null means we have offset 0, i.e. make it
 +                 * the first row
 +                 */
 +                /*
 +                 * TODO [[optimize]]: Is insertFirst or append faster for an
 +                 * empty root?
 +                 */
 +                parent.insertFirst(elem);
 +            }
 +            return elem;
 +        }
 +
 +        abstract protected void recalculateSectionHeight();
 +
 +        /**
 +         * Returns the height of all rows in the row container.
 +         */
 +        protected double calculateTotalRowHeight() {
 +            return getDefaultRowHeight() * getRowCount();
 +        }
 +
 +        /**
 +         * {@inheritDoc}
 +         * <p>
 +         * <em>Implementation detail:</em> This method does no DOM modifications
 +         * (i.e. is very cheap to call) if there is no data for columns when
 +         * this method is called.
 +         * 
 +         * @see #hasColumnAndRowData()
 +         */
 +        @Override
 +        // overridden because of JavaDoc
 +        public void refreshRows(final int index, final int numberOfRows) {
 +            Range rowRange = Range.withLength(index, numberOfRows);
 +            Range colRange = Range.withLength(0, getColumnConfiguration()
 +                    .getColumnCount());
 +            refreshCells(rowRange, colRange);
 +        }
 +
 +        protected abstract void refreshCells(Range logicalRowRange,
 +                Range colRange);
 +
 +        void refreshRow(TableRowElement tr, int logicalRowIndex) {
 +            refreshRow(tr, logicalRowIndex, Range.withLength(0,
 +                    getColumnConfiguration().getColumnCount()));
 +        }
 +
 +        void refreshRow(final TableRowElement tr, final int logicalRowIndex,
 +                Range colRange) {
 +            flyweightRow.setup(tr, logicalRowIndex,
 +                    columnConfiguration.getCalculatedColumnWidths());
 +            Iterable<FlyweightCell> cellsToUpdate = flyweightRow.getCells(
 +                    colRange.getStart(), colRange.length());
 +            updater.update(flyweightRow, cellsToUpdate);
 +
 +            /*
 +             * the "assert" guarantees that this code is run only during
 +             * development/debugging.
 +             */
 +            assert flyweightRow.teardown();
 +        }
 +
 +        /**
 +         * Create and setup an empty cell element.
 +         * 
 +         * @param width
 +         *            the width of the cell, in pixels
 +         * 
 +         * @return a set-up empty cell element
 +         */
 +        public TableCellElement createCellElement(final double width) {
 +            final TableCellElement cellElem = TableCellElement.as(DOM
 +                    .createElement(getCellElementTagName()));
 +
 +            final double height = getDefaultRowHeight();
 +            assert height >= 0 : "defaultRowHeight was negative. There's a setter leak somewhere.";
 +            cellElem.getStyle().setHeight(height, Unit.PX);
 +
 +            if (width >= 0) {
 +                cellElem.getStyle().setWidth(width, Unit.PX);
 +            }
 +            cellElem.addClassName(getStylePrimaryName() + "-cell");
 +            return cellElem;
 +        }
 +
 +        @Override
 +        public TableRowElement getRowElement(int index) {
 +            return getTrByVisualIndex(index);
 +        }
 +
 +        /**
 +         * Gets the child element that is visually at a certain index
 +         * 
 +         * @param index
 +         *            the index of the element to retrieve
 +         * @return the element at position {@code index}
 +         * @throws IndexOutOfBoundsException
 +         *             if {@code index} is not valid within {@link #root}
 +         */
 +        protected abstract TableRowElement getTrByVisualIndex(int index)
 +                throws IndexOutOfBoundsException;
 +
 +        protected void paintRemoveColumns(final int offset,
 +                final int numberOfColumns) {
 +            for (int i = 0; i < getDomRowCount(); i++) {
 +                TableRowElement row = getTrByVisualIndex(i);
 +                flyweightRow.setup(row, i,
 +                        columnConfiguration.getCalculatedColumnWidths());
 +
 +                Iterable<FlyweightCell> attachedCells = flyweightRow.getCells(
 +                        offset, numberOfColumns);
 +                getEscalatorUpdater().preDetach(flyweightRow, attachedCells);
 +
 +                for (int j = 0; j < numberOfColumns; j++) {
 +                    row.getCells().getItem(offset).removeFromParent();
 +                }
 +
 +                Iterable<FlyweightCell> detachedCells = flyweightRow
 +                        .getUnattachedCells(offset, numberOfColumns);
 +                getEscalatorUpdater().postDetach(flyweightRow, detachedCells);
 +
 +                assert flyweightRow.teardown();
 +            }
 +        }
 +
 +        protected void paintInsertColumns(final int offset,
 +                final int numberOfColumns, boolean frozen) {
 +
 +            for (int row = 0; row < getDomRowCount(); row++) {
 +                final TableRowElement tr = getTrByVisualIndex(row);
 +                int logicalRowIndex = getLogicalRowIndex(tr);
 +                paintInsertCells(tr, logicalRowIndex, offset, numberOfColumns);
 +            }
 +            reapplyRowWidths();
 +
 +            if (frozen) {
 +                for (int col = offset; col < offset + numberOfColumns; col++) {
 +                    setColumnFrozen(col, true);
 +                }
 +            }
 +        }
 +
 +        /**
 +         * Inserts new cell elements into a single row element, invoking
 +         * {@link #getEscalatorUpdater()}
 +         * {@link EscalatorUpdater#preAttach(Row, Iterable) preAttach} and
 +         * {@link EscalatorUpdater#postAttach(Row, Iterable) postAttach} before
 +         * and after inserting the cells, respectively.
 +         * <p>
 +         * Precondition: The row must be already attached to the DOM and the
 +         * FlyweightCell instances corresponding to the new columns added to
 +         * {@code flyweightRow}.
 +         * 
 +         * @param tr
 +         *            the row in which to insert the cells
 +         * @param logicalRowIndex
 +         *            the index of the row
 +         * @param offset
 +         *            the index of the first cell
 +         * @param numberOfCells
 +         *            the number of cells to insert
 +         */
 +        private void paintInsertCells(final TableRowElement tr,
 +                int logicalRowIndex, final int offset, final int numberOfCells) {
 +
 +            assert root.isOrHasChild(tr) : "The row must be attached to the document";
 +
 +            flyweightRow.setup(tr, logicalRowIndex,
 +                    columnConfiguration.getCalculatedColumnWidths());
 +
 +            Iterable<FlyweightCell> cells = flyweightRow.getUnattachedCells(
 +                    offset, numberOfCells);
 +
 +            for (FlyweightCell cell : cells) {
 +                final double colWidth = columnConfiguration
 +                        .getColumnWidthActual(cell.getColumn());
 +                final TableCellElement cellElem = createCellElement(colWidth);
 +                cell.setElement(cellElem);
 +            }
 +
 +            getEscalatorUpdater().preAttach(flyweightRow, cells);
 +
 +            Node referenceCell;
 +            if (offset != 0) {
 +                referenceCell = tr.getChild(offset - 1);
 +            } else {
 +                referenceCell = null;
 +            }
 +
 +            for (FlyweightCell cell : cells) {
 +                referenceCell = insertAfterReferenceAndUpdateIt(tr,
 +                        cell.getElement(), referenceCell);
 +            }
 +
 +            getEscalatorUpdater().postAttach(flyweightRow, cells);
 +            getEscalatorUpdater().update(flyweightRow, cells);
 +
 +            assert flyweightRow.teardown();
 +        }
 +
 +        public void setColumnFrozen(int column, boolean frozen) {
 +            toggleFrozenColumnClass(column, frozen, "frozen");
 +
 +            if (frozen) {
 +                updateFreezePosition(column, scroller.lastScrollLeft);
 +            }
 +        }
 +
 +        private void toggleFrozenColumnClass(int column, boolean frozen,
 +                String className) {
 +            final NodeList<TableRowElement> childRows = root.getRows();
 +
 +            for (int row = 0; row < childRows.getLength(); row++) {
 +                final TableRowElement tr = childRows.getItem(row);
 +                if (!rowCanBeFrozen(tr)) {
 +                    continue;
 +                }
 +
 +                TableCellElement cell = tr.getCells().getItem(column);
 +                if (frozen) {
 +                    cell.addClassName(className);
 +                } else {
 +                    cell.removeClassName(className);
 +                    position.reset(cell);
 +                }
 +            }
 +        }
 +
 +        public void setColumnLastFrozen(int column, boolean lastFrozen) {
 +            toggleFrozenColumnClass(column, lastFrozen, "last-frozen");
 +        }
 +
 +        public void updateFreezePosition(int column, double scrollLeft) {
 +            final NodeList<TableRowElement> childRows = root.getRows();
 +
 +            for (int row = 0; row < childRows.getLength(); row++) {
 +                final TableRowElement tr = childRows.getItem(row);
 +
 +                if (rowCanBeFrozen(tr)) {
 +                    TableCellElement cell = tr.getCells().getItem(column);
 +                    position.set(cell, scrollLeft, 0);
 +                }
 +            }
 +        }
 +
 +        /**
 +         * Checks whether a row is an element, or contains such elements, that
 +         * can be frozen.
 +         * <p>
 +         * In practice, this applies for all header and footer rows. For body
 +         * rows, it applies for all rows except spacer rows.
 +         * 
 +         * @since 7.5.0
 +         * 
 +         * @param tr
 +         *            the row element to check for if it is or has elements that
 +         *            can be frozen
 +         * @return <code>true</code> iff this the given element, or any of its
 +         *         descendants, can be frozen
 +         */
 +        abstract protected boolean rowCanBeFrozen(TableRowElement tr);
 +
 +        /**
 +         * Iterates through all the cells in a column and returns the width of
 +         * the widest element in this RowContainer.
 +         * 
 +         * @param index
 +         *            the index of the column to inspect
 +         * @return the pixel width of the widest element in the indicated column
 +         */
 +        public double calculateMaxColWidth(int index) {
 +            TableRowElement row = TableRowElement.as(root
 +                    .getFirstChildElement());
 +            double maxWidth = 0;
 +            while (row != null) {
 +                final TableCellElement cell = row.getCells().getItem(index);
 +                final boolean isVisible = !cell.getStyle().getDisplay()
 +                        .equals(Display.NONE.getCssName());
 +                if (isVisible) {
 +                    maxWidth = Math.max(maxWidth, WidgetUtil
 +                            .getRequiredWidthBoundingClientRectDouble(cell));
 +                }
 +                row = TableRowElement.as(row.getNextSiblingElement());
 +            }
 +            return maxWidth;
 +        }
 +
 +        /**
 +         * Reapplies all the cells' widths according to the calculated widths in
 +         * the column configuration.
 +         */
 +        public void reapplyColumnWidths() {
 +            Element row = root.getFirstChildElement();
 +            while (row != null) {
 +                // Only handle non-spacer rows
 +                if (!body.spacerContainer.isSpacer(row)) {
 +                    Element cell = row.getFirstChildElement();
 +                    int columnIndex = 0;
 +                    while (cell != null) {
 +                        final double width = getCalculatedColumnWidthWithColspan(
 +                                cell, columnIndex);
 +
 +                        /*
 +                         * TODO Should Escalator implement ProvidesResize at
 +                         * some point, this is where we need to do that.
 +                         */
 +                        cell.getStyle().setWidth(width, Unit.PX);
 +
 +                        cell = cell.getNextSiblingElement();
 +                        columnIndex++;
 +                    }
 +                }
 +                row = row.getNextSiblingElement();
 +            }
 +
 +            reapplyRowWidths();
 +        }
 +
 +        private double getCalculatedColumnWidthWithColspan(final Element cell,
 +                final int columnIndex) {
 +            final int colspan = cell.getPropertyInt(FlyweightCell.COLSPAN_ATTR);
 +            Range spannedColumns = Range.withLength(columnIndex, colspan);
 +
 +            /*
 +             * Since browsers don't explode with overflowing colspans, escalator
 +             * shouldn't either.
 +             */
 +            if (spannedColumns.getEnd() > columnConfiguration.getColumnCount()) {
 +                spannedColumns = Range.between(columnIndex,
 +                        columnConfiguration.getColumnCount());
 +            }
 +            return columnConfiguration
 +                    .getCalculatedColumnsWidth(spannedColumns);
 +        }
 +
 +        /**
 +         * Applies the total length of the columns to each row element.
 +         * <p>
 +         * <em>Note:</em> In contrast to {@link #reapplyColumnWidths()}, this
 +         * method only modifies the width of the {@code <tr>} element, not the
 +         * cells within.
 +         */
 +        protected void reapplyRowWidths() {
 +            double rowWidth = columnConfiguration.calculateRowWidth();
 +            if (rowWidth < 0) {
 +                return;
 +            }
 +
 +            Element row = root.getFirstChildElement();
 +            while (row != null) {
 +                // IF there is a rounding error when summing the columns, we
 +                // need to round the tr width up to ensure that columns fit and
 +                // do not wrap
 +                // E.g.122.95+123.25+103.75+209.25+83.52+88.57+263.45+131.21+126.85+113.13=1365.9299999999998
 +                // For this we must set 1365.93 or the last column will wrap
 +                row.getStyle().setWidth(WidgetUtil.roundSizeUp(rowWidth),
 +                        Unit.PX);
 +                row = row.getNextSiblingElement();
 +            }
 +        }
 +
 +        /**
 +         * The primary style name for the container.
 +         * 
 +         * @param primaryStyleName
 +         *            the style name to use as prefix for all row and cell style
 +         *            names.
 +         */
 +        protected void setStylePrimaryName(String primaryStyleName) {
 +            String oldStyle = getStylePrimaryName();
 +            if (SharedUtil.equals(oldStyle, primaryStyleName)) {
 +                return;
 +            }
 +
 +            this.primaryStyleName = primaryStyleName;
 +
 +            // Update already rendered rows and cells
 +            Element row = root.getRows().getItem(0);
 +            while (row != null) {
 +                UIObject.setStylePrimaryName(row, primaryStyleName + "-row");
 +                Element cell = TableRowElement.as(row).getCells().getItem(0);
 +                while (cell != null) {
 +                    assert TableCellElement.is(cell);
 +                    UIObject.setStylePrimaryName(cell, primaryStyleName
 +                            + "-cell");
 +                    cell = cell.getNextSiblingElement();
 +                }
 +                row = row.getNextSiblingElement();
 +            }
 +        }
 +
 +        /**
 +         * Returns the primary style name of the container.
 +         * 
 +         * @return The primary style name or <code>null</code> if not set.
 +         */
 +        protected String getStylePrimaryName() {
 +            return primaryStyleName;
 +        }
 +
 +        @Override
 +        public void setDefaultRowHeight(double px)
 +                throws IllegalArgumentException {
 +            if (px < 1) {
 +                throw new IllegalArgumentException("Height must be positive. "
 +                        + px + " was given.");
 +            }
 +
 +            defaultRowHeightShouldBeAutodetected = false;
 +            defaultRowHeight = px;
 +            reapplyDefaultRowHeights();
 +        }
 +
 +        @Override
 +        public double getDefaultRowHeight() {
 +            return defaultRowHeight;
 +        }
 +
 +        /**
 +         * The default height of rows has (most probably) changed.
 +         * <p>
 +         * Make sure that the displayed rows with a default height are updated
 +         * in height and top position.
 +         * <p>
 +         * <em>Note:</em>This implementation should not call
 +         * {@link Escalator#recalculateElementSizes()} - it is done by the
 +         * discretion of the caller of this method.
 +         */
 +        protected abstract void reapplyDefaultRowHeights();
 +
 +        protected void reapplyRowHeight(final TableRowElement tr,
 +                final double heightPx) {
 +            assert heightPx >= 0 : "Height must not be negative";
 +
 +            Element cellElem = tr.getFirstChildElement();
 +            while (cellElem != null) {
 +                cellElem.getStyle().setHeight(heightPx, Unit.PX);
 +                cellElem = cellElem.getNextSiblingElement();
 +            }
 +
 +            /*
 +             * no need to apply height to tr-element, it'll be resized
 +             * implicitly.
 +             */
 +        }
 +
 +        protected void setRowPosition(final TableRowElement tr, final int x,
 +                final double y) {
 +            positions.set(tr, x, y);
 +        }
 +
 +        /**
 +         * Returns <em>the assigned</em> top position for the given element.
 +         * <p>
 +         * <em>Note:</em> This method does not calculate what a row's top
 +         * position should be. It just returns an assigned value, correct or
 +         * not.
 +         * 
 +         * @param tr
 +         *            the table row element to measure
 +         * @return the current top position for {@code tr}
 +         * @see BodyRowContainerImpl#getRowTop(int)
 +         */
 +        protected double getRowTop(final TableRowElement tr) {
 +            return positions.getTop(tr);
 +        }
 +
 +        protected void removeRowPosition(TableRowElement tr) {
 +            positions.remove(tr);
 +        }
 +
 +        public void autodetectRowHeightLater() {
 +            Scheduler.get().scheduleFinally(new Scheduler.ScheduledCommand() {
 +                @Override
 +                public void execute() {
 +                    if (defaultRowHeightShouldBeAutodetected && isAttached()) {
 +                        autodetectRowHeightNow();
 +                        defaultRowHeightShouldBeAutodetected = false;
 +                    }
 +                }
 +            });
 +        }
 +
 +        public void autodetectRowHeightNow() {
 +            if (!isAttached()) {
 +                // Run again when attached
 +                defaultRowHeightShouldBeAutodetected = true;
 +                return;
 +            }
 +
 +            final Element detectionTr = DOM.createTR();
 +            detectionTr.setClassName(getStylePrimaryName() + "-row");
 +
 +            final Element cellElem = DOM.createElement(getCellElementTagName());
 +            cellElem.setClassName(getStylePrimaryName() + "-cell");
 +            cellElem.setInnerText("Ij");
 +
 +            detectionTr.appendChild(cellElem);
 +            root.appendChild(detectionTr);
 +            double boundingHeight = WidgetUtil
 +                    .getRequiredHeightBoundingClientRectDouble(cellElem);
 +            defaultRowHeight = Math.max(1.0d, boundingHeight);
 +            root.removeChild(detectionTr);
 +
 +            if (root.hasChildNodes()) {
 +                reapplyDefaultRowHeights();
 +                applyHeightByRows();
 +            }
 +        }
 +
 +        @Override
 +        public Cell getCell(final Element element) {
 +            if (element == null) {
 +                throw new IllegalArgumentException("Element cannot be null");
 +            }
 +
 +            /*
 +             * Ensure that element is not root nor the direct descendant of root
 +             * (a row) and ensure the element is inside the dom hierarchy of the
 +             * root element. If not, return.
 +             */
 +            if (root == element || element.getParentElement() == root
 +                    || !root.isOrHasChild(element)) {
 +                return null;
 +            }
 +
 +            /*
 +             * Ensure element is the cell element by iterating up the DOM
 +             * hierarchy until reaching cell element.
 +             */
 +            Element cellElementCandidate = element;
 +            while (cellElementCandidate.getParentElement().getParentElement() != root) {
 +                cellElementCandidate = cellElementCandidate.getParentElement();
 +            }
 +            final TableCellElement cellElement = TableCellElement
 +                    .as(cellElementCandidate);
 +
 +            // Find dom column
 +            int domColumnIndex = -1;
 +            for (Element e = cellElement; e != null; e = e
 +                    .getPreviousSiblingElement()) {
 +                domColumnIndex++;
 +            }
 +
 +            // Find dom row
 +            int domRowIndex = -1;
 +            for (Element e = cellElement.getParentElement(); e != null; e = e
 +                    .getPreviousSiblingElement()) {
 +                domRowIndex++;
 +            }
 +
 +            return new Cell(domRowIndex, domColumnIndex, cellElement);
 +        }
 +
 +        double measureCellWidth(TableCellElement cell, boolean withContent) {
 +            /*
 +             * To get the actual width of the contents, we need to get the cell
 +             * content without any hardcoded height or width.
 +             * 
 +             * But we don't want to modify the existing column, because that
 +             * might trigger some unnecessary listeners and whatnot. So,
 +             * instead, we make a deep clone of that cell, but without any
 +             * explicit dimensions, and measure that instead.
 +             */
 +
 +            TableCellElement cellClone = TableCellElement.as((Element) cell
 +                    .cloneNode(withContent));
 +            cellClone.getStyle().clearHeight();
 +            cellClone.getStyle().clearWidth();
 +
 +            cell.getParentElement().insertBefore(cellClone, cell);
 +            double requiredWidth = WidgetUtil
 +                    .getRequiredWidthBoundingClientRectDouble(cellClone);
 +            if (BrowserInfo.get().isIE()) {
 +                /*
 +                 * IE browsers have some issues with subpixels. Occasionally
 +                 * content is overflown even if not necessary. Increase the
 +                 * counted required size by 0.01 just to be on the safe side.
 +                 */
 +                requiredWidth += 0.01;
 +            }
 +
 +            cellClone.removeFromParent();
 +
 +            return requiredWidth;
 +        }
 +
 +        /**
 +         * Gets the minimum width needed to display the cell properly.
 +         * 
 +         * @param colIndex
 +         *            index of column to measure
 +         * @param withContent
 +         *            <code>true</code> if content is taken into account,
 +         *            <code>false</code> if not
 +         * @return cell width needed for displaying correctly
 +         */
 +        double measureMinCellWidth(int colIndex, boolean withContent) {
 +            assert isAttached() : "Can't measure max width of cell, since Escalator is not attached to the DOM.";
 +
 +            double minCellWidth = -1;
 +            NodeList<TableRowElement> rows = root.getRows();
 +
 +            for (int row = 0; row < rows.getLength(); row++) {
 +
 +                TableCellElement cell = rows.getItem(row).getCells()
 +                        .getItem(colIndex);
 +
 +                if (cell != null && !cellIsPartOfSpan(cell)) {
 +                    double cellWidth = measureCellWidth(cell, withContent);
 +                    minCellWidth = Math.max(minCellWidth, cellWidth);
 +                }
 +            }
 +
 +            return minCellWidth;
 +        }
 +
 +        private boolean cellIsPartOfSpan(TableCellElement cell) {
 +            boolean cellHasColspan = cell.getColSpan() > 1;
 +            boolean cellIsHidden = Display.NONE.getCssName().equals(
 +                    cell.getStyle().getDisplay());
 +            return cellHasColspan || cellIsHidden;
 +        }
 +
 +        void refreshColumns(int index, int numberOfColumns) {
 +            if (getRowCount() > 0) {
 +                Range rowRange = Range.withLength(0, getRowCount());
 +                Range colRange = Range.withLength(index, numberOfColumns);
 +                refreshCells(rowRange, colRange);
 +            }
 +        }
 +
 +        /**
 +         * The height of this table section.
 +         * <p>
 +         * Note that {@link Escalator#getBody() the body} will calculate its
 +         * height, while the others will return a precomputed value.
 +         * 
 +         * @since 7.5.0
 +         * 
 +         * @return the height of this table section
 +         */
 +        protected abstract double getHeightOfSection();
 +
 +        protected int getLogicalRowIndex(final TableRowElement tr) {
 +            return tr.getSectionRowIndex();
 +        };
 +
 +    }
 +
 +    private abstract class AbstractStaticRowContainer extends
 +            AbstractRowContainer {
 +
 +        /** The height of the combined rows in the DOM. Never negative. */
 +        private double heightOfSection = 0;
 +
 +        public AbstractStaticRowContainer(final TableSectionElement headElement) {
 +            super(headElement);
 +        }
 +
 +        @Override
 +        public int getDomRowCount() {
 +            return root.getChildCount();
 +        }
 +
 +        @Override
 +        protected void paintRemoveRows(final int index, final int numberOfRows) {
 +            for (int i = index; i < index + numberOfRows; i++) {
 +                final TableRowElement tr = root.getRows().getItem(index);
 +                paintRemoveRow(tr, index);
 +            }
 +            recalculateSectionHeight();
 +        }
 +
 +        @Override
 +        protected TableRowElement getTrByVisualIndex(final int index)
 +                throws IndexOutOfBoundsException {
 +            if (index >= 0 && index < root.getChildCount()) {
 +                return root.getRows().getItem(index);
 +            } else {
 +                throw new IndexOutOfBoundsException("No such visual index: "
 +                        + index);
 +            }
 +        }
 +
 +        @Override
 +        public void insertRows(int index, int numberOfRows) {
 +            super.insertRows(index, numberOfRows);
 +            recalculateElementSizes();
 +            applyHeightByRows();
 +        }
 +
 +        @Override
 +        public void removeRows(int index, int numberOfRows) {
 +
 +            /*
 +             * While the rows in a static section are removed, the scrollbar is
 +             * temporarily shrunk and then re-expanded. This leads to the fact
 +             * that the scroll position is scooted up a bit. This means that we
 +             * need to reset the position here.
 +             * 
 +             * If Escalator, at some point, gets a JIT evaluation functionality,
 +             * this re-setting is a strong candidate for removal.
 +             */
 +            double oldScrollPos = verticalScrollbar.getScrollPos();
 +
 +            super.removeRows(index, numberOfRows);
 +            recalculateElementSizes();
 +            applyHeightByRows();
 +
 +            verticalScrollbar.setScrollPos(oldScrollPos);
 +        }
 +
 +        @Override
 +        protected void reapplyDefaultRowHeights() {
 +            if (root.getChildCount() == 0) {
 +                return;
 +            }
 +
 +            Profiler.enter("Escalator.AbstractStaticRowContainer.reapplyDefaultRowHeights");
 +
 +            Element tr = root.getRows().getItem(0);
 +            while (tr != null) {
 +                reapplyRowHeight(TableRowElement.as(tr), getDefaultRowHeight());
 +                tr = tr.getNextSiblingElement();
 +            }
 +
 +            /*
 +             * Because all rows are immediately displayed in the static row
 +             * containers, the section's overall height has most probably
 +             * changed.
 +             */
 +            recalculateSectionHeight();
 +
 +            Profiler.leave("Escalator.AbstractStaticRowContainer.reapplyDefaultRowHeights");
 +        }
 +
 +        @Override
 +        protected void recalculateSectionHeight() {
 +            Profiler.enter("Escalator.AbstractStaticRowContainer.recalculateSectionHeight");
 +
 +            double newHeight = calculateTotalRowHeight();
 +            if (newHeight != heightOfSection) {
 +                heightOfSection = newHeight;
 +                sectionHeightCalculated();
 +
 +                /*
 +                 * We need to update the scrollbar dimension at this point. If
 +                 * we are scrolled too far down and the static section shrinks,
 +                 * the body will try to render rows that don't exist during
 +                 * body.verifyEscalatorCount. This is because the logical row
 +                 * indices are calculated from the scrollbar position.
 +                 */
 +                verticalScrollbar.setOffsetSize(heightOfEscalator
 +                        - header.getHeightOfSection()
 +                        - footer.getHeightOfSection());
 +
 +                body.verifyEscalatorCount();
 +                body.spacerContainer.updateSpacerDecosVisibility();
 +            }
 +
 +            Profiler.leave("Escalator.AbstractStaticRowContainer.recalculateSectionHeight");
 +        }
 +
 +        /**
 +         * Informs the row container that the height of its respective table
 +         * section has changed.
 +         * <p>
 +         * These calculations might affect some layouting logic, such as the
 +         * body is being offset by the footer, the footer needs to be readjusted
 +         * according to its height, and so on.
 +         * <p>
 +         * A table section is either header, body or footer.
 +         */
 +        protected abstract void sectionHeightCalculated();
 +
 +        @Override
 +        protected void refreshCells(Range logicalRowRange, Range colRange) {
 +            Profiler.enter("Escalator.AbstractStaticRowContainer.refreshRows");
 +
 +            assertArgumentsAreValidAndWithinRange(logicalRowRange.getStart(),
 +                    logicalRowRange.length());
 +
 +            if (!isAttached()) {
 +                return;
 +            }
 +
 +            if (hasColumnAndRowData()) {
 +                for (int row = logicalRowRange.getStart(); row < logicalRowRange
 +                        .getEnd(); row++) {
 +                    final TableRowElement tr = getTrByVisualIndex(row);
 +                    refreshRow(tr, row, colRange);
 +                }
 +            }
 +
 +            Profiler.leave("Escalator.AbstractStaticRowContainer.refreshRows");
 +        }
 +
 +        @Override
 +        protected void paintInsertRows(int visualIndex, int numberOfRows) {
 +            paintInsertStaticRows(visualIndex, numberOfRows);
 +        }
 +
 +        @Override
 +        protected boolean rowCanBeFrozen(TableRowElement tr) {
 +            assert root.isOrHasChild(tr) : "Row does not belong to this table section";
 +            return true;
 +        }
 +
 +        @Override
 +        protected double getHeightOfSection() {
 +            return Math.max(0, heightOfSection);
 +        }
 +    }
 +
 +    private class HeaderRowContainer extends AbstractStaticRowContainer {
 +        public HeaderRowContainer(final TableSectionElement headElement) {
 +            super(headElement);
 +        }
 +
 +        @Override
 +        protected void sectionHeightCalculated() {
 +            double heightOfSection = getHeightOfSection();
 +            bodyElem.getStyle().setMarginTop(heightOfSection, Unit.PX);
 +            spacerDecoContainer.getStyle().setMarginTop(heightOfSection,
 +                    Unit.PX);
 +            verticalScrollbar.getElement().getStyle()
 +                    .setTop(heightOfSection, Unit.PX);
 +            headerDeco.getStyle().setHeight(heightOfSection, Unit.PX);
 +        }
 +
 +        @Override
 +        protected String getCellElementTagName() {
 +            return "th";
 +        }
 +
 +        @Override
 +        public void setStylePrimaryName(String primaryStyleName) {
 +            super.setStylePrimaryName(primaryStyleName);
 +            UIObject.setStylePrimaryName(root, primaryStyleName + "-header");
 +        }
 +    }
 +
 +    private class FooterRowContainer extends AbstractStaticRowContainer {
 +        public FooterRowContainer(final TableSectionElement footElement) {
 +            super(footElement);
 +        }
 +
 +        @Override
 +        public void setStylePrimaryName(String primaryStyleName) {
 +            super.setStylePrimaryName(primaryStyleName);
 +            UIObject.setStylePrimaryName(root, primaryStyleName + "-footer");
 +        }
 +
 +        @Override
 +        protected String getCellElementTagName() {
 +            return "td";
 +        }
 +
 +        @Override
 +        protected void sectionHeightCalculated() {
 +            double headerHeight = header.getHeightOfSection();
 +            double footerHeight = footer.getHeightOfSection();
 +            int vscrollHeight = (int) Math.floor(heightOfEscalator
 +                    - headerHeight - footerHeight);
 +
 +            final boolean horizontalScrollbarNeeded = columnConfiguration
 +                    .calculateRowWidth() > widthOfEscalator;
 +            if (horizontalScrollbarNeeded) {
 +                vscrollHeight -= horizontalScrollbar.getScrollbarThickness();
 +            }
 +
 +            footerDeco.getStyle().setHeight(footer.getHeightOfSection(),
 +                    Unit.PX);
 +
 +            verticalScrollbar.setOffsetSize(vscrollHeight);
 +        }
 +    }
 +
 +    private class BodyRowContainerImpl extends AbstractRowContainer implements
 +            BodyRowContainer {
 +        /*
 +         * TODO [[optimize]]: check whether a native JsArray might be faster
 +         * than LinkedList
 +         */
 +        /**
 +         * The order in which row elements are rendered visually in the browser,
 +         * with the help of CSS tricks. Usually has nothing to do with the DOM
 +         * order.
 +         * 
 +         * @see #sortDomElements()
 +         */
 +        private final LinkedList<TableRowElement> visualRowOrder = new LinkedList<TableRowElement>();
 +
 +        /**
 +         * The logical index of the topmost row.
 +         * 
 +         * @deprecated Use the accessors {@link #setTopRowLogicalIndex(int)},
 +         *             {@link #updateTopRowLogicalIndex(int)} and
 +         *             {@link #getTopRowLogicalIndex()} instead
 +         */
 +        @Deprecated
 +        private int topRowLogicalIndex = 0;
 +
 +        private void setTopRowLogicalIndex(int topRowLogicalIndex) {
 +            if (LogConfiguration.loggingIsEnabled(Level.INFO)) {
 +                Logger.getLogger("Escalator.BodyRowContainer").fine(
 +                        "topRowLogicalIndex: " + this.topRowLogicalIndex
 +                                + " -> " + topRowLogicalIndex);
 +            }
 +            assert topRowLogicalIndex >= 0 : "topRowLogicalIndex became negative (top left cell contents: "
 +                    + visualRowOrder.getFirst().getCells().getItem(0)
 +                            .getInnerText() + ") ";
 +            /*
 +             * if there's a smart way of evaluating and asserting the max index,
 +             * this would be a nice place to put it. I haven't found out an
 +             * effective and generic solution.
 +             */
 +
 +            this.topRowLogicalIndex = topRowLogicalIndex;
 +        }
 +
 +        public int getTopRowLogicalIndex() {
 +            return topRowLogicalIndex;
 +        }
 +
 +        private void updateTopRowLogicalIndex(int diff) {
 +            setTopRowLogicalIndex(topRowLogicalIndex + diff);
 +        }
 +
 +        private class DeferredDomSorter {
 +            private static final int SORT_DELAY_MILLIS = 50;
 +
 +            // as it happens, 3 frames = 50ms @ 60fps.
 +            private static final int REQUIRED_FRAMES_PASSED = 3;
 +
 +            private final AnimationCallback frameCounter = new AnimationCallback() {
 +                @Override
 +                public void execute(double timestamp) {
 +                    framesPassed++;
 +                    boolean domWasSorted = sortIfConditionsMet();
 +                    if (!domWasSorted) {
 +                        animationHandle = AnimationScheduler.get()
 +                                .requestAnimationFrame(this);
 +                    } else {
 +                        waiting = false;
 +                    }
 +                }
 +            };
 +
 +            private int framesPassed;
 +            private double startTime;
 +            private AnimationHandle animationHandle;
 +
 +            /** <code>true</code> if a sort is scheduled */
 +            public boolean waiting = false;
 +
 +            public void reschedule() {
 +                waiting = true;
 +                resetConditions();
 +                animationHandle = AnimationScheduler.get()
 +                        .requestAnimationFrame(frameCounter);
 +            }
 +
 +            private boolean sortIfConditionsMet() {
 +                boolean enoughFramesHavePassed = framesPassed >= REQUIRED_FRAMES_PASSED;
 +                boolean enoughTimeHasPassed = (Duration.currentTimeMillis() - startTime) >= SORT_DELAY_MILLIS;
 +                boolean notTouchActivity = !scroller.touchHandlerBundle.touching;
 +                boolean conditionsMet = enoughFramesHavePassed
 +                        && enoughTimeHasPassed && notTouchActivity;
 +
 +                if (conditionsMet) {
 +                    resetConditions();
 +                    sortDomElements();
 +                }
 +
 +                return conditionsMet;
 +            }
 +
 +            private void resetConditions() {
 +                if (animationHandle != null) {
 +                    animationHandle.cancel();
 +                    animationHandle = null;
 +                }
 +                startTime = Duration.currentTimeMillis();
 +                framesPassed = 0;
 +            }
 +        }
 +
 +        private DeferredDomSorter domSorter = new DeferredDomSorter();
 +
 +        private final SpacerContainer spacerContainer = new SpacerContainer();
 +
 +        public BodyRowContainerImpl(final TableSectionElement bodyElement) {
 +            super(bodyElement);
 +        }
 +
 +        @Override
 +        public void setStylePrimaryName(String primaryStyleName) {
 +            super.setStylePrimaryName(primaryStyleName);
 +            UIObject.setStylePrimaryName(root, primaryStyleName + "-body");
 +            spacerContainer.setStylePrimaryName(primaryStyleName);
 +        }
 +
 +        public void updateEscalatorRowsOnScroll() {
 +            if (visualRowOrder.isEmpty()) {
 +                return;
 +            }
 +
 +            boolean rowsWereMoved = false;
 +
 +            final double topElementPosition;
 +            final double nextRowBottomOffset;
 +            SpacerContainer.SpacerImpl topSpacer = spacerContainer
 +                    .getSpacer(getTopRowLogicalIndex() - 1);
 +
 +            if (topSpacer != null) {
 +                topElementPosition = topSpacer.getTop();
 +                nextRowBottomOffset = topSpacer.getHeight()
 +                        + getDefaultRowHeight();
 +            } else {
 +                topElementPosition = getRowTop(visualRowOrder.getFirst());
 +                nextRowBottomOffset = getDefaultRowHeight();
 +            }
 +
 +            // TODO [[mpixscroll]]
 +            final double scrollTop = tBodyScrollTop;
 +            final double viewportOffset = topElementPosition - scrollTop;
 +
 +            /*
 +             * TODO [[optimize]] this if-else can most probably be refactored
 +             * into a neater block of code
 +             */
 +
 +            if (viewportOffset > 0) {
 +                // there's empty room on top
 +
 +                double rowPx = getRowHeightsSumBetweenPx(scrollTop,
 +                        topElementPosition);
 +                int originalRowsToMove = (int) Math.ceil(rowPx
 +                        / getDefaultRowHeight());
 +                int rowsToMove = Math.min(originalRowsToMove,
 +                        visualRowOrder.size());
 +
 +                final int end = visualRowOrder.size();
 +                final int start = end - rowsToMove;
 +                final int logicalRowIndex = getLogicalRowIndex(scrollTop);
 +
 +                moveAndUpdateEscalatorRows(Range.between(start, end), 0,
 +                        logicalRowIndex);
 +
 +                setTopRowLogicalIndex(logicalRowIndex);
 +
 +                rowsWereMoved = true;
 +            }
 +
 +            else if (viewportOffset + nextRowBottomOffset <= 0) {
 +                /*
 +                 * the viewport has been scrolled more than the topmost visual
 +                 * row.
 +                 */
 +
 +                double rowPx = getRowHeightsSumBetweenPx(topElementPosition,
 +                        scrollTop);
 +
 +                int originalRowsToMove = (int) (rowPx / getDefaultRowHeight());
 +                int rowsToMove = Math.min(originalRowsToMove,
 +                        visualRowOrder.size());
 +
 +                int logicalRowIndex;
 +                if (rowsToMove < visualRowOrder.size()) {
 +                    /*
 +                     * We scroll so little that we can just keep adding the rows
 +                     * below the current escalator
 +                     */
 +                    logicalRowIndex = getLogicalRowIndex(visualRowOrder
 +                            .getLast()) + 1;
 +                } else {
 +                    /*
 +                     * Since we're moving all escalator rows, we need to
 +                     * calculate the first logical row index from the scroll
 +                     * position.
 +                     */
 +                    logicalRowIndex = getLogicalRowIndex(scrollTop);
 +                }
 +
 +                /*
 +                 * Since we're moving the viewport downwards, the visual index
 +                 * is always at the bottom. Note: Due to how
 +                 * moveAndUpdateEscalatorRows works, this will work out even if
 +                 * we move all the rows, and try to place them "at the end".
 +                 */
 +                final int targetVisualIndex = visualRowOrder.size();
 +
 +                // make sure that we don't move rows over the data boundary
 +                boolean aRowWasLeftBehind = false;
 +                if (logicalRowIndex + rowsToMove > getRowCount()) {
 +                    /*
 +                     * TODO [[spacer]]: with constant row heights, there's
 +                     * always exactly one row that will be moved beyond the data
 +                     * source, when viewport is scrolled to the end. This,
 +                     * however, isn't guaranteed anymore once row heights start
 +                     * varying.
 +                     */
 +                    rowsToMove--;
 +                    aRowWasLeftBehind = true;
 +                }
 +
 +                /*
 +                 * Make sure we don't scroll beyond the row content. This can
 +                 * happen if we have spacers for the last rows.
 +                 */
 +                rowsToMove = Math.max(0,
 +                        Math.min(rowsToMove, getRowCount() - logicalRowIndex));
 +
 +                moveAndUpdateEscalatorRows(Range.between(0, rowsToMove),
 +                        targetVisualIndex, logicalRowIndex);
 +
 +                if (aRowWasLeftBehind) {
 +                    /*
 +                     * To keep visualRowOrder as a spatially contiguous block of
 +                     * rows, let's make sure that the one row we didn't move
 +                     * visually still stays with the pack.
 +                     */
 +                    final Range strayRow = Range.withOnly(0);
 +
 +                    /*
 +                     * We cannot trust getLogicalRowIndex, because it hasn't yet
 +                     * been updated. But since we're leaving rows behind, it
 +                     * means we've scrolled to the bottom. So, instead, we
 +                     * simply count backwards from the end.
 +                     */
 +                    final int topLogicalIndex = getRowCount()
 +                            - visualRowOrder.size();
 +                    moveAndUpdateEscalatorRows(strayRow, 0, topLogicalIndex);
 +                }
 +
 +                final int naiveNewLogicalIndex = getTopRowLogicalIndex()
 +                        + originalRowsToMove;
 +                final int maxLogicalIndex = getRowCount()
 +                        - visualRowOrder.size();
 +                setTopRowLogicalIndex(Math.min(naiveNewLogicalIndex,
 +                        maxLogicalIndex));
 +
 +                rowsWereMoved = true;
 +            }
 +
 +            if (rowsWereMoved) {
 +                fireRowVisibilityChangeEvent();
 +                domSorter.reschedule();
 +            }
 +        }
 +
 +        private double getRowHeightsSumBetweenPx(double y1, double y2) {
 +            assert y1 < y2 : "y1 must be smaller than y2";
 +
 +            double viewportPx = y2 - y1;
 +            double spacerPx = spacerContainer.getSpacerHeightsSumBetweenPx(y1,
 +                    SpacerInclusionStrategy.PARTIAL, y2,
 +                    SpacerInclusionStrategy.PARTIAL);
 +
 +            return viewportPx - spacerPx;
 +        }
 +
 +        private int getLogicalRowIndex(final double px) {
 +            double rowPx = px - spacerContainer.getSpacerHeightsSumUntilPx(px);
 +            return (int) (rowPx / getDefaultRowHeight());
 +        }
 +
 +        @Override
 +        protected void paintInsertRows(final int index, final int numberOfRows) {
 +            if (numberOfRows == 0) {
 +                return;
 +            }
 +
 +            spacerContainer.shiftSpacersByRows(index, numberOfRows);
 +
 +            /*
 +             * TODO: this method should probably only add physical rows, and not
 +             * populate them - let everything be populated as appropriate by the
 +             * logic that follows.
 +             * 
 +             * This also would lead to the fact that paintInsertRows wouldn't
 +             * need to return anything.
 +             */
 +            final List<TableRowElement> addedRows = fillAndPopulateEscalatorRowsIfNeeded(
 +                    index, numberOfRows);
 +
 +            /*
 +             * insertRows will always change the number of rows - update the
 +             * scrollbar sizes.
 +             */
 +            scroller.recalculateScrollbarsForVirtualViewport();
 +
 +            final boolean addedRowsAboveCurrentViewport = index
 +                    * getDefaultRowHeight() < getScrollTop();
 +            final boolean addedRowsBelowCurrentViewport = index
 +                    * getDefaultRowHeight() > getScrollTop()
 +                    + getHeightOfSection();
 +
 +            if (addedRowsAboveCurrentViewport) {
 +                /*
 +                 * We need to tweak the virtual viewport (scroll handle
 +                 * positions, table "scroll position" and row locations), but
 +                 * without re-evaluating any rows.
 +                 */
 +
 +                final double yDelta = numberOfRows * getDefaultRowHeight();
 +                moveViewportAndContent(yDelta);
 +                updateTopRowLogicalIndex(numberOfRows);
 +            }
 +
 +            else if (addedRowsBelowCurrentViewport) {
 +                // NOOP, we already recalculated scrollbars.
 +            }
 +
 +            else { // some rows were added inside the current viewport
 +
 +                final int unupdatedLogicalStart = index + addedRows.size();
 +                final int visualOffset = getLogicalRowIndex(visualRowOrder
 +                        .getFirst());
 +
 +                /*
 +                 * At this point, we have added new escalator rows, if so
 +                 * needed.
 +                 * 
 +                 * If more rows were added than the new escalator rows can
 +                 * account for, we need to start to spin the escalator to update
 +                 * the remaining rows aswell.
 +                 */
 +                final int rowsStillNeeded = numberOfRows - addedRows.size();
 +
 +                if (rowsStillNeeded > 0) {
 +                    final Range unupdatedVisual = convertToVisual(Range
 +                            .withLength(unupdatedLogicalStart, rowsStillNeeded));
 +                    final int end = getDomRowCount();
 +                    final int start = end - unupdatedVisual.length();
 +                    final int visualTargetIndex = unupdatedLogicalStart
 +                            - visualOffset;
 +                    moveAndUpdateEscalatorRows(Range.between(start, end),
 +                            visualTargetIndex, unupdatedLogicalStart);
 +
 +                    // move the surrounding rows to their correct places.
 +                    double rowTop = (unupdatedLogicalStart + (end - start))
 +                            * getDefaultRowHeight();
 +
 +                    // TODO: Get rid of this try/catch block by fixing the
 +                    // underlying issue. The reason for this erroneous behavior
 +                    // might be that Escalator actually works 'by mistake', and
 +                    // the order of operations is, in fact, wrong.
 +                    try {
 +                        final ListIterator<TableRowElement> i = visualRowOrder
 +                                .listIterator(visualTargetIndex + (end - start));
 +
 +                        int logicalRowIndexCursor = unupdatedLogicalStart;
 +                        while (i.hasNext()) {
 +                            rowTop += spacerContainer
 +                                    .getSpacerHeight(logicalRowIndexCursor++);
 +
 +                            final TableRowElement tr = i.next();
 +                            setRowPosition(tr, 0, rowTop);
 +                            rowTop += getDefaultRowHeight();
 +                        }
 +                    } catch (Exception e) {
 +                        Logger logger = getLogger();
 +                        logger.warning("Ignored out-of-bounds row element access");
 +                        logger.warning("Escalator state: start=" + start
 +                                + ", end=" + end + ", visualTargetIndex="
 +                                + visualTargetIndex
 +                                + ", visualRowOrder.size()="
 +                                + visualRowOrder.size());
 +                        logger.warning(e.toString());
 +                    }
 +                }
 +
 +                fireRowVisibilityChangeEvent();
 +                sortDomElements();
 +            }
 +        }
 +
 +        /**
 +         * Move escalator rows around, and make sure everything gets
 +         * appropriately repositioned and repainted.
 +         * 
 +         * @param visualSourceRange
 +         *            the range of rows to move to a new place
 +         * @param visualTargetIndex
 +         *            the visual index where the rows will be placed to
 +         * @param logicalTargetIndex
 +         *            the logical index to be assigned to the first moved row
 +         */
 +        private void moveAndUpdateEscalatorRows(final Range visualSourceRange,
 +                final int visualTargetIndex, final int logicalTargetIndex)
 +                throws IllegalArgumentException {
 +
 +            if (visualSourceRange.isEmpty()) {
 +                return;
 +            }
 +
 +            assert visualSourceRange.getStart() >= 0 : "Visual source start "
 +                    + "must be 0 or greater (was "
 +                    + visualSourceRange.getStart() + ")";
 +
 +            assert logicalTargetIndex >= 0 : "Logical target must be 0 or "
 +                    + "greater (was " + logicalTargetIndex + ")";
 +
 +            assert visualTargetIndex >= 0 : "Visual target must be 0 or greater (was "
 +                    + visualTargetIndex + ")";
 +
 +            assert visualTargetIndex <= getDomRowCount() : "Visual target "
 +                    + "must not be greater than the number of escalator rows (was "
 +                    + visualTargetIndex + ", escalator rows "
 +                    + getDomRowCount() + ")";
 +
 +            assert logicalTargetIndex + visualSourceRange.length() <= getRowCount() : "Logical "
 +                    + "target leads to rows outside of the data range ("
 +                    + Range.withLength(logicalTargetIndex,
 +                            visualSourceRange.length())
 +                    + " goes beyond "
 +                    + Range.withLength(0, getRowCount()) + ")";
 +
 +            /*
 +             * Since we move a range into another range, the indices might move
 +             * about. Having 10 rows, if we move 0..1 to index 10 (to the end of
 +             * the collection), the target range will end up being 8..9, instead
 +             * of 10..11.
 +             * 
 +             * This applies only if we move elements forward in the collection,
 +             * not backward.
 +             */
 +            final int adjustedVisualTargetIndex;
 +            if (visualSourceRange.getStart() < visualTargetIndex) {
 +                adjustedVisualTargetIndex = visualTargetIndex
 +                        - visualSourceRange.length();
 +            } else {
 +                adjustedVisualTargetIndex = visualTargetIndex;
 +            }
 +
 +            if (visualSourceRange.getStart() != adjustedVisualTargetIndex) {
 +
 +                /*
 +                 * Reorder the rows to their correct places within
 +                 * visualRowOrder (unless rows are moved back to their original
 +                 * places)
 +                 */
 +
 +                /*
 +                 * TODO [[optimize]]: move whichever set is smaller: the ones
 +                 * explicitly moved, or the others. So, with 10 escalator rows,
 +                 * if we are asked to move idx[0..8] to the end of the list,
 +                 * it's faster to just move idx[9] to the beginning.
 +                 */
 +
 +                final List<TableRowElement> removedRows = new ArrayList<TableRowElement>(
 +                        visualSourceRange.length());
 +                for (int i = 0; i < visualSourceRange.length(); i++) {
 +                    final TableRowElement tr = visualRowOrder
 +                            .remove(visualSourceRange.getStart());
 +                    removedRows.add(tr);
 +                }
 +                visualRowOrder.addAll(adjustedVisualTargetIndex, removedRows);
 +            }
 +
 +            { // Refresh the contents of the affected rows
 +                final ListIterator<TableRowElement> iter = visualRowOrder
 +                        .listIterator(adjustedVisualTargetIndex);
 +                for (int logicalIndex = logicalTargetIndex; logicalIndex < logicalTargetIndex
 +                        + visualSourceRange.length(); logicalIndex++) {
 +                    final TableRowElement tr = iter.next();
 +                    refreshRow(tr, logicalIndex);
 +                }
 +            }
 +
 +            { // Reposition the rows that were moved
 +                double newRowTop = getRowTop(logicalTargetIndex);
 +
 +                final ListIterator<TableRowElement> iter = visualRowOrder
 +                        .listIterator(adjustedVisualTargetIndex);
 +                for (int i = 0; i < visualSourceRange.length(); i++) {
 +                    final TableRowElement tr = iter.next();
 +                    setRowPosition(tr, 0, newRowTop);
 +
 +                    newRowTop += getDefaultRowHeight();
 +                    newRowTop += spacerContainer
 +                            .getSpacerHeight(logicalTargetIndex + i);
 +                }
 +            }
 +        }
 +
 +        /**
 +         * Adjust the scroll position and move the contained rows.
 +         * <p>
 +         * The difference between using this method and simply scrolling is that
 +         * this method "takes the rows and spacers with it" and renders them
 +         * appropriately. The viewport may be scrolled any arbitrary amount, and
 +         * the contents are moved appropriately, but always snapped into a
 +         * plausible place.
 +         * <p>
 +         * <dl>
 +         * <dt>Example 1</dt>
 +         * <dd>An Escalator with default row height 20px. Adjusting the scroll
 +         * position with 7.5px will move the viewport 7.5px down, but leave the
 +         * row where it is.</dd>
 +         * <dt>Example 2</dt>
 +         * <dd>An Escalator with default row height 20px. Adjusting the scroll
 +         * position with 27.5px will move the viewport 27.5px down, and place
 +         * the row at 20px.</dd>
 +         * </dl>
 +         * 
 +         * @param yDelta
 +         *            the delta of pixels by which to move the viewport and
 +         *            content. A positive value moves everything downwards,
 +         *            while a negative value moves everything upwards
 +         */
 +        public void moveViewportAndContent(final double yDelta) {
 +
 +            if (yDelta == 0) {
 +                return;
 +            }
 +
 +            double newTop = tBodyScrollTop + yDelta;
 +            verticalScrollbar.setScrollPos(newTop);
 +
 +            final double defaultRowHeight = getDefaultRowHeight();
 +            double rowPxDelta = yDelta - (yDelta % defaultRowHeight);
 +            int rowIndexDelta = (int) (yDelta / defaultRowHeight);
 +            if (!WidgetUtil.pixelValuesEqual(rowPxDelta, 0)) {
 +
 +                Collection<SpacerContainer.SpacerImpl> spacers = spacerContainer
 +                        .getSpacersAfterPx(tBodyScrollTop,
 +                                SpacerInclusionStrategy.PARTIAL);
 +                for (SpacerContainer.SpacerImpl spacer : spacers) {
 +                    spacer.setPositionDiff(0, rowPxDelta);
 +                    spacer.setRowIndex(spacer.getRow() + rowIndexDelta);
 +                }
 +
 +                for (TableRowElement tr : visualRowOrder) {
 +                    setRowPosition(tr, 0, getRowTop(tr) + rowPxDelta);
 +                }
 +            }
 +
 +            setBodyScrollPosition(tBodyScrollLeft, newTop);
 +        }
 +
 +        /**
 +         * Adds new physical escalator rows to the DOM at the given index if
 +         * there's still a need for more escalator rows.
 +         * <p>
 +         * If Escalator already is at (or beyond) max capacity, this method does
 +         * nothing to the DOM.
 +         * 
 +         * @param index
 +         *            the index at which to add new escalator rows.
 +         *            <em>Note:</em>It is assumed that the index is both the
 +         *            visual index and the logical index.
 +         * @param numberOfRows
 +         *            the number of rows to add at <code>index</code>
 +         * @return a list of the added rows
 +         */
 +        private List<TableRowElement> fillAndPopulateEscalatorRowsIfNeeded(
 +                final int index, final int numberOfRows) {
 +
 +            final int escalatorRowsStillFit = getMaxEscalatorRowCapacity()
 +                    - getDomRowCount();
 +            final int escalatorRowsNeeded = Math.min(numberOfRows,
 +                    escalatorRowsStillFit);
 +
 +            if (escalatorRowsNeeded > 0) {
 +
 +                final List<TableRowElement> addedRows = paintInsertStaticRows(
 +                        index, escalatorRowsNeeded);
 +                visualRowOrder.addAll(index, addedRows);
 +
 +                double y = index * getDefaultRowHeight()
 +                        + spacerContainer.getSpacerHeightsSumUntilIndex(index);
 +                for (int i = index; i < visualRowOrder.size(); i++) {
 +
 +                    final TableRowElement tr;
 +                    if (i - index < addedRows.size()) {
 +                        tr = addedRows.get(i - index);
 +                    } else {
 +                        tr = visualRowOrder.get(i);
 +                    }
 +
 +                    setRowPosition(tr, 0, y);
 +                    y += getDefaultRowHeight();
 +                    y += spacerContainer.getSpacerHeight(i);
 +                }
 +
 +                return addedRows;
 +            } else {
 +                return Collections.emptyList();
 +            }
 +        }
 +
 +        private int getMaxEscalatorRowCapacity() {
 +            final int maxEscalatorRowCapacity = (int) Math
 +                    .ceil(getHeightOfSection() / getDefaultRowHeight()) + 1;
 +
 +            /*
 +             * maxEscalatorRowCapacity can become negative if the headers and
 +             * footers start to overlap. This is a crazy situation, but Vaadin
 +             * blinks the components a lot, so it's feasible.
 +             */
 +            return Math.max(0, maxEscalatorRowCapacity);
 +        }
 +
 +        @Override
 +        protected void paintRemoveRows(final int index, final int numberOfRows) {
 +            if (numberOfRows == 0) {
 +                return;
 +            }
 +
 +            final Range viewportRange = getVisibleRowRange();
 +            final Range removedRowsRange = Range
 +                    .withLength(index, numberOfRows);
 +
 +            /*
 +             * Removing spacers as the very first step will correct the
 +             * scrollbars and row offsets right away.
 +             * 
 +             * TODO: actually, it kinda sounds like a Grid feature that a spacer
 +             * would be associated with a particular row. Maybe it would be
 +             * better to have a spacer separate from rows, and simply collapse
 +             * them if they happen to end up on top of each other. This would
 +             * probably make supporting the -1 row pretty easy, too.
 +             */
 +            spacerContainer.paintRemoveSpacers(removedRowsRange);
 +
 +            final Range[] partitions = removedRowsRange
 +                    .partitionWith(viewportRange);
 +            final Range removedAbove = partitions[0];
 +            final Range removedLogicalInside = partitions[1];
 +            final Range removedVisualInside = convertToVisual(removedLogicalInside);
 +
 +            /*
 +             * TODO: extract the following if-block to a separate method. I'll
 +             * leave this be inlined for now, to make linediff-based code
 +             * reviewing easier. Probably will be moved in the following patch
 +             * set.
 +             */
 +
 +            /*
 +             * Adjust scroll position in one of two scenarios:
 +             * 
 +             * 1) Rows were removed above. Then we just need to adjust the
 +             * scrollbar by the height of the removed rows.
 +             * 
 +             * 2) There are no logical rows above, and at least the first (if
 +             * not more) visual row is removed. Then we need to snap the scroll
 +             * position to the first visible row (i.e. reset scroll position to
 +             * absolute 0)
 +             * 
 +             * The logic is optimized in such a way that the
 +             * moveViewportAndContent is called only once, to avoid extra
 +             * reflows, and thus the code might seem a bit obscure.
 +             */
 +            final boolean firstVisualRowIsRemoved = !removedVisualInside
 +                    .isEmpty() && removedVisualInside.getStart() == 0;
 +
 +            if (!removedAbove.isEmpty() || firstVisualRowIsRemoved) {
 +                final double yDelta = removedAbove.length()
 +                        * getDefaultRowHeight();
 +                final double firstLogicalRowHeight = getDefaultRowHeight();
 +                final boolean removalScrollsToShowFirstLogicalRow = verticalScrollbar
 +                        .getScrollPos() - yDelta < firstLogicalRowHeight;
 +
 +                if (removedVisualInside.isEmpty()
 +                        && (!removalScrollsToShowFirstLogicalRow || !firstVisualRowIsRemoved)) {
 +                    /*
 +                     * rows were removed from above the viewport, so all we need
 +                     * to do is to adjust the scroll position to account for the
 +                     * removed rows
 +                     */
 +                    moveViewportAndContent(-yDelta);
 +                } else if (removalScrollsToShowFirstLogicalRow) {
 +                    /*
 +                     * It seems like we've removed all rows from above, and also
 +                     * into the current viewport. This means we'll need to even
 +                     * out the scroll position to exactly 0 (i.e. adjust by the
 +                     * current negative scrolltop, presto!), so that it isn't
 +                     * aligned funnily
 +                     */
 +                    moveViewportAndContent(-verticalScrollbar.getScrollPos());
 +                }
 +            }
 +
 +            // ranges evaluated, let's do things.
 +            if (!removedVisualInside.isEmpty()) {
 +                int escalatorRowCount = body.getDomRowCount();
 +
 +                /*
 +                 * remember: the rows have already been subtracted from the row
 +                 * count at this point
 +                 */
 +                int rowsLeft = getRowCount();
 +                if (rowsLeft < escalatorRowCount) {
 +                    int escalatorRowsToRemove = escalatorRowCount - rowsLeft;
 +                    for (int i = 0; i < escalatorRowsToRemove; i++) {
 +                        final TableRowElement tr = visualRowOrder
 +                                .remove(removedVisualInside.getStart());
 +
 +                        paintRemoveRow(tr, index);
 +                        removeRowPosition(tr);
 +                    }
 +                    escalatorRowCount -= escalatorRowsToRemove;
 +
 +                    /*
 +                     * Because we're removing escalator rows, we don't have
 +                     * anything to scroll by. Let's make sure the viewport is
 +                     * scrolled to top, to render any rows possibly left above.
 +                     */
 +                    body.setBodyScrollPosition(tBodyScrollLeft, 0);
 +
 +                    /*
 +                     * We might have removed some rows from the middle, so let's
 +                     * make sure we're not left with any holes. Also remember:
 +                     * visualIndex == logicalIndex applies now.
 +                     */
 +                    final int dirtyRowsStart = removedLogicalInside.getStart();
 +                    double y = getRowTop(dirtyRowsStart);
 +                    for (int i = dirtyRowsStart; i < escalatorRowCount; i++) {
 +                        final TableRowElement tr = visualRowOrder.get(i);
 +                        setRowPosition(tr, 0, y);
 +                        y += getDefaultRowHeight();
 +                        y += spacerContainer.getSpacerHeight(i);
 +                    }
 +
 +                    /*
 +                     * this is how many rows appeared into the viewport from
 +                     * below
 +                     */
 +                    final int rowsToUpdateDataOn = numberOfRows
 +                            - escalatorRowsToRemove;
 +                    final int start = Math.max(0, escalatorRowCount
 +                            - rowsToUpdateDataOn);
 +                    final int end = escalatorRowCount;
 +                    for (int i = start; i < end; i++) {
 +                        final TableRowElement tr = visualRowOrder.get(i);
 +                        refreshRow(tr, i);
 +                    }
 +                }
 +
 +                else {
 +                    // No escalator rows need to be removed.
 +
 +                    /*
 +                     * Two things (or a combination thereof) can happen:
 +                     * 
 +                     * 1) We're scrolled to the bottom, the last rows are
 +                     * removed. SOLUTION: moveAndUpdateEscalatorRows the
 +                     * bottommost rows, and place them at the top to be
 +                     * refreshed.
 +                     * 
 +                     * 2) We're scrolled somewhere in the middle, arbitrary rows
 +                     * are removed. SOLUTION: moveAndUpdateEscalatorRows the
 +                     * removed rows, and place them at the bottom to be
 +                     * refreshed.
 +                     * 
 +                     * Since a combination can also happen, we need to handle
 +                     * this in a smart way, all while avoiding
 +                     * double-refreshing.
 +                     */
 +
 +                    final double contentBottom = getRowCount()
 +                            * getDefaultRowHeight();
 +                    final double viewportBottom = tBodyScrollTop
 +                            + getHeightOfSection();
 +                    if (viewportBottom <= contentBottom) {
 +                        /*
 +                         * We're in the middle of the row container, everything
 +                         * is added to the bottom
 +                         */
 +                        paintRemoveRowsAtMiddle(removedLogicalInside,
 +                                removedVisualInside, 0);
 +                    }
 +
 +                    else if (removedVisualInside.contains(0)
 +                            && numberOfRows >= visualRowOrder.size()) {
 +                        /*
 +                         * We're removing so many rows that the viewport is
 +                         * pushed up more than a screenful. This means we can
 +                         * simply scroll up and everything will work without a
 +                         * sweat.
 +                         */
 +
 +                        double left = horizontalScrollbar.getScrollPos();
 +                        double top = contentBottom - visualRowOrder.size()
 +                                * getDefaultRowHeight();
 +                        setBodyScrollPosition(left, top);
 +
 +                        Range allEscalatorRows = Range.withLength(0,
 +                                visualRowOrder.size());
 +                        int logicalTargetIndex = getRowCount()
 +                                - allEscalatorRows.length();
 +                        moveAndUpdateEscalatorRows(allEscalatorRows, 0,
 +                                logicalTargetIndex);
 +
++                        /*
++                         * moveAndUpdateEscalatorRows recalculates the rows, but
++                         * logical top row index bookkeeping is handled in this
++                         * method.
++                         * 
++                         * TODO: Redesign how to keep it easy to track this.
++                         */
++                        updateTopRowLogicalIndex(-removedLogicalInside.length());
++
 +                        /*
 +                         * Scrolling the body to the correct location will be
 +                         * fixed automatically. Because the amount of rows is
 +                         * decreased, the viewport is pushed up as the scrollbar
 +                         * shrinks. So no need to do anything there.
 +                         * 
 +                         * TODO [[optimize]]: This might lead to a double body
 +                         * refresh. Needs investigation.
 +                         */
 +                    }
 +
 +                    else if (contentBottom
 +                            + (numberOfRows * getDefaultRowHeight())
 +                            - viewportBottom < getDefaultRowHeight()) {
 +                        /*
 +                         * We're at the end of the row container, everything is
 +                         * added to the top.
 +                         */
 +
 +                        /*
 +                         * FIXME [[spacer]]: above if-clause is coded to only
 +                         * work with default row heights - will not work with
 +                         * variable row heights
 +                         */
 +
 +                        paintRemoveRowsAtBottom(removedLogicalInside,
 +                                removedVisualInside);
 +                        updateTopRowLogicalIndex(-removedLogicalInside.length());
 +                    }
 +
 +                    else {
 +                        /*
 +                         * We're in a combination, where we need to both scroll
 +                         * up AND show new rows at the bottom.
 +                         * 
 +                         * Example: Scrolled down to show the second to last
 +                         * row. Remove two. Viewport scrolls up, revealing the
 +                         * row above row. The last element collapses up and into
 +                         * view.
 +                         * 
 +                         * Reminder: this use case handles only the case when
 +                         * there are enough escalator rows to still render a
 +                         * full view. I.e. all escalator rows will _always_ be
 +                         * populated
 +                         */
 +                        /*-
 +                         *  1       1      |1| <- newly rendered
 +                         * |2|     |2|     |2|
 +                         * |3| ==> |*| ==> |5| <- newly rendered
 +                         * |4|     |*|
 +                         *  5       5
 +                         *  
 +                         *  1       1      |1| <- newly rendered
 +                         * |2|     |*|     |4|
 +                         * |3| ==> |*| ==> |5| <- newly rendered
 +                         * |4|     |4|
 +                         *  5       5
 +                         */
 +
 +                        /*
 +                         * STEP 1:
 +                         * 
 +                         * reorganize deprecated escalator rows to bottom, but
 +                         * don't re-render anything yet
 +                         */
 +                        /*-
 +                         *  1       1       1
 +                         * |2|     |*|     |4|
 +                         * |3| ==> |*| ==> |*|
 +                         * |4|     |4|     |*|
 +                         *  5       5       5
 +                         */
 +                        double newTop = getRowTop(visualRowOrder
 +                                .get(removedVisualInside.getStart()));
 +                        for (int i = 0; i < removedVisualInside.length(); i++) {
 +                            final TableRowElement tr = visualRowOrder
 +                                    .remove(removedVisualInside.getStart());
 +                            visualRowOrder.addLast(tr);
 +                        }
 +
 +                        for (int i = removedVisualInside.getStart(); i < escalatorRowCount; i++) {
 +                            final TableRowElement tr = visualRowOrder.get(i);
 +                            setRowPosition(tr, 0, (int) newTop);
 +                            newTop += getDefaultRowHeight();
 +                            newTop += spacerContainer.getSpacerHeight(i
 +                                    + removedLogicalInside.getStart());
 +                        }
 +
 +                        /*
 +                         * STEP 2:
 +                         * 
 +                         * manually scroll
 +                         */
 +                        /*-
 +                         *  1      |1| <-- newly rendered (by scrolling)
 +                         * |4|     |4|
 +                         * |*| ==> |*|
 +                         * |*|       
 +                         *  5       5
 +                         */
 +                        final double newScrollTop = contentBottom
 +                                - getHeightOfSection();
 +                        setScrollTop(newScrollTop);
 +                        /*
 +                         * Manually call the scroll handler, so we get immediate
 +                         * effects in the escalator.
 +                         */
 +                        scroller.onScroll();
 +
 +                        /*
 +                         * Move the bottommost (n+1:th) escalator row to top,
 +                         * because scrolling up doesn't handle that for us
 +                         * automatically
 +                         */
 +                        moveAndUpdateEscalatorRows(
 +                                Range.withOnly(escalatorRowCount - 1),
 +                                0,
 +                                getLogicalRowIndex(visualRowOrder.getFirst()) - 1);
 +                        updateTopRowLogicalIndex(-1);
 +
 +                        /*
 +                         * STEP 3:
 +                         * 
 +                         * update remaining escalator rows
 +                         */
 +                        /*-
 +                         * |1|     |1|
 +                         * |4| ==> |4|
 +                         * |*|     |5| <-- newly rendered
 +                         *           
 +                         *  5
 +                         */
 +
 +                        final int rowsScrolled = (int) (Math
 +                                .ceil((viewportBottom - contentBottom)
 +                                        / getDefaultRowHeight()));
 +                        final int start = escalatorRowCount
 +                                - (removedVisualInside.length() - rowsScrolled);
 +                        final Range visualRefreshRange = Range.between(start,
 +                                escalatorRowCount);
 +                        final int logicalTargetIndex = getLogicalRowIndex(visualRowOrder
 +                                .getFirst()) + start;
 +                        // in-place move simply re-renders the rows.
 +                        moveAndUpdateEscalatorRows(visualRefreshRange, start,
 +                                logicalTargetIndex);
 +                    }
 +                }
 +
 +                fireRowVisibilityChangeEvent();
 +                sortDomElements();
 +            }
 +
 +            updateTopRowLogicalIndex(-removedAbove.length());
 +
 +            /*
 +             * this needs to be done after the escalator has been shrunk down,
 +             * or it won't work correctly (due to setScrollTop invocation)
 +             */
 +            scroller.recalculateScrollbarsForVirtualViewport();
 +        }
 +
 +        private void paintRemoveRowsAtMiddle(final Range removedLogicalInside,
 +                final Range removedVisualInside, final int logicalOffset) {
 +            /*-
 +             *  :       :       :
 +             * |2|     |2|     |2|
 +             * |3| ==> |*| ==> |4|
 +             * |4|     |4|     |6| <- newly rendered
 +             *  :       :       :
 +             */
 +
 +            final int escalatorRowCount = visualRowOrder.size();
 +
 +            final int logicalTargetIndex = getLogicalRowIndex(visualRowOrder
 +                    .getLast())
 +                    - (removedVisualInside.length() - 1)
 +                    + logicalOffset;
 +            moveAndUpdateEscalatorRows(removedVisualInside, escalatorRowCount,
 +                    logicalTargetIndex);
 +
 +            // move the surrounding rows to their correct places.
 +            final ListIterator<TableRowElement> iterator = visualRowOrder
 +                    .listIterator(removedVisualInside.getStart());
 +
 +            double rowTop = getRowTop(removedLogicalInside.getStart()
 +                    + logicalOffset);
 +            for (int i = removedVisualInside.getStart(); i < escalatorRowCount
 +                    - removedVisualInside.length(); i++) {
 +                final TableRowElement tr = iterator.next();
 +                setRowPosition(tr, 0, rowTop);
 +                rowTop += getDefaultRowHeight();
 +                rowTop += spacerContainer.getSpacerHeight(i
 +                        + removedLogicalInside.getStart());
 +            }
 +        }
 +
 +        private void paintRemoveRowsAtBottom(final Range removedLogicalInside,
 +                final Range removedVisualInside) {
 +            /*-
 +             *                  :
 +             *  :       :      |4| <- newly rendered
 +             * |5|     |5|     |5|
 +             * |6| ==> |*| ==> |7|
 +             * |7|     |7|     
 +             */
 +
 +            final int logicalTargetIndex = getLogicalRowIndex(visualRowOrder
 +                    .getFirst()) - removedVisualInside.length();
 +            moveAndUpdateEscalatorRows(removedVisualInside, 0,
 +                    logicalTargetIndex);
 +
 +            // move the surrounding rows to their correct places.
 +            int firstUpdatedIndex = removedVisualInside.getEnd();
 +            final ListIterator<TableRowElement> iterator = visualRowOrder
 +                    .listIterator(firstUpdatedIndex);
 +
 +            double rowTop = getRowTop(removedLogicalInside.getStart());
 +            int i = 0;
 +            while (iterator.hasNext()) {
 +                final TableRowElement tr = iterator.next();
 +                setRowPosition(tr, 0, rowTop);
 +                rowTop += getDefaultRowHeight();
 +                rowTop += spacerContainer.getSpacerHeight(firstUpdatedIndex
 +                        + i++);
 +            }
 +        }
 +
 +        @Override
 +        protected int getLogicalRowIndex(final TableRowElement tr) {
 +            assert tr.getParentNode() == root : "The given element isn't a row element in the body";
 +            int internalIndex = visualRowOrder.indexOf(tr);
 +            return getTopRowLogicalIndex() + internalIndex;
 +        }
 +
 +        @Override
 +        protected void recalculateSectionHeight() {
 +            // NOOP for body, since it doesn't make any sense.
 +        }
 +
 +        /**
 +         * Adjusts the row index and number to be relevant for the current
 +         * virtual viewport.
 +         * <p>
 +         * It converts a logical range of rows index to the matching visual
 +         * range, truncating the resulting range with the viewport.
 +         * <p>
 +         * <ul>
 +         * <li>Escalator contains logical rows 0..100
 +         * <li>Current viewport showing logical rows 20..29
 +         * <li>convertToVisual([20..29]) &rarr; [0..9]
 +         * <li>convertToVisual([15..24]) &rarr; [0..4]
 +         * <li>convertToVisual([25..29]) &rarr; [5..9]
 +         * <li>convertToVisual([26..39]) &rarr; [6..9]
 +         * <li>convertToVisual([0..5]) &rarr; [0..-1] <em>(empty)</em>
 +         * <li>convertToVisual([35..1]) &rarr; [0..-1] <em>(empty)</em>
 +         * <li>convertToVisual([0..100]) &rarr; [0..9]
 +         * </ul>
 +         * 
 +         * @return a logical range converted to a visual range, truncated to the
 +         *         current viewport. The first visual row has the index 0.
 +         */
 +        private Range convertToVisual(final Range logicalRange) {
 +
 +            if (logicalRange.isEmpty()) {
 +                return logicalRange;
 +            } else if (visualRowOrder.isEmpty()) {
 +                // empty range
 +                return Range.withLength(0, 0);
 +            }
 +
 +            /*
 +             * TODO [[spacer]]: these assumptions will be totally broken with
 +             * spacers.
 +             */
 +            final int maxEscalatorRows = getMaxEscalatorRowCapacity();
 +            final int currentTopRowIndex = getLogicalRowIndex(visualRowOrder
 +                    .getFirst());
 +
 +            final Range[] partitions = logicalRange.partitionWith(Range
 +                    .withLength(currentTopRowIndex, maxEscalatorRows));
 +            final Range insideRange = partitions[1];
 +            return insideRange.offsetBy(-currentTopRowIndex);
 +        }
 +
 +        @Override
 +        protected String getCellElementTagName() {
 +            return "td";
 +        }
 +
 +        @Override
 +        protected double getHeightOfSection() {
 +            final int tableHeight = tableWrapper.getOffsetHeight();
 +            final double footerHeight = footer.getHeightOfSection();
 +            final double headerHeight = header.getHeightOfSection();
 +
 +            double heightOfSection = tableHeight - footerHeight - headerHeight;
 +            return Math.max(0, heightOfSection);
 +        }
 +
 +        @Override
 +        protected void refreshCells(Range logicalRowRange, Range colRange) {
 +            Profiler.enter("Escalator.BodyRowContainer.refreshRows");
 +
 +            final Range visualRange = convertToVisual(logicalRowRange);
 +
 +            if (!visualRange.isEmpty()) {
 +                final int firstLogicalRowIndex = getLogicalRowIndex(visualRowOrder
 +                        .getFirst());
 +                for (int rowNumber = visualRange.getStart(); rowNumber < visualRange
 +                        .getEnd(); rowNumber++) {
 +                    refreshRow(visualRowOrder.get(rowNumber),
 +                            firstLogicalRowIndex + rowNumber, colRange);
 +                }
 +            }
 +
 +            Profiler.leave("Escalator.BodyRowContainer.refreshRows");
 +        }
 +
 +        @Override
 +        protected TableRowElement getTrByVisualIndex(final int index)
 +                throws IndexOutOfBoundsException {
 +            if (index >= 0 && index < visualRowOrder.size()) {
 +                return visualRowOrder.get(index);
 +            } else {
 +                throw new IndexOutOfBoundsException("No such visual index: "
 +                        + index);
 +            }
 +        }
 +
 +        @Override
 +        public TableRowElement getRowElement(int index) {
 +            if (index < 0 || index >= getRowCount()) {
 +                throw new IndexOutOfBoundsException("No such logical index: "
 +                        + index);
 +            }
 +            int visualIndex = index
 +                    - getLogicalRowIndex(visualRowOrder.getFirst());
 +            if (visualIndex >= 0 && visualIndex < visualRowOrder.size()) {
 +                return super.getRowElement(visualIndex);
 +            } else {
 +                throw new IllegalStateException("Row with logical index "
 +                        + index + " is currently not available in the DOM");
 +            }
 +        }
 +
 +        private void setBodyScrollPosition(final double scrollLeft,
 +                final double scrollTop) {
 +            tBodyScrollLeft = scrollLeft;
 +            tBodyScrollTop = scrollTop;
 +            position.set(bodyElem, -tBodyScrollLeft, -tBodyScrollTop);
 +            position.set(spacerDecoContainer, 0, -tBodyScrollTop);
 +        }
 +
 +        /**
 +         * Make sure that there is a correct amount of escalator rows: Add more
 +         * if needed, or remove any superfluous ones.
 +         * <p>
 +         * This method should be called when e.g. the height of the Escalator
 +         * changes.
 +         * <p>
 +         * <em>Note:</em> This method will make sure that the escalator rows are
 +         * placed in the proper places. By default new rows are added below, but
 +         * if the content is scrolled down, the rows are populated on top
 +         * instead.
 +         */
 +        public void verifyEscalatorCount() {
 +            /*
 +             * This method indeed has a smell very similar to paintRemoveRows
 +             * and paintInsertRows.
 +             * 
 +             * Unfortunately, those the code can't trivially be shared, since
 +             * there are some slight differences in the respective
 +             * responsibilities. The "paint" methods fake the addition and
 +             * removal of rows, and make sure to either push existing data out
 +             * of view, or draw new data into view. Only in some special cases
 +             * will the DOM element count change.
 +             * 
 +             * This method, however, has the explicit responsibility to verify
 +             * that when "something" happens, we still have the correct amount
 +             * of escalator rows in the DOM, and if not, we make sure to modify
 +             * that count. Only in some special cases do we need to take into
 +             * account other things than simply modifying the DOM element count.
 +             */
 +
 +            Profiler.enter("Escalator.BodyRowContainer.verifyEscalatorCount");
 +
 +            if (!isAttached()) {
 +                return;
 +            }
 +
 +            final int maxEscalatorRows = getMaxEscalatorRowCapacity();
 +            final int neededEscalatorRows = Math.min(maxEscalatorRows,
 +                    body.getRowCount());
 +            final int neededEscalatorRowsDiff = neededEscalatorRows
 +                    - visualRowOrder.size();
 +
 +            if (neededEscalatorRowsDiff > 0) {
 +                // needs more
 +
 +                /*
 +                 * This is a workaround for the issue where we might be scrolled
 +                 * to the bottom, and the widget expands beyond the content
 +                 * range
 +                 */
 +
 +                final int index = visualRowOrder.size();
 +                final int nextLastLogicalIndex;
 +                if (!visualRowOrder.isEmpty()) {
 +                    nextLastLogicalIndex = getLogicalRowIndex(visualRowOrder
 +                            .getLast()) + 1;
 +                } else {
 +                    nextLastLogicalIndex = 0;
 +                }
 +
 +                final boolean contentWillFit = nextLastLogicalIndex < getRowCount()
 +                        - neededEscalatorRowsDiff;
 +                if (contentWillFit) {
 +                    final List<TableRowElement> addedRows = fillAndPopulateEscalatorRowsIfNeeded(
 +                            index, neededEscalatorRowsDiff);
 +
 +                    /*
 +                     * Since fillAndPopulateEscalatorRowsIfNeeded operates on
 +                     * the assumption that index == visual index == logical
 +                     * index, we thank for the added escalator rows, but since
 +                     * they're painted in the wrong CSS position, we need to
 +                     * move them to their actual locations.
 +                     * 
 +                     * Note: this is the second (see body.paintInsertRows)
 +                     * occasion where fillAndPopulateEscalatorRowsIfNeeded would
 +                     * behave "more correctly" if it only would add escalator
 +                     * rows to the DOM and appropriate bookkeping, and not
 +                     * actually populate them :/
 +                     */
 +                    moveAndUpdateEscalatorRows(
 +                            Range.withLength(index, addedRows.size()), index,
 +                            nextLastLogicalIndex);
 +                } else {
 +                    /*
 +                     * TODO [[optimize]]
 +                     * 
 +                     * We're scrolled so far down that all rows can't be simply
 +                     * appended at the end, since we might start displaying
 +                     * escalator rows that don't exist. To avoid the mess that
 +                     * is body.paintRemoveRows, this is a dirty hack that dumbs
 +                     * the problem down to a more basic and already-solved
 +                     * problem:
 +                     * 
 +                     * 1) scroll all the way up 2) add the missing escalator
 +                     * rows 3) scroll back to the original position.
 +                     * 
 +                     * Letting the browser scroll back to our original position
 +                     * will automatically solve any possible overflow problems,
 +                     * since the browser will not allow us to scroll beyond the
 +                     * actual content.
 +                     */
 +
 +                    final double oldScrollTop = getScrollTop();
 +                    setScrollTop(0);
 +                    scroller.onScroll();
 +                    fillAndPopulateEscalatorRowsIfNeeded(index,
 +                            neededEscalatorRowsDiff);
 +                    setScrollTop(oldScrollTop);
 +                    scroller.onScroll();
 +                }
 +            }
 +
 +            else if (neededEscalatorRowsDiff < 0) {
 +                // needs less
 +
 +                final ListIterator<TableRowElement> iter = visualRowOrder
 +                        .listIterator(visualRowOrder.size());
 +                for (int i = 0; i < -neededEscalatorRowsDiff; i++) {
 +                    final Element last = iter.previous();
 +                    last.removeFromParent();
 +                    iter.remove();
 +                }
 +
 +                /*
 +                 * If we were scrolled to the bottom so that we didn't have an
 +                 * extra escalator row at the bottom, we'll probably end up with
 +                 * blank space at the bottom of the escalator, and one extra row
 +                 * above the header.
 +                 * 
 +                 * Experimentation idea #1: calculate "scrollbottom" vs content
 +                 * bottom and remove one row from top, rest from bottom. This
 +                 * FAILED, since setHeight has already happened, thus we never
 +                 * will detect ourselves having been scrolled all the way to the
 +                 * bottom.
 +                 */
 +
 +                if (!visualRowOrder.isEmpty()) {
 +                    final double firstRowTop = getRowTop(visualRowOrder
 +                            .getFirst());
 +                    final double firstRowMinTop = tBodyScrollTop
 +                            - getDefaultRowHeight();
 +                    if (firstRowTop < firstRowMinTop) {
 +                        final int newLogicalIndex = getLogicalRowIndex(visualRowOrder
 +                                .getLast()) + 1;
 +                        moveAndUpdateEscalatorRows(Range.withOnly(0),
 +                                visualRowOrder.size(), newLogicalIndex);
 +                    }
 +                }
 +            }
 +
 +            if (neededEscalatorRowsDiff != 0) {
 +                fireRowVisibilityChangeEvent();
 +            }
 +
 +            Profiler.leave("Escalator.BodyRowContainer.verifyEscalatorCount");
 +        }
 +
 +        @Override
 +        protected void reapplyDefaultRowHeights() {
 +            if (visualRowOrder.isEmpty()) {
 +                return;
 +            }
 +
 +            Profiler.enter("Escalator.BodyRowContainer.reapplyDefaultRowHeights");
 +
 +            /* step 1: resize and reposition rows */
 +            for (int i = 0; i < visualRowOrder.size(); i++) {
 +                TableRowElement tr = visualRowOrder.get(i);
 +                reapplyRowHeight(tr, getDefaultRowHeight());
 +
 +                final int logicalIndex = getTopRowLogicalIndex() + i;
 +                setRowPosition(tr, 0, logicalIndex * getDefaultRowHeight());
 +            }
 +
 +            /*
 +             * step 2: move scrollbar so that it corresponds to its previous
 +             * place
 +             */
 +
 +            /*
 +             * This ratio needs to be calculated with the scrollsize (not max
 +             * scroll position) in order to align the top row with the new
 +             * scroll position.
 +             */
 +            double scrollRatio = verticalScrollbar.getScrollPos()
 +                    / verticalScrollbar.getScrollSize();
 +            scroller.recalculateScrollbarsForVirtualViewport();
 +            verticalScrollbar.setScrollPos((int) (getDefaultRowHeight()
 +                    * getRowCount() * scrollRatio));
 +            setBodyScrollPosition(horizontalScrollbar.getScrollPos(),
 +                    verticalScrollbar.getScrollPos());
 +            scroller.onScroll();
 +
 +            /* step 3: make sure we have the correct amount of escalator rows. */
 +            verifyEscalatorCount();
 +
 +            int logicalLogical = (int) (getRowTop(visualRowOrder.getFirst()) / getDefaultRowHeight());
 +            setTopRowLogicalIndex(logicalLogical);
 +
 +            Profiler.leave("Escalator.BodyRowContainer.reapplyDefaultRowHeights");
 +        }
 +
 +        /**
 +         * Sorts the rows in the DOM to correspond to the visual order.
 +         * 
 +         * @see #visualRowOrder
 +         */
 +        private void sortDomElements() {
 +            final String profilingName = "Escalator.BodyRowContainer.sortDomElements";
 +            Profiler.enter(profilingName);
 +
 +            /*
 +             * Focus is lost from an element if that DOM element is (or any of
 +             * its parents are) removed from the document. Therefore, we sort
 +             * everything around that row instead.
 +             */
 +            final TableRowElement focusedRow = getRowWithFocus();
 +
 +            if (focusedRow != null) {
 +                assert focusedRow.getParentElement() == root : "Trying to sort around a row that doesn't exist in body";
 +                assert visualRowOrder.contains(focusedRow)
 +                        || body.spacerContainer.isSpacer(focusedRow) : "Trying to sort around a row that doesn't exist in visualRowOrder or is not a spacer.";
 +            }
 +
 +            /*
 +             * Two cases handled simultaneously:
 +             * 
 +             * 1) No focus on rows. We iterate visualRowOrder backwards, and
 +             * take the respective element in the DOM, and place it as the first
 +             * child in the body element. Then we take the next-to-last from
 +             * visualRowOrder, and put that first, pushing the previous row as
 +             * the second child. And so on...
 +             * 
 +             * 2) Focus on some row within Escalator body. Again, we iterate
 +             * visualRowOrder backwards. This time, we use the focused row as a
 +             * pivot: Instead of placing rows from the bottom of visualRowOrder
 +             * and placing it first, we place it underneath the focused row.
 +             * Once we hit the focused row, we don't move it (to not reset
 +             * focus) but change sorting mode. After that, we place all rows as
 +             * the first child.
 +             */
 +
 +            List<TableRowElement> orderedBodyRows = new ArrayList<TableRowElement>(
 +                    visualRowOrder);
 +            Map<Integer, SpacerContainer.SpacerImpl> spacers = body.spacerContainer
 +                    .getSpacers();
 +
 +            /*
 +             * Start at -1 to include a spacer that is rendered above the
 +             * viewport, but its parent row is still not shown
 +             */
 +            for (int i = -1; i < visualRowOrder.size(); i++) {
 +                SpacerContainer.SpacerImpl spacer = spacers.remove(Integer
 +                        .valueOf(getTopRowLogicalIndex() + i));
 +
 +                if (spacer != null) {
 +                    orderedBodyRows.add(i + 1, spacer.getRootElement());
 +                    spacer.show();
 +                }
 +            }
 +            /*
 +             * At this point, invisible spacers aren't reordered, so their
 +             * position in the DOM will remain undefined.
 +             */
 +
 +            // If a spacer was not reordered, it means that it's out of view.
 +            for (SpacerContainer.SpacerImpl unmovedSpacer : spacers.values()) {
 +                unmovedSpacer.hide();
 +            }
 +
 +            /*
 +             * If we have a focused row, start in the mode where we put
 +             * everything underneath that row. Otherwise, all rows are placed as
 +             * first child.
 +             */
 +            boolean insertFirst = (focusedRow == null);
 +
 +            final ListIterator<TableRowElement> i = orderedBodyRows
 +                    .listIterator(orderedBodyRows.size());
 +            while (i.hasPrevious()) {
 +                TableRowElement tr = i.previous();
 +
 +                if (tr == focusedRow) {
 +                    insertFirst = true;
 +                } else if (insertFirst) {
 +                    root.insertFirst(tr);
 +                } else {
 +                    root.insertAfter(tr, focusedRow);
 +                }
 +            }
 +
 +            Profiler.leave(profilingName);
 +        }
 +
 +        /**
 +         * Get the {@literal <tbody>} row that contains (or has) focus.
 +         * 
 +         * @return The {@literal <tbody>} row that contains a focused DOM
 +         *         element, or <code>null</code> if focus is outside of a body
 +         *         row.
 +         */
 +        private TableRowElement getRowWithFocus() {
 +            TableRowElement rowContainingFocus = null;
 +
 +            final Element focusedElement = WidgetUtil.getFocusedElement();
 +
 +            if (focusedElement != null && root.isOrHasChild(focusedElement)) {
 +                Element e = focusedElement;
 +
 +                while (e != null && e != root) {
 +                    /*
 +                     * You never know if there's several tables embedded in a
 +                     * cell... We'll take the deepest one.
 +                     */
 +                    if (TableRowElement.is(e)) {
 +                        rowContainingFocus = TableRowElement.as(e);
 +                    }
 +                    e = e.getParentElement();
 +                }
 +            }
 +
 +            return rowContainingFocus;
 +        }
 +
 +        @Override
 +        public Cell getCell(Element element) {
 +            Cell cell = super.getCell(element);
 +            if (cell == null) {
 +                return null;
 +            }
 +
 +            // Convert DOM coordinates to logical coordinates for rows
 +            TableRowElement rowElement = (TableRowElement) cell.getElement()
 +                    .getParentElement();
 +            return new Cell(getLogicalRowIndex(rowElement), cell.getColumn(),
 +                    cell.getElement());
 +        }
 +
 +        @Override
 +        public void setSpacer(int rowIndex, double height)
 +                throws IllegalArgumentException {
 +            spacerContainer.setSpacer(rowIndex, height);
 +        }
 +
 +        @Override
 +        public void setSpacerUpdater(SpacerUpdater spacerUpdater)
 +                throws IllegalArgumentException {
 +            spacerContainer.setSpacerUpdater(spacerUpdater);
 +        }
 +
 +        @Override
 +        public SpacerUpdater getSpacerUpdater() {
 +            return spacerContainer.getSpacerUpdater();
 +        }
 +
 +        /**
 +         * <em>Calculates</em> the correct top position of a row at a logical
 +         * index, regardless if there is one there or not.
 +         * <p>
 +         * A correct result requires that both {@link #getDefaultRowHeight()} is
 +         * consistent, and the placement and height of all spacers above the
 +         * given logical index are consistent.
 +         * 
 +         * @param logicalIndex
 +         *            the logical index of the row for which to calculate the
 +         *            top position
 +         * @return the position at which to place a row in {@code logicalIndex}
 +         * @see #getRowTop(TableRowElement)
 +         */
 +        private double getRowTop(int logicalIndex) {
 +            double top = spacerContainer
 +                    .getSpacerHeightsSumUntilIndex(logicalIndex);
 +            return top + (logicalIndex * getDefaultRowHeight());
 +        }
 +
 +        public void shiftRowPositions(int row, double diff) {
 +            for (TableRowElement tr : getVisibleRowsAfter(row)) {
 +                setRowPosition(tr, 0, getRowTop(tr) + diff);
 +            }
 +        }
 +
 +        private List<TableRowElement> getVisibleRowsAfter(int logicalRow) {
 +            Range visibleRowLogicalRange = getVisibleRowRange();
 +
 +            boolean allRowsAreInView = logicalRow < visibleRowLogicalRange
 +                    .getStart();
 +            boolean noRowsAreInView = logicalRow >= visibleRowLogicalRange
 +                    .getEnd() - 1;
 +
 +            if (allRowsAreInView) {
 +                return Collections.unmodifiableList(visualRowOrder);
 +            } else if (noRowsAreInView) {
 +                return Collections.emptyList();
 +            } else {
 +                int fromIndex = (logicalRow - visibleRowLogicalRange.getStart()) + 1;
 +                int toIndex = visibleRowLogicalRange.length();
 +                List<TableRowElement> sublist = visualRowOrder.subList(
 +                        fromIndex, toIndex);
 +                return Collections.unmodifiableList(sublist);
 +            }
 +        }
 +
 +        @Override
 +        public int getDomRowCount() {
 +            return root.getChildCount()
 +                    - spacerContainer.getSpacersInDom().size();
 +        }
 +
 +        @Override
 +        protected boolean rowCanBeFrozen(TableRowElement tr) {
 +            return visualRowOrder.contains(tr);
 +        }
 +
 +        void reapplySpacerWidths() {
 +            spacerContainer.reapplySpacerWidths();
 +        }
 +
 +        void scrollToSpacer(int spacerIndex, ScrollDestination destination,
 +                int padding) {
 +            spacerContainer.scrollToSpacer(spacerIndex, destination, padding);
 +        }
 +    }
 +
 +    private class ColumnConfigurationImpl implements ColumnConfiguration {
 +        public class Column {
 +            public static final double DEFAULT_COLUMN_WIDTH_PX = 100;
 +
 +            private double definedWidth = -1;
 +            private double calculatedWidth = DEFAULT_COLUMN_WIDTH_PX;
 +            private boolean measuringRequested = false;
 +
 +            public void setWidth(double px) {
 +                definedWidth = px;
 +
 +                if (px < 0) {
 +                    if (isAttached()) {
 +                        calculateWidth();
 +                    } else {
 +                        /*
 +                         * the column's width is calculated at Escalator.onLoad
 +                         * via measureAndSetWidthIfNeeded!
 +                         */
 +                        measuringRequested = true;
 +                    }
 +                } else {
 +                    calculatedWidth = px;
 +                }
 +            }
 +
 +            public double getDefinedWidth() {
 +                return definedWidth;
 +            }
 +
 +            /**
 +             * Returns the actual width in the DOM.
 +             * 
 +             * @return the width in pixels in the DOM. Returns -1 if the column
 +             *         needs measuring, but has not been yet measured
 +             */
 +            public double getCalculatedWidth() {
 +                /*
 +                 * This might return an untrue value (e.g. during init/onload),
 +                 * since we haven't had a proper chance to actually calculate
 +                 * widths yet.
 +                 * 
 +                 * This is fixed during Escalator.onLoad, by the call to
 +                 * "measureAndSetWidthIfNeeded", which fixes "everything".
 +                 */
 +                if (!measuringRequested) {
 +                    return calculatedWidth;
 +                } else {
 +                    return -1;
 +                }
 +            }
 +
 +            /**
 +             * Checks if the column needs measuring, and then measures it.
 +             * <p>
 +             * Called by {@link Escalator#onLoad()}.
 +             */
 +            public boolean measureAndSetWidthIfNeeded() {
 +                assert isAttached() : "Column.measureAndSetWidthIfNeeded() was called even though Escalator was not attached!";
 +
 +                if (measuringRequested) {
 +                    measuringRequested = false;
 +                    setWidth(definedWidth);
 +                    return true;
 +                }
 +                return false;
 +            }
 +
 +            private void calculateWidth() {
 +                calculatedWidth = getMaxCellWidth(columns.indexOf(this));
 +            }
 +        }
 +
 +        private final List<Column> columns = new ArrayList<Column>();
 +        private int frozenColumns = 0;
 +
 +        /*
 +         * TODO: this is a bit of a duplicate functionality with the
 +         * Column.calculatedWidth caching. Probably should use one or the other,
 +         * not both
 +         */
 +        /**
 +         * A cached array of all the calculated column widths.
 +         * 
 +         * @see #getCalculatedColumnWidths()
 +         */
 +        private double[] widthsArray = null;
 +
 +        /**
 +         * {@inheritDoc}
 +         * <p>
 +         * <em>Implementation detail:</em> This method does no DOM modifications
 +         * (i.e. is very cheap to call) if there are no rows in the DOM when
 +         * this method is called.
 +         * 
 +         * @see #hasSomethingInDom()
 +         */
 +        @Override
 +        public void removeColumns(final int index, final int numberOfColumns) {
 +            // Validate
 +            assertArgumentsAreValidAndWithinRange(index, numberOfColumns);
 +
 +            // Move the horizontal scrollbar to the left, if removed columns are
 +            // to the left of the viewport
 +            removeColumnsAdjustScrollbar(index, numberOfColumns);
 +
 +            // Remove from DOM
 +            header.paintRemoveColumns(index, numberOfColumns);
 +            body.paintRemoveColumns(index, numberOfColumns);
 +            footer.paintRemoveColumns(index, numberOfColumns);
 +
 +            // Remove from bookkeeping
 +            flyweightRow.removeCells(index, numberOfColumns);
 +            columns.subList(index, index + numberOfColumns).clear();
 +
 +            // Adjust frozen columns
 +            if (index < getFrozenColumnCount()) {
 +                if (index + numberOfColumns < frozenColumns) {
 +                    /*
 +                     * Last removed column was frozen, meaning that all removed
 +                     * columns were frozen. Just decrement the number of frozen
 +                     * columns accordingly.
 +                     */
 +                    frozenColumns -= numberOfColumns;
 +                } else {
 +                    /*
 +                     * If last removed column was not frozen, we have removed
 +                     * columns beyond the frozen range, so all remaining frozen
 +                     * columns are to the left of the removed columns.
 +                     */
 +                    frozenColumns = index;
 +                }
 +            }
 +
 +            scroller.recalculateScrollbarsForVirtualViewport();
 +            body.verifyEscalatorCount();
 +
 +            if (getColumnConfiguration().getColumnCount() > 0) {
 +                reapplyRowWidths(header);
 +                reapplyRowWidths(body);
 +                reapplyRowWidths(footer);
 +            }
 +
 +            /*
 +             * Colspans make any kind of automatic clever content re-rendering
 +             * impossible: As soon as anything has colspans, removing one might
 +             * reveal further colspans, modifying the DOM structure once again,
 +             * ending in a cascade of updates. Because we don't know how the
 +             * data is updated.
 +             * 
 +             * So, instead, we don't do anything. The client code is responsible
 +             * for re-rendering the content (if so desired). Everything Just
 +             * Works (TM) if colspans aren't used.
 +             */
 +        }
 +
 +        private void reapplyRowWidths(AbstractRowContainer container) {
 +            if (container.getRowCount() > 0) {
 +                container.reapplyRowWidths();
 +            }
 +        }
 +
 +        private void removeColumnsAdjustScrollbar(int index, int numberOfColumns) {
 +            if (horizontalScrollbar.getOffsetSize() >= horizontalScrollbar
 +                    .getScrollSize()) {
 +                return;
 +            }
 +
 +            double leftPosOfFirstColumnToRemove = getCalculatedColumnsWidth(Range
 +                    .between(0, index));
 +            double widthOfColumnsToRemove = getCalculatedColumnsWidth(Range
 +                    .withLength(index, numberOfColumns));
 +
 +            double scrollLeft = horizontalScrollbar.getScrollPos();
 +
 +            if (scrollLeft <= leftPosOfFirstColumnToRemove) {
 +                /*
 +                 * viewport is scrolled to the left of the first removed column,
 +                 * so there's no need to adjust anything
 +                 */
 +                return;
 +            }
 +
 +            double adjustedScrollLeft = Math.max(leftPosOfFirstColumnToRemove,
 +                    scrollLeft - widthOfColumnsToRemove);
 +            horizontalScrollbar.setScrollPos(adjustedScrollLeft);
 +        }
 +
 +        /**
 +         * Calculate the width of a row, as the sum of columns' widths.
 +         * 
 +         * @return the width of a row, in pixels
 +         */
 +        public double calculateRowWidth() {
 +            return getCalculatedColumnsWidth(Range.between(0, getColumnCount()));
 +        }
 +
 +        private void assertArgumentsAreValidAndWithinRange(final int index,
 +                final int numberOfColumns) {
 +            if (numberOfColumns < 1) {
 +                throw new IllegalArgumentException(
 +                        "Number of columns can't be less than 1 (was "
 +                                + numberOfColumns + ")");
 +            }
 +
 +            if (index < 0 || index + numberOfColumns > getColumnCount()) {
 +                throw new IndexOutOfBoundsException("The given "
 +                        + "column range (" + index + ".."
 +                        + (index + numberOfColumns)
 +                        + ") was outside of the current "
 +                        + "number of columns (" + getColumnCount() + ")");
 +            }
 +        }
 +
 +        /**
 +         * {@inheritDoc}
 +         * <p>
 +         * <em>Implementation detail:</em> This method does no DOM modifications
 +         * (i.e. is very cheap to call) if there is no data for rows when this
 +         * method is called.
 +         * 
 +         * @see #hasColumnAndRowData()
 +         */
 +        @Override
 +        public void insertColumns(final int index, final int numberOfColumns) {
 +            // Validate
 +            if (index < 0 || index > getColumnCount()) {
 +                throw new IndexOutOfBoundsException("The given index(" + index
 +                        + ") was outside of the current number of columns (0.."
 +                        + getColumnCount() + ")");
 +            }
 +
 +            if (numberOfColumns < 1) {
 +                throw new IllegalArgumentException(
 +                        "Number of columns must be 1 or greater (was "
 +                                + numberOfColumns);
 +            }
 +
 +            // Add to bookkeeping
 +            flyweightRow.addCells(index, numberOfColumns);
 +            for (int i = 0; i < numberOfColumns; i++) {
 +                columns.add(index, new Column());
 +            }
 +
 +            // Adjust frozen columns
 +            boolean frozen = index < frozenColumns;
 +            if (frozen) {
 +                frozenColumns += numberOfColumns;
 +            }
 +
 +            // this needs to be before the scrollbar adjustment.
 +            boolean scrollbarWasNeeded = horizontalScrollbar.getOffsetSize() < horizontalScrollbar
 +                    .getScrollSize();
 +            scroller.recalculateScrollbarsForVirtualViewport();
 +            boolean scrollbarIsNowNeeded = horizontalScrollbar.getOffsetSize() < horizontalScrollbar
 +                    .getScrollSize();
 +            if (!scrollbarWasNeeded && scrollbarIsNowNeeded) {
 +                body.verifyEscalatorCount();
 +            }
 +
 +            // Add to DOM
 +            header.paintInsertColumns(index, numberOfColumns, frozen);
 +            body.paintInsertColumns(index, numberOfColumns, frozen);
 +            footer.paintInsertColumns(index, numberOfColumns, frozen);
 +
 +            // fix initial width
 +            if (header.getRowCount() > 0 || body.getRowCount() > 0
 +                    || footer.getRowCount() > 0) {
 +
 +                Map<Integer, Double> colWidths = new HashMap<Integer, Double>();
 +                Double width = Double.valueOf(Column.DEFAULT_COLUMN_WIDTH_PX);
 +                for (int i = index; i < index + numberOfColumns; i++) {
 +                    Integer col = Integer.valueOf(i);
 +                    colWidths.put(col, width);
 +                }
 +                getColumnConfiguration().setColumnWidths(colWidths);
 +            }
 +
 +            // Adjust scrollbar
 +            double pixelsToInsertedColumn = columnConfiguration
 +                    .getCalculatedColumnsWidth(Range.withLength(0, index));
 +            final boolean columnsWereAddedToTheLeftOfViewport = scroller.lastScrollLeft > pixelsToInsertedColumn;
 +
 +            if (columnsWereAddedToTheLeftOfViewport) {
 +                double insertedColumnsWidth = columnConfiguration
 +                        .getCalculatedColumnsWidth(Range.withLength(index,
 +                                numberOfColumns));
 +                horizontalScrollbar.setScrollPos(scroller.lastScrollLeft
 +                        + insertedColumnsWidth);
 +            }
 +
 +            /*
 +             * Colspans make any kind of automatic clever content re-rendering
 +             * impossible: As soon as anything has colspans, adding one might
 +             * affect surrounding colspans, modifying the DOM structure once
 +             * again, ending in a cascade of updates. Because we don't know how
 +             * the data is updated.
 +             * 
 +             * So, instead, we don't do anything. The client code is responsible
 +             * for re-rendering the content (if so desired). Everything Just
 +             * Works (TM) if colspans aren't used.
 +             */
 +        }
 +
 +        @Override
 +        public int getColumnCount() {
 +            return columns.size();
 +        }
 +
 +        @Override
 +        public void setFrozenColumnCount(int count)
 +                throws IllegalArgumentException {
 +            if (count < 0 || count > getColumnCount()) {
 +                throw new IllegalArgumentException(
 +                        "count must be between 0 and the current number of columns ("
 +                                + getColumnCount() + ")");
 +            }
 +            int oldCount = frozenColumns;
 +            if (count == oldCount) {
 +                return;
 +            }
 +
 +            frozenColumns = count;
 +
 +            if (hasSomethingInDom()) {
 +                // Are we freezing or unfreezing?
 +                boolean frozen = count > oldCount;
 +
 +                int firstAffectedCol;
 +                int firstUnaffectedCol;
 +
 +                if (frozen) {
 +                    firstAffectedCol = oldCount;
 +                    firstUnaffectedCol = count;
 +                } else {
 +                    firstAffectedCol = count;
 +                    firstUnaffectedCol = oldCount;
 +                }
 +
 +                if (oldCount > 0) {
 +                    header.setColumnLastFrozen(oldCount - 1, false);
 +                    body.setColumnLastFrozen(oldCount - 1, false);
 +                    footer.setColumnLastFrozen(oldCount - 1, false);
 +                }
 +                if (count > 0) {
 +                    header.setColumnLastFrozen(count - 1, true);
 +                    body.setColumnLastFrozen(count - 1, true);
 +                    footer.setColumnLastFrozen(count - 1, true);
 +                }
 +
 +                for (int col = firstAffectedCol; col < firstUnaffectedCol; col++) {
 +                    header.setColumnFrozen(col, frozen);
 +                    body.setColumnFrozen(col, frozen);
 +                    footer.setColumnFrozen(col, frozen);
 +                }
 +            }
 +
 +            scroller.recalculateScrollbarsForVirtualViewport();
 +        }
 +
 +        @Override
 +        public int getFrozenColumnCount() {
 +            return frozenColumns;
 +        }
 +
 +        @Override
 +        public void setColumnWidth(int index, double px)
 +                throws IllegalArgumentException {
 +            setColumnWidths(Collections.singletonMap(Integer.valueOf(index),
 +                    Double.valueOf(px)));
 +        }
 +
 +        @Override
 +        public void setColumnWidths(Map<Integer, Double> indexWidthMap)
 +                throws IllegalArgumentException {
 +
 +            if (indexWidthMap == null) {
 +                throw new IllegalArgumentException("indexWidthMap was null");
 +            }
 +
 +            if (indexWidthMap.isEmpty()) {
 +                return;
 +            }
 +
 +            for (Entry<Integer, Double> entry : indexWidthMap.entrySet()) {
 +                int index = entry.getKey().intValue();
 +                double width = entry.getValue().doubleValue();
 +
 +                checkValidColumnIndex(index);
 +
 +                // Not all browsers will accept any fractional size..
 +                width = WidgetUtil.roundSizeDown(width);
 +                columns.get(index).setWidth(width);
 +
 +            }
 +
 +            widthsArray = null;
 +            header.reapplyColumnWidths();
 +            body.reapplyColumnWidths();
 +            footer.reapplyColumnWidths();
 +
 +            recalculateElementSizes();
 +        }
 +
 +        private void checkValidColumnIndex(int index)
 +                throws IllegalArgumentException {
 +            if (!Range.withLength(0, getColumnCount()).contains(index)) {
 +                throw new IllegalArgumentException("The given column index ("
 +                        + index + ") does not exist");
 +            }
 +        }
 +
 +        @Override
 +        public double getColumnWidth(int index) throws IllegalArgumentException {
 +            checkValidColumnIndex(index);
 +            return columns.get(index).getDefinedWidth();
 +        }
 +
 +        @Override
 +        public double getColumnWidthActual(int index) {
 +            return columns.get(index).getCalculatedWidth();
 +        }
 +
 +        private double getMaxCellWidth(int colIndex)
 +                throws IllegalArgumentException {
 +            double headerWidth = header.measureMinCellWidth(colIndex, true);
 +            double bodyWidth = body.measureMinCellWidth(colIndex, true);
 +            double footerWidth = footer.measureMinCellWidth(colIndex, true);
 +
 +            double maxWidth = Math.max(headerWidth,
 +                    Math.max(bodyWidth, footerWidth));
 +            assert maxWidth >= 0 : "Got a negative max width for a column, which should be impossible.";
 +            return maxWidth;
 +        }
 +
 +        private double getMinCellWidth(int colIndex)
 +                throws IllegalArgumentException {
 +            double headerWidth = header.measureMinCellWidth(colIndex, false);
 +            double bodyWidth = body.measureMinCellWidth(colIndex, false);
 +            double footerWidth = footer.measureMinCellWidth(colIndex, false);
 +
 +            double minWidth = Math.max(headerWidth,
 +                    Math.max(bodyWidth, footerWidth));
 +            assert minWidth >= 0 : "Got a negative max width for a column, which should be impossible.";
 +            return minWidth;
 +        }
 +
 +        /**
 +         * Calculates the width of the columns in a given range.
 +         * 
 +         * @param columns
 +         *            the columns to calculate
 +         * @return the total width of the columns in the given
 +         *         <code>columns</code>
 +         */
 +        double getCalculatedColumnsWidth(final Range columns) {
 +            /*
 +             * This is an assert instead of an exception, since this is an
 +             * internal method.
 +             */
 +            assert columns.isSubsetOf(Range.between(0, getColumnCount())) : "Range "
 +                    + "was outside of current column range (i.e.: "
 +                    + Range.between(0, getColumnCount())
 +                    + ", but was given :"
 +                    + columns;
 +
 +            double sum = 0;
 +            for (int i = columns.getStart(); i < columns.getEnd(); i++) {
 +                double columnWidthActual = getColumnWidthActual(i);
 +                sum += columnWidthActual;
 +            }
 +            return sum;
 +        }
 +
 +        double[] getCalculatedColumnWidths() {
 +            if (widthsArray == null || widthsArray.length != getColumnCount()) {
 +                widthsArray = new double[getColumnCount()];
 +                for (int i = 0; i < columns.size(); i++) {
 +                    widthsArray[i] = columns.get(i).getCalculatedWidth();
 +                }
 +            }
 +            return widthsArray;
 +        }
 +
 +        @Override
 +        public void refreshColumns(int index, int numberOfColumns)
 +                throws IndexOutOfBoundsException, IllegalArgumentException {
 +            if (numberOfColumns < 1) {
 +                throw new IllegalArgumentException(
 +                        "Number of columns must be 1 or greater (was "
 +                                + numberOfColumns + ")");
 +            }
 +
 +            if (index < 0 || index + numberOfColumns > getColumnCount()) {
 +                throw new IndexOutOfBoundsException("The given "
 +                        + "column range (" + index + ".."
 +                        + (index + numberOfColumns)
 +                        + ") was outside of the current number of columns ("
 +                        + getColumnCount() + ")");
 +            }
 +
 +            header.refreshColumns(index, numberOfColumns);
 +            body.refreshColumns(index, numberOfColumns);
 +            footer.refreshColumns(index, numberOfColumns);
 +        }
 +    }
 +
 +    /**
 +     * A decision on how to measure a spacer when it is partially within a
 +     * designated range.
 +     * <p>
 +     * The meaning of each value may differ depending on the context it is being
 +     * used in. Check that particular method's JavaDoc.
 +     */
 +    private enum SpacerInclusionStrategy {
 +        /** A representation of "the entire spacer". */
 +        COMPLETE,
 +
 +        /** A representation of "a partial spacer". */
 +        PARTIAL,
 +
 +        /** A representation of "no spacer at all". */
 +        NONE
 +    }
 +
 +    private class SpacerContainer {
 +
 +        /** This is used mainly for testing purposes */
 +        private static final String SPACER_LOGICAL_ROW_PROPERTY = "vLogicalRow";
 +
 +        private final class SpacerImpl implements Spacer {
 +            private TableCellElement spacerElement;
 +            private TableRowElement root;
 +            private DivElement deco;
 +            private int rowIndex;
 +            private double height = -1;
 +            private boolean domHasBeenSetup = false;
 +            private double decoHeight;
 +            private double defaultCellBorderBottomSize = -1;
 +
 +            public SpacerImpl(int rowIndex) {
 +                this.rowIndex = rowIndex;
 +
 +                root = TableRowElement.as(DOM.createTR());
 +                spacerElement = TableCellElement.as(DOM.createTD());
 +                root.appendChild(spacerElement);
 +                root.setPropertyInt(SPACER_LOGICAL_ROW_PROPERTY, rowIndex);
 +                deco = DivElement.as(DOM.createDiv());
 +            }
 +
 +            public void setPositionDiff(double x, double y) {
 +                setPosition(getLeft() + x, getTop() + y);
 +            }
 +
 +            public void setupDom(double height) {
 +                assert !domHasBeenSetup : "DOM can't be set up twice.";
 +                assert RootPanel.get().getElement().isOrHasChild(root) : "Root element should've been attached to the DOM by now.";
 +                domHasBeenSetup = true;
 +
 +                getRootElement().getStyle().setWidth(getInnerWidth(), Unit.PX);
 +                setHeight(height);
 +
 +                spacerElement.setColSpan(getColumnConfiguration()
 +                        .getColumnCount());
 +
 +                setStylePrimaryName(getStylePrimaryName());
 +            }
 +
 +            public TableRowElement getRootElement() {
 +                return root;
 +            }
 +
 +            @Override
 +            public Element getDecoElement() {
 +                return deco;
 +            }
 +
 +            public void setPosition(double x, double y) {
 +                positions.set(getRootElement(), x, y);
 +                positions
 +                        .set(getDecoElement(), 0, y - getSpacerDecoTopOffset());
 +            }
 +
 +            private double getSpacerDecoTopOffset() {
 +                return getBody().getDefaultRowHeight();
 +            }
 +
 +            public void setStylePrimaryName(String style) {
 +                UIObject.setStylePrimaryName(root, style + "-spacer");
 +                UIObject.setStylePrimaryName(deco, style + "-spacer-deco");
 +            }
 +
 +            public void setHeight(double height) {
 +
 +                assert height >= 0 : "Height must be more >= 0 (was " + height
 +                        + ")";
 +
 +                final double heightDiff = height - Math.max(0, this.height);
 +                final double oldHeight = this.height;
 +
 +                this.height = height;
 +
 +                // since the spacer might be rendered on top of the previous
 +                // rows border (done with css), need to increase height the
 +                // amount of the border thickness
 +                if (defaultCellBorderBottomSize < 0) {
 +                    defaultCellBorderBottomSize = WidgetUtil
 +                            .getBorderBottomThickness(body.getRowElement(
 +                                    getVisibleRowRange().getStart())
 +                                    .getFirstChildElement());
 +                }
 +                root.getStyle().setHeight(height + defaultCellBorderBottomSize,
 +                        Unit.PX);
 +
 +                // move the visible spacers getRow row onwards.
 +                shiftSpacerPositionsAfterRow(getRow(), heightDiff);
 +
 +                /*
 +                 * If we're growing, we'll adjust the scroll size first, then
 +                 * adjust scrolling. If we're shrinking, we do it after the
 +                 * second if-clause.
 +                 */
 +                boolean spacerIsGrowing = heightDiff > 0;
 +                if (spacerIsGrowing) {
 +                    verticalScrollbar.setScrollSize(verticalScrollbar
 +                            .getScrollSize() + heightDiff);
 +                }
 +
 +                /*
 +                 * Don't modify the scrollbars if we're expanding the -1 spacer
 +                 * while we're scrolled to the top.
 +                 */
 +                boolean minusOneSpacerException = spacerIsGrowing
 +                        && getRow() == -1 && body.getTopRowLogicalIndex() == 0;
 +
 +                boolean viewportNeedsScrolling = getRow() < body
 +                        .getTopRowLogicalIndex() && !minusOneSpacerException;
 +                if (viewportNeedsScrolling) {
 +
 +                    /*
 +                     * We can't use adjustScrollPos here, probably because of a
 +                     * bookkeeping-related race condition.
 +                     * 
 +                     * This particular situation is easier, however, since we
 +                     * know exactly how many pixels we need to move (heightDiff)
 +                     * and all elements below the spacer always need to move
 +                     * that pixel amount.
 +                     */
 +
 +                    for (TableRowElement row : body.visualRowOrder) {
 +                        body.setRowPosition(row, 0, body.getRowTop(row)
 +                                + heightDiff);
 +                    }
 +
 +                    double top = getTop();
 +                    double bottom = top + oldHeight;
 +                    double scrollTop = verticalScrollbar.getScrollPos();
 +
 +                    boolean viewportTopIsAtMidSpacer = top < scrollTop
 +                            && scrollTop < bottom;
 +
 +                    final double moveDiff;
 +                    if (viewportTopIsAtMidSpacer && !spacerIsGrowing) {
 +
 +                        /*
 +                         * If the scroll top is in the middle of the modified
 +                         * spacer, we want to scroll the viewport up as usual,
 +                         * but we don't want to scroll past the top of it.
 +                         * 
 +                         * Math.max ensures this (remember: the result is going
 +                         * to be negative).
 +                         */
 +
 +                        moveDiff = Math.max(heightDiff, top - scrollTop);
 +                    } else {
 +                        moveDiff = heightDiff;
 +                    }
 +                    body.setBodyScrollPosition(tBodyScrollLeft, tBodyScrollTop
 +                            + moveDiff);
 +                    verticalScrollbar.setScrollPosByDelta(moveDiff);
 +
 +                } else {
 +                    body.shiftRowPositions(getRow(), heightDiff);
 +                }
 +
 +                if (!spacerIsGrowing) {
 +                    verticalScrollbar.setScrollSize(verticalScrollbar
 +                            .getScrollSize() + heightDiff);
 +                }
 +
 +                updateDecoratorGeometry(height);
 +            }
 +
 +            /** Resizes and places the decorator. */
 +            private void updateDecoratorGeometry(double detailsHeight) {
 +                Style style = deco.getStyle();
 +                decoHeight = detailsHeight + getBody().getDefaultRowHeight();
 +                style.setHeight(decoHeight, Unit.PX);
 +            }
 +
 +            @Override
 +            public Element getElement() {
 +                return spacerElement;
 +            }
 +
 +            @Override
 +            public int getRow() {
 +                return rowIndex;
 +            }
 +
 +            public double getHeight() {
 +                assert height >= 0 : "Height was not previously set by setHeight.";
 +                return height;
 +            }
 +
 +            public double getTop() {
 +                return positions.getTop(getRootElement());
 +            }
 +
 +            public double getLeft() {
 +                return positions.getLeft(getRootElement());
 +            }
 +
 +            /**
 +             * Sets a new row index for this spacer. Also updates the bookeeping
 +             * at {@link SpacerContainer#rowIndexToSpacer}.
 +             */
 +            @SuppressWarnings("boxing")
 +            public void setRowIndex(int rowIndex) {
 +                SpacerImpl spacer = rowIndexToSpacer.remove(this.rowIndex);
 +                assert this == spacer : "trying to move an unexpected spacer.";
 +                this.rowIndex = rowIndex;
 +                root.setPropertyInt(SPACER_LOGICAL_ROW_PROPERTY, rowIndex);
 +                rowIndexToSpacer.put(this.rowIndex, this);
 +            }
 +
 +            /**
 +             * Updates the spacer's visibility parameters, based on whether it
 +             * is being currently visible or not.
 +             */
 +            public void updateVisibility() {
 +                if (isInViewport()) {
 +                    show();
 +                } else {
 +                    hide();
 +                }
 +            }
 +
 +            private boolean isInViewport() {
 +                int top = (int) Math.ceil(getTop());
 +                int height = (int) Math.floor(getHeight());
 +                Range location = Range.withLength(top, height);
 +                return getViewportPixels().intersects(location);
 +            }
 +
 +            public void show() {
 +                getRootElement().getStyle().clearDisplay();
 +                getDecoElement().getStyle().clearDisplay();
 +            }
 +
 +            public void hide() {
 +                getRootElement().getStyle().setDisplay(Display.NONE);
 +                getDecoElement().getStyle().setDisplay(Display.NONE);
 +            }
 +
 +            /**
 +             * Crop the decorator element so that it doesn't overlap the header
 +             * and footer sections.
 +             * 
 +             * @param bodyTop
 +             *            the top cordinate of the escalator body
 +             * @param bodyBottom
 +             *            the bottom cordinate of the escalator body
 +             * @param decoWidth
 +             *            width of the deco
 +             */
 +            private void updateDecoClip(final double bodyTop,
 +                    final double bodyBottom, final double decoWidth) {
 +                final int top = deco.getAbsoluteTop();
 +                final int bottom = deco.getAbsoluteBottom();
 +                /*
 +                 * FIXME
 +                 * 
 +                 * Height and its use is a workaround for the issue where
 +                 * coordinates of the deco are not calculated yet. This will
 +                 * prevent a deco from being displayed when it's added to DOM
 +                 */
 +                final int height = bottom - top;
 +                if (top < bodyTop || bottom > bodyBottom) {
 +                    final double topClip = Math.max(0.0D, bodyTop - top);
 +                    final double bottomClip = height
 +                            - Math.max(0.0D, bottom - bodyBottom);
 +                    // TODO [optimize] not sure how GWT compiles this
 +                    final String clip = new StringBuilder("rect(")
 +                            .append(topClip).append("px,").append(decoWidth)
 +                            .append("px,").append(bottomClip).append("px,0)")
 +                            .toString();
 +                    deco.getStyle().setProperty("clip", clip);
 +                } else {
 +                    deco.getStyle().setProperty("clip", "auto");
 +                }
 +            }
 +        }
 +
 +        private final TreeMap<Integer, SpacerImpl> rowIndexToSpacer = new TreeMap<Integer, SpacerImpl>();
 +
 +        private SpacerUpdater spacerUpdater = SpacerUpdater.NULL;
 +
 +        private final ScrollHandler spacerScroller = new ScrollHandler() {
 +            private double prevScrollX = 0;
 +
 +            @Override
 +            public void onScroll(ScrollEvent event) {
 +                if (WidgetUtil.pixelValuesEqual(getScrollLeft(), prevScrollX)) {
 +                    return;
 +                }
 +
 +                prevScrollX = getScrollLeft();
 +                for (SpacerImpl spacer : rowIndexToSpacer.values()) {
 +                    spacer.setPosition(prevScrollX, spacer.getTop());
 +                }
 +            }
 +        };
 +        private HandlerRegistration spacerScrollerRegistration;
 +
 +        /** Width of the spacers' decos. Calculated once then cached. */
 +        private double spacerDecoWidth = 0.0D;
 +
 +        public void setSpacer(int rowIndex, double height)
 +                throws IllegalArgumentException {
 +
 +            if (rowIndex < -1 || rowIndex >= getBody().getRowCount()) {
 +                throw new IllegalArgumentException("invalid row index: "
 +                        + rowIndex + ", while the body only has "
 +                        + getBody().getRowCount() + " rows.");
 +            }
 +
 +            if (height >= 0) {
 +                if (!spacerExists(rowIndex)) {
 +                    insertNewSpacer(rowIndex, height);
 +                } else {
 +                    updateExistingSpacer(rowIndex, height);
 +                }
 +            } else if (spacerExists(rowIndex)) {
 +                removeSpacer(rowIndex);
 +            }
 +
 +            updateSpacerDecosVisibility();
 +        }
 +
 +        /** Checks if a given element is a spacer element */
 +        public boolean isSpacer(Element row) {
 +
 +            /*
 +             * If this needs optimization, we could do a more heuristic check
 +             * based on stylenames and stuff, instead of iterating through the
 +             * map.
 +             */
 +
 +            for (SpacerImpl spacer : rowIndexToSpacer.values()) {
 +                if (spacer.getRootElement().equals(row)) {
 +                    return true;
 +                }
 +            }
 +
 +            return false;
 +        }
 +
 +        @SuppressWarnings("boxing")
 +        void scrollToSpacer(int spacerIndex, ScrollDestination destination,
 +                int padding) {
 +
 +            assert !destination.equals(ScrollDestination.MIDDLE)
 +                    || padding != 0 : "destination/padding check should be done before this method";
 +
 +            if (!rowIndexToSpacer.containsKey(spacerIndex)) {
 +                throw new IllegalArgumentException("No spacer open at index "
 +                        + spacerIndex);
 +            }
 +
 +            SpacerImpl spacer = rowIndexToSpacer.get(spacerIndex);
 +            double targetStartPx = spacer.getTop();
 +            double targetEndPx = targetStartPx + spacer.getHeight();
 +
 +            Range viewportPixels = getViewportPixels();
 +            double viewportStartPx = viewportPixels.getStart();
 +            double viewportEndPx = viewportPixels.getEnd();
 +
 +            double scrollTop = getScrollPos(destination, targetStartPx,
 +                    targetEndPx, viewportStartPx, viewportEndPx, padding);
 +
 +            setScrollTop(scrollTop);
 +        }
 +
 +        public void reapplySpacerWidths() {
 +            // FIXME #16266 , spacers get couple pixels too much because borders
 +            final double width = getInnerWidth() - spacerDecoWidth;
 +            for (SpacerImpl spacer : rowIndexToSpacer.values()) {
 +                spacer.getRootElement().getStyle().setWidth(width, Unit.PX);
 +            }
 +        }
 +
 +        public void paintRemoveSpacers(Range removedRowsRange) {
 +            removeSpacers(removedRowsRange);
 +            shiftSpacersByRows(removedRowsRange.getStart(),
 +                    -removedRowsRange.length());
 +        }
 +
 +        @SuppressWarnings("boxing")
 +        public void removeSpacers(Range removedRange) {
 +
 +            Map<Integer, SpacerImpl> removedSpacers = rowIndexToSpacer
 +                    .subMap(removedRange.getStart(), true,
 +                            removedRange.getEnd(), false);
 +
 +            if (removedSpacers.isEmpty()) {
 +                return;
 +            }
 +
 +            for (SpacerImpl spacer : removedSpacers.values()) {
 +                /*
 +                 * [[optimization]] TODO: Each invocation of the setHeight
 +                 * method has a cascading effect in the DOM. if this proves to
 +                 * be slow, the DOM offset could be updated as a batch.
 +                 */
 +
 +                destroySpacerContent(spacer);
 +                spacer.setHeight(0); // resets row offsets
 +                spacer.getRootElement().removeFromParent();
 +                spacer.getDecoElement().removeFromParent();
 +            }
 +
 +            removedSpacers.clear();
 +
 +            if (rowIndexToSpacer.isEmpty()) {
 +                assert spacerScrollerRegistration != null : "Spacer scroller registration was null";
 +                spacerScrollerRegistration.removeHandler();
 +                spacerScrollerRegistration = null;
 +            }
 +        }
 +
 +        public Map<Integer, SpacerImpl> getSpacers() {
 +            return new HashMap<Integer, SpacerImpl>(rowIndexToSpacer);
 +        }
 +
 +        /**
 +         * Calculates the sum of all spacers.
 +         * 
 +         * @return sum of all spacers, or 0 if no spacers present
 +         */
 +        public double getSpacerHeightsSum() {
 +            return getHeights(rowIndexToSpacer.values());
 +        }
 +
 +        /**
 +         * Calculates the sum of all spacers from one row index onwards.
 +         * 
 +         * @param logicalRowIndex
 +         *            the spacer to include as the first calculated spacer
 +         * @return the sum of all spacers from {@code logicalRowIndex} and
 +         *         onwards, or 0 if no suitable spacers were found
 +         */
 +        @SuppressWarnings("boxing")
 +        public Collection<SpacerImpl> getSpacersForRowAndAfter(
 +                int logicalRowIndex) {
 +            return new ArrayList<SpacerImpl>(rowIndexToSpacer.tailMap(
 +                    logicalRowIndex, true).values());
 +        }
 +
 +        /**
 +         * Get all spacers from one pixel point onwards.
 +         * <p>
 +         * 
 +         * In this method, the {@link SpacerInclusionStrategy} has the following
 +         * meaning when a spacer lies in the middle of either pixel argument:
 +         * <dl>
 +         * <dt>{@link SpacerInclusionStrategy#COMPLETE COMPLETE}
 +         * <dd>include the spacer
 +         * <dt>{@link SpacerInclusionStrategy#PARTIAL PARTIAL}
 +         * <dd>include the spacer
 +         * <dt>{@link SpacerInclusionStrategy#NONE NONE}
 +         * <dd>ignore the spacer
 +         * </dl>
 +         * 
 +         * @param px
 +         *            the pixel point after which to return all spacers
 +         * @param strategy
 +         *            the inclusion strategy regarding the {@code px}
 +         * @return a collection of the spacers that exist after {@code px}
 +         */
 +        public Collection<SpacerImpl> getSpacersAfterPx(final double px,
 +                final SpacerInclusionStrategy strategy) {
 +
 +            ArrayList<SpacerImpl> spacers = new ArrayList<SpacerImpl>(
 +                    rowIndexToSpacer.values());
 +
 +            for (int i = 0; i < spacers.size(); i++) {
 +                SpacerImpl spacer = spacers.get(i);
 +
 +                double top = spacer.getTop();
 +                double bottom = top + spacer.getHeight();
 +
 +                if (top > px) {
 +                    return spacers.subList(i, spacers.size());
 +                } else if (bottom > px) {
 +                    if (strategy == SpacerInclusionStrategy.NONE) {
 +                        return spacers.subList(i + 1, spacers.size());
 +                    } else {
 +                        return spacers.subList(i, spacers.size());
 +                    }
 +                }
 +            }
 +
 +            return Collections.emptySet();
 +        }
 +
 +        /**
 +         * Gets the spacers currently rendered in the DOM.
 +         * 
 +         * @return an unmodifiable (but live) collection of the spacers
 +         *         currently in the DOM
 +         */
 +        public Collection<SpacerImpl> getSpacersInDom() {
 +            return Collections
 +                    .unmodifiableCollection(rowIndexToSpacer.values());
 +        }
 +
 +        /**
 +         * Gets the amount of pixels occupied by spacers between two pixel
 +         * points.
 +         * <p>
 +         * In this method, the {@link SpacerInclusionStrategy} has the following
 +         * meaning when a spacer lies in the middle of either pixel argument:
 +         * <dl>
 +         * <dt>{@link SpacerInclusionStrategy#COMPLETE COMPLETE}
 +         * <dd>take the entire spacer into account
 +         * <dt>{@link SpacerInclusionStrategy#PARTIAL PARTIAL}
 +         * <dd>take only the visible area into account
 +         * <dt>{@link SpacerInclusionStrategy#NONE NONE}
 +         * <dd>ignore that spacer
 +         * </dl>
 +         * 
 +         * @param rangeTop
 +         *            the top pixel point
 +         * @param topInclusion
 +         *            the inclusion strategy regarding {@code rangeTop}.
 +         * @param rangeBottom
 +         *            the bottom pixel point
 +         * @param bottomInclusion
 +         *            the inclusion strategy regarding {@code rangeBottom}.
 +         * @return the pixels occupied by spacers between {@code rangeTop} and
 +         *         {@code rangeBottom}
 +         */
 +        public double getSpacerHeightsSumBetweenPx(double rangeTop,
 +                SpacerInclusionStrategy topInclusion, double rangeBottom,
 +                SpacerInclusionStrategy bottomInclusion) {
 +
 +            assert rangeTop <= rangeBottom : "rangeTop must be less than rangeBottom";
 +
 +            double heights = 0;
 +
 +            /*
 +             * TODO [[optimize]]: this might be somewhat inefficient (due to
 +             * iterator-based scanning, instead of using the treemap's search
 +             * functionalities). But it should be easy to write, read, verify
 +             * and maintain.
 +             */
 +            for (SpacerImpl spacer : rowIndexToSpacer.values()) {
 +                double top = spacer.getTop();
 +                double height = spacer.getHeight();
 +                double bottom = top + height;
 +
 +                /*
 +                 * If we happen to implement a DoubleRange (in addition to the
 +                 * int-based Range) at some point, the following logic should
 +                 * probably be converted into using the
 +                 * Range.partitionWith-equivalent.
 +                 */
 +
 +                boolean topIsAboveRange = top < rangeTop;
 +                boolean topIsInRange = rangeTop <= top && top <= rangeBottom;
 +                boolean topIsBelowRange = rangeBottom < top;
 +
 +                boolean bottomIsAboveRange = bottom < rangeTop;
 +                boolean bottomIsInRange = rangeTop <= bottom
 +                        && bottom <= rangeBottom;
 +                boolean bottomIsBelowRange = rangeBottom < bottom;
 +
 +                assert topIsAboveRange ^ topIsBelowRange ^ topIsInRange : "Bad top logic";
 +                assert bottomIsAboveRange ^ bottomIsBelowRange
 +                        ^ bottomIsInRange : "Bad bottom logic";
 +
 +                if (bottomIsAboveRange) {
 +                    continue;
 +                } else if (topIsBelowRange) {
 +                    return heights;
 +                }
 +
 +                else if (topIsAboveRange && bottomIsInRange) {
 +                    switch (topInclusion) {
 +                    case PARTIAL:
 +                        heights += bottom - rangeTop;
 +                        break;
 +                    case COMPLETE:
 +                        heights += height;
 +                        break;
 +                    default:
 +                        break;
 +                    }
 +                }
 +
 +                else if (topIsAboveRange && bottomIsBelowRange) {
 +
 +                    /*
 +                     * Here we arbitrarily decide that the top inclusion will
 +                     * have the honor of overriding the bottom inclusion if
 +                     * happens to be a conflict of interests.
 +                     */
 +                    switch (topInclusion) {
 +                    case NONE:
 +                        return 0;
 +                    case COMPLETE:
 +                        return height;
 +                    case PARTIAL:
 +                        return rangeBottom - rangeTop;
 +                    default:
 +                        throw new IllegalArgumentException(
 +                                "Unexpected inclusion state :" + topInclusion);
 +                    }
 +
 +                } else if (topIsInRange && bottomIsInRange) {
 +                    heights += height;
 +                }
 +
 +                else if (topIsInRange && bottomIsBelowRange) {
 +                    switch (bottomInclusion) {
 +                    case PARTIAL:
 +                        heights += rangeBottom - top;
 +                        break;
 +                    case COMPLETE:
 +                        heights += height;
 +                        break;
 +                    default:
 +                        break;
 +                    }
 +
 +                    return heights;
 +                }
 +
 +                else {
 +                    assert false : "Unnaccounted-for situation";
 +                }
 +            }
 +
 +            return heights;
 +        }
 +
 +        /**
 +         * Gets the amount of pixels occupied by spacers from the top until a
 +         * certain spot from the top of the body.
 +         * 
 +         * @param px
 +         *            pixels counted from the top
 +         * @return the pixels occupied by spacers up until {@code px}
 +         */
 +        public double getSpacerHeightsSumUntilPx(double px) {
 +            return getSpacerHeightsSumBetweenPx(0,
 +                    SpacerInclusionStrategy.PARTIAL, px,
 +                    SpacerInclusionStrategy.PARTIAL);
 +        }
 +
 +        /**
 +         * Gets the amount of pixels occupied by spacers until a logical row
 +         * index.
 +         * 
 +         * @param logicalIndex
 +         *            a logical row index
 +         * @return the pixels occupied by spacers up until {@code logicalIndex}
 +         */
 +        @SuppressWarnings("boxing")
 +        public double getSpacerHeightsSumUntilIndex(int logicalIndex) {
 +            return getHeights(rowIndexToSpacer.headMap(logicalIndex, false)
 +                    .values());
 +        }
 +
 +        private double getHeights(Collection<SpacerImpl> spacers) {
 +            double heights = 0;
 +            for (SpacerImpl spacer : spacers) {
 +                heights += spacer.getHeight();
 +            }
 +            return heights;
 +        }
 +
 +        /**
 +         * Gets the height of the spacer for a row index.
 +         * 
 +         * @param rowIndex
 +         *            the index of the row where the spacer should be
 +         * @return the height of the spacer at index {@code rowIndex}, or 0 if
 +         *         there is no spacer there
 +         */
 +        public double getSpacerHeight(int rowIndex) {
 +            SpacerImpl spacer = getSpacer(rowIndex);
 +            if (spacer != null) {
 +                return spacer.getHeight();
 +            } else {
 +                return 0;
 +            }
 +        }
 +
 +        private boolean spacerExists(int rowIndex) {
 +            return rowIndexToSpacer.containsKey(Integer.valueOf(rowIndex));
 +        }
 +
 +        @SuppressWarnings("boxing")
 +        private void insertNewSpacer(int rowIndex, double height) {
 +
 +            if (spacerScrollerRegistration == null) {
 +                spacerScrollerRegistration = addScrollHandler(spacerScroller);
 +            }
 +
 +            final SpacerImpl spacer = new SpacerImpl(rowIndex);
 +
 +            rowIndexToSpacer.put(rowIndex, spacer);
 +            // set the position before adding it to DOM
 +            positions.set(spacer.getRootElement(), getScrollLeft(),
 +                    calculateSpacerTop(rowIndex));
 +
 +            TableRowElement spacerRoot = spacer.getRootElement();
 +            spacerRoot.getStyle().setWidth(
 +                    columnConfiguration.calculateRowWidth(), Unit.PX);
 +            body.getElement().appendChild(spacerRoot);
 +            spacer.setupDom(height);
 +            // set the deco position, requires that spacer is in the DOM
 +            positions.set(spacer.getDecoElement(), 0,
 +                    spacer.getTop() - spacer.getSpacerDecoTopOffset());
 +
 +            spacerDecoContainer.appendChild(spacer.getDecoElement());
 +            if (spacerDecoContainer.getParentElement() == null) {
 +                getElement().appendChild(spacerDecoContainer);
 +                // calculate the spacer deco width, it won't change
 +                spacerDecoWidth = WidgetUtil
 +                        .getRequiredWidthBoundingClientRectDouble(spacer
 +                                .getDecoElement());
 +            }
 +
 +            initSpacerContent(spacer);
 +
 +            body.sortDomElements();
 +        }
 +
 +        private void updateExistingSpacer(int rowIndex, double newHeight) {
 +            getSpacer(rowIndex).setHeight(newHeight);
 +        }
 +
 +        public SpacerImpl getSpacer(int rowIndex) {
 +            return rowIndexToSpacer.get(Integer.valueOf(rowIndex));
 +        }
 +
 +        private void removeSpacer(int rowIndex) {
 +            removeSpacers(Range.withOnly(rowIndex));
 +        }
 +
 +        public void setStylePrimaryName(String style) {
 +            for (SpacerImpl spacer : rowIndexToSpacer.values()) {
 +                spacer.setStylePrimaryName(style);
 +            }
 +        }
 +
 +        public void setSpacerUpdater(SpacerUpdater spacerUpdater)
 +                throws IllegalArgumentException {
 +            if (spacerUpdater == null) {
 +                throw new IllegalArgumentException(
 +                        "spacer updater cannot be null");
 +            }
 +
 +            destroySpacerContent(rowIndexToSpacer.values());
 +            this.spacerUpdater = spacerUpdater;
 +            initSpacerContent(rowIndexToSpacer.values());
 +        }
 +
 +        public SpacerUpdater getSpacerUpdater() {
 +            return spacerUpdater;
 +        }
 +
 +        private void destroySpacerContent(Iterable<SpacerImpl> spacers) {
 +            for (SpacerImpl spacer : spacers) {
 +                destroySpacerContent(spacer);
 +            }
 +        }
 +
 +        private void destroySpacerContent(SpacerImpl spacer) {
 +            assert getElement().isOrHasChild(spacer.getRootElement()) : "Spacer's root element somehow got detached from Escalator before detaching";
 +            assert getElement().isOrHasChild(spacer.getElement()) : "Spacer element somehow got detached from Escalator before detaching";
 +            spacerUpdater.destroy(spacer);
 +            assert getElement().isOrHasChild(spacer.getRootElement()) : "Spacer's root element somehow got detached from Escalator before detaching";
 +            assert getElement().isOrHasChild(spacer.getElement()) : "Spacer element somehow got detached from Escalator before detaching";
 +        }
 +
 +        private void initSpacerContent(Iterable<SpacerImpl> spacers) {
 +            for (SpacerImpl spacer : spacers) {
 +                initSpacerContent(spacer);
 +            }
 +        }
 +
 +        private void initSpacerContent(SpacerImpl spacer) {
 +            assert getElement().isOrHasChild(spacer.getRootElement()) : "Spacer's root element somehow got detached from Escalator before attaching";
 +            assert getElement().isOrHasChild(spacer.getElement()) : "Spacer element somehow got detached from Escalator before attaching";
 +            spacerUpdater.init(spacer);
 +            assert getElement().isOrHasChild(spacer.getRootElement()) : "Spacer's root element somehow got detached from Escalator during attaching";
 +            assert getElement().isOrHasChild(spacer.getElement()) : "Spacer element somehow got detached from Escalator during attaching";
 +
 +            spacer.updateVisibility();
 +        }
 +
 +        public String getSubPartName(Element subElement) {
 +            for (SpacerImpl spacer : rowIndexToSpacer.values()) {
 +                if (spacer.getRootElement().isOrHasChild(subElement)) {
 +                    return "spacer[" + spacer.getRow() + "]";
 +                }
 +            }
 +            return null;
 +        }
 +
 +        public Element getSubPartElement(int index) {
 +            SpacerImpl spacer = rowIndexToSpacer.get(Integer.valueOf(index));
 +            if (spacer != null) {
 +                return spacer.getElement();
 +            } else {
 +                return null;
 +            }
 +        }
 +
 +        private double calculateSpacerTop(int logicalIndex) {
 +            return body.getRowTop(logicalIndex) + body.getDefaultRowHeight();
 +        }
 +
 +        @SuppressWarnings("boxing")
 +        private void shiftSpacerPositionsAfterRow(int changedRowIndex,
 +                double diffPx) {
 +            for (SpacerImpl spacer : rowIndexToSpacer.tailMap(changedRowIndex,
 +                    false).values()) {
 +                spacer.setPositionDiff(0, diffPx);
 +            }
 +        }
 +
 +        /**
 +         * Shifts spacers at and after a specific row by an amount of rows.
 +         * <p>
 +         * This moves both their associated row index and also their visual
 +         * placement.
 +         * <p>
 +         * <em>Note:</em> This method does not check for the validity of any
 +         * arguments.
 +         * 
 +         * @param index
 +         *            the index of first row to move
 +         * @param numberOfRows
 +         *            the number of rows to shift the spacers with. A positive
 +         *            value is downwards, a negative value is upwards.
 +         */
 +        public void shiftSpacersByRows(int index, int numberOfRows) {
 +            final double pxDiff = numberOfRows * body.getDefaultRowHeight();
 +            for (SpacerContainer.SpacerImpl spacer : getSpacersForRowAndAfter(index)) {
 +                spacer.setPositionDiff(0, pxDiff);
 +                spacer.setRowIndex(spacer.getRow() + numberOfRows);
 +            }
 +        }
 +
 +        private void updateSpacerDecosVisibility() {
 +            final Range visibleRowRange = getVisibleRowRange();
 +            Collection<SpacerImpl> visibleSpacers = rowIndexToSpacer.subMap(
 +                    visibleRowRange.getStart() - 1,
 +                    visibleRowRange.getEnd() + 1).values();
 +            if (!visibleSpacers.isEmpty()) {
 +                final double top = tableWrapper.getAbsoluteTop()
 +                        + header.getHeightOfSection();
 +                final double bottom = tableWrapper.getAbsoluteBottom()
 +                        - footer.getHeightOfSection();
 +                for (SpacerImpl spacer : visibleSpacers) {
 +                    spacer.updateDecoClip(top, bottom, spacerDecoWidth);
 +                }
 +            }
 +        }
 +    }
 +
 +    private class ElementPositionBookkeeper {
 +        /**
 +         * A map containing cached values of an element's current top position.
 +         */
 +        private final Map<Element, Double> elementTopPositionMap = new HashMap<Element, Double>();
 +        private final Map<Element, Double> elementLeftPositionMap = new HashMap<Element, Double>();
 +
 +        public void set(final Element e, final double x, final double y) {
 +            assert e != null : "Element was null";
 +            position.set(e, x, y);
 +            elementTopPositionMap.put(e, Double.valueOf(y));
 +            elementLeftPositionMap.put(e, Double.valueOf(x));
 +        }
 +
 +        public double getTop(final Element e) {
 +            Double top = elementTopPositionMap.get(e);
 +            if (top == null) {
 +                throw new IllegalArgumentException("Element " + e
 +                        + " was not found in the position bookkeeping");
 +            }
 +            return top.doubleValue();
 +        }
 +
 +        public double getLeft(final Element e) {
 +            Double left = elementLeftPositionMap.get(e);
 +            if (left == null) {
 +                throw new IllegalArgumentException("Element " + e
 +                        + " was not found in the position bookkeeping");
 +            }
 +            return left.doubleValue();
 +        }
 +
 +        public void remove(Element e) {
 +            elementTopPositionMap.remove(e);
 +            elementLeftPositionMap.remove(e);
 +        }
 +    }
 +
 +    /**
 +     * Utility class for parsing and storing SubPart request string attributes
 +     * for Grid and Escalator.
 +     * 
 +     * @since 7.5.0
 +     */
 +    public static class SubPartArguments {
 +        private String type;
 +        private int[] indices;
 +
 +        private SubPartArguments(String type, int[] indices) {
 +            /*
 +             * The constructor is private so that no third party would by
 +             * mistake start using this parsing scheme, since it's not official
 +             * by TestBench (yet?).
 +             */
 +
 +            this.type = type;
 +            this.indices = indices;
 +        }
 +
 +        public String getType() {
 +            return type;
 +        }
 +
 +        public int getIndicesLength() {
 +            return indices.length;
 +        }
 +
 +        public int getIndex(int i) {
 +            return indices[i];
 +        }
 +
 +        public int[] getIndices() {
 +            return Arrays.copyOf(indices, indices.length);
 +        }
 +
 +        static SubPartArguments create(String subPart) {
 +            String[] splitArgs = subPart.split("\\[");
 +            String type = splitArgs[0];
 +            int[] indices = new int[splitArgs.length - 1];
 +            for (int i = 0; i < indices.length; ++i) {
 +                String tmp = splitArgs[i + 1];
 +                indices[i] = Integer.parseInt(tmp.substring(0,
 +                        tmp.indexOf("]", 1)));
 +            }
 +            return new SubPartArguments(type, indices);
 +        }
 +    }
 +
 +    // abs(atan(y/x))*(180/PI) = n deg, x = 1, solve y
 +    /**
 +     * The solution to
 +     * <code>|tan<sup>-1</sup>(<i>x</i>)|&times;(180/&pi;)&nbsp;=&nbsp;30</code>
 +     * .
 +     * <p>
 +     * This constant is placed in the Escalator class, instead of an inner
 +     * class, since even mathematical expressions aren't allowed in non-static
 +     * inner classes for constants.
 +     */
 +    private static final double RATIO_OF_30_DEGREES = 1 / Math.sqrt(3);
 +    /**
 +     * The solution to
 +     * <code>|tan<sup>-1</sup>(<i>x</i>)|&times;(180/&pi;)&nbsp;=&nbsp;40</code>
 +     * .
 +     * <p>
 +     * This constant is placed in the Escalator class, instead of an inner
 +     * class, since even mathematical expressions aren't allowed in non-static
 +     * inner classes for constants.
 +     */
 +    private static final double RATIO_OF_40_DEGREES = Math.tan(2 * Math.PI / 9);
 +
 +    private static final String DEFAULT_WIDTH = "500.0px";
 +    private static final String DEFAULT_HEIGHT = "400.0px";
 +
 +    private FlyweightRow flyweightRow = new FlyweightRow();
 +
 +    /** The {@code <thead/>} tag. */
 +    private final TableSectionElement headElem = TableSectionElement.as(DOM
 +            .createTHead());
 +    /** The {@code <tbody/>} tag. */
 +    private final TableSectionElement bodyElem = TableSectionElement.as(DOM
 +            .createTBody());
 +    /** The {@code <tfoot/>} tag. */
 +    private final TableSectionElement footElem = TableSectionElement.as(DOM
 +            .createTFoot());
 +
 +    /**
 +     * TODO: investigate whether this field is now unnecessary, as
 +     * {@link ScrollbarBundle} now caches its values.
 +     * 
 +     * @deprecated maybe...
 +     */
 +    @Deprecated
 +    private double tBodyScrollTop = 0;
 +
 +    /**
 +     * TODO: investigate whether this field is now unnecessary, as
 +     * {@link ScrollbarBundle} now caches its values.
 +     * 
 +     * @deprecated maybe...
 +     */
 +    @Deprecated
 +    private double tBodyScrollLeft = 0;
 +
 +    private final VerticalScrollbarBundle verticalScrollbar = new VerticalScrollbarBundle();
 +    private final HorizontalScrollbarBundle horizontalScrollbar = new HorizontalScrollbarBundle();
 +
 +    private final HeaderRowContainer header = new HeaderRowContainer(headElem);
 +    private final BodyRowContainerImpl body = new BodyRowContainerImpl(bodyElem);
 +    private final FooterRowContainer footer = new FooterRowContainer(footElem);
 +
 +    private final Scroller scroller = new Scroller();
 +
 +    private final ColumnConfigurationImpl columnConfiguration = new ColumnConfigurationImpl();
 +    private final DivElement tableWrapper;
 +
 +    private final DivElement horizontalScrollbarDeco = DivElement.as(DOM
 +            .createDiv());
 +    private final DivElement headerDeco = DivElement.as(DOM.createDiv());
 +    private final DivElement footerDeco = DivElement.as(DOM.createDiv());
 +    private final DivElement spacerDecoContainer = DivElement.as(DOM
 +            .createDiv());
 +
 +    private PositionFunction position;
 +
 +    /** The cached width of the escalator, in pixels. */
 +    private double widthOfEscalator = 0;
 +    /** The cached height of the escalator, in pixels. */
 +    private double heightOfEscalator = 0;
 +
 +    /** The height of Escalator in terms of body rows. */
 +    private double heightByRows = 10.0d;
 +
 +    /** The height of Escalator, as defined by {@link #setHeight(String)} */
 +    private String heightByCss = "";
 +
 +    private HeightMode heightMode = HeightMode.CSS;
 +
 +    private boolean layoutIsScheduled = false;
 +    private ScheduledCommand layoutCommand = new ScheduledCommand() {
 +        @Override
 +        public void execute() {
 +            recalculateElementSizes();
 +            layoutIsScheduled = false;
 +        }
 +    };
 +
 +    private final ElementPositionBookkeeper positions = new ElementPositionBookkeeper();
 +
 +    /**
 +     * Creates a new Escalator widget instance.
 +     */
 +    public Escalator() {
 +
 +        detectAndApplyPositionFunction();
 +        getLogger().info(
 +                "Using " + position.getClass().getSimpleName()
 +                        + " for position");
 +
 +        final Element root = DOM.createDiv();
 +        setElement(root);
 +
 +        setupScrollbars(root);
 +
 +        tableWrapper = DivElement.as(DOM.createDiv());
 +
 +        root.appendChild(tableWrapper);
 +
 +        final Element table = DOM.createTable();
 +        tableWrapper.appendChild(table);
 +
 +        table.appendChild(headElem);
 +        table.appendChild(bodyElem);
 +        table.appendChild(footElem);
 +
 +        Style hCornerStyle = headerDeco.getStyle();
 +        hCornerStyle.setWidth(verticalScrollbar.getScrollbarThickness(),
 +                Unit.PX);
 +        hCornerStyle.setDisplay(Display.NONE);
 +        root.appendChild(headerDeco);
 +
 +        Style fCornerStyle = footerDeco.getStyle();
 +        fCornerStyle.setWidth(verticalScrollbar.getScrollbarThickness(),
 +                Unit.PX);
 +        fCornerStyle.setDisplay(Display.NONE);
 +        root.appendChild(footerDeco);
 +
 +        Style hWrapperStyle = horizontalScrollbarDeco.getStyle();
 +        hWrapperStyle.setDisplay(Display.NONE);
 +        hWrapperStyle.setHeight(horizontalScrollbar.getScrollbarThickness(),
 +                Unit.PX);
 +        root.appendChild(horizontalScrollbarDeco);
 +
 +        setStylePrimaryName("v-escalator");
 +
 +        spacerDecoContainer.setAttribute("aria-hidden", "true");
 +
 +        // init default dimensions
 +        setHeight(null);
 +        setWidth(null);
 +    }
 +
 +    private void setupScrollbars(final Element root) {
 +
 +        ScrollHandler scrollHandler = new ScrollHandler() {
 +            @Override
 +            public void onScroll(ScrollEvent event) {
 +                scroller.onScroll();
 +                fireEvent(new ScrollEvent());
 +            }
 +        };
 +
 +        int scrollbarThickness = WidgetUtil.getNativeScrollbarSize();
 +        if (BrowserInfo.get().isIE()) {
 +            /*
 +             * IE refuses to scroll properly if the DIV isn't at least one pixel
 +             * larger than the scrollbar controls themselves. But, probably
 +             * because of subpixel rendering, in Grid, one pixel isn't enough,
 +             * so we'll add two instead.
 +             */
 +            if (BrowserInfo.get().isIE9()) {
 +                scrollbarThickness += 2;
 +            } else {
 +                scrollbarThickness += 1;
 +            }
 +        }
 +
 +        root.appendChild(verticalScrollbar.getElement());
 +        verticalScrollbar.addScrollHandler(scrollHandler);
 +        verticalScrollbar.setScrollbarThickness(scrollbarThickness);
 +
 +        if (BrowserInfo.get().isIE8()) {
 +            /*
 +             * IE8 will have to compensate for a misalignment where it pops the
 +             * scrollbar outside of its box. See Bug 3 in
 +             * http://edskes.net/ie/ie8overflowandexpandingboxbugs.htm
 +             */
 +            Style vScrollStyle = verticalScrollbar.getElement().getStyle();
 +            vScrollStyle.setRight(
 +                    verticalScrollbar.getScrollbarThickness() - 1, Unit.PX);
 +        }
 +
 +        root.appendChild(horizontalScrollbar.getElement());
 +        horizontalScrollbar.addScrollHandler(scrollHandler);
 +        horizontalScrollbar.setScrollbarThickness(scrollbarThickness);
 +        horizontalScrollbar
 +                .addVisibilityHandler(new ScrollbarBundle.VisibilityHandler() {
 +
 +                    private boolean queued = false;
 +
 +                    @Override
 +                    public void visibilityChanged(
 +                            ScrollbarBundle.VisibilityChangeEvent event) {
 +                        if (queued) {
 +                            return;
 +                        }
 +                        queued = true;
 +
 +                        /*
 +                         * We either lost or gained a scrollbar. In any case, we
 +                         * need to change the height, if it's defined by rows.
 +                         */
 +                        Scheduler.get().scheduleFinally(new ScheduledCommand() {
 +
 +                            @Override
 +                            public void execute() {
 +                                applyHeightByRows();
 +                                queued = false;
 +                            }
 +                        });
 +                    }
 +                });
 +
 +        /*
 +         * Because of all the IE hacks we've done above, we now have scrollbars
 +         * hiding underneath a lot of DOM elements.
 +         * 
 +         * This leads to problems with OSX (and many touch-only devices) when
 +         * scrollbars are only shown when scrolling, as the scrollbar elements
 +         * are hidden underneath everything. We trust that the scrollbars behave
 +         * properly in these situations and simply pop them out with a bit of
 +         * z-indexing.
 +         */
 +        if (WidgetUtil.getNativeScrollbarSize() == 0) {
 +            verticalScrollbar.getElement().getStyle().setZIndex(90);
 +            horizontalScrollbar.getElement().getStyle().setZIndex(90);
 +        }
 +    }
 +
 +    @Override
 +    protected void onLoad() {
 +        super.onLoad();
 +
 +        header.autodetectRowHeightLater();
 +        body.autodetectRowHeightLater();
 +        footer.autodetectRowHeightLater();
 +
 +        header.paintInsertRows(0, header.getRowCount());
 +        footer.paintInsertRows(0, footer.getRowCount());
 +
 +        // recalculateElementSizes();
 +
 +        Scheduler.get().scheduleDeferred(new Command() {
 +            @Override
 +            public void execute() {
 +                /*
 +                 * Not a faintest idea why we have to defer this call, but
 +                 * unless it is deferred, the size of the escalator will be 0x0
 +                 * after it is first detached and then reattached to the DOM.
 +                 * This only applies to a bare Escalator; inside a Grid
 +                 * everything works fine either way.
 +                 * 
 +                 * The three autodetectRowHeightLater calls above seem obvious
 +                 * suspects at first. However, they don't seem to have anything
 +                 * to do with the issue, as they are no-ops in the
 +                 * detach-reattach case.
 +                 */
 +                recalculateElementSizes();
 +            }
 +        });
 +
 +        /*
 +         * Note: There's no need to explicitly insert rows into the body.
 +         * 
 +         * recalculateElementSizes will recalculate the height of the body. This
 +         * has the side-effect that as the body's size grows bigger (i.e. from 0
 +         * to its actual height), more escalator rows are populated. Those
 +         * escalator rows are then immediately rendered. This, in effect, is the
 +         * same thing as inserting those rows.
 +         * 
 +         * In fact, having an extra paintInsertRows here would lead to duplicate
 +         * rows.
 +         */
 +
 +        boolean columnsChanged = false;
 +        for (ColumnConfigurationImpl.Column column : columnConfiguration.columns) {
 +            boolean columnChanged = column.measureAndSetWidthIfNeeded();
 +            if (columnChanged) {
 +                columnsChanged = true;
 +            }
 +        }
 +        if (columnsChanged) {
 +            header.reapplyColumnWidths();
 +            body.reapplyColumnWidths();
 +            footer.reapplyColumnWidths();
 +        }
 +
 +        verticalScrollbar.onLoad();
 +        horizontalScrollbar.onLoad();
 +
 +        scroller.attachScrollListener(verticalScrollbar.getElement());
 +        scroller.attachScrollListener(horizontalScrollbar.getElement());
 +        scroller.attachMousewheelListener(getElement());
 +        scroller.attachTouchListeners(getElement());
 +    }
 +
 +    @Override
 +    protected void onUnload() {
 +
 +        scroller.detachScrollListener(verticalScrollbar.getElement());
 +        scroller.detachScrollListener(horizontalScrollbar.getElement());
 +        scroller.detachMousewheelListener(getElement());
 +        scroller.detachTouchListeners(getElement());
 +
 +        /*
 +         * We can call paintRemoveRows here, because static ranges are simple to
 +         * remove.
 +         */
 +        header.paintRemoveRows(0, header.getRowCount());
 +        footer.paintRemoveRows(0, footer.getRowCount());
 +
 +        /*
 +         * We can't call body.paintRemoveRows since it relies on rowCount to be
 +         * updated correctly. Since it isn't, we'll simply and brutally rip out
 +         * the DOM elements (in an elegant way, of course).
 +         */
 +        int rowsToRemove = body.getDomRowCount();
 +        for (int i = 0; i < rowsToRemove; i++) {
 +            int index = rowsToRemove - i - 1;
 +            TableRowElement tr = bodyElem.getRows().getItem(index);
 +            body.paintRemoveRow(tr, index);
 +            positions.remove(tr);
 +        }
 +        body.visualRowOrder.clear();
 +        body.setTopRowLogicalIndex(0);
 +
 +        super.onUnload();
 +    }
 +
 +    private void detectAndApplyPositionFunction() {
 +        /*
 +         * firefox has a bug in its translate operation, showing white space
 +         * when adjusting the scrollbar in BodyRowContainer.paintInsertRows
 +         */
 +        if (Window.Navigator.getUserAgent().contains("Firefox")) {
 +            position = new AbsolutePosition();
 +            return;
 +        }
 +
 +        final Style docStyle = Document.get().getBody().getStyle();
 +        if (hasProperty(docStyle, "transform")) {
 +            if (hasProperty(docStyle, "transformStyle")) {
 +                position = new Translate3DPosition();
 +            } else {
 +                position = new TranslatePosition();
 +            }
 +        } else if (hasProperty(docStyle, "webkitTransform")) {
 +            position = new WebkitTranslate3DPosition();
 +        } else {
 +            position = new AbsolutePosition();
 +        }
 +    }
 +
 +    private Logger getLogger() {
 +        return Logger.getLogger(getClass().getName());
 +    }
 +
 +    private static native boolean hasProperty(Style style, String name)
 +    /*-{
 +        return style[name] !== undefined;
 +    }-*/;
 +
 +    /**
 +     * Check whether there are both columns and any row data (for either
 +     * headers, body or footer).
 +     * 
 +     * @return <code>true</code> iff header, body or footer has rows && there
 +     *         are columns
 +     */
 +    private boolean hasColumnAndRowData() {
 +        return (header.getRowCount() > 0 || body.getRowCount() > 0 || footer
 +                .getRowCount() > 0) && columnConfiguration.getColumnCount() > 0;
 +    }
 +
 +    /**
 +     * Check whether there are any cells in the DOM.
 +     * 
 +     * @return <code>true</code> iff header, body or footer has any child
 +     *         elements
 +     */
 +    private boolean hasSomethingInDom() {
 +        return headElem.hasChildNodes() || bodyElem.hasChildNodes()
 +                || footElem.hasChildNodes();
 +    }
 +
 +    /**
 +     * Returns the row container for the header in this Escalator.
 +     * 
 +     * @return the header. Never <code>null</code>
 +     */
 +    public RowContainer getHeader() {
 +        return header;
 +    }
 +
 +    /**
 +     * Returns the row container for the body in this Escalator.
 +     * 
 +     * @return the body. Never <code>null</code>
 +     */
 +    public BodyRowContainer getBody() {
 +        return body;
 +    }
 +
 +    /**
 +     * Returns the row container for the footer in this Escalator.
 +     * 
 +     * @return the footer. Never <code>null</code>
 +     */
 +    public RowContainer getFooter() {
 +        return footer;
 +    }
 +
 +    /**
 +     * Returns the configuration object for the columns in this Escalator.
 +     * 
 +     * @return the configuration object for the columns in this Escalator. Never
 +     *         <code>null</code>
 +     */
 +    public ColumnConfiguration getColumnConfiguration() {
 +        return columnConfiguration;
 +    }
 +
 +    @Override
 +    public void setWidth(final String width) {
 +        if (width != null && !width.isEmpty()) {
 +            super.setWidth(width);
 +        } else {
 +            super.setWidth(DEFAULT_WIDTH);
 +        }
 +
 +        recalculateElementSizes();
 +    }
 +
 +    /**
 +     * {@inheritDoc}
 +     * <p>
 +     * If Escalator is currently not in {@link HeightMode#CSS}, the given value
 +     * is remembered, and applied once the mode is applied.
 +     * 
 +     * @see #setHeightMode(HeightMode)
 +     */
 +    @Override
 +    public void setHeight(String height) {
 +        /*
 +         * TODO remove method once RequiresResize and the Vaadin layoutmanager
 +         * listening mechanisms are implemented
 +         */
 +
 +        if (height != null && !height.isEmpty()) {
 +            heightByCss = height;
 +        } else {
 +            heightByCss = DEFAULT_HEIGHT;
 +        }
 +
 +        if (getHeightMode() == HeightMode.CSS) {
 +            setHeightInternal(height);
 +        }
 +    }
 +
 +    private void setHeightInternal(final String height) {
 +        final int escalatorRowsBefore = body.visualRowOrder.size();
 +
 +        if (height != null && !height.isEmpty()) {
 +            super.setHeight(height);
 +        } else {
 +            super.setHeight(DEFAULT_HEIGHT);
 +        }
 +
 +        recalculateElementSizes();
 +
 +        if (escalatorRowsBefore != body.visualRowOrder.size()) {
 +            fireRowVisibilityChangeEvent();
 +        }
 +    }
 +
 +    /**
 +     * Returns the vertical scroll offset. Note that this is not necessarily the
 +     * same as the {@code scrollTop} attribute in the DOM.
 +     * 
 +     * @return the logical vertical scroll offset
 +     */
 +    public double getScrollTop() {
 +        return verticalScrollbar.getScrollPos();
 +    }
 +
 +    /**
 +     * Sets the vertical scroll offset. Note that this will not necessarily
 +     * become the same as the {@code scrollTop} attribute in the DOM.
 +     * 
 +     * @param scrollTop
 +     *            the number of pixels to scroll vertically
 +     */
 +    public void setScrollTop(final double scrollTop) {
 +        verticalScrollbar.setScrollPos(scrollTop);
 +    }
 +
 +    /**
 +     * Returns the logical horizontal scroll offset. Note that this is not
 +     * necessarily the same as the {@code scrollLeft} attribute in the DOM.
 +     * 
 +     * @return the logical horizontal scroll offset
 +     */
 +    public double getScrollLeft() {
 +        return horizontalScrollbar.getScrollPos();
 +    }
 +
 +    /**
 +     * Sets the logical horizontal scroll offset. Note that will not necessarily
 +     * become the same as the {@code scrollLeft} attribute in the DOM.
 +     * 
 +     * @param scrollLeft
 +     *            the number of pixels to scroll horizontally
 +     */
 +    public void setScrollLeft(final double scrollLeft) {
 +        horizontalScrollbar.setScrollPos(scrollLeft);
 +    }
 +
 +    /**
 +     * Returns the scroll width for the escalator. Note that this is not
 +     * necessary the same as {@code Element.scrollWidth} in the DOM.
 +     * 
 +     * @since 7.5.0
 +     * @return the scroll width in pixels
 +     */
 +    public double getScrollWidth() {
 +        return horizontalScrollbar.getScrollSize();
 +    }
 +
 +    /**
 +     * Returns the scroll height for the escalator. Note that this is not
 +     * necessary the same as {@code Element.scrollHeight} in the DOM.
 +     * 
 +     * @since 7.5.0
 +     * @return the scroll height in pixels
 +     */
 +    public double getScrollHeight() {
 +        return verticalScrollbar.getScrollSize();
 +    }
 +
 +    /**
 +     * Scrolls the body horizontally so that the column at the given index is
 +     * visible and there is at least {@code padding} pixels in the direction of
 +     * the given scroll destination.
 +     * 
 +     * @param columnIndex
 +     *            the index of the column to scroll to
 +     * @param destination
 +     *            where the column should be aligned visually after scrolling
 +     * @param padding
 +     *            the number pixels to place between the scrolled-to column and
 +     *            the viewport edge.
 +     * @throws IndexOutOfBoundsException
 +     *             if {@code columnIndex} is not a valid index for an existing
 +     *             column
 +     * @throws IllegalArgumentException
 +     *             if {@code destination} is {@link ScrollDestination#MIDDLE}
 +     *             and padding is nonzero; or if the indicated column is frozen;
 +     *             or if {@code destination == null}
 +     */
 +    public void scrollToColumn(final int columnIndex,
 +            final ScrollDestination destination, final int padding)
 +            throws IndexOutOfBoundsException, IllegalArgumentException {
 +        validateScrollDestination(destination, padding);
 +        verifyValidColumnIndex(columnIndex);
 +
 +        if (columnIndex < columnConfiguration.frozenColumns) {
 +            throw new IllegalArgumentException("The given column index "
 +                    + columnIndex + " is frozen.");
 +        }
 +
 +        scroller.scrollToColumn(columnIndex, destination, padding);
 +    }
 +
 +    private void verifyValidColumnIndex(final int columnIndex)
 +            throws IndexOutOfBoundsException {
 +        if (columnIndex < 0
 +                || columnIndex >= columnConfiguration.getColumnCount()) {
 +            throw new IndexOutOfBoundsException("The given column index "
 +                    + columnIndex + " does not exist.");
 +        }
 +    }
 +
 +    /**
 +     * Scrolls the body vertically so that the row at the given index is visible
 +     * and there is at least {@literal padding} pixels to the given scroll
 +     * destination.
 +     * 
 +     * @param rowIndex
 +     *            the index of the logical row to scroll to
 +     * @param destination
 +     *            where the row should be aligned visually after scrolling
 +     * @param padding
 +     *            the number pixels to place between the scrolled-to row and the
 +     *            viewport edge.
 +     * @throws IndexOutOfBoundsException
 +     *             if {@code rowIndex} is not a valid index for an existing row
 +     * @throws IllegalArgumentException
 +     *             if {@code destination} is {@link ScrollDestination#MIDDLE}
 +     *             and padding is nonzero; or if {@code destination == null}
 +     * @see #scrollToRowAndSpacer(int, ScrollDestination, int)
 +     * @see #scrollToSpacer(int, ScrollDestination, int)
 +     */
 +    public void scrollToRow(final int rowIndex,
 +            final ScrollDestination destination, final int padding)
 +            throws IndexOutOfBoundsException, IllegalArgumentException {
 +        Scheduler.get().scheduleFinally(new ScheduledCommand() {
 +            @Override
 +            public void execute() {
 +                validateScrollDestination(destination, padding);
 +                verifyValidRowIndex(rowIndex);
 +                scroller.scrollToRow(rowIndex, destination, padding);
 +            }
 +        });
 +    }
 +
 +    private void verifyValidRowIndex(final int rowIndex) {
 +        if (rowIndex < 0 || rowIndex >= body.getRowCount()) {
 +            throw new IndexOutOfBoundsException("The given row index "
 +                    + rowIndex + " does not exist.");
 +        }
 +    }
 +
 +    /**
 +     * Scrolls the body vertically so that the spacer at the given row index is
 +     * visible and there is at least {@literal padding} pixesl to the given
 +     * scroll destination.
 +     * 
 +     * @since 7.5.0
 +     * @param spacerIndex
 +     *            the row index of the spacer to scroll to
 +     * @param destination
 +     *            where the spacer should be aligned visually after scrolling
 +     * @param padding
 +     *            the number of pixels to place between the scrolled-to spacer
 +     *            and the viewport edge
 +     * @throws IllegalArgumentException
 +     *             if {@code spacerIndex} is not an opened spacer; or if
 +     *             {@code destination} is {@link ScrollDestination#MIDDLE} and
 +     *             padding is nonzero; or if {@code destination == null}
 +     * @see #scrollToRow(int, ScrollDestination, int)
 +     * @see #scrollToRowAndSpacer(int, ScrollDestination, int)
 +     */
 +    public void scrollToSpacer(final int spacerIndex,
 +            ScrollDestination destination, final int padding)
 +            throws IllegalArgumentException {
 +        validateScrollDestination(destination, padding);
 +        body.scrollToSpacer(spacerIndex, destination, padding);
 +    }
 +
 +    /**
 +     * Scrolls vertically to a row and the spacer below it.
 +     * <p>
 +     * If a spacer is not open at that index, this method behaves like
 +     * {@link #scrollToRow(int, ScrollDestination, int)}
 +     * 
 +     * @since 7.5.0
 +     * @param rowIndex
 +     *            the index of the logical row to scroll to. -1 takes the
 +     *            topmost spacer into account as well.
 +     * @param destination
 +     *            where the row should be aligned visually after scrolling
 +     * @param padding
 +     *            the number pixels to place between the scrolled-to row and the
 +     *            viewport edge.
 +     * @see #scrollToRow(int, ScrollDestination, int)
 +     * @see #scrollToSpacer(int, ScrollDestination, int)
 +     * @throws IllegalArgumentException
 +     *             if {@code destination} is {@link ScrollDestination#MIDDLE}
 +     *             and {@code padding} is not zero; or if {@code rowIndex} is
 +     *             not a valid row index, or -1; or if
 +     *             {@code destination == null}; or if {@code rowIndex == -1} and
 +     *             there is no spacer open at that index.
 +     */
 +    public void scrollToRowAndSpacer(final int rowIndex,
 +            final ScrollDestination destination, final int padding)
 +            throws IllegalArgumentException {
 +        Scheduler.get().scheduleFinally(new ScheduledCommand() {
 +            @Override
 +            public void execute() {
 +                validateScrollDestination(destination, padding);
 +                if (rowIndex != -1) {
 +                    verifyValidRowIndex(rowIndex);
 +                }
 +
 +                // row range
 +                final Range rowRange;
 +                if (rowIndex != -1) {
 +                    int rowTop = (int) Math.floor(body.getRowTop(rowIndex));
 +                    int rowHeight = (int) Math.ceil(body.getDefaultRowHeight());
 +                    rowRange = Range.withLength(rowTop, rowHeight);
 +                } else {
 +                    rowRange = Range.withLength(0, 0);
 +                }
 +
 +                // get spacer
 +                final SpacerContainer.SpacerImpl spacer = body.spacerContainer
 +                        .getSpacer(rowIndex);
 +
 +                if (rowIndex == -1 && spacer == null) {
 +                    throw new IllegalArgumentException(
 +                            "Cannot scroll to row index "
 +                                    + "-1, as there is no spacer open at that index.");
 +                }
 +
 +                // make into target range
 +                final Range targetRange;
 +                if (spacer != null) {
 +                    final int spacerTop = (int) Math.floor(spacer.getTop());
 +                    final int spacerHeight = (int) Math.ceil(spacer.getHeight());
 +                    Range spacerRange = Range.withLength(spacerTop,
 +                            spacerHeight);
 +
 +                    targetRange = rowRange.combineWith(spacerRange);
 +                } else {
 +                    targetRange = rowRange;
 +                }
 +
 +                // get params
 +                int targetStart = targetRange.getStart();
 +                int targetEnd = targetRange.getEnd();
 +                double viewportStart = getScrollTop();
 +                double viewportEnd = viewportStart + body.getHeightOfSection();
 +
 +                double scrollPos = getScrollPos(destination, targetStart,
 +                        targetEnd, viewportStart, viewportEnd, padding);
 +
 +                setScrollTop(scrollPos);
 +            }
 +        });
 +    }
 +
 +    private static void validateScrollDestination(
 +            final ScrollDestination destination, final int padding) {
 +        if (destination == null) {
 +            throw new IllegalArgumentException("Destination cannot be null");
 +        }
 +
 +        if (destination == ScrollDestination.MIDDLE && padding != 0) {
 +            throw new IllegalArgumentException(
 +                    "You cannot have a padding with a MIDDLE destination");
 +        }
 +    }
 +
 +    /**
 +     * Recalculates the dimensions for all elements that require manual
 +     * calculations. Also updates the dimension caches.
 +     * <p>
 +     * <em>Note:</em> This method has the <strong>side-effect</strong>
 +     * automatically makes sure that an appropriate amount of escalator rows are
 +     * present. So, if the body area grows, more <strong>escalator rows might be
 +     * inserted</strong>. Conversely, if the body area shrinks,
 +     * <strong>escalator rows might be removed</strong>.
 +     */
 +    private void recalculateElementSizes() {
 +        if (!isAttached()) {
 +            return;
 +        }
 +
 +        Profiler.enter("Escalator.recalculateElementSizes");
 +        widthOfEscalator = Math.max(0, WidgetUtil
 +                .getRequiredWidthBoundingClientRectDouble(getElement()));
 +        heightOfEscalator = Math.max(0, WidgetUtil
 +                .getRequiredHeightBoundingClientRectDouble(getElement()));
 +
 +        header.recalculateSectionHeight();
 +        body.recalculateSectionHeight();
 +        footer.recalculateSectionHeight();
 +
 +        scroller.recalculateScrollbarsForVirtualViewport();
 +        body.verifyEscalatorCount();
 +        body.reapplySpacerWidths();
 +        Profiler.leave("Escalator.recalculateElementSizes");
 +    }
 +
 +    /**
 +     * Snap deltas of x and y to the major four axes (up, down, left, right)
 +     * with a threshold of a number of degrees from those axes.
 +     * 
 +     * @param deltaX
 +     *            the delta in the x axis
 +     * @param deltaY
 +     *            the delta in the y axis
 +     * @param thresholdRatio
 +     *            the threshold in ratio (0..1) between x and y for when to snap
 +     * @return a two-element array: <code>[snappedX, snappedY]</code>
 +     */
 +    private static double[] snapDeltas(final double deltaX,
 +            final double deltaY, final double thresholdRatio) {
 +
 +        final double[] array = new double[2];
 +        if (deltaX != 0 && deltaY != 0) {
 +            final double aDeltaX = Math.abs(deltaX);
 +            final double aDeltaY = Math.abs(deltaY);
 +            final double yRatio = aDeltaY / aDeltaX;
 +            final double xRatio = aDeltaX / aDeltaY;
 +
 +            array[0] = (xRatio < thresholdRatio) ? 0 : deltaX;
 +            array[1] = (yRatio < thresholdRatio) ? 0 : deltaY;
 +        } else {
 +            array[0] = deltaX;
 +            array[1] = deltaY;
 +        }
 +
 +        return array;
 +    }
 +
 +    /**
 +     * Adds an event handler that gets notified when the range of visible rows
 +     * changes e.g. because of scrolling, row resizing or spacers
 +     * appearing/disappearing.
 +     * 
 +     * @param rowVisibilityChangeHandler
 +     *            the event handler
 +     * @return a handler registration for the added handler
 +     */
 +    public HandlerRegistration addRowVisibilityChangeHandler(
 +            RowVisibilityChangeHandler rowVisibilityChangeHandler) {
 +        return addHandler(rowVisibilityChangeHandler,
 +                RowVisibilityChangeEvent.TYPE);
 +    }
 +
 +    private void fireRowVisibilityChangeEvent() {
 +        if (!body.visualRowOrder.isEmpty()) {
 +            int visibleRangeStart = body.getLogicalRowIndex(body.visualRowOrder
 +                    .getFirst());
 +            int visibleRangeEnd = body.getLogicalRowIndex(body.visualRowOrder
 +                    .getLast()) + 1;
 +
 +            int visibleRowCount = visibleRangeEnd - visibleRangeStart;
 +            fireEvent(new RowVisibilityChangeEvent(visibleRangeStart,
 +                    visibleRowCount));
 +        } else {
 +            fireEvent(new RowVisibilityChangeEvent(0, 0));
 +        }
 +    }
 +
 +    /**
 +     * Gets the logical index range of currently visible rows.
 +     * 
 +     * @return logical index range of visible rows
 +     */
 +    public Range getVisibleRowRange() {
 +        if (!body.visualRowOrder.isEmpty()) {
 +            return Range.withLength(body.getTopRowLogicalIndex(),
 +                    body.visualRowOrder.size());
 +        } else {
 +            return Range.withLength(0, 0);
 +        }
 +    }
 +
 +    /**
 +     * Returns the widget from a cell node or <code>null</code> if there is no
 +     * widget in the cell
 +     * 
 +     * @param cellNode
 +     *            The cell node
 +     */
 +    static Widget getWidgetFromCell(Node cellNode) {
 +        Node possibleWidgetNode = cellNode.getFirstChild();
 +        if (possibleWidgetNode != null
 +                && possibleWidgetNode.getNodeType() == Node.ELEMENT_NODE) {
 +            @SuppressWarnings("deprecation")
 +            com.google.gwt.user.client.Element castElement = (com.google.gwt.user.client.Element) possibleWidgetNode
 +                    .cast();
 +            Widget w = WidgetUtil.findWidget(castElement, null);
 +
 +            // Ensure findWidget did not traverse past the cell element in the
 +            // DOM hierarchy
 +            if (cellNode.isOrHasChild(w.getElement())) {
 +                return w;
 +            }
 +        }
 +        return null;
 +    }
 +
 +    @Override
 +    public void setStylePrimaryName(String style) {
 +        super.setStylePrimaryName(style);
 +
 +        verticalScrollbar.setStylePrimaryName(style);
 +        horizontalScrollbar.setStylePrimaryName(style);
 +
 +        UIObject.setStylePrimaryName(tableWrapper, style + "-tablewrapper");
 +        UIObject.setStylePrimaryName(headerDeco, style + "-header-deco");
 +        UIObject.setStylePrimaryName(footerDeco, style + "-footer-deco");
 +        UIObject.setStylePrimaryName(horizontalScrollbarDeco, style
 +                + "-horizontal-scrollbar-deco");
 +        UIObject.setStylePrimaryName(spacerDecoContainer, style
 +                + "-spacer-deco-container");
 +
 +        header.setStylePrimaryName(style);
 +        body.setStylePrimaryName(style);
 +        footer.setStylePrimaryName(style);
 +    }
 +
 +    /**
 +     * Sets the number of rows that should be visible in Escalator's body, while
 +     * {@link #getHeightMode()} is {@link HeightMode#ROW}.
 +     * <p>
 +     * If Escalator is currently not in {@link HeightMode#ROW}, the given value
 +     * is remembered, and applied once the mode is applied.
 +     * 
 +     * @param rows
 +     *            the number of rows that should be visible in Escalator's body
 +     * @throws IllegalArgumentException
 +     *             if {@code rows} is &leq; 0,
 +     *             {@link Double#isInifinite(double) infinite} or
 +     *             {@link Double#isNaN(double) NaN}.
 +     * @see #setHeightMode(HeightMode)
 +     */
 +    public void setHeightByRows(double rows) throws IllegalArgumentException {
 +        if (rows <= 0) {
 +            throw new IllegalArgumentException(
 +                    "The number of rows must be a positive number.");
 +        } else if (Double.isInfinite(rows)) {
 +            throw new IllegalArgumentException(
 +                    "The number of rows must be finite.");
 +        } else if (Double.isNaN(rows)) {
 +            throw new IllegalArgumentException("The number must not be NaN.");
 +        }
 +
 +        heightByRows = rows;
 +        applyHeightByRows();
 +    }
 +
 +    /**
 +     * Gets the amount of rows in Escalator's body that are shown, while
 +     * {@link #getHeightMode()} is {@link HeightMode#ROW}.
 +     * <p>
 +     * By default, it is 10.
 +     * 
 +     * @return the amount of rows that are being shown in Escalator's body
 +     * @see #setHeightByRows(double)
 +     */
 +    public double getHeightByRows() {
 +        return heightByRows;
 +    }
 +
 +    /**
 +     * Reapplies the row-based height of the Grid, if Grid currently should
 +     * define its height that way.
 +     */
 +    private void applyHeightByRows() {
 +        if (heightMode != HeightMode.ROW) {
 +            return;
 +        }
 +
 +        double headerHeight = header.getHeightOfSection();
 +        double footerHeight = footer.getHeightOfSection();
 +        double bodyHeight = body.getDefaultRowHeight() * heightByRows;
 +        double scrollbar = horizontalScrollbar.showsScrollHandle() ? horizontalScrollbar
 +                .getScrollbarThickness() : 0;
 +
 +        double totalHeight = headerHeight + bodyHeight + scrollbar
 +                + footerHeight;
 +        setHeightInternal(totalHeight + "px");
 +    }
 +
 +    /**
 +     * Defines the mode in which the Escalator widget's height is calculated.
 +     * <p>
 +     * If {@link HeightMode#CSS} is given, Escalator will respect the values
 +     * given via {@link #setHeight(String)}, and behave as a traditional Widget.
 +     * <p>
 +     * If {@link HeightMode#ROW} is given, Escalator will make sure that the
 +     * {@link #getBody() body} will display as many rows as
 +     * {@link #getHeightByRows()} defines. <em>Note:</em> If headers/footers are
 +     * inserted or removed, the widget will resize itself to still display the
 +     * required amount of rows in its body. It also takes the horizontal
 +     * scrollbar into account.
 +     * 
 +     * @param heightMode
 +     *            the mode in to which Escalator should be set
 +     */
 +    public void setHeightMode(HeightMode heightMode) {
 +        /*
 +         * This method is a workaround for the fact that Vaadin re-applies
 +         * widget dimensions (height/width) on each state change event. The
 +         * original design was to have setHeight an setHeightByRow be equals,
 +         * and whichever was called the latest was considered in effect.
 +         * 
 +         * But, because of Vaadin always calling setHeight on the widget, this
 +         * approach doesn't work.
 +         */
 +
 +        if (heightMode != this.heightMode) {
 +            this.heightMode = heightMode;
 +
 +            switch (this.heightMode) {
 +            case CSS:
 +                setHeight(heightByCss);
 +                break;
 +            case ROW:
 +                setHeightByRows(heightByRows);
 +                break;
 +            default:
 +                throw new IllegalStateException("Unimplemented feature "
 +                        + "- unknown HeightMode: " + this.heightMode);
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Returns the current {@link HeightMode} the Escalator is in.
 +     * <p>
 +     * Defaults to {@link HeightMode#CSS}.
 +     * 
 +     * @return the current HeightMode
 +     */
 +    public HeightMode getHeightMode() {
 +        return heightMode;
 +    }
 +
 +    /**
 +     * Returns the {@link RowContainer} which contains the element.
 +     * 
 +     * @param element
 +     *            the element to check for
 +     * @return the container the element is in or <code>null</code> if element
 +     *         is not present in any container.
 +     */
 +    public RowContainer findRowContainer(Element element) {
 +        if (getHeader().getElement() != element
 +                && getHeader().getElement().isOrHasChild(element)) {
 +            return getHeader();
 +        } else if (getBody().getElement() != element
 +                && getBody().getElement().isOrHasChild(element)) {
 +            return getBody();
 +        } else if (getFooter().getElement() != element
 +                && getFooter().getElement().isOrHasChild(element)) {
 +            return getFooter();
 +        }
 +        return null;
 +    }
 +
 +    /**
 +     * Sets whether a scroll direction is locked or not.
 +     * <p>
 +     * If a direction is locked, the escalator will refuse to scroll in that
 +     * direction.
 +     * 
 +     * @param direction
 +     *            the orientation of the scroll to set the lock status
 +     * @param locked
 +     *            <code>true</code> to lock, <code>false</code> to unlock
 +     */
 +    public void setScrollLocked(ScrollbarBundle.Direction direction,
 +            boolean locked) {
 +        switch (direction) {
 +        case HORIZONTAL:
 +            horizontalScrollbar.setLocked(locked);
 +            break;
 +        case VERTICAL:
 +            verticalScrollbar.setLocked(locked);
 +            break;
 +        default:
 +            throw new UnsupportedOperationException("Unexpected value: "
 +                    + direction);
 +        }
 +    }
 +
 +    /**
 +     * Checks whether or not an direction is locked for scrolling.
 +     * 
 +     * @param direction
 +     *            the direction of the scroll of which to check the lock status
 +     * @return <code>true</code> iff the direction is locked
 +     */
 +    public boolean isScrollLocked(ScrollbarBundle.Direction direction) {
 +        switch (direction) {
 +        case HORIZONTAL:
 +            return horizontalScrollbar.isLocked();
 +        case VERTICAL:
 +            return verticalScrollbar.isLocked();
 +        default:
 +            throw new UnsupportedOperationException("Unexpected value: "
 +                    + direction);
 +        }
 +    }
 +
 +    /**
 +     * Adds a scroll handler to this escalator
 +     * 
 +     * @param handler
 +     *            the scroll handler to add
 +     * @return a handler registration for the registered scroll handler
 +     */
 +    public HandlerRegistration addScrollHandler(ScrollHandler handler) {
 +        return addHandler(handler, ScrollEvent.TYPE);
 +    }
 +
 +    @Override
 +    public boolean isWorkPending() {
 +        return body.domSorter.waiting || verticalScrollbar.isWorkPending()
 +                || horizontalScrollbar.isWorkPending() || layoutIsScheduled;
 +    }
 +
 +    @Override
 +    public void onResize() {
 +        if (isAttached() && !layoutIsScheduled) {
 +            layoutIsScheduled = true;
 +            Scheduler.get().scheduleFinally(layoutCommand);
 +        }
 +    }
 +
 +    /**
 +     * Gets the maximum number of body rows that can be visible on the screen at
 +     * once.
 +     * 
 +     * @return the maximum capacity
 +     */
 +    public int getMaxVisibleRowCount() {
 +        return body.getMaxEscalatorRowCapacity();
 +    }
 +
 +    /**
 +     * Gets the escalator's inner width. This is the entire width in pixels,
 +     * without the vertical scrollbar.
 +     * 
 +     * @return escalator's inner width
 +     */
 +    public double getInnerWidth() {
 +        return WidgetUtil
 +                .getRequiredWidthBoundingClientRectDouble(tableWrapper);
 +    }
 +
 +    /**
 +     * Resets all cached pixel sizes and reads new values from the DOM. This
 +     * methods should be used e.g. when styles affecting the dimensions of
 +     * elements in this escalator have been changed.
 +     */
 +    public void resetSizesFromDom() {
 +        header.autodetectRowHeightNow();
 +        body.autodetectRowHeightNow();
 +        footer.autodetectRowHeightNow();
 +
 +        for (int i = 0; i < columnConfiguration.getColumnCount(); i++) {
 +            columnConfiguration.setColumnWidth(i,
 +                    columnConfiguration.getColumnWidth(i));
 +        }
 +    }
 +
 +    private Range getViewportPixels() {
 +        int from = (int) Math.floor(verticalScrollbar.getScrollPos());
 +        int to = (int) body.getHeightOfSection();
 +        return Range.withLength(from, to);
 +    }
 +
 +    @Override
 +    @SuppressWarnings("deprecation")
 +    public com.google.gwt.user.client.Element getSubPartElement(String subPart) {
 +        SubPartArguments args = SubPartArguments.create(subPart);
 +
 +        Element tableStructureElement = getSubPartElementTableStructure(args);
 +        if (tableStructureElement != null) {
 +            return DOM.asOld(tableStructureElement);
 +        }
 +
 +        Element spacerElement = getSubPartElementSpacer(args);
 +        if (spacerElement != null) {
 +            return DOM.asOld(spacerElement);
 +        }
 +
 +        return null;
 +    }
 +
 +    private Element getSubPartElementTableStructure(SubPartArguments args) {
 +
 +        String type = args.getType();
 +        int[] indices = args.getIndices();
 +
 +        // Get correct RowContainer for type from Escalator
 +        RowContainer container = null;
 +        if (type.equalsIgnoreCase("header")) {
 +            container = getHeader();
 +        } else if (type.equalsIgnoreCase("cell")) {
 +            // If wanted row is not visible, we need to scroll there.
 +            Range visibleRowRange = getVisibleRowRange();
 +            if (indices.length > 0 && !visibleRowRange.contains(indices[0])) {
 +                try {
 +                    scrollToRow(indices[0], ScrollDestination.ANY, 0);
 +                } catch (IllegalArgumentException e) {
 +                    getLogger().log(Level.SEVERE, e.getMessage());
 +                }
 +                // Scrolling causes a lazy loading event. No element can
 +                // currently be retrieved.
 +                return null;
 +            }
 +            container = getBody();
 +        } else if (type.equalsIgnoreCase("footer")) {
 +            container = getFooter();
 +        }
 +
 +        if (null != container) {
 +            if (indices.length == 0) {
 +                // No indexing. Just return the wanted container element
 +                return container.getElement();
 +            } else {
 +                try {
 +                    return getSubPart(container, indices);
 +                } catch (Exception e) {
 +                    getLogger().log(Level.SEVERE, e.getMessage());
 +                }
 +            }
 +        }
 +        return null;
 +    }
 +
 +    private Element getSubPart(RowContainer container, int[] indices) {
 +        Element targetElement = container.getRowElement(indices[0]);
 +
 +        // Scroll wanted column to view if able
 +        if (indices.length > 1 && targetElement != null) {
 +            if (getColumnConfiguration().getFrozenColumnCount() <= indices[1]) {
 +                scrollToColumn(indices[1], ScrollDestination.ANY, 0);
 +            }
 +
 +            targetElement = getCellFromRow(TableRowElement.as(targetElement),
 +                    indices[1]);
 +
 +            for (int i = 2; i < indices.length && targetElement != null; ++i) {
 +                targetElement = (Element) targetElement.getChild(indices[i]);
 +            }
 +        }
 +
 +        return targetElement;
 +    }
 +
 +    private static Element getCellFromRow(TableRowElement rowElement, int index) {
 +        int childCount = rowElement.getCells().getLength();
 +        if (index < 0 || index >= childCount) {
 +            return null;
 +        }
 +
 +        TableCellElement currentCell = null;
 +        boolean indexInColspan = false;
 +        int i = 0;
 +
 +        while (!indexInColspan) {
 +            currentCell = rowElement.getCells().getItem(i);
 +
 +            // Calculate if this is the cell we are looking for
 +            int colSpan = currentCell.getColSpan();
 +            indexInColspan = index < colSpan + i;
 +
 +            // Increment by colspan to skip over hidden cells
 +            i += colSpan;
 +        }
 +        return currentCell;
 +    }
 +
 +    private Element getSubPartElementSpacer(SubPartArguments args) {
 +        if ("spacer".equals(args.getType()) && args.getIndicesLength() == 1) {
 +            return body.spacerContainer.getSubPartElement(args.getIndex(0));
 +        } else {
 +            return null;
 +        }
 +    }
 +
 +    @Override
 +    @SuppressWarnings("deprecation")
 +    public String getSubPartName(com.google.gwt.user.client.Element subElement) {
 +
 +        /*
 +         * The spacer check needs to be before table structure check, because
 +         * (for now) the table structure will take spacer elements into account
 +         * as well, when it shouldn't.
 +         */
 +
 +        String spacer = getSubPartNameSpacer(subElement);
 +        if (spacer != null) {
 +            return spacer;
 +        }
 +
 +        String tableStructure = getSubPartNameTableStructure(subElement);
 +        if (tableStructure != null) {
 +            return tableStructure;
 +        }
 +
 +        return null;
 +    }
 +
 +    private String getSubPartNameTableStructure(Element subElement) {
 +
 +        List<RowContainer> containers = Arrays.asList(getHeader(), getBody(),
 +                getFooter());
 +        List<String> containerType = Arrays.asList("header", "cell", "footer");
 +
 +        for (int i = 0; i < containers.size(); ++i) {
 +            RowContainer container = containers.get(i);
 +            boolean containerRow = (subElement.getTagName().equalsIgnoreCase(
 +                    "tr") && subElement.getParentElement() == container
 +                    .getElement());
 +            if (containerRow) {
 +                /*
 +                 * Wanted SubPart is row that is a child of containers root to
 +                 * get indices, we use a cell that is a child of this row
 +                 */
 +                subElement = subElement.getFirstChildElement();
 +            }
 +
 +            Cell cell = container.getCell(subElement);
 +            if (cell != null) {
 +                // Skip the column index if subElement was a child of root
 +                return containerType.get(i) + "[" + cell.getRow()
 +                        + (containerRow ? "]" : "][" + cell.getColumn() + "]");
 +            }
 +        }
 +        return null;
 +    }
 +
 +    private String getSubPartNameSpacer(Element subElement) {
 +        return body.spacerContainer.getSubPartName(subElement);
 +    }
 +
 +    private void logWarning(String message) {
 +        getLogger().warning(message);
 +    }
 +
 +    /**
 +     * This is an internal method for calculating minimum width for Column
 +     * resize.
 +     * 
 +     * @return minimum width for column
 +     */
 +    double getMinCellWidth(int colIndex) {
 +        return columnConfiguration.getMinCellWidth(colIndex);
 +    }
 +}
index c4e3491992b22945367f17b37676c2e80ed9ff09,0000000000000000000000000000000000000000..cf9456d8a85f145f3f81891ae4cd501d8684ae5e
mode 100644,000000..100644
--- /dev/null
@@@ -1,8877 -1,0 +1,8881 @@@
-                 refreshRow(rowWithFocus);
 +/*
 + * Copyright 2000-2014 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.widgets;
 +
 +import java.util.ArrayList;
 +import java.util.Arrays;
 +import java.util.Collection;
 +import java.util.Collections;
 +import java.util.HashMap;
 +import java.util.HashSet;
 +import java.util.Iterator;
 +import java.util.LinkedHashMap;
 +import java.util.List;
 +import java.util.Map;
 +import java.util.Map.Entry;
 +import java.util.Set;
 +import java.util.TreeMap;
 +import java.util.logging.Level;
 +import java.util.logging.Logger;
 +
 +import com.google.gwt.core.client.Scheduler;
 +import com.google.gwt.core.client.Scheduler.ScheduledCommand;
 +import com.google.gwt.core.shared.GWT;
 +import com.google.gwt.dom.client.BrowserEvents;
 +import com.google.gwt.dom.client.DivElement;
 +import com.google.gwt.dom.client.Document;
 +import com.google.gwt.dom.client.Element;
 +import com.google.gwt.dom.client.EventTarget;
 +import com.google.gwt.dom.client.NativeEvent;
 +import com.google.gwt.dom.client.Node;
 +import com.google.gwt.dom.client.Style;
 +import com.google.gwt.dom.client.Style.Display;
 +import com.google.gwt.dom.client.Style.Unit;
 +import com.google.gwt.dom.client.TableCellElement;
 +import com.google.gwt.dom.client.TableRowElement;
 +import com.google.gwt.dom.client.TableSectionElement;
 +import com.google.gwt.dom.client.Touch;
 +import com.google.gwt.event.dom.client.ClickEvent;
 +import com.google.gwt.event.dom.client.ClickHandler;
 +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.KeyEvent;
 +import com.google.gwt.event.dom.client.MouseEvent;
 +import com.google.gwt.event.logical.shared.CloseEvent;
 +import com.google.gwt.event.logical.shared.CloseHandler;
 +import com.google.gwt.event.logical.shared.ValueChangeEvent;
 +import com.google.gwt.event.logical.shared.ValueChangeHandler;
 +import com.google.gwt.event.shared.HandlerRegistration;
 +import com.google.gwt.touch.client.Point;
 +import com.google.gwt.user.client.DOM;
 +import com.google.gwt.user.client.Event;
 +import com.google.gwt.user.client.Event.NativePreviewEvent;
 +import com.google.gwt.user.client.Event.NativePreviewHandler;
 +import com.google.gwt.user.client.Timer;
 +import com.google.gwt.user.client.ui.Button;
 +import com.google.gwt.user.client.ui.CheckBox;
 +import com.google.gwt.user.client.ui.Composite;
 +import com.google.gwt.user.client.ui.FlowPanel;
 +import com.google.gwt.user.client.ui.HasEnabled;
 +import com.google.gwt.user.client.ui.HasWidgets;
 +import com.google.gwt.user.client.ui.MenuBar;
 +import com.google.gwt.user.client.ui.MenuItem;
 +import com.google.gwt.user.client.ui.PopupPanel;
 +import com.google.gwt.user.client.ui.ResizeComposite;
 +import com.google.gwt.user.client.ui.Widget;
 +import com.vaadin.client.BrowserInfo;
 +import com.vaadin.client.DeferredWorker;
 +import com.vaadin.client.Focusable;
 +import com.vaadin.client.WidgetUtil;
 +import com.vaadin.client.data.DataChangeHandler;
 +import com.vaadin.client.data.DataSource;
 +import com.vaadin.client.data.DataSource.RowHandle;
 +import com.vaadin.client.renderers.ComplexRenderer;
 +import com.vaadin.client.renderers.Renderer;
 +import com.vaadin.client.renderers.WidgetRenderer;
 +import com.vaadin.client.ui.FocusUtil;
 +import com.vaadin.client.ui.SubPartAware;
 +import com.vaadin.client.ui.dd.DragAndDropHandler;
 +import com.vaadin.client.ui.dd.DragAndDropHandler.DragAndDropCallback;
 +import com.vaadin.client.ui.dd.DragHandle;
 +import com.vaadin.client.ui.dd.DragHandle.DragHandleCallback;
 +import com.vaadin.client.widget.escalator.Cell;
 +import com.vaadin.client.widget.escalator.ColumnConfiguration;
 +import com.vaadin.client.widget.escalator.EscalatorUpdater;
 +import com.vaadin.client.widget.escalator.FlyweightCell;
 +import com.vaadin.client.widget.escalator.Row;
 +import com.vaadin.client.widget.escalator.RowContainer;
 +import com.vaadin.client.widget.escalator.RowVisibilityChangeEvent;
 +import com.vaadin.client.widget.escalator.RowVisibilityChangeHandler;
 +import com.vaadin.client.widget.escalator.ScrollbarBundle.Direction;
 +import com.vaadin.client.widget.escalator.Spacer;
 +import com.vaadin.client.widget.escalator.SpacerUpdater;
 +import com.vaadin.client.widget.grid.AutoScroller;
 +import com.vaadin.client.widget.grid.AutoScroller.AutoScrollerCallback;
 +import com.vaadin.client.widget.grid.AutoScroller.ScrollAxis;
 +import com.vaadin.client.widget.grid.CellReference;
 +import com.vaadin.client.widget.grid.CellStyleGenerator;
 +import com.vaadin.client.widget.grid.DataAvailableEvent;
 +import com.vaadin.client.widget.grid.DataAvailableHandler;
 +import com.vaadin.client.widget.grid.DefaultEditorEventHandler;
 +import com.vaadin.client.widget.grid.DetailsGenerator;
 +import com.vaadin.client.widget.grid.EditorHandler;
 +import com.vaadin.client.widget.grid.EditorHandler.EditorRequest;
 +import com.vaadin.client.widget.grid.EventCellReference;
 +import com.vaadin.client.widget.grid.HeightAwareDetailsGenerator;
 +import com.vaadin.client.widget.grid.RendererCellReference;
 +import com.vaadin.client.widget.grid.RowReference;
 +import com.vaadin.client.widget.grid.RowStyleGenerator;
 +import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler;
 +import com.vaadin.client.widget.grid.events.AbstractGridMouseEventHandler;
 +import com.vaadin.client.widget.grid.events.BodyClickHandler;
 +import com.vaadin.client.widget.grid.events.BodyDoubleClickHandler;
 +import com.vaadin.client.widget.grid.events.BodyKeyDownHandler;
 +import com.vaadin.client.widget.grid.events.BodyKeyPressHandler;
 +import com.vaadin.client.widget.grid.events.BodyKeyUpHandler;
 +import com.vaadin.client.widget.grid.events.ColumnReorderEvent;
 +import com.vaadin.client.widget.grid.events.ColumnReorderHandler;
 +import com.vaadin.client.widget.grid.events.ColumnResizeEvent;
 +import com.vaadin.client.widget.grid.events.ColumnResizeHandler;
 +import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeEvent;
 +import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeHandler;
 +import com.vaadin.client.widget.grid.events.FooterClickHandler;
 +import com.vaadin.client.widget.grid.events.FooterDoubleClickHandler;
 +import com.vaadin.client.widget.grid.events.FooterKeyDownHandler;
 +import com.vaadin.client.widget.grid.events.FooterKeyPressHandler;
 +import com.vaadin.client.widget.grid.events.FooterKeyUpHandler;
 +import com.vaadin.client.widget.grid.events.GridClickEvent;
 +import com.vaadin.client.widget.grid.events.GridDoubleClickEvent;
 +import com.vaadin.client.widget.grid.events.GridKeyDownEvent;
 +import com.vaadin.client.widget.grid.events.GridKeyPressEvent;
 +import com.vaadin.client.widget.grid.events.GridKeyUpEvent;
 +import com.vaadin.client.widget.grid.events.HeaderClickHandler;
 +import com.vaadin.client.widget.grid.events.HeaderDoubleClickHandler;
 +import com.vaadin.client.widget.grid.events.HeaderKeyDownHandler;
 +import com.vaadin.client.widget.grid.events.HeaderKeyPressHandler;
 +import com.vaadin.client.widget.grid.events.HeaderKeyUpHandler;
 +import com.vaadin.client.widget.grid.events.ScrollEvent;
 +import com.vaadin.client.widget.grid.events.ScrollHandler;
 +import com.vaadin.client.widget.grid.events.SelectAllEvent;
 +import com.vaadin.client.widget.grid.events.SelectAllHandler;
 +import com.vaadin.client.widget.grid.selection.HasSelectionHandlers;
 +import com.vaadin.client.widget.grid.selection.MultiSelectionRenderer;
 +import com.vaadin.client.widget.grid.selection.SelectionEvent;
 +import com.vaadin.client.widget.grid.selection.SelectionHandler;
 +import com.vaadin.client.widget.grid.selection.SelectionModel;
 +import com.vaadin.client.widget.grid.selection.SelectionModel.Multi;
 +import com.vaadin.client.widget.grid.selection.SelectionModel.Single;
 +import com.vaadin.client.widget.grid.selection.SelectionModelMulti;
 +import com.vaadin.client.widget.grid.selection.SelectionModelNone;
 +import com.vaadin.client.widget.grid.selection.SelectionModelSingle;
 +import com.vaadin.client.widget.grid.sort.Sort;
 +import com.vaadin.client.widget.grid.sort.SortEvent;
 +import com.vaadin.client.widget.grid.sort.SortHandler;
 +import com.vaadin.client.widget.grid.sort.SortOrder;
 +import com.vaadin.client.widgets.Escalator.AbstractRowContainer;
 +import com.vaadin.client.widgets.Escalator.SubPartArguments;
 +import com.vaadin.client.widgets.Grid.Editor.State;
 +import com.vaadin.client.widgets.Grid.StaticSection.StaticCell;
 +import com.vaadin.client.widgets.Grid.StaticSection.StaticRow;
 +import com.vaadin.shared.data.sort.SortDirection;
 +import com.vaadin.shared.ui.grid.GridConstants;
 +import com.vaadin.shared.ui.grid.GridConstants.Section;
 +import com.vaadin.shared.ui.grid.GridStaticCellType;
 +import com.vaadin.shared.ui.grid.HeightMode;
 +import com.vaadin.shared.ui.grid.Range;
 +import com.vaadin.shared.ui.grid.ScrollDestination;
 +import com.vaadin.shared.util.SharedUtil;
 +
 +/**
 + * A data grid view that supports columns and lazy loading of data rows from a
 + * data source.
 + * 
 + * <h1>Columns</h1>
 + * <p>
 + * Each column in Grid is represented by a {@link Column}. Each
 + * {@code GridColumn} has a custom implementation for
 + * {@link Column#getValue(Object)} that gets the row object as an argument, and
 + * returns the value for that particular column, extracted from the row object.
 + * <p>
 + * Each column also has a Renderer. Its function is to take the value that is
 + * given by the {@code GridColumn} and display it to the user. A simple column
 + * might have a {@link com.vaadin.client.renderers.TextRenderer TextRenderer}
 + * that simply takes in a {@code String} and displays it as the cell's content.
 + * A more complex renderer might be
 + * {@link com.vaadin.client.renderers.ProgressBarRenderer ProgressBarRenderer}
 + * that takes in a floating point number, and displays a progress bar instead,
 + * based on the given number.
 + * <p>
 + * <em>See:</em> {@link #addColumn(Column)}, {@link #addColumn(Column, int)} and
 + * {@link #addColumns(Column...)}. <em>Also</em>
 + * {@link Column#setRenderer(Renderer)}.
 + * 
 + * <h1>Data Sources</h1>
 + * <p>
 + * Grid gets its data from a {@link DataSource}, providing row objects to Grid
 + * from a user-defined endpoint. It can be either a local in-memory data source
 + * (e.g. {@link com.vaadin.client.widget.grid.datasources.ListDataSource
 + * ListDataSource}) or even a remote one, retrieving data from e.g. a REST API
 + * (see {@link com.vaadin.client.data.AbstractRemoteDataSource
 + * AbstractRemoteDataSource}).
 + * 
 + * 
 + * @param <T>
 + *            The row type of the grid. The row type is the POJO type from where
 + *            the data is retrieved into the column cells.
 + * @since 7.4
 + * @author Vaadin Ltd
 + */
 +public class Grid<T> extends ResizeComposite implements
 +        HasSelectionHandlers<T>, SubPartAware, DeferredWorker, Focusable,
 +        com.google.gwt.user.client.ui.Focusable, HasWidgets, HasEnabled {
 +
 +    private static final String STYLE_NAME = "v-grid";
 +
 +    private static final String SELECT_ALL_CHECKBOX_CLASSNAME = "-select-all-checkbox";
 +
 +    /**
 +     * Abstract base class for Grid header and footer sections.
 +     * 
 +     * @since 7.5.0
 +     * 
 +     * @param <ROWTYPE>
 +     *            the type of the rows in the section
 +     */
 +    public abstract static class StaticSection<ROWTYPE extends StaticSection.StaticRow<?>> {
 +
 +        /**
 +         * A header or footer cell. Has a simple textual caption.
 +         * 
 +         */
 +        public static class StaticCell {
 +
 +            private Object content = null;
 +
 +            private int colspan = 1;
 +
 +            private StaticSection<?> section;
 +
 +            private GridStaticCellType type = GridStaticCellType.TEXT;
 +
 +            private String styleName = null;
 +
 +            /**
 +             * Sets the text displayed in this cell.
 +             * 
 +             * @param text
 +             *            a plain text caption
 +             */
 +            public void setText(String text) {
 +                this.content = text;
 +                this.type = GridStaticCellType.TEXT;
 +                section.requestSectionRefresh();
 +            }
 +
 +            /**
 +             * Returns the text displayed in this cell.
 +             * 
 +             * @return the plain text caption
 +             */
 +            public String getText() {
 +                if (type != GridStaticCellType.TEXT) {
 +                    throw new IllegalStateException(
 +                            "Cannot fetch Text from a cell with type " + type);
 +                }
 +                return (String) content;
 +            }
 +
 +            protected StaticSection<?> getSection() {
 +                assert section != null;
 +                return section;
 +            }
 +
 +            protected void setSection(StaticSection<?> section) {
 +                this.section = section;
 +            }
 +
 +            /**
 +             * Returns the amount of columns the cell spans. By default is 1.
 +             * 
 +             * @return The amount of columns the cell spans.
 +             */
 +            public int getColspan() {
 +                return colspan;
 +            }
 +
 +            /**
 +             * Sets the amount of columns the cell spans. Must be more or equal
 +             * to 1. By default is 1.
 +             * 
 +             * @param colspan
 +             *            the colspan to set
 +             */
 +            public void setColspan(int colspan) {
 +                if (colspan < 1) {
 +                    throw new IllegalArgumentException(
 +                            "Colspan cannot be less than 1");
 +                }
 +
 +                this.colspan = colspan;
 +                section.requestSectionRefresh();
 +            }
 +
 +            /**
 +             * Returns the html inside the cell.
 +             * 
 +             * @throws IllegalStateException
 +             *             if trying to retrive HTML from a cell with a type
 +             *             other than {@link GridStaticCellType#HTML}.
 +             * @return the html content of the cell.
 +             */
 +            public String getHtml() {
 +                if (type != GridStaticCellType.HTML) {
 +                    throw new IllegalStateException(
 +                            "Cannot fetch HTML from a cell with type " + type);
 +                }
 +                return (String) content;
 +            }
 +
 +            /**
 +             * Sets the content of the cell to the provided html. All previous
 +             * content is discarded and the cell type is set to
 +             * {@link GridStaticCellType#HTML}.
 +             * 
 +             * @param html
 +             *            The html content of the cell
 +             */
 +            public void setHtml(String html) {
 +                this.content = html;
 +                this.type = GridStaticCellType.HTML;
 +                section.requestSectionRefresh();
 +            }
 +
 +            /**
 +             * Returns the widget in the cell.
 +             * 
 +             * @throws IllegalStateException
 +             *             if the cell is not {@link GridStaticCellType#WIDGET}
 +             * 
 +             * @return the widget in the cell
 +             */
 +            public Widget getWidget() {
 +                if (type != GridStaticCellType.WIDGET) {
 +                    throw new IllegalStateException(
 +                            "Cannot fetch Widget from a cell with type " + type);
 +                }
 +                return (Widget) content;
 +            }
 +
 +            /**
 +             * Set widget as the content of the cell. The type of the cell
 +             * becomes {@link GridStaticCellType#WIDGET}. All previous content
 +             * is discarded.
 +             * 
 +             * @param widget
 +             *            The widget to add to the cell. Should not be
 +             *            previously attached anywhere (widget.getParent ==
 +             *            null).
 +             */
 +            public void setWidget(Widget widget) {
 +                if (this.content == widget) {
 +                    return;
 +                }
 +
 +                if (this.content instanceof Widget) {
 +                    // Old widget in the cell, detach it first
 +                    section.getGrid().detachWidget((Widget) this.content);
 +                }
 +                this.content = widget;
 +                this.type = GridStaticCellType.WIDGET;
 +                section.requestSectionRefresh();
 +            }
 +
 +            /**
 +             * Returns the type of the cell.
 +             * 
 +             * @return the type of content the cell contains.
 +             */
 +            public GridStaticCellType getType() {
 +                return type;
 +            }
 +
 +            /**
 +             * Returns the custom style name for this cell.
 +             * 
 +             * @return the style name or null if no style name has been set
 +             */
 +            public String getStyleName() {
 +                return styleName;
 +            }
 +
 +            /**
 +             * Sets a custom style name for this cell.
 +             * 
 +             * @param styleName
 +             *            the style name to set or null to not use any style
 +             *            name
 +             */
 +            public void setStyleName(String styleName) {
 +                this.styleName = styleName;
 +                section.requestSectionRefresh();
 +
 +            }
 +
 +            /**
 +             * Called when the cell is detached from the row
 +             * 
 +             * @since 7.6.3
 +             */
 +            void detach() {
 +                if (this.content instanceof Widget) {
 +                    // Widget in the cell, detach it
 +                    section.getGrid().detachWidget((Widget) this.content);
 +                }
 +            }
 +        }
 +
 +        /**
 +         * Abstract base class for Grid header and footer rows.
 +         * 
 +         * @param <CELLTYPE>
 +         *            the type of the cells in the row
 +         */
 +        public abstract static class StaticRow<CELLTYPE extends StaticCell> {
 +
 +            private Map<Column<?, ?>, CELLTYPE> cells = new HashMap<Column<?, ?>, CELLTYPE>();
 +
 +            private StaticSection<?> section;
 +
 +            /**
 +             * Map from set of spanned columns to cell meta data.
 +             */
 +            private Map<Set<Column<?, ?>>, CELLTYPE> cellGroups = new HashMap<Set<Column<?, ?>>, CELLTYPE>();
 +
 +            /**
 +             * A custom style name for the row or null if none is set.
 +             */
 +            private String styleName = null;
 +
 +            /**
 +             * Returns the cell on given GridColumn. If the column is merged
 +             * returned cell is the cell for the whole group.
 +             * 
 +             * @param column
 +             *            the column in grid
 +             * @return the cell on given column, merged cell for merged columns,
 +             *         null if not found
 +             */
 +            public CELLTYPE getCell(Column<?, ?> column) {
 +                Set<Column<?, ?>> cellGroup = getCellGroupForColumn(column);
 +                if (cellGroup != null) {
 +                    return cellGroups.get(cellGroup);
 +                }
 +                return cells.get(column);
 +            }
 +
 +            /**
 +             * Returns <code>true</code> if this row contains spanned cells.
 +             * 
 +             * @since 7.5.0
 +             * @return does this row contain spanned cells
 +             */
 +            public boolean hasSpannedCells() {
 +                return !cellGroups.isEmpty();
 +            }
 +
 +            /**
 +             * Merges columns cells in a row
 +             * 
 +             * @param columns
 +             *            the columns which header should be merged
 +             * @return the remaining visible cell after the merge, or the cell
 +             *         on first column if all are hidden
 +             */
 +            public CELLTYPE join(Column<?, ?>... columns) {
 +                if (columns.length <= 1) {
 +                    throw new IllegalArgumentException(
 +                            "You can't merge less than 2 columns together.");
 +                }
 +
 +                HashSet<Column<?, ?>> columnGroup = new HashSet<Column<?, ?>>();
 +                // NOTE: this doesn't care about hidden columns, those are
 +                // filtered in calculateColspans()
 +                for (Column<?, ?> column : columns) {
 +                    if (!cells.containsKey(column)) {
 +                        throw new IllegalArgumentException(
 +                                "Given column does not exists on row " + column);
 +                    } else if (getCellGroupForColumn(column) != null) {
 +                        throw new IllegalStateException(
 +                                "Column is already in a group.");
 +                    }
 +                    columnGroup.add(column);
 +                }
 +
 +                CELLTYPE joinedCell = createCell();
 +                cellGroups.put(columnGroup, joinedCell);
 +                joinedCell.setSection(getSection());
 +
 +                calculateColspans();
 +
 +                return joinedCell;
 +            }
 +
 +            /**
 +             * Merges columns cells in a row
 +             * 
 +             * @param cells
 +             *            The cells to merge. Must be from the same row.
 +             * @return The remaining visible cell after the merge, or the first
 +             *         cell if all columns are hidden
 +             */
 +            public CELLTYPE join(CELLTYPE... cells) {
 +                if (cells.length <= 1) {
 +                    throw new IllegalArgumentException(
 +                            "You can't merge less than 2 cells together.");
 +                }
 +
 +                Column<?, ?>[] columns = new Column<?, ?>[cells.length];
 +
 +                int j = 0;
 +                for (Column<?, ?> column : this.cells.keySet()) {
 +                    CELLTYPE cell = this.cells.get(column);
 +                    if (!this.cells.containsValue(cells[j])) {
 +                        throw new IllegalArgumentException(
 +                                "Given cell does not exists on row");
 +                    } else if (cell.equals(cells[j])) {
 +                        columns[j++] = column;
 +                        if (j == cells.length) {
 +                            break;
 +                        }
 +                    }
 +                }
 +
 +                return join(columns);
 +            }
 +
 +            private Set<Column<?, ?>> getCellGroupForColumn(Column<?, ?> column) {
 +                for (Set<Column<?, ?>> group : cellGroups.keySet()) {
 +                    if (group.contains(column)) {
 +                        return group;
 +                    }
 +                }
 +                return null;
 +            }
 +
 +            void calculateColspans() {
 +                // Reset all cells
 +                for (CELLTYPE cell : this.cells.values()) {
 +                    cell.setColspan(1);
 +                }
 +                // Set colspan for grouped cells
 +                for (Set<Column<?, ?>> group : cellGroups.keySet()) {
 +                    if (!checkMergedCellIsContinuous(group)) {
 +                        // on error simply break the merged cell
 +                        cellGroups.get(group).setColspan(1);
 +                    } else {
 +                        int colSpan = 0;
 +                        for (Column<?, ?> column : group) {
 +                            if (!column.isHidden()) {
 +                                colSpan++;
 +                            }
 +                        }
 +                        // colspan can't be 0
 +                        cellGroups.get(group).setColspan(Math.max(1, colSpan));
 +                    }
 +                }
 +
 +            }
 +
 +            private boolean checkMergedCellIsContinuous(
 +                    Set<Column<?, ?>> mergedCell) {
 +                // no matter if hidden or not, just check for continuous order
 +                final List<Column<?, ?>> columnOrder = new ArrayList<Column<?, ?>>(
 +                        section.grid.getColumns());
 +
 +                if (!columnOrder.containsAll(mergedCell)) {
 +                    return false;
 +                }
 +
 +                for (int i = 0; i < columnOrder.size(); ++i) {
 +                    if (!mergedCell.contains(columnOrder.get(i))) {
 +                        continue;
 +                    }
 +
 +                    for (int j = 1; j < mergedCell.size(); ++j) {
 +                        if (!mergedCell.contains(columnOrder.get(i + j))) {
 +                            return false;
 +                        }
 +                    }
 +                    return true;
 +                }
 +                return false;
 +            }
 +
 +            protected void addCell(Column<?, ?> column) {
 +                CELLTYPE cell = createCell();
 +                cell.setSection(getSection());
 +                cells.put(column, cell);
 +            }
 +
 +            protected void removeCell(Column<?, ?> column) {
 +                cells.remove(column);
 +            }
 +
 +            protected abstract CELLTYPE createCell();
 +
 +            protected StaticSection<?> getSection() {
 +                return section;
 +            }
 +
 +            protected void setSection(StaticSection<?> section) {
 +                this.section = section;
 +            }
 +
 +            /**
 +             * Returns the custom style name for this row.
 +             * 
 +             * @return the style name or null if no style name has been set
 +             */
 +            public String getStyleName() {
 +                return styleName;
 +            }
 +
 +            /**
 +             * Sets a custom style name for this row.
 +             * 
 +             * @param styleName
 +             *            the style name to set or null to not use any style
 +             *            name
 +             */
 +            public void setStyleName(String styleName) {
 +                this.styleName = styleName;
 +                section.requestSectionRefresh();
 +            }
 +
 +            /**
 +             * Called when the row is detached from the grid
 +             * 
 +             * @since 7.6.3
 +             */
 +            void detach() {
 +                // Avoid calling detach twice for a merged cell
 +                HashSet<CELLTYPE> cells = new HashSet<CELLTYPE>();
 +                for (Column<?, ?> column : getSection().grid.getColumns()) {
 +                    cells.add(getCell(column));
 +                }
 +                for (CELLTYPE cell : cells) {
 +                    cell.detach();
 +                }
 +            }
 +        }
 +
 +        private Grid<?> grid;
 +
 +        private List<ROWTYPE> rows = new ArrayList<ROWTYPE>();
 +
 +        private boolean visible = true;
 +
 +        /**
 +         * Creates and returns a new instance of the row type.
 +         * 
 +         * @return the created row
 +         */
 +        protected abstract ROWTYPE createRow();
 +
 +        /**
 +         * Informs the grid that this section should be re-rendered.
 +         * <p>
 +         * <b>Note</b> that re-render means calling update() on each cell,
 +         * preAttach()/postAttach()/preDetach()/postDetach() is not called as
 +         * the cells are not removed from the DOM.
 +         */
 +        protected abstract void requestSectionRefresh();
 +
 +        /**
 +         * Sets the visibility of the whole section.
 +         * 
 +         * @param visible
 +         *            true to show this section, false to hide
 +         */
 +        public void setVisible(boolean visible) {
 +            this.visible = visible;
 +            requestSectionRefresh();
 +        }
 +
 +        /**
 +         * Returns the visibility of this section.
 +         * 
 +         * @return true if visible, false otherwise.
 +         */
 +        public boolean isVisible() {
 +            return visible;
 +        }
 +
 +        /**
 +         * Inserts a new row at the given position. Shifts the row currently at
 +         * that position and any subsequent rows down (adds one to their
 +         * indices).
 +         * 
 +         * @param index
 +         *            the position at which to insert the row
 +         * @return the new row
 +         * 
 +         * @throws IndexOutOfBoundsException
 +         *             if the index is out of bounds
 +         * @see #appendRow()
 +         * @see #prependRow()
 +         * @see #removeRow(int)
 +         * @see #removeRow(StaticRow)
 +         */
 +        public ROWTYPE addRowAt(int index) {
 +            ROWTYPE row = createRow();
 +            row.setSection(this);
 +            for (int i = 0; i < getGrid().getColumnCount(); ++i) {
 +                row.addCell(grid.getColumn(i));
 +            }
 +            rows.add(index, row);
 +
 +            requestSectionRefresh();
 +            return row;
 +        }
 +
 +        /**
 +         * Adds a new row at the top of this section.
 +         * 
 +         * @return the new row
 +         * @see #appendRow()
 +         * @see #addRowAt(int)
 +         * @see #removeRow(int)
 +         * @see #removeRow(StaticRow)
 +         */
 +        public ROWTYPE prependRow() {
 +            return addRowAt(0);
 +        }
 +
 +        /**
 +         * Adds a new row at the bottom of this section.
 +         * 
 +         * @return the new row
 +         * @see #prependRow()
 +         * @see #addRowAt(int)
 +         * @see #removeRow(int)
 +         * @see #removeRow(StaticRow)
 +         */
 +        public ROWTYPE appendRow() {
 +            return addRowAt(rows.size());
 +        }
 +
 +        /**
 +         * Removes the row at the given position.
 +         * 
 +         * @param index
 +         *            the position of the row
 +         * 
 +         * @throws IndexOutOfBoundsException
 +         *             if the index is out of bounds
 +         * @see #addRowAt(int)
 +         * @see #appendRow()
 +         * @see #prependRow()
 +         * @see #removeRow(StaticRow)
 +         */
 +        public void removeRow(int index) {
 +            ROWTYPE row = rows.remove(index);
 +            row.detach();
 +            requestSectionRefresh();
 +        }
 +
 +        /**
 +         * Removes the given row from the section.
 +         * 
 +         * @param row
 +         *            the row to be removed
 +         * 
 +         * @throws IllegalArgumentException
 +         *             if the row does not exist in this section
 +         * @see #addRowAt(int)
 +         * @see #appendRow()
 +         * @see #prependRow()
 +         * @see #removeRow(int)
 +         */
 +        public void removeRow(ROWTYPE row) {
 +            try {
 +                removeRow(rows.indexOf(row));
 +            } catch (IndexOutOfBoundsException e) {
 +                throw new IllegalArgumentException(
 +                        "Section does not contain the given row");
 +            }
 +        }
 +
 +        /**
 +         * Returns the row at the given position.
 +         * 
 +         * @param index
 +         *            the position of the row
 +         * @return the row with the given index
 +         * 
 +         * @throws IndexOutOfBoundsException
 +         *             if the index is out of bounds
 +         */
 +        public ROWTYPE getRow(int index) {
 +            try {
 +                return rows.get(index);
 +            } catch (IndexOutOfBoundsException e) {
 +                throw new IllegalArgumentException("Row with index " + index
 +                        + " does not exist");
 +            }
 +        }
 +
 +        /**
 +         * Returns the number of rows in this section.
 +         * 
 +         * @return the number of rows
 +         */
 +        public int getRowCount() {
 +            return rows.size();
 +        }
 +
 +        protected List<ROWTYPE> getRows() {
 +            return rows;
 +        }
 +
 +        protected int getVisibleRowCount() {
 +            return isVisible() ? getRowCount() : 0;
 +        }
 +
 +        protected void addColumn(Column<?, ?> column) {
 +            for (ROWTYPE row : rows) {
 +                row.addCell(column);
 +            }
 +        }
 +
 +        protected void removeColumn(Column<?, ?> column) {
 +            for (ROWTYPE row : rows) {
 +                row.removeCell(column);
 +            }
 +        }
 +
 +        protected void setGrid(Grid<?> grid) {
 +            this.grid = grid;
 +        }
 +
 +        protected Grid<?> getGrid() {
 +            assert grid != null;
 +            return grid;
 +        }
 +
 +        protected void updateColSpans() {
 +            for (ROWTYPE row : rows) {
 +                if (row.hasSpannedCells()) {
 +                    row.calculateColspans();
 +                }
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Represents the header section of a Grid. A header consists of a single
 +     * header row containing a header cell for each column. Each cell has a
 +     * simple textual caption.
 +     */
 +    protected static class Header extends StaticSection<HeaderRow> {
 +        private HeaderRow defaultRow;
 +
 +        private boolean markAsDirty = false;
 +
 +        @Override
 +        public void removeRow(int index) {
 +            HeaderRow removedRow = getRow(index);
 +            super.removeRow(index);
 +            if (removedRow == defaultRow) {
 +                setDefaultRow(null);
 +            }
 +        }
 +
 +        /**
 +         * Sets the default row of this header. The default row is a special
 +         * header row providing a user interface for sorting columns.
 +         * 
 +         * @param row
 +         *            the new default row, or null for no default row
 +         * 
 +         * @throws IllegalArgumentException
 +         *             this header does not contain the row
 +         */
 +        public void setDefaultRow(HeaderRow row) {
 +            if (row == defaultRow) {
 +                return;
 +            }
 +            if (row != null && !getRows().contains(row)) {
 +                throw new IllegalArgumentException(
 +                        "Cannot set a default row that does not exist in the container");
 +            }
 +            if (defaultRow != null) {
 +                defaultRow.setDefault(false);
 +            }
 +            if (row != null) {
 +                row.setDefault(true);
 +            }
 +
 +            defaultRow = row;
 +            requestSectionRefresh();
 +        }
 +
 +        /**
 +         * Returns the current default row of this header. The default row is a
 +         * special header row providing a user interface for sorting columns.
 +         * 
 +         * @return the default row or null if no default row set
 +         */
 +        public HeaderRow getDefaultRow() {
 +            return defaultRow;
 +        }
 +
 +        @Override
 +        protected HeaderRow createRow() {
 +            return new HeaderRow();
 +        }
 +
 +        @Override
 +        protected void requestSectionRefresh() {
 +            markAsDirty = true;
 +
 +            /*
 +             * Defer the refresh so if we multiple times call refreshSection()
 +             * (for example when updating cell values) we only get one actual
 +             * refresh in the end.
 +             */
 +            Scheduler.get().scheduleFinally(new Scheduler.ScheduledCommand() {
 +
 +                @Override
 +                public void execute() {
 +                    if (markAsDirty) {
 +                        markAsDirty = false;
 +                        getGrid().refreshHeader();
 +                    }
 +                }
 +            });
 +        }
 +
 +        /**
 +         * Returns the events consumed by the header
 +         * 
 +         * @return a collection of BrowserEvents
 +         */
 +        public Collection<String> getConsumedEvents() {
 +            return Arrays.asList(BrowserEvents.TOUCHSTART,
 +                    BrowserEvents.TOUCHMOVE, BrowserEvents.TOUCHEND,
 +                    BrowserEvents.TOUCHCANCEL, BrowserEvents.CLICK);
 +        }
 +
 +        @Override
 +        protected void addColumn(Column<?, ?> column) {
 +            super.addColumn(column);
 +
 +            // Add default content for new columns.
 +            if (defaultRow != null) {
 +                column.setDefaultHeaderContent(defaultRow.getCell(column));
 +            }
 +        }
 +    }
 +
 +    /**
 +     * A single row in a grid header section.
 +     * 
 +     */
 +    public static class HeaderRow extends StaticSection.StaticRow<HeaderCell> {
 +
 +        private boolean isDefault = false;
 +
 +        protected void setDefault(boolean isDefault) {
 +            this.isDefault = isDefault;
 +            if (isDefault) {
 +                for (Column<?, ?> column : getSection().grid.getColumns()) {
 +                    column.setDefaultHeaderContent(getCell(column));
 +                }
 +            }
 +        }
 +
 +        public boolean isDefault() {
 +            return isDefault;
 +        }
 +
 +        @Override
 +        protected HeaderCell createCell() {
 +            return new HeaderCell();
 +        }
 +    }
 +
 +    /**
 +     * A single cell in a grid header row. Has a caption and, if it's in a
 +     * default row, a drag handle.
 +     */
 +    public static class HeaderCell extends StaticSection.StaticCell {
 +    }
 +
 +    /**
 +     * Represents the footer section of a Grid. The footer is always empty.
 +     */
 +    protected static class Footer extends StaticSection<FooterRow> {
 +        private boolean markAsDirty = false;
 +
 +        @Override
 +        protected FooterRow createRow() {
 +            return new FooterRow();
 +        }
 +
 +        @Override
 +        protected void requestSectionRefresh() {
 +            markAsDirty = true;
 +
 +            /*
 +             * Defer the refresh so if we multiple times call refreshSection()
 +             * (for example when updating cell values) we only get one actual
 +             * refresh in the end.
 +             */
 +            Scheduler.get().scheduleFinally(new Scheduler.ScheduledCommand() {
 +
 +                @Override
 +                public void execute() {
 +                    if (markAsDirty) {
 +                        markAsDirty = false;
 +                        getGrid().refreshFooter();
 +                    }
 +                }
 +            });
 +        }
 +    }
 +
 +    /**
 +     * A single cell in a grid Footer row. Has a textual caption.
 +     * 
 +     */
 +    public static class FooterCell extends StaticSection.StaticCell {
 +    }
 +
 +    /**
 +     * A single row in a grid Footer section.
 +     * 
 +     */
 +    public static class FooterRow extends StaticSection.StaticRow<FooterCell> {
 +
 +        @Override
 +        protected FooterCell createCell() {
 +            return new FooterCell();
 +        }
 +    }
 +
 +    private static class EditorRequestImpl<T> implements EditorRequest<T> {
 +
 +        /**
 +         * A callback interface used to notify the invoker of the editor handler
 +         * of completed editor requests.
 +         * 
 +         * @param <T>
 +         *            the row data type
 +         */
 +        public static interface RequestCallback<T> {
 +            /**
 +             * The method that must be called when the request has been
 +             * processed correctly.
 +             * 
 +             * @param request
 +             *            the original request object
 +             */
 +            public void onSuccess(EditorRequest<T> request);
 +
 +            /**
 +             * The method that must be called when processing the request has
 +             * produced an aborting error.
 +             * 
 +             * @param request
 +             *            the original request object
 +             */
 +            public void onError(EditorRequest<T> request);
 +        }
 +
 +        private Grid<T> grid;
 +        private final int rowIndex;
 +        private final int columnIndex;
 +        private RequestCallback<T> callback;
 +        private boolean completed = false;
 +
 +        public EditorRequestImpl(Grid<T> grid, int rowIndex, int columnIndex,
 +                RequestCallback<T> callback) {
 +            this.grid = grid;
 +            this.rowIndex = rowIndex;
 +            this.columnIndex = columnIndex;
 +            this.callback = callback;
 +        }
 +
 +        @Override
 +        public int getRowIndex() {
 +            return rowIndex;
 +        }
 +
 +        @Override
 +        public int getColumnIndex() {
 +            return columnIndex;
 +        }
 +
 +        @Override
 +        public T getRow() {
 +            return grid.getDataSource().getRow(rowIndex);
 +        }
 +
 +        @Override
 +        public Grid<T> getGrid() {
 +            return grid;
 +        }
 +
 +        @Override
 +        public Widget getWidget(Grid.Column<?, T> column) {
 +            Widget w = grid.getEditorWidget(column);
 +            assert w != null;
 +            return w;
 +        }
 +
 +        private void complete(String errorMessage,
 +                Collection<Column<?, T>> errorColumns) {
 +            if (completed) {
 +                throw new IllegalStateException(
 +                        "An EditorRequest must be completed exactly once");
 +            }
 +            completed = true;
 +
 +            if (errorColumns == null) {
 +                errorColumns = Collections.emptySet();
 +            }
 +            grid.getEditor().setEditorError(errorMessage, errorColumns);
 +        }
 +
 +        @Override
 +        public void success() {
 +            complete(null, null);
 +            if (callback != null) {
 +                callback.onSuccess(this);
 +            }
 +        }
 +
 +        @Override
 +        public void failure(String errorMessage,
 +                Collection<Grid.Column<?, T>> errorColumns) {
 +            complete(errorMessage, errorColumns);
 +            if (callback != null) {
 +                callback.onError(this);
 +            }
 +        }
 +
 +        @Override
 +        public boolean isCompleted() {
 +            return completed;
 +        }
 +    }
 +
 +    /**
 +     * A wrapper for native DOM events originating from Grid. In addition to the
 +     * native event, contains a {@link CellReference} instance specifying which
 +     * cell the event originated from.
 +     * 
 +     * @since 7.6
 +     * @param <T>
 +     *            The row type of the grid
 +     */
 +    public static class GridEvent<T> {
 +        private Event event;
 +        private EventCellReference<T> cell;
 +
 +        protected GridEvent(Event event, EventCellReference<T> cell) {
 +            this.event = event;
 +            this.cell = cell;
 +        }
 +
 +        /**
 +         * Returns the wrapped DOM event.
 +         * 
 +         * @return the DOM event
 +         */
 +        public Event getDomEvent() {
 +            return event;
 +        }
 +
 +        /**
 +         * Returns the Grid cell this event originated from.
 +         * 
 +         * @return the event cell
 +         */
 +        public EventCellReference<T> getCell() {
 +            return cell;
 +        }
 +
 +        /**
 +         * Returns the Grid instance this event originated from.
 +         * 
 +         * @return the grid
 +         */
 +        public Grid<T> getGrid() {
 +            return cell.getGrid();
 +        }
 +    }
 +
 +    /**
 +     * A wrapper for native DOM events related to the {@link Editor Grid editor}
 +     * .
 +     * 
 +     * @since 7.6
 +     * @param <T>
 +     *            the row type of the grid
 +     */
 +    public static class EditorDomEvent<T> extends GridEvent<T> {
 +
 +        private final Widget editorWidget;
 +
 +        protected EditorDomEvent(Event event, EventCellReference<T> cell,
 +                Widget editorWidget) {
 +            super(event, cell);
 +            this.editorWidget = editorWidget;
 +        }
 +
 +        /**
 +         * Returns the editor of the Grid this event originated from.
 +         * 
 +         * @return the related editor instance
 +         */
 +        public Editor<T> getEditor() {
 +            return getGrid().getEditor();
 +        }
 +
 +        /**
 +         * Returns the currently focused editor widget.
 +         * 
 +         * @return the focused editor widget or {@code null} if not editable
 +         */
 +        public Widget getEditorWidget() {
 +            return editorWidget;
 +        }
 +
 +        /**
 +         * Returns the row index the editor is open at. If the editor is not
 +         * open, returns -1.
 +         * 
 +         * @return the index of the edited row or -1 if editor is not open
 +         */
 +        public int getRowIndex() {
 +            return getEditor().rowIndex;
 +        }
 +
 +        /**
 +         * Returns the column index the editor was opened at. If the editor is
 +         * not open, returns -1.
 +         * 
 +         * @return the column index or -1 if editor is not open
 +         */
 +        public int getFocusedColumnIndex() {
 +            return getEditor().focusedColumnIndex;
 +        }
 +    }
 +
 +    /**
 +     * An editor UI for Grid rows. A single Grid row at a time can be opened for
 +     * editing.
 +     * 
 +     * @since 7.6
 +     * @param <T>
 +     *            the row type of the grid
 +     */
 +    public static class Editor<T> implements DeferredWorker {
 +
 +        public static final int KEYCODE_SHOW = KeyCodes.KEY_ENTER;
 +        public static final int KEYCODE_HIDE = KeyCodes.KEY_ESCAPE;
 +
 +        private static final String ERROR_CLASS_NAME = "error";
 +        private static final String NOT_EDITABLE_CLASS_NAME = "not-editable";
 +
 +        ScheduledCommand fieldFocusCommand = new ScheduledCommand() {
 +            private int count = 0;
 +
 +            @Override
 +            public void execute() {
 +                Element focusedElement = WidgetUtil.getFocusedElement();
 +                if (focusedElement == grid.getElement()
 +                        || focusedElement == Document.get().getBody()
 +                        || count > 2) {
 +                    focusColumn(focusedColumnIndex);
 +                } else {
 +                    ++count;
 +                    Scheduler.get().scheduleDeferred(this);
 +                }
 +            }
 +        };
 +
 +        /**
 +         * A handler for events related to the Grid editor. Responsible for
 +         * opening, moving or closing the editor based on the received event.
 +         * 
 +         * @since 7.6
 +         * @author Vaadin Ltd
 +         * @param <T>
 +         *            the row type of the grid
 +         */
 +        public interface EventHandler<T> {
 +            /**
 +             * Handles editor-related events in an appropriate way. Opens,
 +             * moves, or closes the editor based on the given event.
 +             * 
 +             * @param event
 +             *            the received event
 +             * @return true if the event was handled and nothing else should be
 +             *         done, false otherwise
 +             */
 +            boolean handleEvent(EditorDomEvent<T> event);
 +        }
 +
 +        protected enum State {
 +            INACTIVE, ACTIVATING, BINDING, ACTIVE, SAVING
 +        }
 +
 +        private Grid<T> grid;
 +        private EditorHandler<T> handler;
 +        private EventHandler<T> eventHandler = GWT
 +                .create(DefaultEditorEventHandler.class);
 +
 +        private DivElement editorOverlay = DivElement.as(DOM.createDiv());
 +        private DivElement cellWrapper = DivElement.as(DOM.createDiv());
 +        private DivElement frozenCellWrapper = DivElement.as(DOM.createDiv());
 +
 +        private DivElement messageAndButtonsWrapper = DivElement.as(DOM
 +                .createDiv());
 +
 +        private DivElement messageWrapper = DivElement.as(DOM.createDiv());
 +        private DivElement buttonsWrapper = DivElement.as(DOM.createDiv());
 +
 +        // Element which contains the error message for the editor
 +        // Should only be added to the DOM when there's a message to show
 +        private DivElement message = DivElement.as(DOM.createDiv());
 +
 +        private Map<Column<?, T>, Widget> columnToWidget = new HashMap<Column<?, T>, Widget>();
 +        private List<HandlerRegistration> focusHandlers = new ArrayList<HandlerRegistration>();
 +
 +        private boolean enabled = false;
 +        private State state = State.INACTIVE;
 +        private int rowIndex = -1;
 +        private int focusedColumnIndex = -1;
 +        private String styleName = null;
 +
 +        private HandlerRegistration hScrollHandler;
 +        private HandlerRegistration vScrollHandler;
 +
 +        private final Button saveButton;
 +        private final Button cancelButton;
 +
 +        private static final int SAVE_TIMEOUT_MS = 5000;
 +        private final Timer saveTimeout = new Timer() {
 +            @Override
 +            public void run() {
 +                getLogger().warning(
 +                        "Editor save action is taking longer than expected ("
 +                                + SAVE_TIMEOUT_MS + "ms). Does your "
 +                                + EditorHandler.class.getSimpleName()
 +                                + " remember to call success() or fail()?");
 +            }
 +        };
 +
 +        private final EditorRequestImpl.RequestCallback<T> saveRequestCallback = new EditorRequestImpl.RequestCallback<T>() {
 +            @Override
 +            public void onSuccess(EditorRequest<T> request) {
 +                if (state == State.SAVING) {
 +                    cleanup();
 +                    cancel();
 +                    grid.clearSortOrder();
 +                }
 +            }
 +
 +            @Override
 +            public void onError(EditorRequest<T> request) {
 +                if (state == State.SAVING) {
 +                    cleanup();
 +                }
 +            }
 +
 +            private void cleanup() {
 +                state = State.ACTIVE;
 +                setButtonsEnabled(true);
 +                saveTimeout.cancel();
 +            }
 +        };
 +
 +        private static final int BIND_TIMEOUT_MS = 5000;
 +        private final Timer bindTimeout = new Timer() {
 +            @Override
 +            public void run() {
 +                getLogger().warning(
 +                        "Editor bind action is taking longer than expected ("
 +                                + BIND_TIMEOUT_MS + "ms). Does your "
 +                                + EditorHandler.class.getSimpleName()
 +                                + " remember to call success() or fail()?");
 +            }
 +        };
 +
 +        private final EditorRequestImpl.RequestCallback<T> bindRequestCallback = new EditorRequestImpl.RequestCallback<T>() {
 +            @Override
 +            public void onSuccess(EditorRequest<T> request) {
 +                if (state == State.BINDING) {
 +                    state = State.ACTIVE;
 +                    bindTimeout.cancel();
 +
 +                    rowIndex = request.getRowIndex();
 +                    focusedColumnIndex = request.getColumnIndex();
 +                    if (focusedColumnIndex >= 0) {
 +                        // Update internal focus of Grid
 +                        grid.focusCell(rowIndex, focusedColumnIndex);
 +                    }
 +
 +                    showOverlay();
 +                }
 +            }
 +
 +            @Override
 +            public void onError(EditorRequest<T> request) {
 +                if (state == State.BINDING) {
 +                    if (rowIndex == -1) {
 +                        doCancel();
 +                    } else {
 +                        state = State.ACTIVE;
 +                        // TODO: Maybe restore focus?
 +                    }
 +                    bindTimeout.cancel();
 +                }
 +            }
 +        };
 +
 +        /** A set of all the columns that display an error flag. */
 +        private final Set<Column<?, T>> columnErrors = new HashSet<Grid.Column<?, T>>();
 +        private boolean buffered = true;
 +
 +        /** Original position of editor */
 +        private double originalTop;
 +        /** Original scroll position of grid when editor was opened */
 +        private double originalScrollTop;
 +        private RowHandle<T> pinnedRowHandle;
 +
 +        public Editor() {
 +            saveButton = new Button();
 +            saveButton.setText(GridConstants.DEFAULT_SAVE_CAPTION);
 +            saveButton.addClickHandler(new ClickHandler() {
 +                @Override
 +                public void onClick(ClickEvent event) {
 +                    save();
 +                }
 +            });
 +
 +            cancelButton = new Button();
 +            cancelButton.setText(GridConstants.DEFAULT_CANCEL_CAPTION);
 +            cancelButton.addClickHandler(new ClickHandler() {
 +                @Override
 +                public void onClick(ClickEvent event) {
 +                    cancel();
 +                }
 +            });
 +        }
 +
 +        public void setEditorError(String errorMessage,
 +                Collection<Column<?, T>> errorColumns) {
 +
 +            if (errorMessage == null) {
 +                message.removeFromParent();
 +            } else {
 +                message.setInnerText(errorMessage);
 +                if (message.getParentElement() == null) {
 +                    messageWrapper.appendChild(message);
 +                }
 +            }
 +            // In unbuffered mode only show message wrapper if there is an error
 +            if (!isBuffered()) {
 +                setMessageAndButtonsWrapperVisible(errorMessage != null);
 +            }
 +
 +            if (state == State.ACTIVE || state == State.SAVING) {
 +                for (Column<?, T> c : grid.getColumns()) {
 +                    grid.getEditor().setEditorColumnError(c,
 +                            errorColumns.contains(c));
 +                }
 +            }
 +        }
 +
 +        public int getRow() {
 +            return rowIndex;
 +        }
 +
 +        /**
 +         * If a cell of this Grid had focus once this editRow call was
 +         * triggered, the editor component at the previously focused column
 +         * index will be focused.
 +         * 
 +         * If a Grid cell was not focused prior to calling this method, it will
 +         * be equivalent to {@code editRow(rowIndex, -1)}.
 +         * 
 +         * @see #editRow(int, int)
 +         */
 +        public void editRow(int rowIndex) {
 +            // Focus the last focused column in the editor iff grid or its child
 +            // was focused before the edit request
 +            Cell focusedCell = grid.cellFocusHandler.getFocusedCell();
 +            Element focusedElement = WidgetUtil.getFocusedElement();
 +            if (focusedCell != null && focusedElement != null
 +                    && grid.getElement().isOrHasChild(focusedElement)) {
 +                editRow(rowIndex, focusedCell.getColumn());
 +            } else {
 +                editRow(rowIndex, -1);
 +            }
 +        }
 +
 +        /**
 +         * Opens the editor over the row with the given index and attempts to
 +         * focus the editor widget in the given column index. Does not move
 +         * focus if the widget is not focusable or if the column index is -1.
 +         * 
 +         * @param rowIndex
 +         *            the index of the row to be edited
 +         * @param columnIndex
 +         *            the column index of the editor widget that should be
 +         *            initially focused or -1 to not set focus
 +         * 
 +         * @throws IllegalStateException
 +         *             if this editor is not enabled
 +         * @throws IllegalStateException
 +         *             if this editor is already in edit mode and in buffered
 +         *             mode
 +         * 
 +         * @since 7.5
 +         */
 +        public void editRow(final int rowIndex, final int columnIndex) {
 +            if (!enabled) {
 +                throw new IllegalStateException(
 +                        "Cannot edit row: editor is not enabled");
 +            }
 +
 +            if (isWorkPending()) {
 +                // Request pending a response, don't move try to start another
 +                // request.
 +                return;
 +            }
 +
 +            if (state != State.INACTIVE && this.rowIndex != rowIndex) {
 +                if (isBuffered()) {
 +                    throw new IllegalStateException(
 +                            "Cannot edit row: editor already in edit mode");
 +                } else if (!columnErrors.isEmpty()) {
 +                    // Don't move row if errors are present
 +
 +                    // FIXME: Should attempt bind if error field values have
 +                    // changed.
 +
 +                    return;
 +                }
 +            }
 +            if (columnIndex >= grid.getVisibleColumns().size()) {
 +                throw new IllegalArgumentException("Edited column index "
 +                        + columnIndex
 +                        + " was bigger than visible column count.");
 +            }
 +
 +            if (this.rowIndex == rowIndex && focusedColumnIndex == columnIndex) {
 +                // NO-OP
 +                return;
 +            }
 +
 +            if (this.rowIndex == rowIndex) {
 +                if (focusedColumnIndex != columnIndex) {
 +                    if (columnIndex >= grid.getFrozenColumnCount()) {
 +                        // Scroll to new focused column.
 +                        grid.getEscalator().scrollToColumn(columnIndex,
 +                                ScrollDestination.ANY, 0);
 +                    }
 +
 +                    focusedColumnIndex = columnIndex;
 +                }
 +
 +                updateHorizontalScrollPosition();
 +
 +                // Update Grid internal focus and focus widget if possible
 +                if (focusedColumnIndex >= 0) {
 +                    grid.focusCell(rowIndex, focusedColumnIndex);
 +                    focusColumn(focusedColumnIndex);
 +                }
 +
 +                // No need to request anything from the editor handler.
 +                return;
 +            }
 +            state = State.ACTIVATING;
 +
 +            final Escalator escalator = grid.getEscalator();
 +            if (escalator.getVisibleRowRange().contains(rowIndex)) {
 +                show(rowIndex, columnIndex);
 +            } else {
 +                vScrollHandler = grid.addScrollHandler(new ScrollHandler() {
 +                    @Override
 +                    public void onScroll(ScrollEvent event) {
 +                        if (escalator.getVisibleRowRange().contains(rowIndex)) {
 +                            show(rowIndex, columnIndex);
 +                            vScrollHandler.removeHandler();
 +                        }
 +                    }
 +                });
 +                grid.scrollToRow(rowIndex,
 +                        isBuffered() ? ScrollDestination.MIDDLE
 +                                : ScrollDestination.ANY);
 +            }
 +        }
 +
 +        /**
 +         * Cancels the currently active edit and hides the editor. Any changes
 +         * that are not {@link #save() saved} are lost.
 +         * 
 +         * @throws IllegalStateException
 +         *             if this editor is not enabled
 +         * @throws IllegalStateException
 +         *             if this editor is not in edit mode
 +         */
 +        public void cancel() {
 +            if (!enabled) {
 +                throw new IllegalStateException(
 +                        "Cannot cancel edit: editor is not enabled");
 +            }
 +            if (state == State.INACTIVE) {
 +                throw new IllegalStateException(
 +                        "Cannot cancel edit: editor is not in edit mode");
 +            }
 +            handler.cancel(new EditorRequestImpl<T>(grid, rowIndex,
 +                    focusedColumnIndex, null));
 +            doCancel();
 +        }
 +
 +        private void doCancel() {
 +            hideOverlay();
 +            state = State.INACTIVE;
 +            rowIndex = -1;
 +            focusedColumnIndex = -1;
 +            grid.getEscalator().setScrollLocked(Direction.VERTICAL, false);
 +            updateSelectionCheckboxesAsNeeded(true);
 +        }
 +
 +        private void updateSelectionCheckboxesAsNeeded(boolean isEnabled) {
 +            // FIXME: This is too much guessing. Define a better way to do this.
 +            if (grid.selectionColumn != null
 +                    && grid.selectionColumn.getRenderer() instanceof MultiSelectionRenderer) {
 +                grid.refreshBody();
 +                CheckBox checkBox = (CheckBox) grid.getDefaultHeaderRow()
 +                        .getCell(grid.selectionColumn).getWidget();
 +                checkBox.setEnabled(isEnabled);
 +            }
 +        }
 +
 +        /**
 +         * Saves any unsaved changes to the data source and hides the editor.
 +         * 
 +         * @throws IllegalStateException
 +         *             if this editor is not enabled
 +         * @throws IllegalStateException
 +         *             if this editor is not in edit mode
 +         */
 +        public void save() {
 +            if (!enabled) {
 +                throw new IllegalStateException(
 +                        "Cannot save: editor is not enabled");
 +            }
 +            if (state != State.ACTIVE) {
 +                throw new IllegalStateException(
 +                        "Cannot save: editor is not in edit mode");
 +            }
 +
 +            state = State.SAVING;
 +            setButtonsEnabled(false);
 +            saveTimeout.schedule(SAVE_TIMEOUT_MS);
 +            EditorRequest<T> request = new EditorRequestImpl<T>(grid, rowIndex,
 +                    focusedColumnIndex, saveRequestCallback);
 +            handler.save(request);
 +            updateSelectionCheckboxesAsNeeded(true);
 +        }
 +
 +        /**
 +         * Returns the handler responsible for binding data and editor widgets
 +         * to this editor.
 +         * 
 +         * @return the editor handler or null if not set
 +         */
 +        public EditorHandler<T> getHandler() {
 +            return handler;
 +        }
 +
 +        /**
 +         * Sets the handler responsible for binding data and editor widgets to
 +         * this editor.
 +         * 
 +         * @param rowHandler
 +         *            the new editor handler
 +         * 
 +         * @throws IllegalStateException
 +         *             if this editor is currently in edit mode
 +         */
 +        public void setHandler(EditorHandler<T> rowHandler) {
 +            if (state != State.INACTIVE) {
 +                throw new IllegalStateException(
 +                        "Cannot set EditorHandler: editor is currently in edit mode");
 +            }
 +            handler = rowHandler;
 +        }
 +
 +        public boolean isEnabled() {
 +            return enabled;
 +        }
 +
 +        /**
 +         * Sets the enabled state of this editor.
 +         * 
 +         * @param enabled
 +         *            true if enabled, false otherwise
 +         * 
 +         * @throws IllegalStateException
 +         *             if in edit mode and trying to disable
 +         * @throws IllegalStateException
 +         *             if the editor handler is not set
 +         */
 +        public void setEnabled(boolean enabled) {
 +            if (enabled == false && state != State.INACTIVE) {
 +                throw new IllegalStateException(
 +                        "Cannot disable: editor is in edit mode");
 +            } else if (enabled == true && getHandler() == null) {
 +                throw new IllegalStateException(
 +                        "Cannot enable: EditorHandler not set");
 +            }
 +            this.enabled = enabled;
 +        }
 +
 +        protected void show(int rowIndex, int columnIndex) {
 +            if (state == State.ACTIVATING) {
 +                state = State.BINDING;
 +                bindTimeout.schedule(BIND_TIMEOUT_MS);
 +                EditorRequest<T> request = new EditorRequestImpl<T>(grid,
 +                        rowIndex, columnIndex, bindRequestCallback);
 +                handler.bind(request);
 +                grid.getEscalator().setScrollLocked(Direction.VERTICAL,
 +                        isBuffered());
 +                updateSelectionCheckboxesAsNeeded(false);
 +            }
 +        }
 +
 +        protected void setGrid(final Grid<T> grid) {
 +            assert grid != null : "Grid cannot be null";
 +            assert this.grid == null : "Can only attach editor to Grid once";
 +
 +            this.grid = grid;
 +        }
 +
 +        protected State getState() {
 +            return state;
 +        }
 +
 +        protected void setState(State state) {
 +            this.state = state;
 +        }
 +
 +        /**
 +         * Returns the editor widget associated with the given column. If the
 +         * editor is not active or the column is not
 +         * {@link Grid.Column#isEditable() editable}, returns null.
 +         * 
 +         * @param column
 +         *            the column
 +         * @return the widget if the editor is open and the column is editable,
 +         *         null otherwise
 +         */
 +        protected Widget getWidget(Column<?, T> column) {
 +            return columnToWidget.get(column);
 +        }
 +
 +        /**
 +         * Equivalent to {@code showOverlay()}. The argument is ignored.
 +         * 
 +         * @param unused
 +         *            ignored argument
 +         * 
 +         * @deprecated As of 7.5, use {@link #showOverlay()} instead.
 +         */
 +        @Deprecated
 +        protected void showOverlay(TableRowElement unused) {
 +            showOverlay();
 +        }
 +
 +        /**
 +         * Opens the editor overlay over the table row indicated by
 +         * {@link #getRow()}.
 +         * 
 +         * @since 7.5
 +         */
 +        protected void showOverlay() {
 +            // Ensure overlay is hidden initially
 +            hideOverlay();
 +            DivElement gridElement = DivElement.as(grid.getElement());
 +
 +            TableRowElement tr = grid.getEscalator().getBody()
 +                    .getRowElement(rowIndex);
 +
 +            hScrollHandler = grid.addScrollHandler(new ScrollHandler() {
 +                @Override
 +                public void onScroll(ScrollEvent event) {
 +                    updateHorizontalScrollPosition();
 +                    updateVerticalScrollPosition();
 +                }
 +            });
 +
 +            gridElement.appendChild(editorOverlay);
 +            editorOverlay.appendChild(frozenCellWrapper);
 +            editorOverlay.appendChild(cellWrapper);
 +            editorOverlay.appendChild(messageAndButtonsWrapper);
 +
 +            updateBufferedStyleName();
 +
 +            int frozenColumns = grid.getVisibleFrozenColumnCount();
 +            double frozenColumnsWidth = 0;
 +            double cellHeight = 0;
 +
 +            for (int i = 0; i < tr.getCells().getLength(); i++) {
 +                Element cell = createCell(tr.getCells().getItem(i));
 +                cellHeight = Math.max(cellHeight, WidgetUtil
 +                        .getRequiredHeightBoundingClientRectDouble(tr
 +                                .getCells().getItem(i)));
 +
 +                Column<?, T> column = grid.getVisibleColumn(i);
 +
 +                if (i < frozenColumns) {
 +                    frozenCellWrapper.appendChild(cell);
 +                    frozenColumnsWidth += WidgetUtil
 +                            .getRequiredWidthBoundingClientRectDouble(tr
 +                                    .getCells().getItem(i));
 +                } else {
 +                    cellWrapper.appendChild(cell);
 +                }
 +
 +                if (column.isEditable()) {
 +                    Widget editor = getHandler().getWidget(column);
 +
 +                    if (editor != null) {
 +                        columnToWidget.put(column, editor);
 +                        grid.attachWidget(editor, cell);
 +                    }
 +
 +                    if (i == focusedColumnIndex) {
 +                        if (BrowserInfo.get().isIE8()) {
 +                            Scheduler.get().scheduleDeferred(fieldFocusCommand);
 +                        } else {
 +                            focusColumn(focusedColumnIndex);
 +                        }
 +                    }
 +                } else {
 +                    cell.addClassName(NOT_EDITABLE_CLASS_NAME);
 +                    cell.addClassName(tr.getCells().getItem(i).getClassName());
 +                    // If the focused or frozen stylename is present it should
 +                    // not be inherited by the editor cell as it is not useful
 +                    // in the editor and would look broken without additional
 +                    // style rules. This is a bit of a hack.
 +                    cell.removeClassName(grid.cellFocusStyleName);
 +                    cell.removeClassName("frozen");
 +
 +                    if (column == grid.selectionColumn) {
 +                        // Duplicate selection column CheckBox
 +
 +                        pinnedRowHandle = grid.getDataSource().getHandle(
 +                                grid.getDataSource().getRow(rowIndex));
 +                        pinnedRowHandle.pin();
 +
 +                        // We need to duplicate the selection CheckBox for the
 +                        // editor overlay since the original one is hidden by
 +                        // the overlay
 +                        final CheckBox checkBox = GWT.create(CheckBox.class);
 +                        checkBox.setValue(grid.isSelected(pinnedRowHandle
 +                                .getRow()));
 +                        checkBox.sinkEvents(Event.ONCLICK);
 +
 +                        checkBox.addClickHandler(new ClickHandler() {
 +                            @Override
 +                            public void onClick(ClickEvent event) {
 +                                T row = pinnedRowHandle.getRow();
 +                                if (grid.isSelected(row)) {
 +                                    grid.deselect(row);
 +                                } else {
 +                                    grid.select(row);
 +                                }
 +                            }
 +                        });
 +                        grid.attachWidget(checkBox, cell);
 +                        columnToWidget.put(column, checkBox);
 +
 +                        // Only enable CheckBox in non-buffered mode
 +                        checkBox.setEnabled(!isBuffered());
 +
 +                    } else if (!(column.getRenderer() instanceof WidgetRenderer)) {
 +                        // Copy non-widget content directly
 +                        cell.setInnerHTML(tr.getCells().getItem(i)
 +                                .getInnerHTML());
 +                    }
 +                }
 +            }
 +
 +            setBounds(frozenCellWrapper, 0, 0, frozenColumnsWidth, 0);
 +            setBounds(cellWrapper, frozenColumnsWidth, 0, tr.getOffsetWidth()
 +                    - frozenColumnsWidth, cellHeight);
 +
 +            // Only add these elements once
 +            if (!messageAndButtonsWrapper.isOrHasChild(messageWrapper)) {
 +                messageAndButtonsWrapper.appendChild(messageWrapper);
 +                messageAndButtonsWrapper.appendChild(buttonsWrapper);
 +            }
 +
 +            if (isBuffered()) {
 +                grid.attachWidget(saveButton, buttonsWrapper);
 +                grid.attachWidget(cancelButton, buttonsWrapper);
 +            }
 +
 +            setMessageAndButtonsWrapperVisible(isBuffered());
 +
 +            updateHorizontalScrollPosition();
 +
 +            AbstractRowContainer body = (AbstractRowContainer) grid
 +                    .getEscalator().getBody();
 +            double rowTop = body.getRowTop(tr);
 +
 +            int bodyTop = body.getElement().getAbsoluteTop();
 +            int gridTop = gridElement.getAbsoluteTop();
 +            double overlayTop = rowTop + bodyTop - gridTop;
 +
 +            originalScrollTop = grid.getScrollTop();
 +            if (!isBuffered() || buttonsShouldBeRenderedBelow(tr)) {
 +                // Default case, editor buttons are below the edited row
 +                editorOverlay.getStyle().setTop(overlayTop, Unit.PX);
 +                originalTop = overlayTop;
 +                editorOverlay.getStyle().clearBottom();
 +            } else {
 +                // Move message and buttons wrapper on top of cell wrapper if
 +                // there is not enough space visible space under and fix the
 +                // overlay from the bottom
 +                editorOverlay.insertFirst(messageAndButtonsWrapper);
 +                int gridHeight = grid.getElement().getOffsetHeight();
 +                editorOverlay.getStyle()
 +                        .setBottom(
 +                                gridHeight - overlayTop - tr.getOffsetHeight(),
 +                                Unit.PX);
 +                editorOverlay.getStyle().clearTop();
 +            }
 +
 +            // Do not render over the vertical scrollbar
 +            editorOverlay.getStyle().setWidth(grid.escalator.getInnerWidth(),
 +                    Unit.PX);
 +        }
 +
 +        private void focusColumn(int colIndex) {
 +            if (colIndex < 0 || colIndex >= grid.getVisibleColumns().size()) {
 +                // NO-OP
 +                return;
 +            }
 +
 +            Widget editor = getWidget(grid.getVisibleColumn(colIndex));
 +            if (editor instanceof Focusable) {
 +                ((Focusable) editor).focus();
 +            } else if (editor instanceof com.google.gwt.user.client.ui.Focusable) {
 +                ((com.google.gwt.user.client.ui.Focusable) editor)
 +                        .setFocus(true);
 +            } else {
 +                grid.focus();
 +            }
 +        }
 +
 +        private boolean buttonsShouldBeRenderedBelow(TableRowElement tr) {
 +            TableSectionElement tfoot = grid.escalator.getFooter().getElement();
 +            double tfootPageTop = WidgetUtil.getBoundingClientRect(tfoot)
 +                    .getTop();
 +            double trPageBottom = WidgetUtil.getBoundingClientRect(tr)
 +                    .getBottom();
 +            int messageAndButtonsHeight = messageAndButtonsWrapper
 +                    .getOffsetHeight();
 +            double bottomOfButtons = trPageBottom + messageAndButtonsHeight;
 +
 +            return bottomOfButtons < tfootPageTop;
 +        }
 +
 +        protected void hideOverlay() {
 +            if (editorOverlay.getParentElement() == null) {
 +                return;
 +            }
 +
 +            if (pinnedRowHandle != null) {
 +                pinnedRowHandle.unpin();
 +                pinnedRowHandle = null;
 +            }
 +
 +            for (HandlerRegistration r : focusHandlers) {
 +                r.removeHandler();
 +            }
 +            focusHandlers.clear();
 +
 +            for (Widget w : columnToWidget.values()) {
 +                setParent(w, null);
 +            }
 +            columnToWidget.clear();
 +
 +            if (isBuffered()) {
 +                grid.detachWidget(saveButton);
 +                grid.detachWidget(cancelButton);
 +            }
 +
 +            editorOverlay.removeAllChildren();
 +            cellWrapper.removeAllChildren();
 +            frozenCellWrapper.removeAllChildren();
 +            editorOverlay.removeFromParent();
 +
 +            hScrollHandler.removeHandler();
 +
 +            clearEditorColumnErrors();
 +        }
 +
 +        private void updateBufferedStyleName() {
 +            if (isBuffered()) {
 +                editorOverlay.removeClassName("unbuffered");
 +                editorOverlay.addClassName("buffered");
 +            } else {
 +                editorOverlay.removeClassName("buffered");
 +                editorOverlay.addClassName("unbuffered");
 +            }
 +        }
 +
 +        protected void setStylePrimaryName(String primaryName) {
 +            if (styleName != null) {
 +                editorOverlay.removeClassName(styleName);
 +
 +                cellWrapper.removeClassName(styleName + "-cells");
 +                frozenCellWrapper.removeClassName(styleName + "-cells");
 +                messageAndButtonsWrapper.removeClassName(styleName + "-footer");
 +
 +                messageWrapper.removeClassName(styleName + "-message");
 +                buttonsWrapper.removeClassName(styleName + "-buttons");
 +
 +                saveButton.removeStyleName(styleName + "-save");
 +                cancelButton.removeStyleName(styleName + "-cancel");
 +            }
 +            styleName = primaryName + "-editor";
 +            editorOverlay.setClassName(styleName);
 +
 +            cellWrapper.setClassName(styleName + "-cells");
 +            frozenCellWrapper.setClassName(styleName + "-cells frozen");
 +            messageAndButtonsWrapper.setClassName(styleName + "-footer");
 +
 +            messageWrapper.setClassName(styleName + "-message");
 +            buttonsWrapper.setClassName(styleName + "-buttons");
 +
 +            saveButton.setStyleName(styleName + "-save");
 +            cancelButton.setStyleName(styleName + "-cancel");
 +        }
 +
 +        /**
 +         * Creates an editor cell corresponding to the given table cell. The
 +         * returned element is empty and has the same dimensions and position as
 +         * the table cell.
 +         * 
 +         * @param td
 +         *            the table cell used as a reference
 +         * @return an editor cell corresponding to the given cell
 +         */
 +        protected Element createCell(TableCellElement td) {
 +            DivElement cell = DivElement.as(DOM.createDiv());
 +            double width = WidgetUtil
 +                    .getRequiredWidthBoundingClientRectDouble(td);
 +            double height = WidgetUtil
 +                    .getRequiredHeightBoundingClientRectDouble(td);
 +            setBounds(cell, td.getOffsetLeft(), td.getOffsetTop(), width,
 +                    height);
 +            return cell;
 +        }
 +
 +        private static void setBounds(Element e, double left, double top,
 +                double width, double height) {
 +            Style style = e.getStyle();
 +            style.setLeft(left, Unit.PX);
 +            style.setTop(top, Unit.PX);
 +            style.setWidth(width, Unit.PX);
 +            style.setHeight(height, Unit.PX);
 +        }
 +
 +        private void updateHorizontalScrollPosition() {
 +            double scrollLeft = grid.getScrollLeft();
 +            cellWrapper.getStyle().setLeft(
 +                    frozenCellWrapper.getOffsetWidth() - scrollLeft, Unit.PX);
 +        }
 +
 +        /**
 +         * Moves the editor overlay on scroll so that it stays on top of the
 +         * edited row. This will also snap the editor to top or bottom of the
 +         * row container if the edited row is scrolled out of the visible area.
 +         */
 +        private void updateVerticalScrollPosition() {
 +            if (isBuffered()) {
 +                return;
 +            }
 +
 +            double newScrollTop = grid.getScrollTop();
 +
 +            int gridTop = grid.getElement().getAbsoluteTop();
 +            int editorHeight = editorOverlay.getOffsetHeight();
 +
 +            Escalator escalator = grid.getEscalator();
 +            TableSectionElement header = escalator.getHeader().getElement();
 +            int footerTop = escalator.getFooter().getElement().getAbsoluteTop();
 +            int headerBottom = header.getAbsoluteBottom();
 +
 +            double newTop = originalTop - (newScrollTop - originalScrollTop);
 +
 +            if (newTop + gridTop < headerBottom) {
 +                // Snap editor to top of the row container
 +                newTop = header.getOffsetHeight();
 +            } else if (newTop + gridTop > footerTop - editorHeight) {
 +                // Snap editor to the bottom of the row container
 +                newTop = footerTop - editorHeight - gridTop;
 +            }
 +
 +            editorOverlay.getStyle().setTop(newTop, Unit.PX);
 +        }
 +
 +        protected void setGridEnabled(boolean enabled) {
 +            // TODO: This should be informed to handler as well so possible
 +            // fields can be disabled.
 +            setButtonsEnabled(enabled);
 +        }
 +
 +        private void setButtonsEnabled(boolean enabled) {
 +            saveButton.setEnabled(enabled);
 +            cancelButton.setEnabled(enabled);
 +        }
 +
 +        public void setSaveCaption(String saveCaption)
 +                throws IllegalArgumentException {
 +            if (saveCaption == null) {
 +                throw new IllegalArgumentException(
 +                        "Save caption cannot be null");
 +            }
 +            saveButton.setText(saveCaption);
 +        }
 +
 +        public String getSaveCaption() {
 +            return saveButton.getText();
 +        }
 +
 +        public void setCancelCaption(String cancelCaption)
 +                throws IllegalArgumentException {
 +            if (cancelCaption == null) {
 +                throw new IllegalArgumentException(
 +                        "Cancel caption cannot be null");
 +            }
 +            cancelButton.setText(cancelCaption);
 +        }
 +
 +        public String getCancelCaption() {
 +            return cancelButton.getText();
 +        }
 +
 +        public void setEditorColumnError(Column<?, T> column, boolean hasError) {
 +            if (state != State.ACTIVE && state != State.SAVING) {
 +                throw new IllegalStateException("Cannot set cell error "
 +                        + "status: editor is neither active nor saving.");
 +            }
 +
 +            if (isEditorColumnError(column) == hasError) {
 +                return;
 +            }
 +
 +            Element editorCell = getWidget(column).getElement()
 +                    .getParentElement();
 +            if (hasError) {
 +                editorCell.addClassName(ERROR_CLASS_NAME);
 +                columnErrors.add(column);
 +            } else {
 +                editorCell.removeClassName(ERROR_CLASS_NAME);
 +                columnErrors.remove(column);
 +            }
 +        }
 +
 +        public void clearEditorColumnErrors() {
 +
 +            /*
 +             * editorOverlay has no children if it's not active, effectively
 +             * making this loop a NOOP.
 +             */
 +            Element e = editorOverlay.getFirstChildElement();
 +            while (e != null) {
 +                e.removeClassName(ERROR_CLASS_NAME);
 +                e = e.getNextSiblingElement();
 +            }
 +
 +            columnErrors.clear();
 +        }
 +
 +        public boolean isEditorColumnError(Column<?, T> column) {
 +            return columnErrors.contains(column);
 +        }
 +
 +        public void setBuffered(boolean buffered) {
 +            this.buffered = buffered;
 +            setMessageAndButtonsWrapperVisible(buffered);
 +        }
 +
 +        public boolean isBuffered() {
 +            return buffered;
 +        }
 +
 +        private void setMessageAndButtonsWrapperVisible(boolean visible) {
 +            if (visible) {
 +                messageAndButtonsWrapper.getStyle().clearDisplay();
 +            } else {
 +                messageAndButtonsWrapper.getStyle().setDisplay(Display.NONE);
 +            }
 +        }
 +
 +        /**
 +         * Sets the event handler for this Editor.
 +         * 
 +         * @since 7.6
 +         * @param handler
 +         *            the new event handler
 +         */
 +        public void setEventHandler(EventHandler<T> handler) {
 +            eventHandler = handler;
 +        }
 +
 +        /**
 +         * Returns the event handler of this Editor.
 +         * 
 +         * @since 7.6
 +         * @return the current event handler
 +         */
 +        public EventHandler<T> getEventHandler() {
 +            return eventHandler;
 +        }
 +
 +        @Override
 +        public boolean isWorkPending() {
 +            return saveTimeout.isRunning() || bindTimeout.isRunning();
 +        }
 +
 +        protected int getElementColumn(Element e) {
 +            int frozenCells = frozenCellWrapper.getChildCount();
 +            if (frozenCellWrapper.isOrHasChild(e)) {
 +                for (int i = 0; i < frozenCells; ++i) {
 +                    if (frozenCellWrapper.getChild(i).isOrHasChild(e)) {
 +                        return i;
 +                    }
 +                }
 +            }
 +
 +            if (cellWrapper.isOrHasChild(e)) {
 +                for (int i = 0; i < cellWrapper.getChildCount(); ++i) {
 +                    if (cellWrapper.getChild(i).isOrHasChild(e)) {
 +                        return i + frozenCells;
 +                    }
 +                }
 +            }
 +
 +            return -1;
 +        }
 +    }
 +
 +    public static abstract class AbstractGridKeyEvent<HANDLER extends AbstractGridKeyEventHandler>
 +            extends KeyEvent<HANDLER> {
 +
 +        private Grid<?> grid;
 +        private final Type<HANDLER> associatedType = new Type<HANDLER>(
 +                getBrowserEventType(), this);
 +        private final CellReference<?> targetCell;
 +
 +        public AbstractGridKeyEvent(Grid<?> grid, CellReference<?> targetCell) {
 +            this.grid = grid;
 +            this.targetCell = targetCell;
 +        }
 +
 +        protected abstract String getBrowserEventType();
 +
 +        /**
 +         * Gets the Grid instance for this event.
 +         * 
 +         * @return grid
 +         */
 +        public Grid<?> getGrid() {
 +            return grid;
 +        }
 +
 +        /**
 +         * Gets the focused cell for this event.
 +         * 
 +         * @return focused cell
 +         */
 +        public CellReference<?> getFocusedCell() {
 +            return targetCell;
 +        }
 +
 +        @Override
 +        protected void dispatch(HANDLER handler) {
 +            EventTarget target = getNativeEvent().getEventTarget();
 +            if (Element.is(target)
 +                    && !grid.isElementInChildWidget(Element.as(target))) {
 +
 +                Section section = Section.FOOTER;
 +                final RowContainer container = grid.cellFocusHandler.containerWithFocus;
 +                if (container == grid.escalator.getHeader()) {
 +                    section = Section.HEADER;
 +                } else if (container == grid.escalator.getBody()) {
 +                    section = Section.BODY;
 +                }
 +
 +                doDispatch(handler, section);
 +            }
 +        }
 +
 +        protected abstract void doDispatch(HANDLER handler, Section section);
 +
 +        @Override
 +        public Type<HANDLER> getAssociatedType() {
 +            return associatedType;
 +        }
 +    }
 +
 +    public static abstract class AbstractGridMouseEvent<HANDLER extends AbstractGridMouseEventHandler>
 +            extends MouseEvent<HANDLER> {
 +
 +        private Grid<?> grid;
 +        private final CellReference<?> targetCell;
 +        private final Type<HANDLER> associatedType = new Type<HANDLER>(
 +                getBrowserEventType(), this);
 +
 +        public AbstractGridMouseEvent(Grid<?> grid, CellReference<?> targetCell) {
 +            this.grid = grid;
 +            this.targetCell = targetCell;
 +        }
 +
 +        protected abstract String getBrowserEventType();
 +
 +        /**
 +         * Gets the Grid instance for this event.
 +         * 
 +         * @return grid
 +         */
 +        public Grid<?> getGrid() {
 +            return grid;
 +        }
 +
 +        /**
 +         * Gets the reference of target cell for this event.
 +         * 
 +         * @return target cell
 +         */
 +        public CellReference<?> getTargetCell() {
 +            return targetCell;
 +        }
 +
 +        @Override
 +        protected void dispatch(HANDLER handler) {
 +            EventTarget target = getNativeEvent().getEventTarget();
 +            if (!Element.is(target)) {
 +                // Target is not an element
 +                return;
 +            }
 +
 +            Element targetElement = Element.as(target);
 +            if (grid.isElementInChildWidget(targetElement)) {
 +                // Target is some widget inside of Grid
 +                return;
 +            }
 +
 +            final RowContainer container = grid.escalator
 +                    .findRowContainer(targetElement);
 +            if (container == null) {
 +                // No container for given element
 +                return;
 +            }
 +
 +            Section section = Section.FOOTER;
 +            if (container == grid.escalator.getHeader()) {
 +                section = Section.HEADER;
 +            } else if (container == grid.escalator.getBody()) {
 +                section = Section.BODY;
 +            }
 +
 +            doDispatch(handler, section);
 +        }
 +
 +        protected abstract void doDispatch(HANDLER handler, Section section);
 +
 +        @Override
 +        public Type<HANDLER> getAssociatedType() {
 +            return associatedType;
 +        }
 +    }
 +
 +    private static final String CUSTOM_STYLE_PROPERTY_NAME = "customStyle";
 +
 +    /**
 +     * An initial height that is given to new details rows before rendering the
 +     * appropriate widget that we then can be measure
 +     * 
 +     * @see GridSpacerUpdater
 +     */
 +    private static final double DETAILS_ROW_INITIAL_HEIGHT = 50;
 +
 +    private EventCellReference<T> eventCell = new EventCellReference<T>(this);
 +    private GridKeyDownEvent keyDown = new GridKeyDownEvent(this, eventCell);
 +    private GridKeyUpEvent keyUp = new GridKeyUpEvent(this, eventCell);
 +    private GridKeyPressEvent keyPress = new GridKeyPressEvent(this, eventCell);
 +    private GridClickEvent clickEvent = new GridClickEvent(this, eventCell);
 +    private GridDoubleClickEvent doubleClickEvent = new GridDoubleClickEvent(
 +            this, eventCell);
 +
 +    private class CellFocusHandler {
 +
 +        private RowContainer containerWithFocus = escalator.getBody();
 +        private int rowWithFocus = 0;
 +        private Range cellFocusRange = Range.withLength(0, 1);
 +        private int lastFocusedBodyRow = 0;
 +        private int lastFocusedHeaderRow = 0;
 +        private int lastFocusedFooterRow = 0;
 +        private TableCellElement cellWithFocusStyle = null;
 +        private TableRowElement rowWithFocusStyle = null;
 +
 +        public CellFocusHandler() {
 +            sinkEvents(getNavigationEvents());
 +        }
 +
 +        private Cell getFocusedCell() {
 +            return new Cell(rowWithFocus, cellFocusRange.getStart(),
 +                    cellWithFocusStyle);
 +        }
 +
 +        /**
 +         * Sets style names for given cell when needed.
 +         */
 +        public void updateFocusedCellStyle(FlyweightCell cell,
 +                RowContainer cellContainer) {
 +            int cellRow = cell.getRow();
 +            int cellColumn = cell.getColumn();
 +            int colSpan = cell.getColSpan();
 +            boolean columnHasFocus = Range.withLength(cellColumn, colSpan)
 +                    .intersects(cellFocusRange);
 +
 +            if (cellContainer == containerWithFocus) {
 +                // Cell is in the current container
 +                if (cellRow == rowWithFocus && columnHasFocus) {
 +                    if (cellWithFocusStyle != cell.getElement()) {
 +                        // Cell is correct but it does not have focused style
 +                        if (cellWithFocusStyle != null) {
 +                            // Remove old focus style
 +                            setStyleName(cellWithFocusStyle,
 +                                    cellFocusStyleName, false);
 +                        }
 +                        cellWithFocusStyle = cell.getElement();
 +
 +                        // Add focus style to correct cell.
 +                        setStyleName(cellWithFocusStyle, cellFocusStyleName,
 +                                true);
 +                    }
 +                } else if (cellWithFocusStyle == cell.getElement()) {
 +                    // Due to escalator reusing cells, a new cell has the same
 +                    // element but is not the focused cell.
 +                    setStyleName(cellWithFocusStyle, cellFocusStyleName, false);
 +                    cellWithFocusStyle = null;
 +                }
 +            }
 +        }
 +
 +        /**
 +         * Sets focus style for the given row if needed.
 +         * 
 +         * @param row
 +         *            a row object
 +         */
 +        public void updateFocusedRowStyle(Row row) {
 +            if (rowWithFocus == row.getRow()
 +                    && containerWithFocus == escalator.getBody()) {
 +                if (row.getElement() != rowWithFocusStyle) {
 +                    // Row should have focus style but does not have it.
 +                    if (rowWithFocusStyle != null) {
 +                        setStyleName(rowWithFocusStyle, rowFocusStyleName,
 +                                false);
 +                    }
 +                    rowWithFocusStyle = row.getElement();
 +                    setStyleName(rowWithFocusStyle, rowFocusStyleName, true);
 +                }
 +            } else if (rowWithFocusStyle == row.getElement()
 +                    || (containerWithFocus != escalator.getBody() && rowWithFocusStyle != null)) {
 +                // Remove focus style.
 +                setStyleName(rowWithFocusStyle, rowFocusStyleName, false);
 +                rowWithFocusStyle = null;
 +            }
 +        }
 +
 +        /**
 +         * Sets the currently focused.
 +         * <p>
 +         * <em>NOTE:</em> the column index is the index in DOM, not the logical
 +         * column index which includes hidden columns.
 +         * 
 +         * @param rowIndex
 +         *            the index of the row having focus
 +         * @param columnIndexDOM
 +         *            the index of the cell having focus
 +         * @param container
 +         *            the row container having focus
 +         */
 +        private void setCellFocus(int rowIndex, int columnIndexDOM,
 +                RowContainer container) {
 +            if (rowIndex == rowWithFocus
 +                    && cellFocusRange.contains(columnIndexDOM)
 +                    && container == this.containerWithFocus) {
 +                return;
 +            }
 +
 +            int oldRow = rowWithFocus;
 +            rowWithFocus = rowIndex;
 +            Range oldRange = cellFocusRange;
 +
 +            if (container == escalator.getBody()) {
 +                scrollToRow(rowWithFocus);
 +                cellFocusRange = Range.withLength(columnIndexDOM, 1);
 +            } else {
 +                int i = 0;
 +                Element cell = container.getRowElement(rowWithFocus)
 +                        .getFirstChildElement();
 +                do {
 +                    int colSpan = cell
 +                            .getPropertyInt(FlyweightCell.COLSPAN_ATTR);
 +                    Range cellRange = Range.withLength(i, colSpan);
 +                    if (cellRange.contains(columnIndexDOM)) {
 +                        cellFocusRange = cellRange;
 +                        break;
 +                    }
 +                    cell = cell.getNextSiblingElement();
 +                    ++i;
 +                } while (cell != null);
 +            }
 +            int columnIndex = getColumns().indexOf(
 +                    getVisibleColumn(columnIndexDOM));
 +            if (columnIndex >= escalator.getColumnConfiguration()
 +                    .getFrozenColumnCount()) {
 +                escalator.scrollToColumn(columnIndexDOM, ScrollDestination.ANY,
 +                        10);
 +            }
 +
 +            if (this.containerWithFocus == container) {
 +                if (oldRange.equals(cellFocusRange) && oldRow != rowWithFocus) {
 +                    refreshRow(oldRow);
 +                } else {
 +                    refreshHeader();
 +                    refreshFooter();
 +                }
 +            } else {
 +                RowContainer oldContainer = this.containerWithFocus;
 +                this.containerWithFocus = container;
 +
 +                if (oldContainer == escalator.getBody()) {
 +                    lastFocusedBodyRow = oldRow;
 +                } else if (oldContainer == escalator.getHeader()) {
 +                    lastFocusedHeaderRow = oldRow;
 +                } else {
 +                    lastFocusedFooterRow = oldRow;
 +                }
 +
 +                if (!oldRange.equals(cellFocusRange)) {
 +                    refreshHeader();
 +                    refreshFooter();
 +                    if (oldContainer == escalator.getBody()) {
 +                        oldContainer.refreshRows(oldRow, 1);
 +                    }
 +                } else {
 +                    oldContainer.refreshRows(oldRow, 1);
 +                }
 +            }
 +            refreshRow(rowWithFocus);
 +        }
 +
 +        /**
 +         * Sets focus on a cell.
 +         * 
 +         * <p>
 +         * <em>Note</em>: cell focus is not the same as JavaScript's
 +         * {@code document.activeElement}.
 +         * 
 +         * @param cell
 +         *            a cell object
 +         */
 +        public void setCellFocus(CellReference<T> cell) {
 +            setCellFocus(cell.getRowIndex(), cell.getColumnIndexDOM(),
 +                    escalator.findRowContainer(cell.getElement()));
 +        }
 +
 +        /**
 +         * Gets list of events that can be used for cell focusing.
 +         * 
 +         * @return list of navigation related event types
 +         */
 +        public Collection<String> getNavigationEvents() {
 +            return Arrays.asList(BrowserEvents.KEYDOWN, BrowserEvents.CLICK);
 +        }
 +
 +        /**
 +         * Handle events that can move the cell focus.
 +         */
 +        public void handleNavigationEvent(Event event, CellReference<T> cell) {
 +            if (event.getType().equals(BrowserEvents.CLICK)) {
 +                setCellFocus(cell);
 +                // Grid should have focus when clicked.
 +                getElement().focus();
 +            } else if (event.getType().equals(BrowserEvents.KEYDOWN)) {
 +                int newRow = rowWithFocus;
 +                RowContainer newContainer = containerWithFocus;
 +                int newColumn = cellFocusRange.getStart();
 +
 +                switch (event.getKeyCode()) {
 +                case KeyCodes.KEY_DOWN:
 +                    ++newRow;
 +                    break;
 +                case KeyCodes.KEY_UP:
 +                    --newRow;
 +                    break;
 +                case KeyCodes.KEY_RIGHT:
 +                    if (cellFocusRange.getEnd() >= getVisibleColumns().size()) {
 +                        return;
 +                    }
 +                    newColumn = cellFocusRange.getEnd();
 +                    break;
 +                case KeyCodes.KEY_LEFT:
 +                    if (newColumn == 0) {
 +                        return;
 +                    }
 +                    --newColumn;
 +                    break;
 +                case KeyCodes.KEY_TAB:
 +                    if (event.getShiftKey()) {
 +                        newContainer = getPreviousContainer(containerWithFocus);
 +                    } else {
 +                        newContainer = getNextContainer(containerWithFocus);
 +                    }
 +
 +                    if (newContainer == containerWithFocus) {
 +                        return;
 +                    }
 +                    break;
 +                case KeyCodes.KEY_HOME:
 +                    if (newContainer.getRowCount() > 0) {
 +                        newRow = 0;
 +                    }
 +                    break;
 +                case KeyCodes.KEY_END:
 +                    if (newContainer.getRowCount() > 0) {
 +                        newRow = newContainer.getRowCount() - 1;
 +                    }
 +                    break;
 +                case KeyCodes.KEY_PAGEDOWN:
 +                case KeyCodes.KEY_PAGEUP:
 +                    if (newContainer.getRowCount() > 0) {
 +                        boolean down = event.getKeyCode() == KeyCodes.KEY_PAGEDOWN;
 +                        // If there is a visible focused cell, scroll by one
 +                        // page from its position. Otherwise, use the first or
 +                        // the last visible row as the scroll start position.
 +                        // This avoids jumping when using both keyboard and the
 +                        // scroll bar for scrolling.
 +                        int firstVisible = getFirstVisibleRowIndex();
 +                        int lastVisible = getLastVisibleRowIndex();
 +                        if (newRow < firstVisible || newRow > lastVisible) {
 +                            newRow = down ? lastVisible : firstVisible;
 +                        }
 +                        // Scroll by a little less than the visible area to
 +                        // account for the possibility that the top and the
 +                        // bottom row are only partially visible.
 +                        int moveFocusBy = Math.max(1, lastVisible
 +                                - firstVisible - 1);
 +                        moveFocusBy *= down ? 1 : -1;
 +                        newRow += moveFocusBy;
 +                        newRow = Math.max(0, Math.min(
 +                                newContainer.getRowCount() - 1, newRow));
 +                    }
 +                    break;
 +                default:
 +                    return;
 +                }
 +
 +                if (newContainer != containerWithFocus) {
 +                    if (newContainer == escalator.getBody()) {
 +                        newRow = lastFocusedBodyRow;
 +                    } else if (newContainer == escalator.getHeader()) {
 +                        newRow = lastFocusedHeaderRow;
 +                    } else {
 +                        newRow = lastFocusedFooterRow;
 +                    }
 +                } else if (newRow < 0) {
 +                    newContainer = getPreviousContainer(newContainer);
 +
 +                    if (newContainer == containerWithFocus) {
 +                        newRow = 0;
 +                    } else if (newContainer == escalator.getBody()) {
 +                        newRow = getLastVisibleRowIndex();
 +                    } else {
 +                        newRow = newContainer.getRowCount() - 1;
 +                    }
 +                } else if (newRow >= containerWithFocus.getRowCount()) {
 +                    newContainer = getNextContainer(newContainer);
 +
 +                    if (newContainer == containerWithFocus) {
 +                        newRow = containerWithFocus.getRowCount() - 1;
 +                    } else if (newContainer == escalator.getBody()) {
 +                        newRow = getFirstVisibleRowIndex();
 +                    } else {
 +                        newRow = 0;
 +                    }
 +                }
 +
 +                if (newContainer.getRowCount() == 0) {
 +                    /*
 +                     * There are no rows in the container. Can't change the
 +                     * focused cell.
 +                     */
 +                    return;
 +                }
 +
 +                event.preventDefault();
 +                event.stopPropagation();
 +
 +                setCellFocus(newRow, newColumn, newContainer);
 +            }
 +
 +        }
 +
 +        private RowContainer getPreviousContainer(RowContainer current) {
 +            if (current == escalator.getFooter()) {
 +                current = escalator.getBody();
 +            } else if (current == escalator.getBody()) {
 +                current = escalator.getHeader();
 +            } else {
 +                return current;
 +            }
 +
 +            if (current.getRowCount() == 0) {
 +                return getPreviousContainer(current);
 +            }
 +            return current;
 +        }
 +
 +        private RowContainer getNextContainer(RowContainer current) {
 +            if (current == escalator.getHeader()) {
 +                current = escalator.getBody();
 +            } else if (current == escalator.getBody()) {
 +                current = escalator.getFooter();
 +            } else {
 +                return current;
 +            }
 +
 +            if (current.getRowCount() == 0) {
 +                return getNextContainer(current);
 +            }
 +            return current;
 +        }
 +
 +        private void refreshRow(int row) {
 +            containerWithFocus.refreshRows(row, 1);
 +        }
 +
 +        /**
 +         * Offsets the focused cell's range.
 +         * 
 +         * @param offset
 +         *            offset for fixing focused cell's range
 +         */
 +        public void offsetRangeBy(int offset) {
 +            cellFocusRange = cellFocusRange.offsetBy(offset);
 +        }
 +
 +        /**
 +         * Informs {@link CellFocusHandler} that certain range of rows has been
 +         * added to the Grid body. {@link CellFocusHandler} will fix indices
 +         * accordingly.
 +         * 
 +         * @param added
 +         *            a range of added rows
 +         */
 +        public void rowsAddedToBody(Range added) {
 +            boolean bodyHasFocus = (containerWithFocus == escalator.getBody());
 +            boolean insertionIsAboveFocusedCell = (added.getStart() <= rowWithFocus);
 +            if (bodyHasFocus && insertionIsAboveFocusedCell) {
 +                rowWithFocus += added.length();
 +                rowWithFocus = Math.min(rowWithFocus, escalator.getBody()
 +                        .getRowCount() - 1);
 +                refreshRow(rowWithFocus);
 +            }
 +        }
 +
 +        /**
 +         * Informs {@link CellFocusHandler} that certain range of rows has been
 +         * removed from the Grid body. {@link CellFocusHandler} will fix indices
 +         * accordingly.
 +         * 
 +         * @param removed
 +         *            a range of removed rows
 +         */
 +        public void rowsRemovedFromBody(Range removed) {
 +            if (containerWithFocus != escalator.getBody()) {
 +                return;
 +            } else if (!removed.contains(rowWithFocus)) {
 +                if (removed.getStart() > rowWithFocus) {
 +                    return;
 +                }
 +                rowWithFocus = rowWithFocus - removed.length();
 +            } else {
 +                if (containerWithFocus.getRowCount() > removed.getEnd()) {
 +                    rowWithFocus = removed.getStart();
 +                } else if (removed.getStart() > 0) {
 +                    rowWithFocus = removed.getStart() - 1;
 +                } else {
 +                    if (escalator.getHeader().getRowCount() > 0) {
 +                        rowWithFocus = Math.min(lastFocusedHeaderRow, escalator
 +                                .getHeader().getRowCount() - 1);
 +                        containerWithFocus = escalator.getHeader();
 +                    } else if (escalator.getFooter().getRowCount() > 0) {
 +                        rowWithFocus = Math.min(lastFocusedFooterRow, escalator
 +                                .getFooter().getRowCount() - 1);
 +                        containerWithFocus = escalator.getFooter();
 +                    }
 +                }
 +            }
 +            refreshRow(rowWithFocus);
 +        }
 +    }
 +
 +    public final class SelectionColumn extends Column<Boolean, T> {
 +
 +        private boolean initDone = false;
 +        private boolean selected = false;
 +        private CheckBox selectAllCheckBox;
 +
 +        SelectionColumn(final Renderer<Boolean> selectColumnRenderer) {
 +            super(selectColumnRenderer);
 +        }
 +
 +        void initDone() {
 +            setWidth(-1);
 +
 +            setEditable(false);
 +            setResizable(false);
 +
 +            initDone = true;
 +        }
 +
 +        @Override
 +        protected void setDefaultHeaderContent(HeaderCell selectionCell) {
 +            /*
 +             * TODO: Currently the select all check box is shown when multi
 +             * selection is in use. This might result in malfunctions if no
 +             * SelectAllHandlers are present.
 +             * 
 +             * Later on this could be fixed so that it check such handlers
 +             * exist.
 +             */
 +            final SelectionModel.Multi<T> model = (Multi<T>) getSelectionModel();
 +
 +            if (selectAllCheckBox == null) {
 +                selectAllCheckBox = GWT.create(CheckBox.class);
 +                selectAllCheckBox.setStylePrimaryName(getStylePrimaryName()
 +                        + SELECT_ALL_CHECKBOX_CLASSNAME);
 +                selectAllCheckBox
 +                        .addValueChangeHandler(new ValueChangeHandler<Boolean>() {
 +
 +                            @Override
 +                            public void onValueChange(
 +                                    ValueChangeEvent<Boolean> event) {
 +                                if (event.getValue()) {
 +                                    fireEvent(new SelectAllEvent<T>(model));
 +                                    selected = true;
 +                                } else {
 +                                    model.deselectAll();
 +                                    selected = false;
 +                                }
 +                            }
 +                        });
 +                selectAllCheckBox.setValue(selected);
 +
 +                addHeaderClickHandler(new HeaderClickHandler() {
 +                    @Override
 +                    public void onClick(GridClickEvent event) {
 +                        CellReference<?> targetCell = event.getTargetCell();
 +                        int defaultRowIndex = getHeader().getRows().indexOf(
 +                                getDefaultHeaderRow());
 +
 +                        if (targetCell.getColumnIndex() == 0
 +                                && targetCell.getRowIndex() == defaultRowIndex) {
 +                            selectAllCheckBox.setValue(
 +                                    !selectAllCheckBox.getValue(), true);
 +                        }
 +                    }
 +                });
 +
 +                // Select all with space when "select all" cell is active
 +                addHeaderKeyUpHandler(new HeaderKeyUpHandler() {
 +                    @Override
 +                    public void onKeyUp(GridKeyUpEvent event) {
 +                        if (event.getNativeKeyCode() != KeyCodes.KEY_SPACE) {
 +                            return;
 +                        }
 +                        HeaderRow targetHeaderRow = getHeader().getRow(
 +                                event.getFocusedCell().getRowIndex());
 +                        if (!targetHeaderRow.isDefault()) {
 +                            return;
 +                        }
 +                        if (event.getFocusedCell().getColumn() == SelectionColumn.this) {
 +                            // Send events to ensure state is updated
 +                            selectAllCheckBox.setValue(
 +                                    !selectAllCheckBox.getValue(), true);
 +                        }
 +                    }
 +                });
 +            } else {
 +                for (HeaderRow row : header.getRows()) {
 +                    if (row.getCell(this).getType() == GridStaticCellType.WIDGET) {
 +                        // Detach from old header.
 +                        row.getCell(this).setText("");
 +                    }
 +                }
 +            }
 +
 +            selectionCell.setWidget(selectAllCheckBox);
 +        }
 +
 +        @Override
 +        public Column<Boolean, T> setWidth(double pixels) {
 +            if (pixels != getWidth() && initDone) {
 +                throw new UnsupportedOperationException("The selection "
 +                        + "column cannot be modified after init");
 +            } else {
 +                super.setWidth(pixels);
 +            }
 +
 +            return this;
 +        }
 +
 +        @Override
 +        public Boolean getValue(T row) {
 +            return Boolean.valueOf(isSelected(row));
 +        }
 +
 +        @Override
 +        public Column<Boolean, T> setExpandRatio(int ratio) {
 +            throw new UnsupportedOperationException(
 +                    "can't change the expand ratio of the selection column");
 +        }
 +
 +        @Override
 +        public int getExpandRatio() {
 +            return 0;
 +        }
 +
 +        @Override
 +        public Column<Boolean, T> setMaximumWidth(double pixels) {
 +            throw new UnsupportedOperationException(
 +                    "can't change the maximum width of the selection column");
 +        }
 +
 +        @Override
 +        public double getMaximumWidth() {
 +            return -1;
 +        }
 +
 +        @Override
 +        public Column<Boolean, T> setMinimumWidth(double pixels) {
 +            throw new UnsupportedOperationException(
 +                    "can't change the minimum width of the selection column");
 +        }
 +
 +        @Override
 +        public double getMinimumWidth() {
 +            return -1;
 +        }
 +
 +        @Override
 +        public Column<Boolean, T> setEditable(boolean editable) {
 +            if (initDone) {
 +                throw new UnsupportedOperationException(
 +                        "can't set the selection column editable");
 +            }
 +            super.setEditable(editable);
 +            return this;
 +        }
 +    }
 +
 +    /**
 +     * Helper class for performing sorting through the user interface. Controls
 +     * the sort() method, reporting USER as the event originator. This is a
 +     * completely internal class, and is, as such, safe to re-name should a more
 +     * descriptive name come to mind.
 +     */
 +    private final class UserSorter {
 +
 +        private final Timer timer;
 +        private boolean scheduledMultisort;
 +        private Column<?, T> column;
 +
 +        private UserSorter() {
 +            timer = new Timer() {
 +
 +                @Override
 +                public void run() {
 +                    UserSorter.this.sort(column, scheduledMultisort);
 +                }
 +            };
 +        }
 +
 +        /**
 +         * Toggle sorting for a cell. If the multisort parameter is set to true,
 +         * the cell's sort order is modified as a natural part of a multi-sort
 +         * chain. If false, the sorting order is set to ASCENDING for that
 +         * cell's column. If that column was already the only sorted column in
 +         * the Grid, the sort direction is flipped.
 +         * 
 +         * @param cell
 +         *            a valid cell reference
 +         * @param multisort
 +         *            whether the sort command should act as a multi-sort stack
 +         *            or not
 +         */
 +        public void sort(Column<?, ?> column, boolean multisort) {
 +
 +            if (!columns.contains(column)) {
 +                throw new IllegalArgumentException(
 +                        "Given column is not a column in this grid. "
 +                                + column.toString());
 +            }
 +
 +            if (!column.isSortable()) {
 +                return;
 +            }
 +
 +            final SortOrder so = getSortOrder(column);
 +
 +            if (multisort) {
 +
 +                // If the sort order exists, replace existing value with its
 +                // opposite
 +                if (so != null) {
 +                    final int idx = sortOrder.indexOf(so);
 +                    sortOrder.set(idx, so.getOpposite());
 +                } else {
 +                    // If it doesn't, just add a new sort order to the end of
 +                    // the list
 +                    sortOrder.add(new SortOrder(column));
 +                }
 +
 +            } else {
 +
 +                // Since we're doing single column sorting, first clear the
 +                // list. Then, if the sort order existed, add its opposite,
 +                // otherwise just add a new sort value
 +
 +                int items = sortOrder.size();
 +                sortOrder.clear();
 +                if (so != null && items == 1) {
 +                    sortOrder.add(so.getOpposite());
 +                } else {
 +                    sortOrder.add(new SortOrder(column));
 +                }
 +            }
 +
 +            // sortOrder has been changed; tell the Grid to re-sort itself by
 +            // user request.
 +            Grid.this.sort(true);
 +        }
 +
 +        /**
 +         * Perform a sort after a delay.
 +         * 
 +         * @param delay
 +         *            delay, in milliseconds
 +         */
 +        public void sortAfterDelay(int delay, boolean multisort) {
 +            column = eventCell.getColumn();
 +            scheduledMultisort = multisort;
 +            timer.schedule(delay);
 +        }
 +
 +        /**
 +         * Check if a delayed sort command has been issued but not yet carried
 +         * out.
 +         * 
 +         * @return a boolean value
 +         */
 +        public boolean isDelayedSortScheduled() {
 +            return timer.isRunning();
 +        }
 +
 +        /**
 +         * Cancel a scheduled sort.
 +         */
 +        public void cancelDelayedSort() {
 +            timer.cancel();
 +        }
 +
 +    }
 +
 +    /**
 +     * @see Grid#autoColumnWidthsRecalculator
 +     */
 +    private class AutoColumnWidthsRecalculator {
 +        private double lastCalculatedInnerWidth = -1;
 +
 +        private final ScheduledCommand calculateCommand = new ScheduledCommand() {
 +
 +            @Override
 +            public void execute() {
 +                if (!isScheduled) {
 +                    // something cancelled running this.
 +                    return;
 +                }
 +
 +                if (header.markAsDirty || footer.markAsDirty) {
 +                    if (rescheduleCount < 10) {
 +                        /*
 +                         * Headers and footers are rendered as finally, this way
 +                         * we re-schedule this loop as finally, at the end of
 +                         * the queue, so that the headers have a chance to
 +                         * render themselves.
 +                         */
 +                        Scheduler.get().scheduleFinally(this);
 +                        rescheduleCount++;
 +                    } else {
 +                        /*
 +                         * We've tried too many times reschedule finally. Seems
 +                         * like something is being deferred. Let the queue
 +                         * execute and retry again.
 +                         */
 +                        rescheduleCount = 0;
 +                        Scheduler.get().scheduleDeferred(this);
 +                    }
 +                } else if (dataIsBeingFetched) {
 +                    Scheduler.get().scheduleDeferred(this);
 +                } else {
 +                    calculate();
 +                }
 +            }
 +        };
 +
 +        private int rescheduleCount = 0;
 +        private boolean isScheduled;
 +
 +        /**
 +         * Calculates and applies column widths, taking into account fixed
 +         * widths and column expand rules
 +         * 
 +         * @param immediately
 +         *            <code>true</code> if the widths should be executed
 +         *            immediately (ignoring lazy loading completely), or
 +         *            <code>false</code> if the command should be run after a
 +         *            while (duplicate non-immediately invocations are ignored).
 +         * @see Column#setWidth(double)
 +         * @see Column#setExpandRatio(int)
 +         * @see Column#setMinimumWidth(double)
 +         * @see Column#setMaximumWidth(double)
 +         */
 +        public void schedule() {
 +            if (!isScheduled && isAttached()) {
 +                isScheduled = true;
 +                Scheduler.get().scheduleFinally(calculateCommand);
 +            }
 +        }
 +
 +        private void calculate() {
 +            isScheduled = false;
 +            rescheduleCount = 0;
 +
 +            assert !dataIsBeingFetched : "Trying to calculate column widths even though data is still being fetched.";
 +
 +            if (columnsAreGuaranteedToBeWiderThanGrid()) {
 +                applyColumnWidths();
 +            } else {
 +                applyColumnWidthsWithExpansion();
 +            }
 +
 +            // Update latest width to prevent recalculate on height change.
 +            lastCalculatedInnerWidth = escalator.getInnerWidth();
 +        }
 +
 +        private boolean columnsAreGuaranteedToBeWiderThanGrid() {
 +            double freeSpace = escalator.getInnerWidth();
 +            for (Column<?, ?> column : getVisibleColumns()) {
 +                if (column.getWidth() >= 0) {
 +                    freeSpace -= column.getWidth();
 +                } else if (column.getMinimumWidth() >= 0) {
 +                    freeSpace -= column.getMinimumWidth();
 +                }
 +            }
 +            return freeSpace < 0;
 +        }
 +
 +        @SuppressWarnings("boxing")
 +        private void applyColumnWidths() {
 +
 +            /* Step 1: Apply all column widths as they are. */
 +
 +            Map<Integer, Double> selfWidths = new LinkedHashMap<Integer, Double>();
 +            List<Column<?, T>> columns = getVisibleColumns();
 +            for (int index = 0; index < columns.size(); index++) {
 +                selfWidths.put(index, columns.get(index).getWidth());
 +            }
 +            Grid.this.escalator.getColumnConfiguration().setColumnWidths(
 +                    selfWidths);
 +
 +            /*
 +             * Step 2: Make sure that each column ends up obeying their min/max
 +             * width constraints if defined as autowidth. If constraints are
 +             * violated, fix it.
 +             */
 +
 +            Map<Integer, Double> constrainedWidths = new LinkedHashMap<Integer, Double>();
 +            for (int index = 0; index < columns.size(); index++) {
 +                Column<?, T> column = columns.get(index);
 +
 +                boolean hasAutoWidth = column.getWidth() < 0;
 +                if (!hasAutoWidth) {
 +                    continue;
 +                }
 +
 +                // TODO: bug: these don't honor the CSS max/min. :(
 +                double actualWidth = column.getWidthActual();
 +                if (actualWidth < getMinWidth(column)) {
 +                    constrainedWidths.put(index, column.getMinimumWidth());
 +                } else if (actualWidth > getMaxWidth(column)) {
 +                    constrainedWidths.put(index, column.getMaximumWidth());
 +                }
 +            }
 +            Grid.this.escalator.getColumnConfiguration().setColumnWidths(
 +                    constrainedWidths);
 +        }
 +
 +        private void applyColumnWidthsWithExpansion() {
 +            boolean defaultExpandRatios = true;
 +            int totalRatios = 0;
 +            double reservedPixels = 0;
 +            final Set<Column<?, T>> columnsToExpand = new HashSet<Column<?, T>>();
 +            List<Column<?, T>> nonFixedColumns = new ArrayList<Column<?, T>>();
 +            Map<Integer, Double> columnSizes = new HashMap<Integer, Double>();
 +            final List<Column<?, T>> visibleColumns = getVisibleColumns();
 +
 +            /*
 +             * Set all fixed widths and also calculate the size-to-fit widths
 +             * for the autocalculated columns.
 +             * 
 +             * This way we know with how many pixels we have left to expand the
 +             * rest.
 +             */
 +            for (Column<?, T> column : visibleColumns) {
 +                final double widthAsIs = column.getWidth();
 +                final boolean isFixedWidth = widthAsIs >= 0;
 +                // Check for max width just to be sure we don't break the limits
 +                final double widthFixed = Math.max(
 +                        Math.min(getMaxWidth(column), widthAsIs),
 +                        column.getMinimumWidth());
 +                defaultExpandRatios = defaultExpandRatios
 +                        && (column.getExpandRatio() == -1 || column == selectionColumn);
 +
 +                if (isFixedWidth) {
 +                    columnSizes.put(visibleColumns.indexOf(column), widthFixed);
 +                    reservedPixels += widthFixed;
 +                } else {
 +                    nonFixedColumns.add(column);
 +                    columnSizes.put(visibleColumns.indexOf(column), -1.0d);
 +                }
 +            }
 +
 +            setColumnSizes(columnSizes);
 +
 +            for (Column<?, T> column : nonFixedColumns) {
 +                final int expandRatio = (defaultExpandRatios ? 1 : column
 +                        .getExpandRatio());
 +                final double maxWidth = getMaxWidth(column);
 +                final double newWidth = Math.min(maxWidth,
 +                        column.getWidthActual());
 +                boolean shouldExpand = newWidth < maxWidth && expandRatio > 0
 +                        && column != selectionColumn;
 +                if (shouldExpand) {
 +                    totalRatios += expandRatio;
 +                    columnsToExpand.add(column);
 +                }
 +                reservedPixels += newWidth;
 +                columnSizes.put(visibleColumns.indexOf(column), newWidth);
 +            }
 +
 +            /*
 +             * Now that we know how many pixels we need at the very least, we
 +             * can distribute the remaining pixels to all columns according to
 +             * their expand ratios.
 +             */
 +            double pixelsToDistribute = escalator.getInnerWidth()
 +                    - reservedPixels;
 +            if (pixelsToDistribute <= 0 || totalRatios <= 0) {
 +                if (pixelsToDistribute <= 0) {
 +                    // Set column sizes for expanding columns
 +                    setColumnSizes(columnSizes);
 +                }
 +
 +                return;
 +            }
 +
 +            /*
 +             * Check for columns that hit their max width. Adjust
 +             * pixelsToDistribute and totalRatios accordingly. Recheck. Stop
 +             * when no new columns hit their max width
 +             */
 +            boolean aColumnHasMaxedOut;
 +            do {
 +                aColumnHasMaxedOut = false;
 +                final double widthPerRatio = pixelsToDistribute / totalRatios;
 +                final Iterator<Column<?, T>> i = columnsToExpand.iterator();
 +                while (i.hasNext()) {
 +                    final Column<?, T> column = i.next();
 +                    final int expandRatio = getExpandRatio(column,
 +                            defaultExpandRatios);
 +                    final int columnIndex = visibleColumns.indexOf(column);
 +                    final double autoWidth = columnSizes.get(columnIndex);
 +                    final double maxWidth = getMaxWidth(column);
 +                    double expandedWidth = autoWidth + widthPerRatio
 +                            * expandRatio;
 +
 +                    if (maxWidth <= expandedWidth) {
 +                        i.remove();
 +                        totalRatios -= expandRatio;
 +                        aColumnHasMaxedOut = true;
 +                        pixelsToDistribute -= maxWidth - autoWidth;
 +                        columnSizes.put(columnIndex, maxWidth);
 +                    }
 +                }
 +            } while (aColumnHasMaxedOut);
 +
 +            if (totalRatios <= 0 && columnsToExpand.isEmpty()) {
 +                setColumnSizes(columnSizes);
 +                return;
 +            }
 +            assert pixelsToDistribute > 0 : "We've run out of pixels to distribute ("
 +                    + pixelsToDistribute
 +                    + "px to "
 +                    + totalRatios
 +                    + " ratios between " + columnsToExpand.size() + " columns)";
 +            assert totalRatios > 0 && !columnsToExpand.isEmpty() : "Bookkeeping out of sync. Ratios: "
 +                    + totalRatios + " Columns: " + columnsToExpand.size();
 +
 +            /*
 +             * If we still have anything left, distribute the remaining pixels
 +             * to the remaining columns.
 +             */
 +            final double widthPerRatio;
 +            int leftOver = 0;
 +            if (BrowserInfo.get().isIE8() || BrowserInfo.get().isIE9()
 +                    || BrowserInfo.getBrowserString().contains("PhantomJS")) {
 +                // These browsers report subpixels as integers. this usually
 +                // results into issues..
 +                widthPerRatio = (int) (pixelsToDistribute / totalRatios);
 +                leftOver = (int) (pixelsToDistribute - widthPerRatio
 +                        * totalRatios);
 +            } else {
 +                widthPerRatio = pixelsToDistribute / totalRatios;
 +            }
 +            for (Column<?, T> column : columnsToExpand) {
 +                final int expandRatio = getExpandRatio(column,
 +                        defaultExpandRatios);
 +                final int columnIndex = visibleColumns.indexOf(column);
 +                final double autoWidth = columnSizes.get(columnIndex);
 +                double totalWidth = autoWidth + widthPerRatio * expandRatio;
 +                if (leftOver > 0) {
 +                    totalWidth += 1;
 +                    leftOver--;
 +                }
 +                columnSizes.put(columnIndex, totalWidth);
 +
 +                totalRatios -= expandRatio;
 +            }
 +            assert totalRatios == 0 : "Bookkeeping error: there were still some ratios left undistributed: "
 +                    + totalRatios;
 +
 +            /*
 +             * Check the guarantees for minimum width and scoot back the columns
 +             * that don't care.
 +             */
 +            boolean minWidthsCausedReflows;
 +            do {
 +                minWidthsCausedReflows = false;
 +
 +                /*
 +                 * First, let's check which columns were too cramped, and expand
 +                 * them. Also keep track on how many pixels we grew - we need to
 +                 * remove those pixels from other columns
 +                 */
 +                double pixelsToRemoveFromOtherColumns = 0;
 +                for (Column<?, T> column : visibleColumns) {
 +                    /*
 +                     * We can't iterate over columnsToExpand, even though that
 +                     * would be convenient. This is because some column without
 +                     * an expand ratio might still have a min width - those
 +                     * wouldn't show up in that set.
 +                     */
 +
 +                    double minWidth = getMinWidth(column);
 +                    final int columnIndex = visibleColumns.indexOf(column);
 +                    double currentWidth = columnSizes.get(columnIndex);
 +                    boolean hasAutoWidth = column.getWidth() < 0;
 +                    if (hasAutoWidth && currentWidth < minWidth) {
 +                        columnSizes.put(columnIndex, minWidth);
 +                        pixelsToRemoveFromOtherColumns += (minWidth - currentWidth);
 +                        minWidthsCausedReflows = true;
 +
 +                        /*
 +                         * Remove this column form the set if it exists. This
 +                         * way we make sure that it doesn't get shrunk in the
 +                         * next step.
 +                         */
 +                        columnsToExpand.remove(column);
 +                    }
 +                }
 +
 +                /*
 +                 * Now we need to shrink the remaining columns according to
 +                 * their ratios. Recalculate the sum of remaining ratios.
 +                 */
 +                totalRatios = 0;
 +                for (Column<?, ?> column : columnsToExpand) {
 +                    totalRatios += getExpandRatio(column, defaultExpandRatios);
 +                }
 +                final double pixelsToRemovePerRatio = pixelsToRemoveFromOtherColumns
 +                        / totalRatios;
 +                for (Column<?, T> column : columnsToExpand) {
 +                    final double pixelsToRemove = pixelsToRemovePerRatio
 +                            * getExpandRatio(column, defaultExpandRatios);
 +                    int colIndex = visibleColumns.indexOf(column);
 +                    columnSizes.put(colIndex, columnSizes.get(colIndex)
 +                            - pixelsToRemove);
 +                }
 +
 +            } while (minWidthsCausedReflows);
 +
 +            // Finally set all the column sizes.
 +            setColumnSizes(columnSizes);
 +        }
 +
 +        private void setColumnSizes(Map<Integer, Double> columnSizes) {
 +            // Set all widths at once
 +            escalator.getColumnConfiguration().setColumnWidths(columnSizes);
 +        }
 +
 +        private int getExpandRatio(Column<?, ?> column,
 +                boolean defaultExpandRatios) {
 +            int expandRatio = column.getExpandRatio();
 +            if (expandRatio > 0) {
 +                return expandRatio;
 +            } else if (expandRatio < 0) {
 +                assert defaultExpandRatios : "No columns should've expanded";
 +                return 1;
 +            } else {
 +                assert false : "this method should've not been called at all if expandRatio is 0";
 +                return 0;
 +            }
 +        }
 +
 +        /**
 +         * Returns the maximum width of the column, or {@link Double#MAX_VALUE}
 +         * if defined as negative.
 +         */
 +        private double getMaxWidth(Column<?, ?> column) {
 +            double maxWidth = column.getMaximumWidth();
 +            if (maxWidth >= 0) {
 +                return maxWidth;
 +            } else {
 +                return Double.MAX_VALUE;
 +            }
 +        }
 +
 +        /**
 +         * Returns the minimum width of the column, or {@link Double#MIN_VALUE}
 +         * if defined as negative.
 +         */
 +        private double getMinWidth(Column<?, ?> column) {
 +            double minWidth = column.getMinimumWidth();
 +            if (minWidth >= 0) {
 +                return minWidth;
 +            } else {
 +                return Double.MIN_VALUE;
 +            }
 +        }
 +
 +        /**
 +         * Check whether the auto width calculation is currently scheduled.
 +         * 
 +         * @return <code>true</code> if auto width calculation is currently
 +         *         scheduled
 +         */
 +        public boolean isScheduled() {
 +            return isScheduled;
 +        }
 +    }
 +
 +    private class GridSpacerUpdater implements SpacerUpdater {
 +
 +        private static final String STRIPE_CLASSNAME = "stripe";
 +
 +        private final Map<Element, Widget> elementToWidgetMap = new HashMap<Element, Widget>();
 +
 +        @Override
 +        public void init(Spacer spacer) {
 +            initTheming(spacer);
 +
 +            int rowIndex = spacer.getRow();
 +
 +            Widget detailsWidget = null;
 +            try {
 +                detailsWidget = detailsGenerator.getDetails(rowIndex);
 +            } catch (Throwable e) {
 +                getLogger().log(
 +                        Level.SEVERE,
 +                        "Exception while generating details for row "
 +                                + rowIndex, e);
 +            }
 +
 +            final double spacerHeight;
 +            Element spacerElement = spacer.getElement();
 +            if (detailsWidget == null) {
 +                spacerElement.removeAllChildren();
 +                spacerHeight = DETAILS_ROW_INITIAL_HEIGHT;
 +            } else {
 +                Element element = detailsWidget.getElement();
 +                spacerElement.appendChild(element);
 +                setParent(detailsWidget, Grid.this);
 +                Widget previousWidget = elementToWidgetMap.put(element,
 +                        detailsWidget);
 +
 +                assert previousWidget == null : "Overwrote a pre-existing widget on row "
 +                        + rowIndex + " without proper removal first.";
 +
 +                /*
 +                 * Once we have the content properly inside the DOM, we should
 +                 * re-measure it to make sure that it's the correct height.
 +                 * 
 +                 * This is rather tricky, since the row (tr) will get the
 +                 * height, but the spacer cell (td) has the borders, which
 +                 * should go on top of the previous row and next row.
 +                 */
 +                double contentHeight;
 +                if (detailsGenerator instanceof HeightAwareDetailsGenerator) {
 +                    HeightAwareDetailsGenerator sadg = (HeightAwareDetailsGenerator) detailsGenerator;
 +                    contentHeight = sadg.getDetailsHeight(rowIndex);
 +                } else {
 +                    contentHeight = WidgetUtil
 +                            .getRequiredHeightBoundingClientRectDouble(element);
 +                }
 +                double borderTopAndBottomHeight = WidgetUtil
 +                        .getBorderTopAndBottomThickness(spacerElement);
 +                double measuredHeight = contentHeight
 +                        + borderTopAndBottomHeight;
 +                assert getElement().isOrHasChild(spacerElement) : "The spacer element wasn't in the DOM during measurement, but was assumed to be.";
 +                spacerHeight = measuredHeight;
 +            }
 +
 +            escalator.getBody().setSpacer(rowIndex, spacerHeight);
 +        }
 +
 +        @Override
 +        public void destroy(Spacer spacer) {
 +            Element spacerElement = spacer.getElement();
 +
 +            assert getElement().isOrHasChild(spacerElement) : "Trying "
 +                    + "to destroy a spacer that is not connected to this "
 +                    + "Grid's DOM. (row: " + spacer.getRow() + ", element: "
 +                    + spacerElement + ")";
 +
 +            Widget detailsWidget = elementToWidgetMap.remove(spacerElement
 +                    .getFirstChildElement());
 +
 +            if (detailsWidget != null) {
 +                /*
 +                 * The widget may be null here if the previous generator
 +                 * returned a null widget.
 +                 */
 +
 +                assert spacerElement.getFirstChild() != null : "The "
 +                        + "details row to destroy did not contain a widget - "
 +                        + "probably removed by something else without "
 +                        + "permission? (row: " + spacer.getRow()
 +                        + ", element: " + spacerElement + ")";
 +
 +                setParent(detailsWidget, null);
 +                spacerElement.removeAllChildren();
 +            }
 +        }
 +
 +        private void initTheming(Spacer spacer) {
 +            Element spacerRoot = spacer.getElement();
 +
 +            if (spacer.getRow() % 2 == 1) {
 +                spacerRoot.getParentElement().addClassName(STRIPE_CLASSNAME);
 +            } else {
 +                spacerRoot.getParentElement().removeClassName(STRIPE_CLASSNAME);
 +            }
 +        }
 +
 +    }
 +
 +    /**
 +     * Sidebar displaying toggles for hidable columns and custom widgets
 +     * provided by the application.
 +     * <p>
 +     * The button for opening the sidebar is automatically visible inside the
 +     * grid, if it contains any column hiding options or custom widgets. The
 +     * column hiding toggles and custom widgets become visible once the sidebar
 +     * has been opened.
 +     * 
 +     * @since 7.5.0
 +     */
 +    private static class Sidebar extends Composite implements HasEnabled {
 +
 +        private final ClickHandler openCloseButtonHandler = new ClickHandler() {
 +
 +            @Override
 +            public void onClick(ClickEvent event) {
 +                if (!isOpen()) {
 +                    open();
 +                } else {
 +                    close();
 +                }
 +            }
 +        };
 +
 +        private final FlowPanel rootContainer;
 +
 +        private final FlowPanel content;
 +
 +        private final MenuBar menuBar;
 +
 +        private final Button openCloseButton;
 +
 +        private final Grid<?> grid;
 +
 +        private Overlay overlay;
 +
 +        private Sidebar(Grid<?> grid) {
 +            this.grid = grid;
 +
 +            rootContainer = new FlowPanel();
 +            initWidget(rootContainer);
 +
 +            openCloseButton = new Button();
 +
 +            openCloseButton.addClickHandler(openCloseButtonHandler);
 +
 +            rootContainer.add(openCloseButton);
 +
 +            content = new FlowPanel() {
 +                @Override
 +                public boolean remove(Widget w) {
 +                    // Check here to catch child.removeFromParent() calls
 +                    boolean removed = super.remove(w);
 +                    if (removed) {
 +                        updateVisibility();
 +                    }
 +
 +                    return removed;
 +                }
 +            };
 +
 +            createOverlay();
 +
 +            menuBar = new MenuBar(true) {
 +
 +                @Override
 +                public MenuItem insertItem(MenuItem item, int beforeIndex)
 +                        throws IndexOutOfBoundsException {
 +                    if (getParent() == null) {
 +                        content.insert(this, 0);
 +                        updateVisibility();
 +                    }
 +                    return super.insertItem(item, beforeIndex);
 +                }
 +
 +                @Override
 +                public void removeItem(MenuItem item) {
 +                    super.removeItem(item);
 +                    if (getItems().isEmpty()) {
 +                        menuBar.removeFromParent();
 +                    }
 +                }
 +
 +                @Override
 +                public void onBrowserEvent(Event event) {
 +                    // selecting a item with enter will lose the focus and
 +                    // selected item, which means that further keyboard
 +                    // selection won't work unless we do this:
 +                    if (event.getTypeInt() == Event.ONKEYDOWN
 +                            && event.getKeyCode() == KeyCodes.KEY_ENTER) {
 +                        final MenuItem item = getSelectedItem();
 +                        super.onBrowserEvent(event);
 +                        Scheduler.get().scheduleDeferred(
 +                                new ScheduledCommand() {
 +
 +                                    @Override
 +                                    public void execute() {
 +                                        selectItem(item);
 +                                        focus();
 +                                    }
 +                                });
 +
 +                    } else {
 +                        super.onBrowserEvent(event);
 +                    }
 +                }
 +
 +            };
 +            KeyDownHandler keyDownHandler = new KeyDownHandler() {
 +
 +                @Override
 +                public void onKeyDown(KeyDownEvent event) {
 +                    if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) {
 +                        close();
 +                    }
 +                }
 +            };
 +            openCloseButton.addDomHandler(keyDownHandler,
 +                    KeyDownEvent.getType());
 +            menuBar.addDomHandler(keyDownHandler, KeyDownEvent.getType());
 +        }
 +
 +        /**
 +         * Creates and initializes the overlay.
 +         */
 +        private void createOverlay() {
 +            overlay = GWT.create(Overlay.class);
 +            overlay.setOwner(grid);
 +            overlay.setAutoHideEnabled(true);
 +            overlay.addStyleDependentName("popup");
 +            overlay.add(content);
 +            overlay.addAutoHidePartner(rootContainer.getElement());
 +            overlay.addCloseHandler(new CloseHandler<PopupPanel>() {
 +                @Override
 +                public void onClose(CloseEvent<PopupPanel> event) {
 +                    removeStyleName("open");
 +                    addStyleName("closed");
 +                }
 +            });
 +        }
 +
 +        /**
 +         * Opens the sidebar if not yet opened. Opening the sidebar has no
 +         * effect if it is empty.
 +         */
 +        public void open() {
 +            if (!isOpen() && isInDOM()) {
 +                addStyleName("open");
 +                removeStyleName("closed");
 +                overlay.showRelativeTo(rootContainer);
 +            }
 +        }
 +
 +        /**
 +         * Closes the sidebar if not yet closed.
 +         */
 +        public void close() {
 +            overlay.hide();
 +        }
 +
 +        /**
 +         * Returns whether the sidebar is open or not.
 +         * 
 +         * @return <code>true</code> if open, <code>false</code> if not
 +         */
 +        public boolean isOpen() {
 +            return overlay != null && overlay.isShowing();
 +        }
 +
 +        @Override
 +        public void setStylePrimaryName(String styleName) {
 +            super.setStylePrimaryName(styleName);
 +            overlay.setStylePrimaryName(styleName);
 +            content.setStylePrimaryName(styleName + "-content");
 +            openCloseButton.setStylePrimaryName(styleName + "-button");
 +            if (isOpen()) {
 +                addStyleName("open");
 +                removeStyleName("closed");
 +            } else {
 +                removeStyleName("open");
 +                addStyleName("closed");
 +            }
 +        }
 +
 +        @Override
 +        public void addStyleName(String style) {
 +            super.addStyleName(style);
 +            overlay.addStyleName(style);
 +        }
 +
 +        @Override
 +        public void removeStyleName(String style) {
 +            super.removeStyleName(style);
 +            overlay.removeStyleName(style);
 +        }
 +
 +        private void setHeightToHeaderCellHeight() {
 +            RowContainer header = grid.escalator.getHeader();
 +            if (header.getRowCount() == 0
 +                    || !header.getRowElement(0).hasChildNodes()) {
 +                getLogger()
 +                        .info("No header cell available when calculating sidebar button height");
 +                openCloseButton.setHeight(header.getDefaultRowHeight() + "px");
 +
 +                return;
 +            }
 +
 +            Element firstHeaderCell = header.getRowElement(0)
 +                    .getFirstChildElement();
 +            double height = WidgetUtil
 +                    .getRequiredHeightBoundingClientRectDouble(firstHeaderCell)
 +                    - (WidgetUtil.measureVerticalBorder(getElement()) / 2);
 +            openCloseButton.setHeight(height + "px");
 +        }
 +
 +        private void updateVisibility() {
 +            final boolean hasWidgets = content.getWidgetCount() > 0;
 +            final boolean isVisible = isInDOM();
 +            if (isVisible && !hasWidgets) {
 +                Grid.setParent(this, null);
 +                getElement().removeFromParent();
 +            } else if (!isVisible && hasWidgets) {
 +                close();
 +                grid.getElement().appendChild(getElement());
 +                Grid.setParent(this, grid);
 +                // border calculation won't work until attached
 +                setHeightToHeaderCellHeight();
 +            }
 +        }
 +
 +        private boolean isInDOM() {
 +            return getParent() != null;
 +        }
 +
 +        @Override
 +        protected void onAttach() {
 +            super.onAttach();
 +            // make sure the button will get correct height if the button should
 +            // be visible when the grid is rendered the first time.
 +            Scheduler.get().scheduleDeferred(new ScheduledCommand() {
 +
 +                @Override
 +                public void execute() {
 +                    setHeightToHeaderCellHeight();
 +                }
 +            });
 +        }
 +
 +        @Override
 +        public boolean isEnabled() {
 +            return openCloseButton.isEnabled();
 +        }
 +
 +        @Override
 +        public void setEnabled(boolean enabled) {
 +            if (!enabled && isOpen()) {
 +                close();
 +            }
 +
 +            openCloseButton.setEnabled(enabled);
 +        }
 +    }
 +
 +    /**
 +     * UI and functionality related to hiding columns with toggles in the
 +     * sidebar.
 +     */
 +    private final class ColumnHider {
 +
 +        /** Map from columns to their hiding toggles, component might change */
 +        private HashMap<Column<?, T>, MenuItem> columnToHidingToggleMap = new HashMap<Grid.Column<?, T>, MenuItem>();
 +
 +        /**
 +         * When column is being hidden with a toggle, do not refresh toggles for
 +         * no reason. Also helps for keeping the keyboard navigation working.
 +         */
 +        private boolean hidingColumn;
 +
 +        private void updateColumnHidable(final Column<?, T> column) {
 +            if (column.isHidable()) {
 +                MenuItem toggle = columnToHidingToggleMap.get(column);
 +                if (toggle == null) {
 +                    toggle = createToggle(column);
 +                }
 +                toggle.setStyleName("hidden", column.isHidden());
 +            } else if (columnToHidingToggleMap.containsKey(column)) {
 +                sidebar.menuBar.removeItem((columnToHidingToggleMap
 +                        .remove(column)));
 +            }
 +            updateTogglesOrder();
 +        }
 +
 +        private MenuItem createToggle(final Column<?, T> column) {
 +            MenuItem toggle = new MenuItem(createHTML(column), true,
 +                    new ScheduledCommand() {
 +
 +                        @Override
 +                        public void execute() {
 +                            hidingColumn = true;
 +                            column.setHidden(!column.isHidden(), true);
 +                            hidingColumn = false;
 +                        }
 +                    });
 +            toggle.addStyleName("column-hiding-toggle");
 +            columnToHidingToggleMap.put(column, toggle);
 +            return toggle;
 +        }
 +
 +        private String createHTML(Column<?, T> column) {
 +            final StringBuffer buf = new StringBuffer();
 +            buf.append("<span class=\"");
 +            if (column.isHidden()) {
 +                buf.append("v-off");
 +            } else {
 +                buf.append("v-on");
 +            }
 +            buf.append("\"><div>");
 +            String caption = column.getHidingToggleCaption();
 +            if (caption == null) {
 +                caption = column.headerCaption;
 +            }
 +            buf.append(caption);
 +            buf.append("</div></span>");
 +
 +            return buf.toString();
 +        }
 +
 +        private void updateTogglesOrder() {
 +            if (!hidingColumn) {
 +                int lastIndex = 0;
 +                for (Column<?, T> column : getColumns()) {
 +                    if (column.isHidable()) {
 +                        final MenuItem menuItem = columnToHidingToggleMap
 +                                .get(column);
 +                        sidebar.menuBar.removeItem(menuItem);
 +                        sidebar.menuBar.insertItem(menuItem, lastIndex++);
 +                    }
 +                }
 +            }
 +        }
 +
 +        private void updateHidingToggle(Column<?, T> column) {
 +            if (column.isHidable()) {
 +                MenuItem toggle = columnToHidingToggleMap.get(column);
 +                toggle.setHTML(createHTML(column));
 +                toggle.setStyleName("hidden", column.isHidden());
 +            } // else we can just ignore
 +        }
 +
 +        private void removeColumnHidingToggle(Column<?, T> column) {
 +            sidebar.menuBar.removeItem(columnToHidingToggleMap.get(column));
 +        }
 +
 +    }
 +
 +    /**
 +     * Escalator used internally by grid to render the rows
 +     */
 +    private Escalator escalator = GWT.create(Escalator.class);
 +
 +    private final Header header = GWT.create(Header.class);
 +
 +    private final Footer footer = GWT.create(Footer.class);
 +
 +    private final Sidebar sidebar = new Sidebar(this);
 +
 +    /**
 +     * List of columns in the grid. Order defines the visible order.
 +     */
 +    private List<Column<?, T>> columns = new ArrayList<Column<?, T>>();
 +
 +    /**
 +     * The datasource currently in use. <em>Note:</em> it is <code>null</code>
 +     * on initialization, but not after that.
 +     */
 +    private DataSource<T> dataSource;
 +
 +    /**
 +     * Currently available row range in DataSource.
 +     */
 +    private Range currentDataAvailable = Range.withLength(0, 0);
 +
 +    /**
 +     * The number of frozen columns, 0 freezes the selection column if
 +     * displayed, -1 also prevents selection col from freezing.
 +     */
 +    private int frozenColumnCount = 0;
 +
 +    /**
 +     * Current sort order. The (private) sort() method reads this list to
 +     * determine the order in which to present rows.
 +     */
 +    private List<SortOrder> sortOrder = new ArrayList<SortOrder>();
 +
 +    private Renderer<Boolean> selectColumnRenderer = null;
 +
 +    private SelectionColumn selectionColumn;
 +
 +    private String rowStripeStyleName;
 +    private String rowHasDataStyleName;
 +    private String rowSelectedStyleName;
 +    private String cellFocusStyleName;
 +    private String rowFocusStyleName;
 +
 +    /**
 +     * Current selection model.
 +     */
 +    private SelectionModel<T> selectionModel;
 +
 +    protected final CellFocusHandler cellFocusHandler;
 +
 +    private final UserSorter sorter = new UserSorter();
 +
 +    private final Editor<T> editor = GWT.create(Editor.class);
 +
 +    private boolean dataIsBeingFetched = false;
 +
 +    /**
 +     * The cell a click event originated from
 +     * <p>
 +     * This is a workaround to make Chrome work like Firefox. In Chrome,
 +     * normally if you start a drag on one cell and release on:
 +     * <ul>
 +     * <li>that same cell, the click event is that {@code <td>}.
 +     * <li>a cell on that same row, the click event is the parent {@code <tr>}.
 +     * <li>a cell on another row, the click event is the table section ancestor
 +     * ({@code <thead>}, {@code <tbody>} or {@code <tfoot>}).
 +     * </ul>
 +     * 
 +     * @see #onBrowserEvent(Event)
 +     */
 +    private Cell cellOnPrevMouseDown;
 +
 +    /**
 +     * A scheduled command to re-evaluate the widths of <em>all columns</em>
 +     * that have calculated widths. Most probably called because
 +     * minwidth/maxwidth/expandratio has changed.
 +     */
 +    private final AutoColumnWidthsRecalculator autoColumnWidthsRecalculator = new AutoColumnWidthsRecalculator();
 +
 +    private boolean enabled = true;
 +
 +    private DetailsGenerator detailsGenerator = DetailsGenerator.NULL;
 +    private GridSpacerUpdater gridSpacerUpdater = new GridSpacerUpdater();
 +    /** A set keeping track of the indices of all currently open details */
 +    private Set<Integer> visibleDetails = new HashSet<Integer>();
 +
 +    private boolean columnReorderingAllowed;
 +
 +    private ColumnHider columnHider = new ColumnHider();
 +
 +    private DragAndDropHandler dndHandler = new DragAndDropHandler();
 +
 +    private AutoScroller autoScroller = new AutoScroller(this);
 +
 +    private DragAndDropHandler.DragAndDropCallback headerCellDndCallback = new DragAndDropCallback() {
 +
 +        private final AutoScrollerCallback autoScrollerCallback = new AutoScrollerCallback() {
 +
 +            @Override
 +            public void onAutoScroll(int scrollDiff) {
 +                autoScrollX = scrollDiff;
 +                onDragUpdate(null);
 +            }
 +
 +            @Override
 +            public void onAutoScrollReachedMin() {
 +                // make sure the drop marker is visible on the left
 +                autoScrollX = 0;
 +                updateDragDropMarker(clientX);
 +            }
 +
 +            @Override
 +            public void onAutoScrollReachedMax() {
 +                // make sure the drop marker is visible on the right
 +                autoScrollX = 0;
 +                updateDragDropMarker(clientX);
 +            }
 +        };
 +        /**
 +         * Elements for displaying the dragged column(s) and drop marker
 +         * properly
 +         */
 +        private Element table;
 +        private Element tableHeader;
 +        /** Marks the column drop location */
 +        private Element dropMarker;
 +        /** A copy of the dragged column(s), moves with cursor. */
 +        private Element dragElement;
 +        /** Tracks index of the column whose left side the drop would occur */
 +        private int latestColumnDropIndex;
 +        /**
 +         * Map of possible drop positions for the column and the corresponding
 +         * column index.
 +         */
 +        private final TreeMap<Double, Integer> possibleDropPositions = new TreeMap<Double, Integer>();
 +        /**
 +         * Makes sure that drag cancel doesn't cause anything unwanted like sort
 +         */
 +        private HandlerRegistration columnSortPreventRegistration;
 +
 +        private int clientX;
 +
 +        /** How much the grid is being auto scrolled while dragging. */
 +        private int autoScrollX;
 +
 +        /** Captures the value of the focused column before reordering */
 +        private int focusedColumnIndex;
 +
 +        /** Offset caused by the drag and drop marker width */
 +        private double dropMarkerWidthOffset;
 +
 +        private void initHeaderDragElementDOM() {
 +            if (table == null) {
 +                tableHeader = DOM.createTHead();
 +                dropMarker = DOM.createDiv();
 +                tableHeader.appendChild(dropMarker);
 +                table = DOM.createTable();
 +                table.appendChild(tableHeader);
 +                table.setClassName("header-drag-table");
 +            }
 +            // update the style names on each run in case primary name has been
 +            // modified
 +            tableHeader.setClassName(escalator.getHeader().getElement()
 +                    .getClassName());
 +            dropMarker.setClassName(getStylePrimaryName() + "-drop-marker");
 +            int topOffset = 0;
 +            for (int i = 0; i < eventCell.getRowIndex(); i++) {
 +                topOffset += escalator.getHeader().getRowElement(i)
 +                        .getFirstChildElement().getOffsetHeight();
 +            }
 +            tableHeader.getStyle().setTop(topOffset, Unit.PX);
 +
 +            getElement().appendChild(table);
 +
 +            dropMarkerWidthOffset = WidgetUtil
 +                    .getRequiredWidthBoundingClientRectDouble(dropMarker) / 2;
 +        }
 +
 +        @Override
 +        public void onDragUpdate(Event e) {
 +            if (e != null) {
 +                clientX = WidgetUtil.getTouchOrMouseClientX(e);
 +                autoScrollX = 0;
 +            }
 +            resolveDragElementHorizontalPosition(clientX);
 +            updateDragDropMarker(clientX);
 +        }
 +
 +        private void updateDragDropMarker(final int clientX) {
 +            final double scrollLeft = getScrollLeft();
 +            final double cursorXCoordinate = clientX
 +                    - escalator.getHeader().getElement().getAbsoluteLeft();
 +            final Entry<Double, Integer> cellEdgeOnRight = possibleDropPositions
 +                    .ceilingEntry(cursorXCoordinate);
 +            final Entry<Double, Integer> cellEdgeOnLeft = possibleDropPositions
 +                    .floorEntry(cursorXCoordinate);
 +            final double diffToRightEdge = cellEdgeOnRight == null ? Double.MAX_VALUE
 +                    : cellEdgeOnRight.getKey() - cursorXCoordinate;
 +            final double diffToLeftEdge = cellEdgeOnLeft == null ? Double.MAX_VALUE
 +                    : cursorXCoordinate - cellEdgeOnLeft.getKey();
 +
 +            double dropMarkerLeft = 0 - scrollLeft;
 +            if (diffToRightEdge > diffToLeftEdge) {
 +                latestColumnDropIndex = cellEdgeOnLeft.getValue();
 +                dropMarkerLeft += cellEdgeOnLeft.getKey();
 +            } else {
 +                latestColumnDropIndex = cellEdgeOnRight.getValue();
 +                dropMarkerLeft += cellEdgeOnRight.getKey();
 +            }
 +
 +            dropMarkerLeft += autoScrollX;
 +
 +            final double frozenColumnsWidth = autoScroller
 +                    .getFrozenColumnsWidth();
 +            final double rightBoundaryForDrag = getSidebarBoundaryComparedTo(dropMarkerLeft);
 +            final int visibleColumns = getVisibleColumns().size();
 +
 +            // First check if the drop marker should move left because of the
 +            // sidebar opening button. this only the case if the grid is
 +            // scrolled to the right
 +            if (latestColumnDropIndex == visibleColumns
 +                    && rightBoundaryForDrag < dropMarkerLeft
 +                    && dropMarkerLeft <= escalator.getInnerWidth()) {
 +                dropMarkerLeft = rightBoundaryForDrag - dropMarkerWidthOffset;
 +            }
 +
 +            // Check if the drop marker shouldn't be shown at all
 +            else if (dropMarkerLeft < frozenColumnsWidth
 +                    || dropMarkerLeft > Math.min(rightBoundaryForDrag,
 +                            escalator.getInnerWidth()) || dropMarkerLeft < 0) {
 +                dropMarkerLeft = -10000000;
 +            }
 +            dropMarker.getStyle().setLeft(dropMarkerLeft, Unit.PX);
 +        }
 +
 +        private void resolveDragElementHorizontalPosition(final int clientX) {
 +            double left = clientX - table.getAbsoluteLeft();
 +
 +            // Do not show the drag element beyond a spanned header cell
 +            // limitation
 +            final Double leftBound = possibleDropPositions.firstKey();
 +            final Double rightBound = possibleDropPositions.lastKey();
 +            final double scrollLeft = getScrollLeft();
 +            if (left + scrollLeft < leftBound) {
 +                left = leftBound - scrollLeft + autoScrollX;
 +            } else if (left + scrollLeft > rightBound) {
 +                left = rightBound - scrollLeft + autoScrollX;
 +            }
 +
 +            // Do not show the drag element beyond the grid
 +            final double sidebarBoundary = getSidebarBoundaryComparedTo(left);
 +            final double gridBoundary = escalator.getInnerWidth();
 +            final double rightBoundary = Math
 +                    .min(sidebarBoundary, gridBoundary);
 +
 +            // Do not show on left of the frozen columns (even if scrolled)
 +            final int frozenColumnsWidth = (int) autoScroller
 +                    .getFrozenColumnsWidth();
 +
 +            left = Math.max(frozenColumnsWidth, Math.min(left, rightBoundary));
 +
 +            left -= dragElement.getClientWidth() / 2;
 +            dragElement.getStyle().setLeft(left, Unit.PX);
 +        }
 +
 +        private boolean isSidebarOnDraggedRow() {
 +            return eventCell.getRowIndex() == 0 && sidebar.isInDOM()
 +                    && !sidebar.isOpen();
 +        }
 +
 +        /**
 +         * Returns the sidebar left coordinate, in relation to the grid. Or
 +         * Double.MAX_VALUE if it doesn't cause a boundary.
 +         */
 +        private double getSidebarBoundaryComparedTo(double left) {
 +            if (isSidebarOnDraggedRow()) {
 +                double absoluteLeft = left + getElement().getAbsoluteLeft();
 +                double sidebarLeft = sidebar.getElement().getAbsoluteLeft();
 +                double diff = absoluteLeft - sidebarLeft;
 +
 +                if (diff > 0) {
 +                    return left - diff;
 +                }
 +            }
 +            return Double.MAX_VALUE;
 +        }
 +
 +        @Override
 +        public boolean onDragStart(Event e) {
 +            calculatePossibleDropPositions();
 +
 +            if (possibleDropPositions.isEmpty()) {
 +                return false;
 +            }
 +
 +            initHeaderDragElementDOM();
 +            // needs to clone focus and sorting indicators too (UX)
 +            dragElement = DOM.clone(eventCell.getElement(), true);
 +            dragElement.getStyle().clearWidth();
 +            dropMarker.getStyle().setProperty("height",
 +                    dragElement.getStyle().getHeight());
 +            tableHeader.appendChild(dragElement);
 +            // mark the column being dragged for styling
 +            eventCell.getElement().addClassName("dragged");
 +            // mark the floating cell, for styling & testing
 +            dragElement.addClassName("dragged-column-header");
 +
 +            // start the auto scroll handler
 +            autoScroller.setScrollArea(60);
 +            autoScroller.start(e, ScrollAxis.HORIZONTAL, autoScrollerCallback);
 +            return true;
 +        }
 +
 +        @Override
 +        public void onDragEnd() {
 +            table.removeFromParent();
 +            dragElement.removeFromParent();
 +            eventCell.getElement().removeClassName("dragged");
 +        }
 +
 +        @Override
 +        public void onDrop() {
 +            final int draggedColumnIndex = eventCell.getColumnIndex();
 +            final int colspan = header.getRow(eventCell.getRowIndex())
 +                    .getCell(eventCell.getColumn()).getColspan();
 +            if (latestColumnDropIndex != draggedColumnIndex
 +                    && latestColumnDropIndex != (draggedColumnIndex + colspan)) {
 +                List<Column<?, T>> columns = getColumns();
 +                List<Column<?, T>> reordered = new ArrayList<Column<?, T>>();
 +                if (draggedColumnIndex < latestColumnDropIndex) {
 +                    reordered.addAll(columns.subList(0, draggedColumnIndex));
 +                    reordered.addAll(columns.subList(draggedColumnIndex
 +                            + colspan, latestColumnDropIndex));
 +                    reordered.addAll(columns.subList(draggedColumnIndex,
 +                            draggedColumnIndex + colspan));
 +                    reordered.addAll(columns.subList(latestColumnDropIndex,
 +                            columns.size()));
 +                } else {
 +                    reordered.addAll(columns.subList(0, latestColumnDropIndex));
 +                    reordered.addAll(columns.subList(draggedColumnIndex,
 +                            draggedColumnIndex + colspan));
 +                    reordered.addAll(columns.subList(latestColumnDropIndex,
 +                            draggedColumnIndex));
 +                    reordered.addAll(columns.subList(draggedColumnIndex
 +                            + colspan, columns.size()));
 +                }
 +                reordered.remove(selectionColumn); // since setColumnOrder will
 +                                                   // add it anyway!
 +
 +                // capture focused cell column before reorder
 +                Cell focusedCell = cellFocusHandler.getFocusedCell();
 +                if (focusedCell != null) {
 +                    // take hidden columns into account
 +                    focusedColumnIndex = getColumns().indexOf(
 +                            getVisibleColumn(focusedCell.getColumn()));
 +                }
 +
 +                Column<?, T>[] array = reordered.toArray(new Column[reordered
 +                        .size()]);
 +                setColumnOrder(array);
 +                transferCellFocusOnDrop();
 +            } // else no reordering
 +        }
 +
 +        private void transferCellFocusOnDrop() {
 +            final Cell focusedCell = cellFocusHandler.getFocusedCell();
 +            if (focusedCell != null) {
 +                final int focusedColumnIndexDOM = focusedCell.getColumn();
 +                final int focusedRowIndex = focusedCell.getRow();
 +                final int draggedColumnIndex = eventCell.getColumnIndex();
 +                // transfer focus if it was effected by the new column order
 +                final RowContainer rowContainer = escalator
 +                        .findRowContainer(focusedCell.getElement());
 +                if (focusedColumnIndex == draggedColumnIndex) {
 +                    // move with the dragged column
 +                    int adjustedDropIndex = latestColumnDropIndex > draggedColumnIndex ? latestColumnDropIndex - 1
 +                            : latestColumnDropIndex;
 +                    // remove hidden columns from indexing
 +                    adjustedDropIndex = getVisibleColumns().indexOf(
 +                            getColumn(adjustedDropIndex));
 +                    cellFocusHandler.setCellFocus(focusedRowIndex,
 +                            adjustedDropIndex, rowContainer);
 +                } else if (latestColumnDropIndex <= focusedColumnIndex
 +                        && draggedColumnIndex > focusedColumnIndex) {
 +                    cellFocusHandler.setCellFocus(focusedRowIndex,
 +                            focusedColumnIndexDOM + 1, rowContainer);
 +                } else if (latestColumnDropIndex > focusedColumnIndex
 +                        && draggedColumnIndex < focusedColumnIndex) {
 +                    cellFocusHandler.setCellFocus(focusedRowIndex,
 +                            focusedColumnIndexDOM - 1, rowContainer);
 +                }
 +            }
 +        }
 +
 +        @Override
 +        public void onDragCancel() {
 +            // cancel next click so that we may prevent column sorting if
 +            // mouse was released on top of the dragged cell
 +            if (columnSortPreventRegistration == null) {
 +                columnSortPreventRegistration = Event
 +                        .addNativePreviewHandler(new NativePreviewHandler() {
 +
 +                            @Override
 +                            public void onPreviewNativeEvent(
 +                                    NativePreviewEvent event) {
 +                                if (event.getTypeInt() == Event.ONCLICK) {
 +                                    event.cancel();
 +                                    event.getNativeEvent().preventDefault();
 +                                    columnSortPreventRegistration
 +                                            .removeHandler();
 +                                    columnSortPreventRegistration = null;
 +                                }
 +                            }
 +                        });
 +            }
 +            autoScroller.stop();
 +        }
 +
 +        /**
 +         * Returns the amount of frozen columns. The selection column is always
 +         * considered frozen, since it can't be moved.
 +         */
 +        private int getSelectionAndFrozenColumnCount() {
 +            // no matter if selection column is frozen or not, it is considered
 +            // frozen for column dnd reorder
 +            if (getSelectionModel().getSelectionColumnRenderer() != null) {
 +                return Math.max(0, getFrozenColumnCount()) + 1;
 +            } else {
 +                return Math.max(0, getFrozenColumnCount());
 +            }
 +        }
 +
 +        @SuppressWarnings("boxing")
 +        private void calculatePossibleDropPositions() {
 +            possibleDropPositions.clear();
 +
 +            final int draggedColumnIndex = eventCell.getColumnIndex();
 +            final StaticRow<?> draggedCellRow = header.getRow(eventCell
 +                    .getRowIndex());
 +            final int draggedColumnRightIndex = draggedColumnIndex
 +                    + draggedCellRow.getCell(eventCell.getColumn())
 +                            .getColspan();
 +            final int frozenColumns = getSelectionAndFrozenColumnCount();
 +            final Range draggedCellRange = Range.between(draggedColumnIndex,
 +                    draggedColumnRightIndex);
 +            /*
 +             * If the dragged cell intersects with a spanned cell in any other
 +             * header or footer row, then the drag is limited inside that
 +             * spanned cell. The same rules apply: the cell can't be dropped
 +             * inside another spanned cell. The left and right bounds keep track
 +             * of the edges of the most limiting spanned cell.
 +             */
 +            int leftBound = -1;
 +            int rightBound = getColumnCount() + 1;
 +
 +            final HashSet<Integer> unavailableColumnDropIndices = new HashSet<Integer>();
 +            final List<StaticRow<?>> rows = new ArrayList<StaticRow<?>>();
 +            rows.addAll(header.getRows());
 +            rows.addAll(footer.getRows());
 +            for (StaticRow<?> row : rows) {
 +                if (!row.hasSpannedCells()) {
 +                    continue;
 +                }
 +                final boolean isDraggedCellRow = row.equals(draggedCellRow);
 +                for (int cellColumnIndex = frozenColumns; cellColumnIndex < getColumnCount(); cellColumnIndex++) {
 +                    StaticCell cell = row.getCell(getColumn(cellColumnIndex));
 +                    int colspan = cell.getColspan();
 +                    if (colspan <= 1) {
 +                        continue;
 +                    }
 +                    final int cellColumnRightIndex = cellColumnIndex + colspan;
 +                    final Range cellRange = Range.between(cellColumnIndex,
 +                            cellColumnRightIndex);
 +                    final boolean intersects = draggedCellRange
 +                            .intersects(cellRange);
 +                    if (intersects && !isDraggedCellRow) {
 +                        // if the currently iterated cell is inside or same as
 +                        // the dragged cell, then it doesn't restrict the drag
 +                        if (cellRange.isSubsetOf(draggedCellRange)) {
 +                            cellColumnIndex = cellColumnRightIndex - 1;
 +                            continue;
 +                        }
 +                        /*
 +                         * if the dragged cell is a spanned cell and it crosses
 +                         * with the currently iterated cell without sharing
 +                         * either start or end then not possible to drag the
 +                         * cell.
 +                         */
 +                        if (!draggedCellRange.isSubsetOf(cellRange)) {
 +                            return;
 +                        }
 +                        // the spanned cell overlaps the dragged cell (but is
 +                        // not the dragged cell)
 +                        if (cellColumnIndex <= draggedColumnIndex
 +                                && cellColumnIndex > leftBound) {
 +                            leftBound = cellColumnIndex;
 +                        }
 +                        if (cellColumnRightIndex < rightBound) {
 +                            rightBound = cellColumnRightIndex;
 +                        }
 +                        cellColumnIndex = cellColumnRightIndex - 1;
 +                    }
 +
 +                    else { // can't drop inside a spanned cell, or this is the
 +                           // dragged cell
 +                        while (colspan > 1) {
 +                            cellColumnIndex++;
 +                            colspan--;
 +                            unavailableColumnDropIndices.add(cellColumnIndex);
 +                        }
 +                    }
 +                }
 +            }
 +
 +            if (leftBound == (rightBound - 1)) {
 +                return;
 +            }
 +
 +            double position = autoScroller.getFrozenColumnsWidth();
 +            // iterate column indices and add possible drop positions
 +            for (int i = frozenColumns; i < getColumnCount(); i++) {
 +                Column<?, T> column = getColumn(i);
 +                if (!unavailableColumnDropIndices.contains(i)
 +                        && !column.isHidden()) {
 +                    if (leftBound != -1) {
 +                        if (i >= leftBound && i <= rightBound) {
 +                            possibleDropPositions.put(position, i);
 +                        }
 +                    } else {
 +                        possibleDropPositions.put(position, i);
 +                    }
 +                }
 +                position += column.getWidthActual();
 +            }
 +
 +            if (leftBound == -1) {
 +                // add the right side of the last column as columns.size()
 +                possibleDropPositions.put(position, getColumnCount());
 +            }
 +        }
 +
 +    };
 +
 +    /**
 +     * Enumeration for easy setting of selection mode.
 +     */
 +    public enum SelectionMode {
 +
 +        /**
 +         * Shortcut for {@link SelectionModelSingle}.
 +         */
 +        SINGLE {
 +
 +            @Override
 +            protected <T> SelectionModel<T> createModel() {
 +                return GWT.create(SelectionModelSingle.class);
 +            }
 +        },
 +
 +        /**
 +         * Shortcut for {@link SelectionModelMulti}.
 +         */
 +        MULTI {
 +
 +            @Override
 +            protected <T> SelectionModel<T> createModel() {
 +                return GWT.create(SelectionModelMulti.class);
 +            }
 +        },
 +
 +        /**
 +         * Shortcut for {@link SelectionModelNone}.
 +         */
 +        NONE {
 +
 +            @Override
 +            protected <T> SelectionModel<T> createModel() {
 +                return GWT.create(SelectionModelNone.class);
 +            }
 +        };
 +
 +        protected abstract <T> SelectionModel<T> createModel();
 +    }
 +
 +    /**
 +     * Base class for grid columns internally used by the Grid. The user should
 +     * use {@link Column} when creating new columns.
 +     * 
 +     * @param <C>
 +     *            the column type
 +     * 
 +     * @param <T>
 +     *            the row type
 +     */
 +    public static abstract class Column<C, T> {
 +
 +        /**
 +         * Default renderer for GridColumns. Renders everything into text
 +         * through {@link Object#toString()}.
 +         */
 +        private final class DefaultTextRenderer implements Renderer<Object> {
 +            boolean warned = false;
 +            private final String DEFAULT_RENDERER_WARNING = "This column uses a dummy default TextRenderer. "
 +                    + "A more suitable renderer should be set using the setRenderer() method.";
 +
 +            @Override
 +            public void render(RendererCellReference cell, Object data) {
 +                if (!warned && !(data instanceof String)) {
 +                    getLogger().warning(
 +                            Column.this.toString() + ": "
 +                                    + DEFAULT_RENDERER_WARNING);
 +                    warned = true;
 +                }
 +
 +                final String text;
 +                if (data == null) {
 +                    text = "";
 +                } else {
 +                    text = data.toString();
 +                }
 +
 +                cell.getElement().setInnerText(text);
 +            }
 +        }
 +
 +        /**
 +         * the column is associated with
 +         */
 +        private Grid<T> grid;
 +
 +        /**
 +         * Width of column in pixels as {@link #setWidth(double)} has been
 +         * called
 +         */
 +        private double widthUser = GridConstants.DEFAULT_COLUMN_WIDTH_PX;
 +
 +        /**
 +         * Renderer for rendering a value into the cell
 +         */
 +        private Renderer<? super C> bodyRenderer;
 +
 +        private boolean sortable = false;
 +
 +        private boolean editable = true;
 +
 +        private boolean resizable = true;
 +
 +        private boolean hidden = false;
 +
 +        private boolean hidable = false;
 +
 +        private String headerCaption = "";
 +
 +        private String hidingToggleCaption = null;
 +
 +        private double minimumWidthPx = GridConstants.DEFAULT_MIN_WIDTH;
 +        private double maximumWidthPx = GridConstants.DEFAULT_MAX_WIDTH;
 +        private int expandRatio = GridConstants.DEFAULT_EXPAND_RATIO;
 +
 +        /**
 +         * Constructs a new column with a simple TextRenderer.
 +         */
 +        public Column() {
 +            setRenderer(new DefaultTextRenderer());
 +        }
 +
 +        /**
 +         * Constructs a new column with a simple TextRenderer.
 +         * 
 +         * @param caption
 +         *            The header caption for this column
 +         * 
 +         * @throws IllegalArgumentException
 +         *             if given header caption is null
 +         */
 +        public Column(String caption) throws IllegalArgumentException {
 +            this();
 +            setHeaderCaption(caption);
 +        }
 +
 +        /**
 +         * Constructs a new column with a custom renderer.
 +         * 
 +         * @param renderer
 +         *            The renderer to use for rendering the cells
 +         * 
 +         * @throws IllegalArgumentException
 +         *             if given Renderer is null
 +         */
 +        public Column(Renderer<? super C> renderer)
 +                throws IllegalArgumentException {
 +            setRenderer(renderer);
 +        }
 +
 +        /**
 +         * Constructs a new column with a custom renderer.
 +         * 
 +         * @param renderer
 +         *            The renderer to use for rendering the cells
 +         * @param caption
 +         *            The header caption for this column
 +         * 
 +         * @throws IllegalArgumentException
 +         *             if given Renderer or header caption is null
 +         */
 +        public Column(String caption, Renderer<? super C> renderer)
 +                throws IllegalArgumentException {
 +            this(renderer);
 +            setHeaderCaption(caption);
 +        }
 +
 +        /**
 +         * Internally used by the grid to set itself
 +         * 
 +         * @param grid
 +         */
 +        private void setGrid(Grid<T> grid) {
 +            if (this.grid != null && grid != null) {
 +                // Trying to replace grid
 +                throw new IllegalStateException("Column already is attached "
 +                        + "to a grid. Remove the column first from the grid "
 +                        + "and then add it. (in: " + toString() + ")");
 +            }
 +
 +            if (this.grid != null) {
 +                this.grid.recalculateColumnWidths();
 +            }
 +            this.grid = grid;
 +            if (this.grid != null) {
 +                this.grid.recalculateColumnWidths();
 +            }
 +        }
 +
 +        /**
 +         * Sets a header caption for this column.
 +         * 
 +         * @param caption
 +         *            The header caption for this column
 +         * @return the column itself
 +         * 
 +         */
 +        public Column<C, T> setHeaderCaption(String caption) {
 +            if (caption == null) {
 +                caption = "";
 +            }
 +
 +            if (!this.headerCaption.equals(caption)) {
 +                this.headerCaption = caption;
 +                if (grid != null) {
 +                    updateHeader();
 +                }
 +            }
 +
 +            return this;
 +        }
 +
 +        /**
 +         * Returns the current header caption for this column
 +         * 
 +         * @since 7.6
 +         * @return the header caption string
 +         */
 +        public String getHeaderCaption() {
 +            return headerCaption;
 +        }
 +
 +        private void updateHeader() {
 +            HeaderRow row = grid.getHeader().getDefaultRow();
 +            if (row != null) {
 +                row.getCell(this).setText(headerCaption);
 +                if (isHidable()) {
 +                    grid.columnHider.updateHidingToggle(this);
 +                }
 +            }
 +        }
 +
 +        /**
 +         * Returns the data that should be rendered into the cell. By default
 +         * returning Strings and Widgets are supported. If the return type is a
 +         * String then it will be treated as preformatted text.
 +         * <p>
 +         * To support other types you will need to pass a custom renderer to the
 +         * column via the column constructor.
 +         * 
 +         * @param row
 +         *            The row object that provides the cell content.
 +         * 
 +         * @return The cell content
 +         */
 +        public abstract C getValue(T row);
 +
 +        /**
 +         * The renderer to render the cell with. By default renders the data as
 +         * a String or adds the widget into the cell if the column type is of
 +         * widget type.
 +         * 
 +         * @return The renderer to render the cell content with
 +         */
 +        public Renderer<? super C> getRenderer() {
 +            return bodyRenderer;
 +        }
 +
 +        /**
 +         * Sets a custom {@link Renderer} for this column.
 +         * 
 +         * @param renderer
 +         *            The renderer to use for rendering the cells
 +         * @return the column itself
 +         * 
 +         * @throws IllegalArgumentException
 +         *             if given Renderer is null
 +         */
 +        public Column<C, T> setRenderer(Renderer<? super C> renderer)
 +                throws IllegalArgumentException {
 +            if (renderer == null) {
 +                throw new IllegalArgumentException("Renderer cannot be null.");
 +            }
 +
 +            if (renderer != bodyRenderer) {
 +                // Variables used to restore removed column.
 +                boolean columnRemoved = false;
 +                double widthInConfiguration = 0.0d;
 +                ColumnConfiguration conf = null;
 +                int index = 0;
 +
 +                if (grid != null
 +                        && (bodyRenderer instanceof WidgetRenderer || renderer instanceof WidgetRenderer)) {
 +                    // Column needs to be recreated.
 +                    index = grid.getColumns().indexOf(this);
 +                    conf = grid.escalator.getColumnConfiguration();
 +                    widthInConfiguration = conf.getColumnWidth(index);
 +
 +                    conf.removeColumns(index, 1);
 +                    columnRemoved = true;
 +                }
 +
 +                // Complex renderers need to be destroyed.
 +                if (bodyRenderer instanceof ComplexRenderer) {
 +                    ((ComplexRenderer) bodyRenderer).destroy();
 +                }
 +
 +                bodyRenderer = renderer;
 +
 +                if (columnRemoved) {
 +                    // Restore the column.
 +                    conf.insertColumns(index, 1);
 +                    conf.setColumnWidth(index, widthInConfiguration);
 +                }
 +
 +                if (grid != null) {
 +                    grid.refreshBody();
 +                }
 +            }
 +            return this;
 +        }
 +
 +        /**
 +         * Sets the pixel width of the column. Use a negative value for the grid
 +         * to autosize column based on content and available space.
 +         * <p>
 +         * This action is done "finally", once the current execution loop
 +         * returns. This is done to reduce overhead of unintentionally always
 +         * recalculate all columns, when modifying several columns at once.
 +         * <p>
 +         * If the column is currently {@link #isHidden() hidden}, then this set
 +         * width has effect only once the column has been made visible again.
 +         * 
 +         * @param pixels
 +         *            the width in pixels or negative for auto sizing
 +         */
 +        public Column<C, T> setWidth(double pixels) {
 +            if (!WidgetUtil.pixelValuesEqual(widthUser, pixels)) {
 +                widthUser = pixels;
 +                if (!isHidden()) {
 +                    scheduleColumnWidthRecalculator();
 +                }
 +            }
 +            return this;
 +        }
 +
 +        void doSetWidth(double pixels) {
 +            assert !isHidden() : "applying width for a hidden column";
 +            if (grid != null) {
 +                int index = grid.getVisibleColumns().indexOf(this);
 +                ColumnConfiguration conf = grid.escalator
 +                        .getColumnConfiguration();
 +                conf.setColumnWidth(index, pixels);
 +            }
 +        }
 +
 +        /**
 +         * Returns the pixel width of the column as given by the user.
 +         * <p>
 +         * <em>Note:</em> If a negative value was given to
 +         * {@link #setWidth(double)}, that same negative value is returned here.
 +         * <p>
 +         * <em>Note:</em> Returns the value, even if the column is currently
 +         * {@link #isHidden() hidden}.
 +         * 
 +         * @return pixel width of the column, or a negative number if the column
 +         *         width has been automatically calculated.
 +         * @see #setWidth(double)
 +         * @see #getWidthActual()
 +         */
 +        public double getWidth() {
 +            return widthUser;
 +        }
 +
 +        /**
 +         * Returns the effective pixel width of the column.
 +         * <p>
 +         * This differs from {@link #getWidth()} only when the column has been
 +         * automatically resized, or when the column is currently
 +         * {@link #isHidden() hidden}, when the value is 0.
 +         * 
 +         * @return pixel width of the column.
 +         */
 +        public double getWidthActual() {
 +            if (isHidden()) {
 +                return 0;
 +            }
 +            return grid.escalator.getColumnConfiguration()
 +                    .getColumnWidthActual(
 +                            grid.getVisibleColumns().indexOf(this));
 +        }
 +
 +        void reapplyWidth() {
 +            scheduleColumnWidthRecalculator();
 +        }
 +
 +        /**
 +         * Sets whether the column should be sortable by the user. The grid can
 +         * be sorted by a sortable column by clicking or tapping the column's
 +         * default header. Programmatic sorting using the Grid#sort methods is
 +         * not affected by this setting.
 +         * 
 +         * @param sortable
 +         *            {@code true} if the user should be able to sort the
 +         *            column, {@code false} otherwise
 +         * @return the column itself
 +         */
 +        public Column<C, T> setSortable(boolean sortable) {
 +            if (this.sortable != sortable) {
 +                this.sortable = sortable;
 +                if (grid != null) {
 +                    grid.refreshHeader();
 +                }
 +            }
 +            return this;
 +        }
 +
 +        /**
 +         * Returns whether the user can sort the grid by this column.
 +         * <p>
 +         * <em>Note:</em> it is possible to sort by this column programmatically
 +         * using the Grid#sort methods regardless of the returned value.
 +         * 
 +         * @return {@code true} if the column is sortable by the user,
 +         *         {@code false} otherwise
 +         */
 +        public boolean isSortable() {
 +            return sortable;
 +        }
 +
 +        /**
 +         * Sets whether this column can be resized by the user.
 +         * 
 +         * @since 7.6
 +         * 
 +         * @param resizable
 +         *            {@code true} if this column should be resizable,
 +         *            {@code false} otherwise
 +         */
 +        public Column<C, T> setResizable(boolean resizable) {
 +            if (this.resizable != resizable) {
 +                this.resizable = resizable;
 +                if (grid != null) {
 +                    grid.refreshHeader();
 +                }
 +            }
 +            return this;
 +        }
 +
 +        /**
 +         * Returns whether this column can be resized by the user. Default is
 +         * {@code true}.
 +         * <p>
 +         * <em>Note:</em> the column can be programmatically resized using
 +         * {@link #setWidth(double)} and {@link #setWidthUndefined()} regardless
 +         * of the returned value.
 +         * 
 +         * @since 7.6
 +         * 
 +         * @return {@code true} if this column is resizable, {@code false}
 +         *         otherwise
 +         */
 +        public boolean isResizable() {
 +            return resizable;
 +        }
 +
 +        /**
 +         * Hides or shows the column. By default columns are visible before
 +         * explicitly hiding them.
 +         * 
 +         * @since 7.5.0
 +         * @param hidden
 +         *            <code>true</code> to hide the column, <code>false</code>
 +         *            to show
 +         */
 +        public Column<C, T> setHidden(boolean hidden) {
 +            setHidden(hidden, false);
 +            return this;
 +        }
 +
 +        private void setHidden(boolean hidden, boolean userOriginated) {
 +            if (this.hidden != hidden) {
 +                if (hidden) {
 +                    grid.escalator.getColumnConfiguration().removeColumns(
 +                            grid.getVisibleColumns().indexOf(this), 1);
 +                    this.hidden = hidden;
 +                } else {
 +                    this.hidden = hidden;
 +
 +                    final int columnIndex = grid.getVisibleColumns().indexOf(
 +                            this);
 +                    grid.escalator.getColumnConfiguration().insertColumns(
 +                            columnIndex, 1);
 +
 +                    // make sure column is set to frozen if it needs to be,
 +                    // escalator doesn't handle situation where the added column
 +                    // would be the last frozen column
 +                    int gridFrozenColumns = grid.getFrozenColumnCount();
 +                    int escalatorFrozenColumns = grid.escalator
 +                            .getColumnConfiguration().getFrozenColumnCount();
 +                    if (gridFrozenColumns > escalatorFrozenColumns
 +                            && escalatorFrozenColumns == columnIndex) {
 +                        grid.escalator.getColumnConfiguration()
 +                                .setFrozenColumnCount(++escalatorFrozenColumns);
 +                    }
 +                }
 +                grid.columnHider.updateHidingToggle(this);
 +                grid.header.updateColSpans();
 +                grid.footer.updateColSpans();
 +                scheduleColumnWidthRecalculator();
 +                this.grid.fireEvent(new ColumnVisibilityChangeEvent<T>(this,
 +                        hidden, userOriginated));
 +            }
 +        }
 +
 +        /**
 +         * Returns whether this column is hidden. Default is {@code false}.
 +         * 
 +         * @since 7.5.0
 +         * @return {@code true} if the column is currently hidden, {@code false}
 +         *         otherwise
 +         */
 +        public boolean isHidden() {
 +            return hidden;
 +        }
 +
 +        /**
 +         * Set whether it is possible for the user to hide this column or not.
 +         * Default is {@code false}.
 +         * <p>
 +         * <em>Note:</em> it is still possible to hide the column
 +         * programmatically using {@link #setHidden(boolean)}.
 +         * 
 +         * @since 7.5.0
 +         * @param hidable
 +         *            {@code true} the user can hide this column, {@code false}
 +         *            otherwise
 +         */
 +        public Column<C, T> setHidable(boolean hidable) {
 +            if (this.hidable != hidable) {
 +                this.hidable = hidable;
 +                grid.columnHider.updateColumnHidable(this);
 +            }
 +            return this;
 +        }
 +
 +        /**
 +         * Is it possible for the the user to hide this column. Default is
 +         * {@code false}.
 +         * <p>
 +         * <em>Note:</em> the column can be programmatically hidden using
 +         * {@link #setHidden(boolean)} regardless of the returned value.
 +         * 
 +         * @since 7.5.0
 +         * @return <code>true</code> if the user can hide the column,
 +         *         <code>false</code> if not
 +         */
 +        public boolean isHidable() {
 +            return hidable;
 +        }
 +
 +        /**
 +         * Sets the hiding toggle's caption for this column. Shown in the toggle
 +         * for this column in the grid's sidebar when the column is
 +         * {@link #isHidable() hidable}.
 +         * <p>
 +         * The default value is <code>null</code>. In this case the header
 +         * caption is used, see {@link #setHeaderCaption(String)}.
 +         * 
 +         * @since 7.5.0
 +         * @param hidingToggleCaption
 +         *            the caption for the hiding toggle for this column
 +         */
 +        public Column<C, T> setHidingToggleCaption(String hidingToggleCaption) {
 +            this.hidingToggleCaption = hidingToggleCaption;
 +            if (isHidable()) {
 +                grid.columnHider.updateHidingToggle(this);
 +            }
 +            return this;
 +        }
 +
 +        /**
 +         * Gets the hiding toggle caption for this column.
 +         * 
 +         * @since 7.5.0
 +         * @see #setHidingToggleCaption(String)
 +         * @return the hiding toggle's caption for this column
 +         */
 +        public String getHidingToggleCaption() {
 +            return hidingToggleCaption;
 +        }
 +
 +        @Override
 +        public String toString() {
 +            String details = "";
 +
 +            if (headerCaption != null && !headerCaption.isEmpty()) {
 +                details += "header:\"" + headerCaption + "\" ";
 +            } else {
 +                details += "header:empty ";
 +            }
 +
 +            if (grid != null) {
 +                int index = grid.getColumns().indexOf(this);
 +                if (index != -1) {
 +                    details += "attached:#" + index + " ";
 +                } else {
 +                    details += "attached:unindexed ";
 +                }
 +            } else {
 +                details += "detached ";
 +            }
 +
 +            details += "sortable:" + sortable + " ";
 +
 +            return getClass().getSimpleName() + "[" + details.trim() + "]";
 +        }
 +
 +        /**
 +         * Sets the minimum width for this column.
 +         * <p>
 +         * This defines the minimum guaranteed pixel width of the column
 +         * <em>when it is set to expand</em>.
 +         * <p>
 +         * This action is done "finally", once the current execution loop
 +         * returns. This is done to reduce overhead of unintentionally always
 +         * recalculate all columns, when modifying several columns at once.
 +         * 
 +         * @param pixels
 +         *            the minimum width
 +         * @return this column
 +         */
 +        public Column<C, T> setMinimumWidth(double pixels) {
 +            final double maxwidth = getMaximumWidth();
 +            if (pixels >= 0 && pixels > maxwidth && maxwidth >= 0) {
 +                throw new IllegalArgumentException("New minimum width ("
 +                        + pixels + ") was greater than maximum width ("
 +                        + maxwidth + ")");
 +            }
 +
 +            if (minimumWidthPx != pixels) {
 +                minimumWidthPx = pixels;
 +                scheduleColumnWidthRecalculator();
 +            }
 +            return this;
 +        }
 +
 +        /**
 +         * Sets the maximum width for this column.
 +         * <p>
 +         * This defines the maximum allowed pixel width of the column
 +         * <em>when it is set to expand</em>.
 +         * <p>
 +         * This action is done "finally", once the current execution loop
 +         * returns. This is done to reduce overhead of unintentionally always
 +         * recalculate all columns, when modifying several columns at once.
 +         * 
 +         * @param pixels
 +         *            the maximum width
 +         * @param immediately
 +         *            <code>true</code> if the widths should be executed
 +         *            immediately (ignoring lazy loading completely), or
 +         *            <code>false</code> if the command should be run after a
 +         *            while (duplicate non-immediately invocations are ignored).
 +         * @return this column
 +         */
 +        public Column<C, T> setMaximumWidth(double pixels) {
 +            final double minwidth = getMinimumWidth();
 +            if (pixels >= 0 && pixels < minwidth && minwidth >= 0) {
 +                throw new IllegalArgumentException("New maximum width ("
 +                        + pixels + ") was less than minimum width (" + minwidth
 +                        + ")");
 +            }
 +
 +            if (maximumWidthPx != pixels) {
 +                maximumWidthPx = pixels;
 +                scheduleColumnWidthRecalculator();
 +            }
 +            return this;
 +        }
 +
 +        /**
 +         * Sets the ratio with which the column expands.
 +         * <p>
 +         * By default, all columns expand equally (treated as if all of them had
 +         * an expand ratio of 1). Once at least one column gets a defined expand
 +         * ratio, the implicit expand ratio is removed, and only the defined
 +         * expand ratios are taken into account.
 +         * <p>
 +         * If a column has a defined width ({@link #setWidth(double)}), it
 +         * overrides this method's effects.
 +         * <p>
 +         * <em>Example:</em> A grid with three columns, with expand ratios 0, 1
 +         * and 2, respectively. The column with a <strong>ratio of 0 is exactly
 +         * as wide as its contents requires</strong>. The column with a ratio of
 +         * 1 is as wide as it needs, <strong>plus a third of any excess
 +         * space</strong>, bceause we have 3 parts total, and this column
 +         * reservs only one of those. The column with a ratio of 2, is as wide
 +         * as it needs to be, <strong>plus two thirds</strong> of the excess
 +         * width.
 +         * <p>
 +         * This action is done "finally", once the current execution loop
 +         * returns. This is done to reduce overhead of unintentionally always
 +         * recalculate all columns, when modifying several columns at once.
 +         * 
 +         * @param expandRatio
 +         *            the expand ratio of this column. {@code 0} to not have it
 +         *            expand at all. A negative number to clear the expand
 +         *            value.
 +         * @return this column
 +         */
 +        public Column<C, T> setExpandRatio(int ratio) {
 +            if (expandRatio != ratio) {
 +                expandRatio = ratio;
 +                scheduleColumnWidthRecalculator();
 +            }
 +            return this;
 +        }
 +
 +        /**
 +         * Clears the column's expand ratio.
 +         * <p>
 +         * Same as calling {@link #setExpandRatio(int) setExpandRatio(-1)}
 +         * 
 +         * @return this column
 +         */
 +        public Column<C, T> clearExpandRatio() {
 +            return setExpandRatio(-1);
 +        }
 +
 +        /**
 +         * Gets the minimum width for this column.
 +         * 
 +         * @return the minimum width for this column
 +         * @see #setMinimumWidth(double)
 +         */
 +        public double getMinimumWidth() {
 +            return minimumWidthPx;
 +        }
 +
 +        /**
 +         * Gets the maximum width for this column.
 +         * 
 +         * @return the maximum width for this column
 +         * @see #setMaximumWidth(double)
 +         */
 +        public double getMaximumWidth() {
 +            return maximumWidthPx;
 +        }
 +
 +        /**
 +         * Gets the expand ratio for this column.
 +         * 
 +         * @return the expand ratio for this column
 +         * @see #setExpandRatio(int)
 +         */
 +        public int getExpandRatio() {
 +            return expandRatio;
 +        }
 +
 +        /**
 +         * Sets whether the values in this column should be editable by the user
 +         * when the row editor is active. By default columns are editable.
 +         * 
 +         * @param editable
 +         *            {@code true} to set this column editable, {@code false}
 +         *            otherwise
 +         * @return this column
 +         * 
 +         * @throws IllegalStateException
 +         *             if the editor is currently active
 +         * 
 +         * @see Grid#editRow(int)
 +         * @see Grid#isEditorActive()
 +         */
 +        public Column<C, T> setEditable(boolean editable) {
 +            if (editable != this.editable && grid.isEditorActive()) {
 +                throw new IllegalStateException(
 +                        "Cannot change column editable status while the editor is active");
 +            }
 +            this.editable = editable;
 +            return this;
 +        }
 +
 +        /**
 +         * Returns whether the values in this column are editable by the user
 +         * when the row editor is active.
 +         * 
 +         * @return {@code true} if this column is editable, {@code false}
 +         *         otherwise
 +         * 
 +         * @see #setEditable(boolean)
 +         */
 +        public boolean isEditable() {
 +            return editable;
 +        }
 +
 +        private void scheduleColumnWidthRecalculator() {
 +            if (grid != null) {
 +                grid.recalculateColumnWidths();
 +            } else {
 +                /*
 +                 * NOOP
 +                 * 
 +                 * Since setGrid() will call reapplyWidths as the colum is
 +                 * attached to a grid, it will call setWidth, which, in turn,
 +                 * will call this method again. Therefore, it's guaranteed that
 +                 * the recalculation is scheduled eventually, once the column is
 +                 * attached to a grid.
 +                 */
 +            }
 +        }
 +
 +        /**
 +         * Resets the default header cell contents to column header captions.
 +         * 
 +         * @since 7.5.1
 +         * @param cell
 +         *            default header cell for this column
 +         */
 +        protected void setDefaultHeaderContent(HeaderCell cell) {
 +            cell.setText(headerCaption);
 +        }
 +    }
 +
 +    protected class BodyUpdater implements EscalatorUpdater {
 +
 +        @Override
 +        public void preAttach(Row row, Iterable<FlyweightCell> cellsToAttach) {
 +            int rowIndex = row.getRow();
 +            rowReference.set(rowIndex, getDataSource().getRow(rowIndex),
 +                    row.getElement());
 +            for (FlyweightCell cell : cellsToAttach) {
 +                Renderer<?> renderer = findRenderer(cell);
 +                if (renderer instanceof ComplexRenderer) {
 +                    try {
 +                        Column<?, T> column = getVisibleColumn(cell.getColumn());
 +                        rendererCellReference.set(cell,
 +                                getColumns().indexOf(column), column);
 +                        ((ComplexRenderer<?>) renderer)
 +                                .init(rendererCellReference);
 +                    } catch (RuntimeException e) {
 +                        getLogger().log(
 +                                Level.SEVERE,
 +                                "Error initing cell in column "
 +                                        + cell.getColumn(), e);
 +                    }
 +                }
 +            }
 +        }
 +
 +        @Override
 +        public void postAttach(Row row, Iterable<FlyweightCell> attachedCells) {
 +            for (FlyweightCell cell : attachedCells) {
 +                Renderer<?> renderer = findRenderer(cell);
 +                if (renderer instanceof WidgetRenderer) {
 +                    try {
 +                        WidgetRenderer<?, ?> widgetRenderer = (WidgetRenderer<?, ?>) renderer;
 +
 +                        Widget widget = widgetRenderer.createWidget();
 +                        assert widget != null : "WidgetRenderer.createWidget() returned null. It should return a widget.";
 +                        assert widget.getParent() == null : "WidgetRenderer.createWidget() returned a widget which already is attached.";
 +                        assert cell.getElement().getChildCount() == 0 : "Cell content should be empty when adding Widget";
 +
 +                        // Physical attach
 +                        cell.getElement().appendChild(widget.getElement());
 +
 +                        // Logical attach
 +                        setParent(widget, Grid.this);
 +                    } catch (RuntimeException e) {
 +                        getLogger().log(
 +                                Level.SEVERE,
 +                                "Error attaching child widget in column "
 +                                        + cell.getColumn(), e);
 +                    }
 +                }
 +            }
 +        }
 +
 +        @Override
 +        public void update(Row row, Iterable<FlyweightCell> cellsToUpdate) {
 +            int rowIndex = row.getRow();
 +            TableRowElement rowElement = row.getElement();
 +            T rowData = dataSource.getRow(rowIndex);
 +
 +            boolean hasData = rowData != null;
 +
 +            /*
 +             * TODO could be more efficient to build a list of all styles that
 +             * should be used and update the element only once instead of
 +             * attempting to update only the ones that have changed.
 +             */
 +
 +            // Assign stylename for rows with data
 +            boolean usedToHaveData = rowElement
 +                    .hasClassName(rowHasDataStyleName);
 +
 +            if (usedToHaveData != hasData) {
 +                setStyleName(rowElement, rowHasDataStyleName, hasData);
 +            }
 +
 +            boolean isEvenIndex = (row.getRow() % 2 == 0);
 +            setStyleName(rowElement, rowStripeStyleName, !isEvenIndex);
 +
 +            rowReference.set(rowIndex, rowData, rowElement);
 +
 +            if (hasData) {
 +                setStyleName(rowElement, rowSelectedStyleName,
 +                        isSelected(rowData));
 +
 +                if (rowStyleGenerator != null) {
 +                    try {
 +                        String rowStylename = rowStyleGenerator
 +                                .getStyle(rowReference);
 +                        setCustomStyleName(rowElement, rowStylename);
 +                    } catch (RuntimeException e) {
 +                        getLogger().log(
 +                                Level.SEVERE,
 +                                "Error generating styles for row "
 +                                        + row.getRow(), e);
 +                    }
 +                } else {
 +                    // Remove in case there was a generator previously
 +                    setCustomStyleName(rowElement, null);
 +                }
 +            } else if (usedToHaveData) {
 +                setStyleName(rowElement, rowSelectedStyleName, false);
 +
 +                setCustomStyleName(rowElement, null);
 +            }
 +
 +            cellFocusHandler.updateFocusedRowStyle(row);
 +
 +            for (FlyweightCell cell : cellsToUpdate) {
 +                Column<?, T> column = getVisibleColumn(cell.getColumn());
 +                final int columnIndex = getColumns().indexOf(column);
 +
 +                assert column != null : "Column was not found from cell ("
 +                        + cell.getColumn() + "," + cell.getRow() + ")";
 +
 +                cellFocusHandler.updateFocusedCellStyle(cell,
 +                        escalator.getBody());
 +
 +                if (hasData && cellStyleGenerator != null) {
 +                    try {
 +                        cellReference
 +                                .set(cell.getColumn(), columnIndex, column);
 +                        String generatedStyle = cellStyleGenerator
 +                                .getStyle(cellReference);
 +                        setCustomStyleName(cell.getElement(), generatedStyle);
 +                    } catch (RuntimeException e) {
 +                        getLogger().log(
 +                                Level.SEVERE,
 +                                "Error generating style for cell in column "
 +                                        + cell.getColumn(), e);
 +                    }
 +                } else if (hasData || usedToHaveData) {
 +                    setCustomStyleName(cell.getElement(), null);
 +                }
 +
 +                Renderer renderer = column.getRenderer();
 +
 +                try {
 +                    rendererCellReference.set(cell, columnIndex, column);
 +                    if (renderer instanceof ComplexRenderer) {
 +                        // Hide cell content if needed
 +                        ComplexRenderer clxRenderer = (ComplexRenderer) renderer;
 +                        if (hasData) {
 +                            if (!usedToHaveData) {
 +                                // Prepare cell for rendering
 +                                clxRenderer.setContentVisible(
 +                                        rendererCellReference, true);
 +                            }
 +
 +                            Object value = column.getValue(rowData);
 +                            clxRenderer.render(rendererCellReference, value);
 +
 +                        } else {
 +                            // Prepare cell for no data
 +                            clxRenderer.setContentVisible(
 +                                    rendererCellReference, false);
 +                        }
 +
 +                    } else if (hasData) {
 +                        // Simple renderers just render
 +                        Object value = column.getValue(rowData);
 +                        renderer.render(rendererCellReference, value);
 +
 +                    } else {
 +                        // Clear cell if there is no data
 +                        cell.getElement().removeAllChildren();
 +                    }
 +                } catch (RuntimeException e) {
 +                    getLogger().log(
 +                            Level.SEVERE,
 +                            "Error rendering cell in column "
 +                                    + cell.getColumn(), e);
 +                }
 +            }
 +        }
 +
 +        @Override
 +        public void preDetach(Row row, Iterable<FlyweightCell> cellsToDetach) {
 +            for (FlyweightCell cell : cellsToDetach) {
 +                Renderer<?> renderer = findRenderer(cell);
 +                if (renderer instanceof WidgetRenderer) {
 +                    try {
 +                        Widget w = WidgetUtil.findWidget(cell.getElement()
 +                                .getFirstChildElement(), null);
 +                        if (w != null) {
 +
 +                            // Logical detach
 +                            setParent(w, null);
 +
 +                            // Physical detach
 +                            cell.getElement().removeChild(w.getElement());
 +                        }
 +                    } catch (RuntimeException e) {
 +                        getLogger().log(
 +                                Level.SEVERE,
 +                                "Error detaching widget in column "
 +                                        + cell.getColumn(), e);
 +                    }
 +                }
 +            }
 +        }
 +
 +        @Override
 +        public void postDetach(Row row, Iterable<FlyweightCell> detachedCells) {
 +            int rowIndex = row.getRow();
 +            // Passing null row data since it might not exist in the data source
 +            // any more
 +            rowReference.set(rowIndex, null, row.getElement());
 +            for (FlyweightCell cell : detachedCells) {
 +                Renderer<?> renderer = findRenderer(cell);
 +                if (renderer instanceof ComplexRenderer) {
 +                    try {
 +                        Column<?, T> column = getVisibleColumn(cell.getColumn());
 +                        rendererCellReference.set(cell,
 +                                getColumns().indexOf(column), column);
 +                        ((ComplexRenderer) renderer)
 +                                .destroy(rendererCellReference);
 +                    } catch (RuntimeException e) {
 +                        getLogger().log(
 +                                Level.SEVERE,
 +                                "Error destroying cell in column "
 +                                        + cell.getColumn(), e);
 +                    }
 +                }
 +            }
 +        }
 +    }
 +
 +    protected class StaticSectionUpdater implements EscalatorUpdater {
 +
 +        private StaticSection<?> section;
 +        private RowContainer container;
 +
 +        public StaticSectionUpdater(StaticSection<?> section,
 +                RowContainer container) {
 +            super();
 +            this.section = section;
 +            this.container = container;
 +        }
 +
 +        @Override
 +        public void update(Row row, Iterable<FlyweightCell> cellsToUpdate) {
 +            StaticSection.StaticRow<?> staticRow = section.getRow(row.getRow());
 +            final List<Column<?, T>> columns = getVisibleColumns();
 +
 +            setCustomStyleName(row.getElement(), staticRow.getStyleName());
 +
 +            for (FlyweightCell cell : cellsToUpdate) {
 +                final StaticSection.StaticCell metadata = staticRow
 +                        .getCell(columns.get(cell.getColumn()));
 +
 +                // Decorate default row with sorting indicators
 +                if (staticRow instanceof HeaderRow) {
 +                    addSortingIndicatorsToHeaderRow((HeaderRow) staticRow, cell);
 +                }
 +
 +                // Assign colspan to cell before rendering
 +                cell.setColSpan(metadata.getColspan());
 +
 +                Element td = cell.getElement();
 +                td.removeAllChildren();
 +                setCustomStyleName(td, metadata.getStyleName());
 +
 +                Element content;
 +                // Wrap text or html content in default header to isolate
 +                // the content from the possible column resize drag handle
 +                // next to it
 +                if (metadata.getType() != GridStaticCellType.WIDGET) {
 +                    content = DOM.createDiv();
 +
 +                    if (staticRow instanceof HeaderRow) {
 +                        content.setClassName(getStylePrimaryName()
 +                                + "-column-header-content");
 +                        if (((HeaderRow) staticRow).isDefault()) {
 +                            content.setClassName(content.getClassName() + " "
 +                                    + getStylePrimaryName()
 +                                    + "-column-default-header-content");
 +                        }
 +                    } else if (staticRow instanceof FooterRow) {
 +                        content.setClassName(getStylePrimaryName()
 +                                + "-column-footer-content");
 +                    } else {
 +                        getLogger().severe(
 +                                "Unhandled static row type "
 +                                        + staticRow.getClass()
 +                                                .getCanonicalName());
 +                    }
 +
 +                    td.appendChild(content);
 +                } else {
 +                    content = td;
 +                }
 +
 +                switch (metadata.getType()) {
 +                case TEXT:
 +                    content.setInnerText(metadata.getText());
 +                    break;
 +                case HTML:
 +                    content.setInnerHTML(metadata.getHtml());
 +                    break;
 +                case WIDGET:
 +                    preDetach(row, Arrays.asList(cell));
 +                    content.setInnerHTML("");
 +                    postAttach(row, Arrays.asList(cell));
 +                    break;
 +                }
 +
 +                // XXX: Should add only once in preAttach/postAttach or when
 +                // resizable status changes
 +                // Only add resize handles to default header row for now
 +                if (columns.get(cell.getColumn()).isResizable()
 +                        && staticRow instanceof HeaderRow
 +                        && ((HeaderRow) staticRow).isDefault()) {
 +
 +                    final int column = cell.getColumn();
 +                    DragHandle dragger = new DragHandle(getStylePrimaryName()
 +                            + "-column-resize-handle",
 +                            new DragHandleCallback() {
 +
 +                                private Column<?, T> col = getVisibleColumn(column);
 +                                private double initialWidth = 0;
 +                                private double minCellWidth;
 +
 +                                @Override
 +                                public void onUpdate(double deltaX,
 +                                        double deltaY) {
 +                                    col.setWidth(Math.max(minCellWidth,
 +                                            initialWidth + deltaX));
 +                                }
 +
 +                                @Override
 +                                public void onStart() {
 +                                    initialWidth = col.getWidthActual();
 +
 +                                    minCellWidth = escalator
 +                                            .getMinCellWidth(getColumns()
 +                                                    .indexOf(col));
 +                                    for (Column<?, T> c : getColumns()) {
 +                                        if (selectionColumn == c) {
 +                                            // Don't modify selection column.
 +                                            continue;
 +                                        }
 +
 +                                        if (c.getWidth() < 0) {
 +                                            c.setWidth(c.getWidthActual());
 +                                            fireEvent(new ColumnResizeEvent<T>(
 +                                                    c));
 +                                        }
 +                                    }
 +
 +                                    WidgetUtil.setTextSelectionEnabled(
 +                                            getElement(), false);
 +                                }
 +
 +                                @Override
 +                                public void onComplete() {
 +                                    fireEvent(new ColumnResizeEvent<T>(col));
 +
 +                                    WidgetUtil.setTextSelectionEnabled(
 +                                            getElement(), true);
 +                                }
 +
 +                                @Override
 +                                public void onCancel() {
 +                                    col.setWidth(initialWidth);
 +
 +                                    WidgetUtil.setTextSelectionEnabled(
 +                                            getElement(), true);
 +                                }
 +                            });
 +                    dragger.addTo(td);
 +                }
 +
 +                cellFocusHandler.updateFocusedCellStyle(cell, container);
 +            }
 +        }
 +
 +        private void addSortingIndicatorsToHeaderRow(HeaderRow headerRow,
 +                FlyweightCell cell) {
 +
 +            Element cellElement = cell.getElement();
 +
 +            boolean sortedBefore = cellElement.hasClassName("sort-asc")
 +                    || cellElement.hasClassName("sort-desc");
 +
 +            cleanup(cell);
 +            if (!headerRow.isDefault()) {
 +                // Nothing more to do if not in the default row
 +                return;
 +            }
 +
 +            final Column<?, T> column = getVisibleColumn(cell.getColumn());
 +            SortOrder sortingOrder = getSortOrder(column);
 +            boolean sortable = column.isSortable();
 +
 +            if (sortable) {
 +                cellElement.addClassName("sortable");
 +            }
 +
 +            if (!sortable || sortingOrder == null) {
 +                // Only apply sorting indicators to sortable header columns
 +                return;
 +            }
 +
 +            if (SortDirection.ASCENDING == sortingOrder.getDirection()) {
 +                cellElement.addClassName("sort-asc");
 +            } else {
 +                cellElement.addClassName("sort-desc");
 +            }
 +
 +            int sortIndex = Grid.this.getSortOrder().indexOf(sortingOrder);
 +            if (sortIndex > -1 && Grid.this.getSortOrder().size() > 1) {
 +                // Show sort order indicator if column is
 +                // sorted and other sorted columns also exists.
 +                cellElement.setAttribute("sort-order",
 +                        String.valueOf(sortIndex + 1));
 +            }
 +
 +            if (!sortedBefore) {
 +                verifyColumnWidth(column);
 +            }
 +        }
 +
 +        /**
 +         * Sort indicator requires a bit more space from the cell than normally.
 +         * This method check that the now sorted column has enough width.
 +         * 
 +         * @param column
 +         *            sorted column
 +         */
 +        private void verifyColumnWidth(Column<?, T> column) {
 +            int colIndex = getColumns().indexOf(column);
 +            double minWidth = escalator.getMinCellWidth(colIndex);
 +            if (column.getWidthActual() < minWidth) {
 +                // Fix column size
 +                escalator.getColumnConfiguration().setColumnWidth(colIndex,
 +                        minWidth);
 +
 +                fireEvent(new ColumnResizeEvent<T>(column));
 +            }
 +        }
 +
 +        /**
 +         * Finds the sort order for this column
 +         */
 +        private SortOrder getSortOrder(Column<?, ?> column) {
 +            for (SortOrder order : Grid.this.getSortOrder()) {
 +                if (order.getColumn() == column) {
 +                    return order;
 +                }
 +            }
 +            return null;
 +        }
 +
 +        private void cleanup(FlyweightCell cell) {
 +            Element cellElement = cell.getElement();
 +            cellElement.removeAttribute("sort-order");
 +            cellElement.removeClassName("sort-desc");
 +            cellElement.removeClassName("sort-asc");
 +            cellElement.removeClassName("sortable");
 +        }
 +
 +        @Override
 +        public void preAttach(Row row, Iterable<FlyweightCell> cellsToAttach) {
 +        }
 +
 +        @Override
 +        public void postAttach(Row row, Iterable<FlyweightCell> attachedCells) {
 +            StaticSection.StaticRow<?> gridRow = section.getRow(row.getRow());
 +            List<Column<?, T>> columns = getVisibleColumns();
 +
 +            for (FlyweightCell cell : attachedCells) {
 +                StaticSection.StaticCell metadata = gridRow.getCell(columns
 +                        .get(cell.getColumn()));
 +                /*
 +                 * If the cell contains widgets that are not currently attached
 +                 * then attach them now.
 +                 */
 +                if (GridStaticCellType.WIDGET.equals(metadata.getType())) {
 +                    final Widget widget = metadata.getWidget();
 +                    if (widget != null && !widget.isAttached()) {
 +                        getGrid().attachWidget(metadata.getWidget(),
 +                                cell.getElement());
 +                    }
 +                }
 +            }
 +        }
 +
 +        @Override
 +        public void preDetach(Row row, Iterable<FlyweightCell> cellsToDetach) {
 +            if (section.getRowCount() > row.getRow()) {
 +                StaticSection.StaticRow<?> gridRow = section.getRow(row
 +                        .getRow());
 +                List<Column<?, T>> columns = getVisibleColumns();
 +                for (FlyweightCell cell : cellsToDetach) {
 +                    StaticSection.StaticCell metadata = gridRow.getCell(columns
 +                            .get(cell.getColumn()));
 +
 +                    if (GridStaticCellType.WIDGET.equals(metadata.getType())
 +                            && metadata.getWidget() != null
 +                            && metadata.getWidget().isAttached()) {
 +
 +                        getGrid().detachWidget(metadata.getWidget());
 +                    }
 +                }
 +            }
 +        }
 +
 +        protected Grid getGrid() {
 +            return section.grid;
 +        }
 +
 +        @Override
 +        public void postDetach(Row row, Iterable<FlyweightCell> detachedCells) {
 +        }
 +    };
 +
 +    /**
 +     * Creates a new instance.
 +     */
 +    public Grid() {
 +        initWidget(escalator);
 +        getElement().setTabIndex(0);
 +        cellFocusHandler = new CellFocusHandler();
 +
 +        setStylePrimaryName(STYLE_NAME);
 +
 +        escalator.getHeader().setEscalatorUpdater(createHeaderUpdater());
 +        escalator.getBody().setEscalatorUpdater(createBodyUpdater());
 +        escalator.getFooter().setEscalatorUpdater(createFooterUpdater());
 +
 +        header.setGrid(this);
 +        HeaderRow defaultRow = header.appendRow();
 +        header.setDefaultRow(defaultRow);
 +
 +        footer.setGrid(this);
 +
 +        editor.setGrid(this);
 +
 +        setSelectionMode(SelectionMode.SINGLE);
 +
 +        escalator.getBody().setSpacerUpdater(gridSpacerUpdater);
 +
 +        escalator.addScrollHandler(new ScrollHandler() {
 +            @Override
 +            public void onScroll(ScrollEvent event) {
 +                fireEvent(new ScrollEvent());
 +            }
 +        });
 +
 +        escalator
 +                .addRowVisibilityChangeHandler(new RowVisibilityChangeHandler() {
 +                    @Override
 +                    public void onRowVisibilityChange(
 +                            RowVisibilityChangeEvent event) {
 +                        if (dataSource != null && dataSource.size() != 0) {
 +                            dataIsBeingFetched = true;
 +                            dataSource.ensureAvailability(
 +                                    event.getFirstVisibleRow(),
 +                                    event.getVisibleRowCount());
 +                        }
 +                    }
 +                });
 +
 +        // Default action on SelectionEvents. Refresh the body so changed
 +        // become visible.
 +        addSelectionHandler(new SelectionHandler<T>() {
 +
 +            @Override
 +            public void onSelect(SelectionEvent<T> event) {
 +                refreshBody();
 +            }
 +        });
 +
 +        // Sink header events and key events
 +        sinkEvents(getHeader().getConsumedEvents());
 +        sinkEvents(Arrays.asList(BrowserEvents.KEYDOWN, BrowserEvents.KEYUP,
 +                BrowserEvents.KEYPRESS, BrowserEvents.DBLCLICK,
 +                BrowserEvents.MOUSEDOWN, BrowserEvents.CLICK));
 +
 +        // Make ENTER and SHIFT+ENTER in the header perform sorting
 +        addHeaderKeyUpHandler(new HeaderKeyUpHandler() {
 +            @Override
 +            public void onKeyUp(GridKeyUpEvent event) {
 +                if (event.getNativeKeyCode() != KeyCodes.KEY_ENTER) {
 +                    return;
 +                }
 +                if (getHeader().getRow(event.getFocusedCell().getRowIndex())
 +                        .isDefault()) {
 +                    // Only sort for enter on the default header
 +                    sorter.sort(event.getFocusedCell().getColumn(),
 +                            event.isShiftKeyDown());
 +                }
 +            }
 +        });
 +
 +        addDataAvailableHandler(new DataAvailableHandler() {
 +            @Override
 +            public void onDataAvailable(DataAvailableEvent event) {
 +                dataIsBeingFetched = false;
 +            }
 +        });
 +    }
 +
 +    @Override
 +    public boolean isEnabled() {
 +        return enabled;
 +    }
 +
 +    @Override
 +    public void setEnabled(boolean enabled) {
 +        if (enabled == this.enabled) {
 +            return;
 +        }
 +
 +        this.enabled = enabled;
 +        getElement().setTabIndex(enabled ? 0 : -1);
 +
 +        // Editor save and cancel buttons need to be disabled.
 +        boolean editorOpen = editor.getState() != State.INACTIVE;
 +        if (editorOpen) {
 +            editor.setGridEnabled(enabled);
 +        }
 +
 +        sidebar.setEnabled(enabled);
 +
 +        getEscalator().setScrollLocked(Direction.VERTICAL,
 +                !enabled || editorOpen);
 +        getEscalator().setScrollLocked(Direction.HORIZONTAL, !enabled);
 +    }
 +
 +    @Override
 +    public void setStylePrimaryName(String style) {
 +        super.setStylePrimaryName(style);
 +        escalator.setStylePrimaryName(style);
 +        editor.setStylePrimaryName(style);
 +        sidebar.setStylePrimaryName(style + "-sidebar");
 +        sidebar.addStyleName("v-contextmenu");
 +
 +        String rowStyle = getStylePrimaryName() + "-row";
 +        rowHasDataStyleName = rowStyle + "-has-data";
 +        rowSelectedStyleName = rowStyle + "-selected";
 +        rowStripeStyleName = rowStyle + "-stripe";
 +
 +        cellFocusStyleName = getStylePrimaryName() + "-cell-focused";
 +        rowFocusStyleName = getStylePrimaryName() + "-row-focused";
 +
 +        if (isAttached()) {
 +            refreshHeader();
 +            refreshBody();
 +            refreshFooter();
 +        }
 +    }
 +
 +    /**
 +     * Creates the escalator updater used to update the header rows in this
 +     * grid. The updater is invoked when header rows or columns are added or
 +     * removed, or the content of existing header cells is changed.
 +     * 
 +     * @return the new header updater instance
 +     * 
 +     * @see GridHeader
 +     * @see Grid#getHeader()
 +     */
 +    protected EscalatorUpdater createHeaderUpdater() {
 +        return new StaticSectionUpdater(header, escalator.getHeader());
 +    }
 +
 +    /**
 +     * Creates the escalator updater used to update the body rows in this grid.
 +     * The updater is invoked when body rows or columns are added or removed,
 +     * the content of body cells is changed, or the body is scrolled to expose
 +     * previously hidden content.
 +     * 
 +     * @return the new body updater instance
 +     */
 +    protected EscalatorUpdater createBodyUpdater() {
 +        return new BodyUpdater();
 +    }
 +
 +    /**
 +     * Creates the escalator updater used to update the footer rows in this
 +     * grid. The updater is invoked when header rows or columns are added or
 +     * removed, or the content of existing header cells is changed.
 +     * 
 +     * @return the new footer updater instance
 +     * 
 +     * @see GridFooter
 +     * @see #getFooter()
 +     */
 +    protected EscalatorUpdater createFooterUpdater() {
 +        return new StaticSectionUpdater(footer, escalator.getFooter());
 +    }
 +
 +    /**
 +     * Refreshes header or footer rows on demand
 +     * 
 +     * @param rows
 +     *            The row container
 +     * @param firstRowIsVisible
 +     *            is the first row visible
 +     * @param isHeader
 +     *            <code>true</code> if we refreshing the header, else assumed
 +     *            the footer
 +     */
 +    private void refreshRowContainer(RowContainer rows, StaticSection<?> section) {
 +
 +        // Add or Remove rows on demand
 +        int rowDiff = section.getVisibleRowCount() - rows.getRowCount();
 +        if (rowDiff > 0) {
 +            rows.insertRows(0, rowDiff);
 +        } else if (rowDiff < 0) {
 +            rows.removeRows(0, -rowDiff);
 +        }
 +
 +        // Refresh all the rows
 +        if (rows.getRowCount() > 0) {
 +            rows.refreshRows(0, rows.getRowCount());
 +        }
 +    }
 +
 +    /**
 +     * Focus a body cell by row and column index.
 +     * 
 +     * @param rowIndex
 +     *            index of row to focus
 +     * @param columnIndex
 +     *            index of cell to focus
 +     */
 +    void focusCell(int rowIndex, int columnIndex) {
 +        final Range rowRange = Range.between(0, dataSource.size());
 +        final Range columnRange = Range.between(0, getVisibleColumns().size());
 +
 +        assert rowRange.contains(rowIndex) : "Illegal row index. Should be in range "
 +                + rowRange;
 +        assert columnRange.contains(columnIndex) : "Illegal column index. Should be in range "
 +                + columnRange;
 +
 +        if (rowRange.contains(rowIndex) && columnRange.contains(columnIndex)) {
 +            cellFocusHandler.setCellFocus(rowIndex, columnIndex,
 +                    escalator.getBody());
 +            WidgetUtil.focus(getElement());
 +        }
 +    }
 +
 +    /**
 +     * Refreshes all header rows
 +     */
 +    void refreshHeader() {
 +        refreshRowContainer(escalator.getHeader(), header);
 +    }
 +
 +    /**
 +     * Refreshes all body rows
 +     */
 +    private void refreshBody() {
 +        escalator.getBody().refreshRows(0, escalator.getBody().getRowCount());
 +    }
 +
 +    /**
 +     * Refreshes all footer rows
 +     */
 +    void refreshFooter() {
 +        refreshRowContainer(escalator.getFooter(), footer);
 +    }
 +
 +    /**
 +     * Adds columns as the last columns in the grid.
 +     * 
 +     * @param columns
 +     *            the columns to add
 +     */
 +    public void addColumns(Column<?, T>... columns) {
 +        int count = getColumnCount();
 +        for (Column<?, T> column : columns) {
 +            addColumn(column, count++);
 +        }
 +    }
 +
 +    /**
 +     * Adds a column as the last column in the grid.
 +     * 
 +     * @param column
 +     *            the column to add
 +     * @return given column
 +     */
 +    public <C extends Column<?, T>> C addColumn(C column) {
 +        addColumn(column, getColumnCount());
 +        return column;
 +    }
 +
 +    /**
 +     * Inserts a column into a specific position in the grid.
 +     * 
 +     * @param index
 +     *            the index where the column should be inserted into
 +     * @param column
 +     *            the column to add
 +     * @return given column
 +     * 
 +     * @throws IllegalStateException
 +     *             if Grid's current selection model renders a selection column,
 +     *             and {@code index} is 0.
 +     */
 +    public <C extends Column<?, T>> C addColumn(C column, int index) {
 +        if (column == selectionColumn) {
 +            throw new IllegalArgumentException("The selection column many "
 +                    + "not be added manually");
 +        } else if (selectionColumn != null && index == 0) {
 +            throw new IllegalStateException("A column cannot be inserted "
 +                    + "before the selection column");
 +        }
 +
 +        addColumnSkipSelectionColumnCheck(column, index);
 +        return column;
 +    }
 +
 +    private void addColumnSkipSelectionColumnCheck(Column<?, T> column,
 +            int index) {
 +        // Register column with grid
 +        columns.add(index, column);
 +
 +        header.addColumn(column);
 +        footer.addColumn(column);
 +
 +        // Register this grid instance with the column
 +        ((Column<?, T>) column).setGrid(this);
 +
 +        // Grid knows about hidden columns, Escalator only knows about what is
 +        // visible so column indexes do not match
 +        if (!column.isHidden()) {
 +            int escalatorIndex = index;
 +            for (int existingColumn = 0; existingColumn < index; existingColumn++) {
 +                if (getColumn(existingColumn).isHidden()) {
 +                    escalatorIndex--;
 +                }
 +            }
 +            escalator.getColumnConfiguration().insertColumns(escalatorIndex, 1);
 +        }
 +
 +        // Reapply column width
 +        column.reapplyWidth();
 +
 +        // Sink all renderer events
 +        Set<String> events = new HashSet<String>();
 +        events.addAll(getConsumedEventsForRenderer(column.getRenderer()));
 +
 +        if (column.isHidable()) {
 +            columnHider.updateColumnHidable(column);
 +        }
 +
 +        sinkEvents(events);
 +    }
 +
 +    private void sinkEvents(Collection<String> events) {
 +        assert events != null;
 +
 +        int eventsToSink = 0;
 +        for (String typeName : events) {
 +            int typeInt = Event.getTypeInt(typeName);
 +            if (typeInt < 0) {
 +                // Type not recognized by typeInt
 +                sinkBitlessEvent(typeName);
 +            } else {
 +                eventsToSink |= typeInt;
 +            }
 +        }
 +
 +        if (eventsToSink > 0) {
 +            sinkEvents(eventsToSink);
 +        }
 +    }
 +
 +    private Renderer<?> findRenderer(FlyweightCell cell) {
 +        Column<?, T> column = getVisibleColumn(cell.getColumn());
 +        assert column != null : "Could not find column at index:"
 +                + cell.getColumn();
 +        return column.getRenderer();
 +    }
 +
 +    /**
 +     * Removes a column from the grid.
 +     * 
 +     * @param column
 +     *            the column to remove
 +     */
 +    public void removeColumn(Column<?, T> column) {
 +        if (column != null && column.equals(selectionColumn)) {
 +            throw new IllegalArgumentException(
 +                    "The selection column may not be removed manually.");
 +        }
 +
 +        removeColumnSkipSelectionColumnCheck(column);
 +    }
 +
 +    private void removeColumnSkipSelectionColumnCheck(Column<?, T> column) {
 +        int columnIndex = columns.indexOf(column);
 +
 +        // Remove from column configuration
 +        escalator.getColumnConfiguration().removeColumns(
 +                getVisibleColumns().indexOf(column), 1);
 +
 +        updateFrozenColumns();
 +
 +        header.removeColumn(column);
 +        footer.removeColumn(column);
 +
 +        // de-register column with grid
 +        ((Column<?, T>) column).setGrid(null);
 +
 +        columns.remove(columnIndex);
 +
 +        if (column.isHidable()) {
 +            columnHider.removeColumnHidingToggle(column);
 +        }
 +    }
 +
 +    /**
 +     * Returns the amount of columns in the grid.
 +     * <p>
 +     * <em>NOTE:</em> this includes the hidden columns in the count.
 +     * 
 +     * @return The number of columns in the grid
 +     */
 +    public int getColumnCount() {
 +        return columns.size();
 +    }
 +
 +    /**
 +     * Returns a list columns in the grid, including hidden columns.
 +     * <p>
 +     * For currently visible columns, use {@link #getVisibleColumns()}.
 +     * 
 +     * @return A unmodifiable list of the columns in the grid
 +     */
 +    public List<Column<?, T>> getColumns() {
 +        return Collections
 +                .unmodifiableList(new ArrayList<Column<?, T>>(columns));
 +    }
 +
 +    /**
 +     * Returns a list of the currently visible columns in the grid.
 +     * <p>
 +     * No {@link Column#isHidden() hidden} columns included.
 +     * 
 +     * @since 7.5.0
 +     * @return A unmodifiable list of the currently visible columns in the grid
 +     */
 +    public List<Column<?, T>> getVisibleColumns() {
 +        ArrayList<Column<?, T>> visible = new ArrayList<Column<?, T>>();
 +        for (Column<?, T> c : columns) {
 +            if (!c.isHidden()) {
 +                visible.add(c);
 +            }
 +        }
 +        return Collections.unmodifiableList(visible);
 +    }
 +
 +    /**
 +     * Returns a column by its index in the grid.
 +     * <p>
 +     * <em>NOTE:</em> The indexing includes hidden columns.
 +     * 
 +     * @param index
 +     *            the index of the column
 +     * @return The column in the given index
 +     * @throws IllegalArgumentException
 +     *             if the column index does not exist in the grid
 +     */
 +    public Column<?, T> getColumn(int index) throws IllegalArgumentException {
 +        if (index < 0 || index >= columns.size()) {
 +            throw new IllegalStateException("Column not found.");
 +        }
 +        return columns.get(index);
 +    }
 +
 +    private Column<?, T> getVisibleColumn(int index)
 +            throws IllegalArgumentException {
 +        List<Column<?, T>> visibleColumns = getVisibleColumns();
 +        if (index < 0 || index >= visibleColumns.size()) {
 +            throw new IllegalStateException("Column not found.");
 +        }
 +        return visibleColumns.get(index);
 +    }
 +
 +    /**
 +     * Returns the header section of this grid. The default header contains a
 +     * single row displaying the column captions.
 +     * 
 +     * @return the header
 +     */
 +    protected Header getHeader() {
 +        return header;
 +    }
 +
 +    /**
 +     * Gets the header row at given index.
 +     * 
 +     * @param rowIndex
 +     *            0 based index for row. Counted from top to bottom
 +     * @return header row at given index
 +     * @throws IllegalArgumentException
 +     *             if no row exists at given index
 +     */
 +    public HeaderRow getHeaderRow(int rowIndex) {
 +        return header.getRow(rowIndex);
 +    }
 +
 +    /**
 +     * Inserts a new row at the given position to the header section. Shifts the
 +     * row currently at that position and any subsequent rows down (adds one to
 +     * their indices).
 +     * 
 +     * @param index
 +     *            the position at which to insert the row
 +     * @return the new row
 +     * 
 +     * @throws IllegalArgumentException
 +     *             if the index is less than 0 or greater than row count
 +     * @see #appendHeaderRow()
 +     * @see #prependHeaderRow()
 +     * @see #removeHeaderRow(HeaderRow)
 +     * @see #removeHeaderRow(int)
 +     */
 +    public HeaderRow addHeaderRowAt(int index) {
 +        return header.addRowAt(index);
 +    }
 +
 +    /**
 +     * Adds a new row at the bottom of the header section.
 +     * 
 +     * @return the new row
 +     * @see #prependHeaderRow()
 +     * @see #addHeaderRowAt(int)
 +     * @see #removeHeaderRow(HeaderRow)
 +     * @see #removeHeaderRow(int)
 +     */
 +    public HeaderRow appendHeaderRow() {
 +        return header.appendRow();
 +    }
 +
 +    /**
 +     * Returns the current default row of the header section. The default row is
 +     * a special header row providing a user interface for sorting columns.
 +     * Setting a header caption for column updates cells in the default header.
 +     * 
 +     * @return the default row or null if no default row set
 +     */
 +    public HeaderRow getDefaultHeaderRow() {
 +        return header.getDefaultRow();
 +    }
 +
 +    /**
 +     * Gets the row count for the header section.
 +     * 
 +     * @return row count
 +     */
 +    public int getHeaderRowCount() {
 +        return header.getRowCount();
 +    }
 +
 +    /**
 +     * Adds a new row at the top of the header section.
 +     * 
 +     * @return the new row
 +     * @see #appendHeaderRow()
 +     * @see #addHeaderRowAt(int)
 +     * @see #removeHeaderRow(HeaderRow)
 +     * @see #removeHeaderRow(int)
 +     */
 +    public HeaderRow prependHeaderRow() {
 +        return header.prependRow();
 +    }
 +
 +    /**
 +     * Removes the given row from the header section.
 +     * 
 +     * @param row
 +     *            the row to be removed
 +     * 
 +     * @throws IllegalArgumentException
 +     *             if the row does not exist in this section
 +     * @see #removeHeaderRow(int)
 +     * @see #addHeaderRowAt(int)
 +     * @see #appendHeaderRow()
 +     * @see #prependHeaderRow()
 +     */
 +    public void removeHeaderRow(HeaderRow row) {
 +        header.removeRow(row);
 +    }
 +
 +    /**
 +     * Removes the row at the given position from the header section.
 +     * 
 +     * @param index
 +     *            the position of the row
 +     * 
 +     * @throws IllegalArgumentException
 +     *             if no row exists at given index
 +     * @see #removeHeaderRow(HeaderRow)
 +     * @see #addHeaderRowAt(int)
 +     * @see #appendHeaderRow()
 +     * @see #prependHeaderRow()
 +     */
 +    public void removeHeaderRow(int rowIndex) {
 +        header.removeRow(rowIndex);
 +    }
 +
 +    /**
 +     * Sets the default row of the header. The default row is a special header
 +     * row providing a user interface for sorting columns.
 +     * <p>
 +     * Note: Setting the default header row will reset all cell contents to
 +     * Column defaults.
 +     * 
 +     * @param row
 +     *            the new default row, or null for no default row
 +     * 
 +     * @throws IllegalArgumentException
 +     *             header does not contain the row
 +     */
 +    public void setDefaultHeaderRow(HeaderRow row) {
 +        header.setDefaultRow(row);
 +    }
 +
 +    /**
 +     * Sets the visibility of the header section.
 +     * 
 +     * @param visible
 +     *            true to show header section, false to hide
 +     */
 +    public void setHeaderVisible(boolean visible) {
 +        header.setVisible(visible);
 +    }
 +
 +    /**
 +     * Returns the visibility of the header section.
 +     * 
 +     * @return true if visible, false otherwise.
 +     */
 +    public boolean isHeaderVisible() {
 +        return header.isVisible();
 +    }
 +
 +    /* Grid Footers */
 +
 +    /**
 +     * Returns the footer section of this grid. The default footer is empty.
 +     * 
 +     * @return the footer
 +     */
 +    protected Footer getFooter() {
 +        return footer;
 +    }
 +
 +    /**
 +     * Gets the footer row at given index.
 +     * 
 +     * @param rowIndex
 +     *            0 based index for row. Counted from top to bottom
 +     * @return footer row at given index
 +     * @throws IllegalArgumentException
 +     *             if no row exists at given index
 +     */
 +    public FooterRow getFooterRow(int rowIndex) {
 +        return footer.getRow(rowIndex);
 +    }
 +
 +    /**
 +     * Inserts a new row at the given position to the footer section. Shifts the
 +     * row currently at that position and any subsequent rows down (adds one to
 +     * their indices).
 +     * 
 +     * @param index
 +     *            the position at which to insert the row
 +     * @return the new row
 +     * 
 +     * @throws IllegalArgumentException
 +     *             if the index is less than 0 or greater than row count
 +     * @see #appendFooterRow()
 +     * @see #prependFooterRow()
 +     * @see #removeFooterRow(FooterRow)
 +     * @see #removeFooterRow(int)
 +     */
 +    public FooterRow addFooterRowAt(int index) {
 +        return footer.addRowAt(index);
 +    }
 +
 +    /**
 +     * Adds a new row at the bottom of the footer section.
 +     * 
 +     * @return the new row
 +     * @see #prependFooterRow()
 +     * @see #addFooterRowAt(int)
 +     * @see #removeFooterRow(FooterRow)
 +     * @see #removeFooterRow(int)
 +     */
 +    public FooterRow appendFooterRow() {
 +        return footer.appendRow();
 +    }
 +
 +    /**
 +     * Gets the row count for the footer.
 +     * 
 +     * @return row count
 +     */
 +    public int getFooterRowCount() {
 +        return footer.getRowCount();
 +    }
 +
 +    /**
 +     * Adds a new row at the top of the footer section.
 +     * 
 +     * @return the new row
 +     * @see #appendFooterRow()
 +     * @see #addFooterRowAt(int)
 +     * @see #removeFooterRow(FooterRow)
 +     * @see #removeFooterRow(int)
 +     */
 +    public FooterRow prependFooterRow() {
 +        return footer.prependRow();
 +    }
 +
 +    /**
 +     * Removes the given row from the footer section.
 +     * 
 +     * @param row
 +     *            the row to be removed
 +     * 
 +     * @throws IllegalArgumentException
 +     *             if the row does not exist in this section
 +     * @see #removeFooterRow(int)
 +     * @see #addFooterRowAt(int)
 +     * @see #appendFooterRow()
 +     * @see #prependFooterRow()
 +     */
 +    public void removeFooterRow(FooterRow row) {
 +        footer.removeRow(row);
 +    }
 +
 +    /**
 +     * Removes the row at the given position from the footer section.
 +     * 
 +     * @param index
 +     *            the position of the row
 +     * 
 +     * @throws IllegalArgumentException
 +     *             if no row exists at given index
 +     * @see #removeFooterRow(FooterRow)
 +     * @see #addFooterRowAt(int)
 +     * @see #appendFooterRow()
 +     * @see #prependFooterRow()
 +     */
 +    public void removeFooterRow(int rowIndex) {
 +        footer.removeRow(rowIndex);
 +    }
 +
 +    /**
 +     * Sets the visibility of the footer section.
 +     * 
 +     * @param visible
 +     *            true to show footer section, false to hide
 +     */
 +    public void setFooterVisible(boolean visible) {
 +        footer.setVisible(visible);
 +    }
 +
 +    /**
 +     * Returns the visibility of the footer section.
 +     * 
 +     * @return true if visible, false otherwise.
 +     */
 +    public boolean isFooterVisible() {
 +        return footer.isVisible();
 +    }
 +
 +    public Editor<T> getEditor() {
 +        return editor;
 +    }
 +
 +    protected Escalator getEscalator() {
 +        return escalator;
 +    }
 +
 +    /**
 +     * {@inheritDoc}
 +     * <p>
 +     * <em>Note:</em> This method will change the widget's size in the browser
 +     * only if {@link #getHeightMode()} returns {@link HeightMode#CSS}.
 +     * 
 +     * @see #setHeightMode(HeightMode)
 +     */
 +    @Override
 +    public void setHeight(String height) {
 +        escalator.setHeight(height);
 +    }
 +
 +    @Override
 +    public void setWidth(String width) {
 +        escalator.setWidth(width);
 +    }
 +
 +    /**
 +     * Sets the data source used by this grid.
 +     * 
 +     * @param dataSource
 +     *            the data source to use, not null
 +     * @throws IllegalArgumentException
 +     *             if <code>dataSource</code> is <code>null</code>
 +     */
 +    public void setDataSource(final DataSource<T> dataSource)
 +            throws IllegalArgumentException {
 +        if (dataSource == null) {
 +            throw new IllegalArgumentException("dataSource can't be null.");
 +        }
 +
 +        selectionModel.reset();
 +
 +        if (this.dataSource != null) {
 +            this.dataSource.setDataChangeHandler(null);
 +        }
 +
 +        this.dataSource = dataSource;
 +        dataSource.setDataChangeHandler(new DataChangeHandler() {
 +            @Override
 +            public void dataUpdated(int firstIndex, int numberOfItems) {
 +                escalator.getBody().refreshRows(firstIndex, numberOfItems);
 +            }
 +
 +            @Override
 +            public void dataRemoved(int firstIndex, int numberOfItems) {
 +                escalator.getBody().removeRows(firstIndex, numberOfItems);
 +                Range removed = Range.withLength(firstIndex, numberOfItems);
 +                cellFocusHandler.rowsRemovedFromBody(removed);
 +            }
 +
 +            @Override
 +            public void dataAdded(int firstIndex, int numberOfItems) {
 +                escalator.getBody().insertRows(firstIndex, numberOfItems);
 +                Range added = Range.withLength(firstIndex, numberOfItems);
 +                cellFocusHandler.rowsAddedToBody(added);
 +            }
 +
 +            @Override
 +            public void dataAvailable(int firstIndex, int numberOfItems) {
 +                currentDataAvailable = Range.withLength(firstIndex,
 +                        numberOfItems);
 +                fireEvent(new DataAvailableEvent(currentDataAvailable));
 +            }
 +
 +            @Override
 +            public void resetDataAndSize(int newSize) {
 +                RowContainer body = escalator.getBody();
 +                int oldSize = body.getRowCount();
 +
 +                // Hide all details.
 +                Set<Integer> oldDetails = new HashSet<Integer>(visibleDetails);
 +                for (int i : oldDetails) {
 +                    setDetailsVisible(i, false);
 +                }
 +
 +                if (newSize > oldSize) {
 +                    body.insertRows(oldSize, newSize - oldSize);
 +                    cellFocusHandler.rowsAddedToBody(Range.withLength(oldSize,
 +                            newSize - oldSize));
 +                } else if (newSize < oldSize) {
 +                    body.removeRows(newSize, oldSize - newSize);
 +                    cellFocusHandler.rowsRemovedFromBody(Range.withLength(
 +                            newSize, oldSize - newSize));
 +                }
 +
 +                if (newSize > 0) {
 +                    dataIsBeingFetched = true;
 +                    Range visibleRowRange = escalator.getVisibleRowRange();
 +                    dataSource.ensureAvailability(visibleRowRange.getStart(),
 +                            visibleRowRange.length());
 +                } else {
 +                    // We won't expect any data more data updates, so just make
 +                    // the bookkeeping happy
 +                    dataAvailable(0, 0);
 +                }
 +
 +                assert body.getRowCount() == newSize;
 +            }
 +        });
 +
 +        int previousRowCount = escalator.getBody().getRowCount();
 +        if (previousRowCount != 0) {
 +            escalator.getBody().removeRows(0, previousRowCount);
 +        }
 +
 +        setEscalatorSizeFromDataSource();
 +    }
 +
 +    private void setEscalatorSizeFromDataSource() {
 +        assert escalator.getBody().getRowCount() == 0;
 +
 +        int size = dataSource.size();
 +        if (size == -1 && isAttached()) {
 +            // Exact size is not yet known, start with some reasonable guess
 +            // just to get an initial backend request going
 +            size = getEscalator().getMaxVisibleRowCount();
 +        }
 +        if (size > 0) {
 +            escalator.getBody().insertRows(0, size);
 +        }
 +    }
 +
 +    /**
 +     * Gets the {@Link DataSource} for this Grid.
 +     * 
 +     * @return the data source used by this grid
 +     */
 +    public DataSource<T> getDataSource() {
 +        return dataSource;
 +    }
 +
 +    /**
 +     * Sets the number of frozen columns in this grid. Setting the count to 0
 +     * means that no data columns will be frozen, but the built-in selection
 +     * checkbox column will still be frozen if it's in use. Setting the count to
 +     * -1 will also disable the selection column.
 +     * <p>
 +     * The default value is 0.
 +     * 
 +     * @param numberOfColumns
 +     *            the number of columns that should be frozen
 +     * 
 +     * @throws IllegalArgumentException
 +     *             if the column count is < -1 or > the number of visible
 +     *             columns
 +     */
 +    public void setFrozenColumnCount(int numberOfColumns) {
 +        if (numberOfColumns < -1 || numberOfColumns > getColumnCount()) {
 +            throw new IllegalArgumentException(
 +                    "count must be between -1 and the current number of columns ("
 +                            + getColumnCount() + ")");
 +        }
 +
 +        frozenColumnCount = numberOfColumns;
 +        updateFrozenColumns();
 +    }
 +
 +    private void updateFrozenColumns() {
 +        escalator.getColumnConfiguration().setFrozenColumnCount(
 +                getVisibleFrozenColumnCount());
 +    }
 +
 +    private int getVisibleFrozenColumnCount() {
 +        int numberOfColumns = getFrozenColumnCount();
 +
 +        // for the escalator the hidden columns are not in the frozen column
 +        // count, but for grid they are. thus need to convert the index
 +        for (int i = 0; i < frozenColumnCount; i++) {
 +            if (getColumn(i).isHidden()) {
 +                numberOfColumns--;
 +            }
 +        }
 +
 +        if (numberOfColumns == -1) {
 +            numberOfColumns = 0;
 +        } else if (selectionColumn != null) {
 +            numberOfColumns++;
 +        }
 +        return numberOfColumns;
 +    }
 +
 +    /**
 +     * Gets the number of frozen columns in this grid. 0 means that no data
 +     * columns will be frozen, but the built-in selection checkbox column will
 +     * still be frozen if it's in use. -1 means that not even the selection
 +     * column is frozen.
 +     * <p>
 +     * <em>NOTE:</em> This includes {@link Column#isHidden() hidden columns} in
 +     * the count.
 +     * 
 +     * @return the number of frozen columns
 +     */
 +    public int getFrozenColumnCount() {
 +        return frozenColumnCount;
 +    }
 +
 +    public HandlerRegistration addRowVisibilityChangeHandler(
 +            RowVisibilityChangeHandler handler) {
 +        /*
 +         * Reusing Escalator's RowVisibilityChangeHandler, since a scroll
 +         * concept is too abstract. e.g. the event needs to be re-sent when the
 +         * widget is resized.
 +         */
 +        return escalator.addRowVisibilityChangeHandler(handler);
 +    }
 +
 +    /**
 +     * Scrolls to a certain row, using {@link ScrollDestination#ANY}.
 +     * <p>
 +     * If the details for that row are visible, those will be taken into account
 +     * as well.
 +     * 
 +     * @param rowIndex
 +     *            zero-based index of the row to scroll to.
 +     * @throws IllegalArgumentException
 +     *             if rowIndex is below zero, or above the maximum value
 +     *             supported by the data source.
 +     */
 +    public void scrollToRow(int rowIndex) throws IllegalArgumentException {
 +        scrollToRow(rowIndex, ScrollDestination.ANY,
 +                GridConstants.DEFAULT_PADDING);
 +    }
 +
 +    /**
 +     * Scrolls to a certain row, using user-specified scroll destination.
 +     * <p>
 +     * If the details for that row are visible, those will be taken into account
 +     * as well.
 +     * 
 +     * @param rowIndex
 +     *            zero-based index of the row to scroll to.
 +     * @param destination
 +     *            desired destination placement of scrolled-to-row. See
 +     *            {@link ScrollDestination} for more information.
 +     * @throws IllegalArgumentException
 +     *             if rowIndex is below zero, or above the maximum value
 +     *             supported by the data source.
 +     */
 +    public void scrollToRow(int rowIndex, ScrollDestination destination)
 +            throws IllegalArgumentException {
 +        scrollToRow(rowIndex, destination,
 +                destination == ScrollDestination.MIDDLE ? 0
 +                        : GridConstants.DEFAULT_PADDING);
 +    }
 +
 +    /**
 +     * Scrolls to a certain row using only user-specified parameters.
 +     * <p>
 +     * If the details for that row are visible, those will be taken into account
 +     * as well.
 +     * 
 +     * @param rowIndex
 +     *            zero-based index of the row to scroll to.
 +     * @param destination
 +     *            desired destination placement of scrolled-to-row. See
 +     *            {@link ScrollDestination} for more information.
 +     * @param paddingPx
 +     *            number of pixels to overscroll. Behavior depends on
 +     *            destination.
 +     * @throws IllegalArgumentException
 +     *             if {@code destination} is {@link ScrollDestination#MIDDLE}
 +     *             and padding is nonzero, because having a padding on a
 +     *             centered row is undefined behavior, or if rowIndex is below
 +     *             zero or above the row count of the data source.
 +     */
 +    private void scrollToRow(int rowIndex, ScrollDestination destination,
 +            int paddingPx) throws IllegalArgumentException {
 +        int maxsize = escalator.getBody().getRowCount() - 1;
 +
 +        if (rowIndex < 0) {
 +            throw new IllegalArgumentException("Row index (" + rowIndex
 +                    + ") is below zero!");
 +        }
 +
 +        if (rowIndex > maxsize) {
 +            throw new IllegalArgumentException("Row index (" + rowIndex
 +                    + ") is above maximum (" + maxsize + ")!");
 +        }
 +
 +        escalator.scrollToRowAndSpacer(rowIndex, destination, paddingPx);
 +    }
 +
 +    /**
 +     * Scrolls to the beginning of the very first row.
 +     */
 +    public void scrollToStart() {
 +        scrollToRow(0, ScrollDestination.START);
 +    }
 +
 +    /**
 +     * Scrolls to the end of the very last row.
 +     */
 +    public void scrollToEnd() {
 +        scrollToRow(escalator.getBody().getRowCount() - 1,
 +                ScrollDestination.END);
 +    }
 +
 +    /**
 +     * Sets the vertical scroll offset.
 +     * 
 +     * @param px
 +     *            the number of pixels this grid should be scrolled down
 +     */
 +    public void setScrollTop(double px) {
 +        escalator.setScrollTop(px);
 +    }
 +
 +    /**
 +     * Gets the vertical scroll offset
 +     * 
 +     * @return the number of pixels this grid is scrolled down
 +     */
 +    public double getScrollTop() {
 +        return escalator.getScrollTop();
 +    }
 +
 +    /**
 +     * Sets the horizontal scroll offset
 +     * 
 +     * @since 7.5.0
 +     * @param px
 +     *            the number of pixels this grid should be scrolled right
 +     */
 +    public void setScrollLeft(double px) {
 +        escalator.setScrollLeft(px);
 +    }
 +
 +    /**
 +     * Gets the horizontal scroll offset
 +     * 
 +     * @return the number of pixels this grid is scrolled to the right
 +     */
 +    public double getScrollLeft() {
 +        return escalator.getScrollLeft();
 +    }
 +
 +    /**
 +     * Returns the height of the scrollable area in pixels.
 +     * 
 +     * @since 7.5.0
 +     * @return the height of the scrollable area in pixels
 +     */
 +    public double getScrollHeight() {
 +        return escalator.getScrollHeight();
 +    }
 +
 +    /**
 +     * Returns the width of the scrollable area in pixels.
 +     * 
 +     * @since 7.5.0
 +     * @return the width of the scrollable area in pixels.
 +     */
 +    public double getScrollWidth() {
 +        return escalator.getScrollWidth();
 +    }
 +
 +    private static final Logger getLogger() {
 +        return Logger.getLogger(Grid.class.getName());
 +    }
 +
 +    /**
 +     * Sets the number of rows that should be visible in Grid's body, while
 +     * {@link #getHeightMode()} is {@link HeightMode#ROW}.
 +     * <p>
 +     * If Grid is currently not in {@link HeightMode#ROW}, the given value is
 +     * remembered, and applied once the mode is applied.
 +     * 
 +     * @param rows
 +     *            The height in terms of number of rows displayed in Grid's
 +     *            body. If Grid doesn't contain enough rows, white space is
 +     *            displayed instead.
 +     * @throws IllegalArgumentException
 +     *             if {@code rows} is zero or less
 +     * @throws IllegalArgumentException
 +     *             if {@code rows} is {@link Double#isInifinite(double)
 +     *             infinite}
 +     * @throws IllegalArgumentException
 +     *             if {@code rows} is {@link Double#isNaN(double) NaN}
 +     * 
 +     * @see #setHeightMode(HeightMode)
 +     */
 +    public void setHeightByRows(double rows) throws IllegalArgumentException {
 +        escalator.setHeightByRows(rows);
 +    }
 +
 +    /**
 +     * Gets the amount of rows in Grid's body that are shown, while
 +     * {@link #getHeightMode()} is {@link HeightMode#ROW}.
 +     * <p>
 +     * By default, it is {@value Escalator#DEFAULT_HEIGHT_BY_ROWS}.
 +     * 
 +     * @return the amount of rows that should be shown in Grid's body, while in
 +     *         {@link HeightMode#ROW}.
 +     * @see #setHeightByRows(double)
 +     */
 +    public double getHeightByRows() {
 +        return escalator.getHeightByRows();
 +    }
 +
 +    /**
 +     * Defines the mode in which the Grid widget's height is calculated.
 +     * <p>
 +     * If {@link HeightMode#CSS} is given, Grid will respect the values given
 +     * via {@link #setHeight(String)}, and behave as a traditional Widget.
 +     * <p>
 +     * If {@link HeightMode#ROW} is given, Grid will make sure that the body
 +     * will display as many rows as {@link #getHeightByRows()} defines.
 +     * <em>Note:</em> If headers/footers are inserted or removed, the widget
 +     * will resize itself to still display the required amount of rows in its
 +     * body. It also takes the horizontal scrollbar into account.
 +     * 
 +     * @param heightMode
 +     *            the mode in to which Grid should be set
 +     */
 +    public void setHeightMode(HeightMode heightMode) {
 +        /*
 +         * This method is a workaround for the fact that Vaadin re-applies
 +         * widget dimensions (height/width) on each state change event. The
 +         * original design was to have setHeight an setHeightByRow be equals,
 +         * and whichever was called the latest was considered in effect.
 +         * 
 +         * But, because of Vaadin always calling setHeight on the widget, this
 +         * approach doesn't work.
 +         */
 +
 +        escalator.setHeightMode(heightMode);
 +    }
 +
 +    /**
 +     * Returns the current {@link HeightMode} the Grid is in.
 +     * <p>
 +     * Defaults to {@link HeightMode#CSS}.
 +     * 
 +     * @return the current HeightMode
 +     */
 +    public HeightMode getHeightMode() {
 +        return escalator.getHeightMode();
 +    }
 +
 +    private Set<String> getConsumedEventsForRenderer(Renderer<?> renderer) {
 +        Set<String> events = new HashSet<String>();
 +        if (renderer instanceof ComplexRenderer) {
 +            Collection<String> consumedEvents = ((ComplexRenderer<?>) renderer)
 +                    .getConsumedEvents();
 +            if (consumedEvents != null) {
 +                events.addAll(consumedEvents);
 +            }
 +        }
 +        return events;
 +    }
 +
 +    @Override
 +    public void onBrowserEvent(Event event) {
 +        if (!isEnabled()) {
 +            return;
 +        }
 +
 +        String eventType = event.getType();
 +
 +        if (eventType.equals(BrowserEvents.FOCUS)
 +                || eventType.equals(BrowserEvents.BLUR)) {
 +            super.onBrowserEvent(event);
 +            return;
 +        }
 +
 +        EventTarget target = event.getEventTarget();
 +
 +        if (!Element.is(target) || isOrContainsInSpacer(Element.as(target))) {
 +            return;
 +        }
 +
 +        Element e = Element.as(target);
 +        RowContainer container = escalator.findRowContainer(e);
 +        Cell cell;
 +
 +        if (container == null) {
 +            if (eventType.equals(BrowserEvents.KEYDOWN)
 +                    || eventType.equals(BrowserEvents.KEYUP)
 +                    || eventType.equals(BrowserEvents.KEYPRESS)) {
 +                cell = cellFocusHandler.getFocusedCell();
 +                container = cellFocusHandler.containerWithFocus;
 +            } else {
 +                // Click might be in an editor cell, should still map.
 +                if (editor.editorOverlay != null
 +                        && editor.editorOverlay.isOrHasChild(e)) {
 +                    container = escalator.getBody();
 +                    int rowIndex = editor.getRow();
 +                    int colIndex = editor.getElementColumn(e);
 +
 +                    if (colIndex < 0) {
 +                        // Click in editor, but not for any column.
 +                        return;
 +                    }
 +
 +                    TableCellElement cellElement = container
 +                            .getRowElement(rowIndex).getCells()
 +                            .getItem(colIndex);
 +
 +                    cell = new Cell(rowIndex, colIndex, cellElement);
 +                } else {
 +                    if (escalator.getElement().isOrHasChild(e)) {
 +                        eventCell.set(new Cell(-1, -1, null), Section.BODY);
 +                        // Fire native events.
 +                        super.onBrowserEvent(event);
 +                    }
 +                    return;
 +                }
 +            }
 +        } else {
 +            cell = container.getCell(e);
 +            if (eventType.equals(BrowserEvents.MOUSEDOWN)) {
 +                cellOnPrevMouseDown = cell;
 +            } else if (cell == null && eventType.equals(BrowserEvents.CLICK)) {
 +                /*
 +                 * Chrome has an interesting idea on click targets (see
 +                 * cellOnPrevMouseDown javadoc). Firefox, on the other hand, has
 +                 * the mousedown target as the click target.
 +                 */
 +                cell = cellOnPrevMouseDown;
 +            }
 +        }
 +
 +        assert cell != null : "received " + eventType
 +                + "-event with a null cell target";
 +        eventCell.set(cell, getSectionFromContainer(container));
 +
 +        // Editor can steal focus from Grid and is still handled
 +        if (isEditorEnabled() && handleEditorEvent(event, container)) {
 +            return;
 +        }
 +
 +        // Fire GridKeyEvents and GridClickEvents. Pass the event to escalator.
 +        super.onBrowserEvent(event);
 +
 +        if (!isElementInChildWidget(e)) {
 +
 +            if (handleHeaderCellDragStartEvent(event, container)) {
 +                return;
 +            }
 +
 +            // Sorting through header Click / KeyUp
 +            if (handleHeaderDefaultRowEvent(event, container)) {
 +                return;
 +            }
 +
 +            if (handleRendererEvent(event, container)) {
 +                return;
 +            }
 +
 +            if (handleCellFocusEvent(event, container)) {
 +                return;
 +            }
 +        }
 +    }
 +
 +    private Section getSectionFromContainer(RowContainer container) {
 +        assert container != null : "RowContainer should not be null";
 +
 +        if (container == escalator.getBody()) {
 +            return Section.BODY;
 +        } else if (container == escalator.getFooter()) {
 +            return Section.FOOTER;
 +        } else if (container == escalator.getHeader()) {
 +            return Section.HEADER;
 +        }
 +        assert false : "RowContainer was not header, footer or body.";
 +        return null;
 +    }
 +
 +    private boolean isOrContainsInSpacer(Node node) {
 +        Node n = node;
 +        while (n != null && n != getElement()) {
 +            boolean isElement = Element.is(n);
 +            if (isElement) {
 +                String className = Element.as(n).getClassName();
 +                if (className.contains(getStylePrimaryName() + "-spacer")) {
 +                    return true;
 +                }
 +            }
 +            n = n.getParentNode();
 +        }
 +        return false;
 +    }
 +
 +    private boolean isElementInChildWidget(Element e) {
 +        Widget w = WidgetUtil.findWidget(e, null);
 +
 +        if (w == this) {
 +            return false;
 +        }
 +
 +        /*
 +         * If e is directly inside this grid, but the grid is wrapped in a
 +         * Composite, findWidget is not going to find this, only the wrapper.
 +         * Thus we need to check its parents to see if we encounter this; if we
 +         * don't, the found widget is actually a parent of this, so we should
 +         * return false.
 +         */
 +        while (w != null && w != this) {
 +            w = w.getParent();
 +        }
 +        return w != null;
 +    }
 +
 +    private boolean handleEditorEvent(Event event, RowContainer container) {
 +        Widget w;
 +        if (editor.focusedColumnIndex < 0) {
 +            w = null;
 +        } else {
 +            w = editor.getWidget(getColumn(editor.focusedColumnIndex));
 +        }
 +
 +        EditorDomEvent<T> editorEvent = new EditorDomEvent<T>(event,
 +                getEventCell(), w);
 +
 +        return getEditor().getEventHandler().handleEvent(editorEvent);
 +    }
 +
 +    private boolean handleRendererEvent(Event event, RowContainer container) {
 +
 +        if (container == escalator.getBody()) {
 +            Column<?, T> gridColumn = eventCell.getColumn();
 +            boolean enterKey = event.getType().equals(BrowserEvents.KEYDOWN)
 +                    && event.getKeyCode() == KeyCodes.KEY_ENTER;
 +            boolean doubleClick = event.getType()
 +                    .equals(BrowserEvents.DBLCLICK);
 +
 +            if (gridColumn.getRenderer() instanceof ComplexRenderer) {
 +                ComplexRenderer<?> cplxRenderer = (ComplexRenderer<?>) gridColumn
 +                        .getRenderer();
 +                if (cplxRenderer.getConsumedEvents().contains(event.getType())) {
 +                    if (cplxRenderer.onBrowserEvent(eventCell, event)) {
 +                        return true;
 +                    }
 +                }
 +
 +                // Calls onActivate if KeyDown and Enter or double click
 +                if ((enterKey || doubleClick)
 +                        && cplxRenderer.onActivate(eventCell)) {
 +                    return true;
 +                }
 +            }
 +        }
 +        return false;
 +    }
 +
 +    private boolean handleCellFocusEvent(Event event, RowContainer container) {
 +        Collection<String> navigation = cellFocusHandler.getNavigationEvents();
 +        if (navigation.contains(event.getType())) {
 +            cellFocusHandler.handleNavigationEvent(event, eventCell);
 +        }
 +        return false;
 +    }
 +
 +    private boolean handleHeaderCellDragStartEvent(Event event,
 +            RowContainer container) {
 +        if (!isColumnReorderingAllowed()) {
 +            return false;
 +        }
 +        if (container != escalator.getHeader()) {
 +            return false;
 +        }
 +        if (eventCell.getColumnIndex() < escalator.getColumnConfiguration()
 +                .getFrozenColumnCount()) {
 +            return false;
 +        }
 +
 +        if (event.getTypeInt() == Event.ONMOUSEDOWN
 +                && event.getButton() == NativeEvent.BUTTON_LEFT
 +                || event.getTypeInt() == Event.ONTOUCHSTART) {
 +            dndHandler.onDragStartOnDraggableElement(event,
 +                    headerCellDndCallback);
 +            event.preventDefault();
 +            event.stopPropagation();
 +            return true;
 +        }
 +        return false;
 +    }
 +
 +    private Point rowEventTouchStartingPoint;
 +    private CellStyleGenerator<T> cellStyleGenerator;
 +    private RowStyleGenerator<T> rowStyleGenerator;
 +    private RowReference<T> rowReference = new RowReference<T>(this);
 +    private CellReference<T> cellReference = new CellReference<T>(rowReference);
 +    private RendererCellReference rendererCellReference = new RendererCellReference(
 +            (RowReference<Object>) rowReference);
 +
 +    private boolean handleHeaderDefaultRowEvent(Event event,
 +            RowContainer container) {
 +        if (container != escalator.getHeader()) {
 +            return false;
 +        }
 +        if (!getHeader().getRow(eventCell.getRowIndex()).isDefault()) {
 +            return false;
 +        }
 +        if (!eventCell.getColumn().isSortable()) {
 +            // Only handle sorting events if the column is sortable
 +            return false;
 +        }
 +
 +        if (BrowserEvents.MOUSEDOWN.equals(event.getType())
 +                && event.getShiftKey()) {
 +            // Don't select text when shift clicking on a header.
 +            event.preventDefault();
 +        }
 +
 +        if (BrowserEvents.TOUCHSTART.equals(event.getType())) {
 +            if (event.getTouches().length() > 1) {
 +                return false;
 +            }
 +
 +            event.preventDefault();
 +
 +            Touch touch = event.getChangedTouches().get(0);
 +            rowEventTouchStartingPoint = new Point(touch.getClientX(),
 +                    touch.getClientY());
 +
 +            sorter.sortAfterDelay(GridConstants.LONG_TAP_DELAY, true);
 +
 +            return true;
 +
 +        } else if (BrowserEvents.TOUCHMOVE.equals(event.getType())) {
 +            if (event.getTouches().length() > 1) {
 +                return false;
 +            }
 +
 +            event.preventDefault();
 +
 +            Touch touch = event.getChangedTouches().get(0);
 +            double diffX = Math.abs(touch.getClientX()
 +                    - rowEventTouchStartingPoint.getX());
 +            double diffY = Math.abs(touch.getClientY()
 +                    - rowEventTouchStartingPoint.getY());
 +
 +            // Cancel long tap if finger strays too far from
 +            // starting point
 +            if (diffX > GridConstants.LONG_TAP_THRESHOLD
 +                    || diffY > GridConstants.LONG_TAP_THRESHOLD) {
 +                sorter.cancelDelayedSort();
 +            }
 +
 +            return true;
 +
 +        } else if (BrowserEvents.TOUCHEND.equals(event.getType())) {
 +            if (event.getTouches().length() > 1) {
 +                return false;
 +            }
 +
 +            if (sorter.isDelayedSortScheduled()) {
 +                // Not a long tap yet, perform single sort
 +                sorter.cancelDelayedSort();
 +                sorter.sort(eventCell.getColumn(), false);
 +            }
 +
 +            return true;
 +
 +        } else if (BrowserEvents.TOUCHCANCEL.equals(event.getType())) {
 +            if (event.getTouches().length() > 1) {
 +                return false;
 +            }
 +
 +            sorter.cancelDelayedSort();
 +
 +            return true;
 +
 +        } else if (BrowserEvents.CLICK.equals(event.getType())) {
 +
 +            sorter.sort(eventCell.getColumn(), event.getShiftKey());
 +
 +            // Click events should go onward to cell focus logic
 +            return false;
 +        } else {
 +            return false;
 +        }
 +    }
 +
 +    @Override
 +    @SuppressWarnings("deprecation")
 +    public com.google.gwt.user.client.Element getSubPartElement(String subPart) {
 +
 +        /*
 +         * handles details[] (translated to spacer[] for Escalator), cell[],
 +         * header[] and footer[]
 +         */
 +
 +        // "#header[0][0]/DRAGhANDLE"
 +        Element escalatorElement = escalator.getSubPartElement(subPart
 +                .replaceFirst("^details\\[", "spacer["));
 +
 +        if (escalatorElement != null) {
 +
 +            int detailIdx = subPart.indexOf("/");
 +            if (detailIdx > 0) {
 +                String detail = subPart.substring(detailIdx + 1);
 +                getLogger().severe(
 +                        "Looking up detail from index " + detailIdx
 +                                + " onward: \"" + detail + "\"");
 +                if (detail.equalsIgnoreCase("content")) {
 +                    // XXX: Fix this to look up by class name!
 +                    return DOM.asOld(Element.as(escalatorElement.getChild(0)));
 +                }
 +                if (detail.equalsIgnoreCase("draghandle")) {
 +                    // XXX: Fix this to look up by class name!
 +                    return DOM.asOld(Element.as(escalatorElement.getChild(1)));
 +                }
 +            }
 +
 +            return DOM.asOld(escalatorElement);
 +        }
 +
 +        SubPartArguments args = SubPartArguments.create(subPart);
 +        Element editor = getSubPartElementEditor(args);
 +        if (editor != null) {
 +            return DOM.asOld(editor);
 +        }
 +
 +        return null;
 +    }
 +
 +    private Element getSubPartElementEditor(SubPartArguments args) {
 +
 +        if (!args.getType().equalsIgnoreCase("editor")
 +                || editor.getState() != State.ACTIVE) {
 +            return null;
 +        }
 +
 +        if (args.getIndicesLength() == 0) {
 +            return editor.editorOverlay;
 +        } else if (args.getIndicesLength() == 1) {
 +            int index = args.getIndex(0);
 +            if (index >= columns.size()) {
 +                return null;
 +            }
 +
 +            escalator.scrollToColumn(index, ScrollDestination.ANY, 0);
 +            Widget widget = editor.getWidget(columns.get(index));
 +
 +            if (widget != null) {
 +                return widget.getElement();
 +            }
 +
 +            // No widget for the column.
 +            return null;
 +        }
 +
 +        return null;
 +    }
 +
 +    @Override
 +    @SuppressWarnings("deprecation")
 +    public String getSubPartName(com.google.gwt.user.client.Element subElement) {
 +
 +        String escalatorStructureName = escalator.getSubPartName(subElement);
 +        if (escalatorStructureName != null) {
 +            return escalatorStructureName.replaceFirst("^spacer", "details");
 +        }
 +
 +        String editorName = getSubPartNameEditor(subElement);
 +        if (editorName != null) {
 +            return editorName;
 +        }
 +
 +        return null;
 +    }
 +
 +    private String getSubPartNameEditor(Element subElement) {
 +
 +        if (editor.getState() != State.ACTIVE
 +                || !editor.editorOverlay.isOrHasChild(subElement)) {
 +            return null;
 +        }
 +
 +        int i = 0;
 +        for (Column<?, T> column : columns) {
 +            if (editor.getWidget(column).getElement().isOrHasChild(subElement)) {
 +                return "editor[" + i + "]";
 +            }
 +            ++i;
 +        }
 +
 +        return "editor";
 +    }
 +
 +    private void setSelectColumnRenderer(
 +            final Renderer<Boolean> selectColumnRenderer) {
 +        if (this.selectColumnRenderer == selectColumnRenderer) {
 +            return;
 +        }
 +
 +        if (this.selectColumnRenderer != null) {
 +            if (this.selectColumnRenderer instanceof ComplexRenderer) {
 +                // End of Life for the old selection column renderer.
 +                ((ComplexRenderer<?>) this.selectColumnRenderer).destroy();
 +            }
 +
 +            // Clear field so frozen column logic in the remove method knows
 +            // what to do
 +            Column<?, T> colToRemove = selectionColumn;
 +            selectionColumn = null;
 +            removeColumnSkipSelectionColumnCheck(colToRemove);
 +            cellFocusHandler.offsetRangeBy(-1);
 +        }
 +
 +        this.selectColumnRenderer = selectColumnRenderer;
 +
 +        if (selectColumnRenderer != null) {
 +            cellFocusHandler.offsetRangeBy(1);
 +            selectionColumn = new SelectionColumn(selectColumnRenderer);
 +
 +            addColumnSkipSelectionColumnCheck(selectionColumn, 0);
 +            selectionColumn.initDone();
 +        } else {
 +            selectionColumn = null;
 +            refreshBody();
 +        }
 +
 +        updateFrozenColumns();
 +    }
 +
 +    /**
 +     * Sets the current selection model.
 +     * <p>
 +     * This function will call {@link SelectionModel#setGrid(Grid)}.
 +     * 
 +     * @param selectionModel
 +     *            a selection model implementation.
 +     * @throws IllegalArgumentException
 +     *             if selection model argument is null
 +     */
 +    public void setSelectionModel(SelectionModel<T> selectionModel) {
 +
 +        if (selectionModel == null) {
 +            throw new IllegalArgumentException("Selection model can't be null");
 +        }
 +
 +        if (this.selectionModel != null) {
 +            // Detach selection model from Grid.
 +            this.selectionModel.setGrid(null);
 +        }
 +
 +        this.selectionModel = selectionModel;
 +        selectionModel.setGrid(this);
 +        setSelectColumnRenderer(this.selectionModel
 +                .getSelectionColumnRenderer());
 +
 +        // Refresh rendered rows to update selection, if it has changed
 +        refreshBody();
 +    }
 +
 +    /**
 +     * Gets a reference to the current selection model.
 +     * 
 +     * @return the currently used SelectionModel instance.
 +     */
 +    public SelectionModel<T> getSelectionModel() {
 +        return selectionModel;
 +    }
 +
 +    /**
 +     * Sets current selection mode.
 +     * <p>
 +     * This is a shorthand method for {@link Grid#setSelectionModel}.
 +     * 
 +     * @param mode
 +     *            a selection mode value
 +     * @see {@link SelectionMode}.
 +     */
 +    public void setSelectionMode(SelectionMode mode) {
 +        SelectionModel<T> model = mode.createModel();
 +        setSelectionModel(model);
 +    }
 +
 +    /**
 +     * Test if a row is selected.
 +     * 
 +     * @param row
 +     *            a row object
 +     * @return true, if the current selection model considers the provided row
 +     *         object selected.
 +     */
 +    public boolean isSelected(T row) {
 +        return selectionModel.isSelected(row);
 +    }
 +
 +    /**
 +     * Select a row using the current selection model.
 +     * <p>
 +     * Only selection models implementing {@link SelectionModel.Single} and
 +     * {@link SelectionModel.Multi} are supported; for anything else, an
 +     * exception will be thrown.
 +     * 
 +     * @param row
 +     *            a row object
 +     * @return <code>true</code> iff the current selection changed
 +     * @throws IllegalStateException
 +     *             if the current selection model is not an instance of
 +     *             {@link SelectionModel.Single} or {@link SelectionModel.Multi}
 +     */
 +    public boolean select(T row) {
 +        if (selectionModel instanceof SelectionModel.Single<?>) {
 +            return ((SelectionModel.Single<T>) selectionModel).select(row);
 +        } else if (selectionModel instanceof SelectionModel.Multi<?>) {
 +            return ((SelectionModel.Multi<T>) selectionModel)
 +                    .select(Collections.singleton(row));
 +        } else {
 +            throw new IllegalStateException("Unsupported selection model");
 +        }
 +    }
 +
 +    /**
 +     * Deselect a row using the current selection model.
 +     * <p>
 +     * Only selection models implementing {@link SelectionModel.Single} and
 +     * {@link SelectionModel.Multi} are supported; for anything else, an
 +     * exception will be thrown.
 +     * 
 +     * @param row
 +     *            a row object
 +     * @return <code>true</code> iff the current selection changed
 +     * @throws IllegalStateException
 +     *             if the current selection model is not an instance of
 +     *             {@link SelectionModel.Single} or {@link SelectionModel.Multi}
 +     */
 +    public boolean deselect(T row) {
 +        if (selectionModel instanceof SelectionModel.Single<?>) {
 +            return ((SelectionModel.Single<T>) selectionModel).deselect(row);
 +        } else if (selectionModel instanceof SelectionModel.Multi<?>) {
 +            return ((SelectionModel.Multi<T>) selectionModel)
 +                    .deselect(Collections.singleton(row));
 +        } else {
 +            throw new IllegalStateException("Unsupported selection model");
 +        }
 +    }
 +
 +    /**
 +     * Deselect all rows using the current selection model.
 +     * 
 +     * @param row
 +     *            a row object
 +     * @return <code>true</code> iff the current selection changed
 +     * @throws IllegalStateException
 +     *             if the current selection model is not an instance of
 +     *             {@link SelectionModel.Single} or {@link SelectionModel.Multi}
 +     */
 +    public boolean deselectAll() {
 +        if (selectionModel instanceof SelectionModel.Single<?>) {
 +            Single<T> single = ((SelectionModel.Single<T>) selectionModel);
 +            if (single.getSelectedRow() != null) {
 +                return single.deselect(single.getSelectedRow());
 +            } else {
 +                return false;
 +            }
 +        } else if (selectionModel instanceof SelectionModel.Multi<?>) {
 +            return ((SelectionModel.Multi<T>) selectionModel).deselectAll();
 +        } else {
 +            throw new IllegalStateException("Unsupported selection model");
 +        }
 +    }
 +
 +    /**
 +     * Gets last selected row from the current SelectionModel.
 +     * <p>
 +     * Only selection models implementing {@link SelectionModel.Single} are
 +     * valid for this method; for anything else, use the
 +     * {@link Grid#getSelectedRows()} method.
 +     * 
 +     * @return a selected row reference, or null, if no row is selected
 +     * @throws IllegalStateException
 +     *             if the current selection model is not an instance of
 +     *             {@link SelectionModel.Single}
 +     */
 +    public T getSelectedRow() {
 +        if (selectionModel instanceof SelectionModel.Single<?>) {
 +            return ((SelectionModel.Single<T>) selectionModel).getSelectedRow();
 +        } else {
 +            throw new IllegalStateException(
 +                    "Unsupported selection model; can not get single selected row");
 +        }
 +    }
 +
 +    /**
 +     * Gets currently selected rows from the current selection model.
 +     * 
 +     * @return a non-null collection containing all currently selected rows.
 +     */
 +    public Collection<T> getSelectedRows() {
 +        return selectionModel.getSelectedRows();
 +    }
 +
 +    @Override
 +    public HandlerRegistration addSelectionHandler(
 +            final SelectionHandler<T> handler) {
 +        return addHandler(handler, SelectionEvent.getType());
 +    }
 +
 +    /**
 +     * Sets the current sort order using the fluid Sort API. Read the
 +     * documentation for {@link Sort} for more information.
 +     * 
 +     * @param s
 +     *            a sort instance
 +     */
 +    public void sort(Sort s) {
 +        setSortOrder(s.build());
 +    }
 +
 +    /**
 +     * Sorts the Grid data in ascending order along one column.
 +     * 
 +     * @param column
 +     *            a grid column reference
 +     */
 +    public <C> void sort(Column<C, T> column) {
 +        sort(column, SortDirection.ASCENDING);
 +    }
 +
 +    /**
 +     * Sorts the Grid data along one column.
 +     * 
 +     * @param column
 +     *            a grid column reference
 +     * @param direction
 +     *            a sort direction value
 +     */
 +    public <C> void sort(Column<C, T> column, SortDirection direction) {
 +        sort(Sort.by(column, direction));
 +    }
 +
 +    /**
 +     * Sets the sort order to use. Setting this causes the Grid to re-sort
 +     * itself.
 +     * 
 +     * @param order
 +     *            a sort order list. If set to null, the sort order is cleared.
 +     */
 +    public void setSortOrder(List<SortOrder> order) {
 +        setSortOrder(order, false);
 +    }
 +
 +    /**
 +     * Clears the sort order and indicators without re-sorting.
 +     */
 +    private void clearSortOrder() {
 +        sortOrder.clear();
 +        refreshHeader();
 +    }
 +
 +    private void setSortOrder(List<SortOrder> order, boolean userOriginated) {
 +        if (order != sortOrder) {
 +            sortOrder.clear();
 +            if (order != null) {
 +                sortOrder.addAll(order);
 +            }
 +        }
 +        sort(userOriginated);
 +    }
 +
 +    /**
 +     * Get a copy of the current sort order array.
 +     * 
 +     * @return a copy of the current sort order array
 +     */
 +    public List<SortOrder> getSortOrder() {
 +        return Collections.unmodifiableList(sortOrder);
 +    }
 +
 +    /**
 +     * Finds the sorting order for this column
 +     */
 +    private SortOrder getSortOrder(Column<?, ?> column) {
 +        for (SortOrder order : getSortOrder()) {
 +            if (order.getColumn() == column) {
 +                return order;
 +            }
 +        }
 +        return null;
 +    }
 +
 +    /**
 +     * Register a GWT event handler for a sorting event. This handler gets
 +     * called whenever this Grid needs its data source to provide data sorted in
 +     * a specific order.
 +     * 
 +     * @param handler
 +     *            a sort event handler
 +     * @return the registration for the event
 +     */
 +    public HandlerRegistration addSortHandler(SortHandler<T> handler) {
 +        return addHandler(handler, SortEvent.getType());
 +    }
 +
 +    /**
 +     * Register a GWT event handler for a select all event. This handler gets
 +     * called whenever Grid needs all rows selected.
 +     * 
 +     * @param handler
 +     *            a select all event handler
 +     */
 +    public HandlerRegistration addSelectAllHandler(SelectAllHandler<T> handler) {
 +        return addHandler(handler, SelectAllEvent.getType());
 +    }
 +
 +    /**
 +     * Register a GWT event handler for a data available event. This handler
 +     * gets called whenever the {@link DataSource} for this Grid has new data
 +     * available.
 +     * <p>
 +     * This handle will be fired with the current available data after
 +     * registration is done.
 +     * 
 +     * @param handler
 +     *            a data available event handler
 +     * @return the registartion for the event
 +     */
 +    public HandlerRegistration addDataAvailableHandler(
 +            final DataAvailableHandler handler) {
 +        // Deferred call to handler with current row range
 +        Scheduler.get().scheduleFinally(new ScheduledCommand() {
 +            @Override
 +            public void execute() {
 +                if (!dataIsBeingFetched) {
 +                    handler.onDataAvailable(new DataAvailableEvent(
 +                            currentDataAvailable));
 +                }
 +            }
 +        });
 +        return addHandler(handler, DataAvailableEvent.TYPE);
 +    }
 +
 +    /**
 +     * Register a BodyKeyDownHandler to this Grid. The event for this handler is
 +     * fired when a KeyDown event occurs while cell focus is in the Body of this
 +     * Grid.
 +     * 
 +     * @param handler
 +     *            the key handler to register
 +     * @return the registration for the event
 +     */
 +    public HandlerRegistration addBodyKeyDownHandler(BodyKeyDownHandler handler) {
 +        return addHandler(handler, keyDown.getAssociatedType());
 +    }
 +
 +    /**
 +     * Register a BodyKeyUpHandler to this Grid. The event for this handler is
 +     * fired when a KeyUp event occurs while cell focus is in the Body of this
 +     * Grid.
 +     * 
 +     * @param handler
 +     *            the key handler to register
 +     * @return the registration for the event
 +     */
 +    public HandlerRegistration addBodyKeyUpHandler(BodyKeyUpHandler handler) {
 +        return addHandler(handler, keyUp.getAssociatedType());
 +    }
 +
 +    /**
 +     * Register a BodyKeyPressHandler to this Grid. The event for this handler
 +     * is fired when a KeyPress event occurs while cell focus is in the Body of
 +     * this Grid.
 +     * 
 +     * @param handler
 +     *            the key handler to register
 +     * @return the registration for the event
 +     */
 +    public HandlerRegistration addBodyKeyPressHandler(
 +            BodyKeyPressHandler handler) {
 +        return addHandler(handler, keyPress.getAssociatedType());
 +    }
 +
 +    /**
 +     * Register a HeaderKeyDownHandler to this Grid. The event for this handler
 +     * is fired when a KeyDown event occurs while cell focus is in the Header of
 +     * this Grid.
 +     * 
 +     * @param handler
 +     *            the key handler to register
 +     * @return the registration for the event
 +     */
 +    public HandlerRegistration addHeaderKeyDownHandler(
 +            HeaderKeyDownHandler handler) {
 +        return addHandler(handler, keyDown.getAssociatedType());
 +    }
 +
 +    /**
 +     * Register a HeaderKeyUpHandler to this Grid. The event for this handler is
 +     * fired when a KeyUp event occurs while cell focus is in the Header of this
 +     * Grid.
 +     * 
 +     * @param handler
 +     *            the key handler to register
 +     * @return the registration for the event
 +     */
 +    public HandlerRegistration addHeaderKeyUpHandler(HeaderKeyUpHandler handler) {
 +        return addHandler(handler, keyUp.getAssociatedType());
 +    }
 +
 +    /**
 +     * Register a HeaderKeyPressHandler to this Grid. The event for this handler
 +     * is fired when a KeyPress event occurs while cell focus is in the Header
 +     * of this Grid.
 +     * 
 +     * @param handler
 +     *            the key handler to register
 +     * @return the registration for the event
 +     */
 +    public HandlerRegistration addHeaderKeyPressHandler(
 +            HeaderKeyPressHandler handler) {
 +        return addHandler(handler, keyPress.getAssociatedType());
 +    }
 +
 +    /**
 +     * Register a FooterKeyDownHandler to this Grid. The event for this handler
 +     * is fired when a KeyDown event occurs while cell focus is in the Footer of
 +     * this Grid.
 +     * 
 +     * @param handler
 +     *            the key handler to register
 +     * @return the registration for the event
 +     */
 +    public HandlerRegistration addFooterKeyDownHandler(
 +            FooterKeyDownHandler handler) {
 +        return addHandler(handler, keyDown.getAssociatedType());
 +    }
 +
 +    /**
 +     * Register a FooterKeyUpHandler to this Grid. The event for this handler is
 +     * fired when a KeyUp event occurs while cell focus is in the Footer of this
 +     * Grid.
 +     * 
 +     * @param handler
 +     *            the key handler to register
 +     * @return the registration for the event
 +     */
 +    public HandlerRegistration addFooterKeyUpHandler(FooterKeyUpHandler handler) {
 +        return addHandler(handler, keyUp.getAssociatedType());
 +    }
 +
 +    /**
 +     * Register a FooterKeyPressHandler to this Grid. The event for this handler
 +     * is fired when a KeyPress event occurs while cell focus is in the Footer
 +     * of this Grid.
 +     * 
 +     * @param handler
 +     *            the key handler to register
 +     * @return the registration for the event
 +     */
 +    public HandlerRegistration addFooterKeyPressHandler(
 +            FooterKeyPressHandler handler) {
 +        return addHandler(handler, keyPress.getAssociatedType());
 +    }
 +
 +    /**
 +     * Register a BodyClickHandler to this Grid. The event for this handler is
 +     * fired when a Click event occurs in the Body of this Grid.
 +     * 
 +     * @param handler
 +     *            the click handler to register
 +     * @return the registration for the event
 +     */
 +    public HandlerRegistration addBodyClickHandler(BodyClickHandler handler) {
 +        return addHandler(handler, clickEvent.getAssociatedType());
 +    }
 +
 +    /**
 +     * Register a HeaderClickHandler to this Grid. The event for this handler is
 +     * fired when a Click event occurs in the Header of this Grid.
 +     * 
 +     * @param handler
 +     *            the click handler to register
 +     * @return the registration for the event
 +     */
 +    public HandlerRegistration addHeaderClickHandler(HeaderClickHandler handler) {
 +        return addHandler(handler, clickEvent.getAssociatedType());
 +    }
 +
 +    /**
 +     * Register a FooterClickHandler to this Grid. The event for this handler is
 +     * fired when a Click event occurs in the Footer of this Grid.
 +     * 
 +     * @param handler
 +     *            the click handler to register
 +     * @return the registration for the event
 +     */
 +    public HandlerRegistration addFooterClickHandler(FooterClickHandler handler) {
 +        return addHandler(handler, clickEvent.getAssociatedType());
 +    }
 +
 +    /**
 +     * Register a BodyDoubleClickHandler to this Grid. The event for this
 +     * handler is fired when a double click event occurs in the Body of this
 +     * Grid.
 +     * 
 +     * @param handler
 +     *            the double click handler to register
 +     * @return the registration for the event
 +     */
 +    public HandlerRegistration addBodyDoubleClickHandler(
 +            BodyDoubleClickHandler handler) {
 +        return addHandler(handler, doubleClickEvent.getAssociatedType());
 +    }
 +
 +    /**
 +     * Register a HeaderDoubleClickHandler to this Grid. The event for this
 +     * handler is fired when a double click event occurs in the Header of this
 +     * Grid.
 +     * 
 +     * @param handler
 +     *            the double click handler to register
 +     * @return the registration for the event
 +     */
 +    public HandlerRegistration addHeaderDoubleClickHandler(
 +            HeaderDoubleClickHandler handler) {
 +        return addHandler(handler, doubleClickEvent.getAssociatedType());
 +    }
 +
 +    /**
 +     * Register a FooterDoubleClickHandler to this Grid. The event for this
 +     * handler is fired when a double click event occurs in the Footer of this
 +     * Grid.
 +     * 
 +     * @param handler
 +     *            the double click handler to register
 +     * @return the registration for the event
 +     */
 +    public HandlerRegistration addFooterDoubleClickHandler(
 +            FooterDoubleClickHandler handler) {
 +        return addHandler(handler, doubleClickEvent.getAssociatedType());
 +    }
 +
 +    /**
 +     * Register a column reorder handler to this Grid. The event for this
 +     * handler is fired when the Grid's columns are reordered.
 +     * 
 +     * @since 7.5.0
 +     * @param handler
 +     *            the handler for the event
 +     * @return the registration for the event
 +     */
 +    public HandlerRegistration addColumnReorderHandler(
 +            ColumnReorderHandler<T> handler) {
 +        return addHandler(handler, ColumnReorderEvent.getType());
 +    }
 +
 +    /**
 +     * Register a column visibility change handler to this Grid. The event for
 +     * this handler is fired when the Grid's columns change visibility.
 +     * 
 +     * @since 7.5.0
 +     * @param handler
 +     *            the handler for the event
 +     * @return the registration for the event
 +     */
 +    public HandlerRegistration addColumnVisibilityChangeHandler(
 +            ColumnVisibilityChangeHandler<T> handler) {
 +        return addHandler(handler, ColumnVisibilityChangeEvent.getType());
 +    }
 +
 +    /**
 +     * Register a column resize handler to this Grid. The event for this handler
 +     * is fired when the Grid's columns are resized.
 +     * 
 +     * @since 7.6
 +     * @param handler
 +     *            the handler for the event
 +     * @return the registration for the event
 +     */
 +    public HandlerRegistration addColumnResizeHandler(
 +            ColumnResizeHandler<T> handler) {
 +        return addHandler(handler, ColumnResizeEvent.getType());
 +    }
 +
 +    /**
 +     * Apply sorting to data source.
 +     */
 +    private void sort(boolean userOriginated) {
 +        refreshHeader();
 +        fireEvent(new SortEvent<T>(this,
 +                Collections.unmodifiableList(sortOrder), userOriginated));
 +    }
 +
 +    private int getLastVisibleRowIndex() {
 +        int lastRowIndex = escalator.getVisibleRowRange().getEnd();
 +        int footerTop = escalator.getFooter().getElement().getAbsoluteTop();
 +        Element lastRow;
 +
 +        do {
 +            lastRow = escalator.getBody().getRowElement(--lastRowIndex);
 +        } while (lastRow.getAbsoluteTop() > footerTop);
 +
 +        return lastRowIndex;
 +    }
 +
 +    private int getFirstVisibleRowIndex() {
 +        int firstRowIndex = escalator.getVisibleRowRange().getStart();
 +        int headerBottom = escalator.getHeader().getElement()
 +                .getAbsoluteBottom();
 +        Element firstRow = escalator.getBody().getRowElement(firstRowIndex);
 +
 +        while (firstRow.getAbsoluteBottom() < headerBottom) {
 +            firstRow = escalator.getBody().getRowElement(++firstRowIndex);
 +        }
 +
 +        return firstRowIndex;
 +    }
 +
 +    /**
 +     * Adds a scroll handler to this grid
 +     * 
 +     * @param handler
 +     *            the scroll handler to add
 +     * @return a handler registration for the registered scroll handler
 +     */
 +    public HandlerRegistration addScrollHandler(ScrollHandler handler) {
 +        return addHandler(handler, ScrollEvent.TYPE);
 +    }
 +
 +    @Override
 +    public boolean isWorkPending() {
 +        return escalator.isWorkPending() || dataIsBeingFetched
 +                || autoColumnWidthsRecalculator.isScheduled()
 +                || editor.isWorkPending();
 +    }
 +
 +    /**
 +     * Returns whether columns can be reordered with drag and drop.
 +     * 
 +     * @since 7.5.0
 +     * @return <code>true</code> if columns can be reordered, false otherwise
 +     */
 +    public boolean isColumnReorderingAllowed() {
 +        return columnReorderingAllowed;
 +    }
 +
 +    /**
 +     * Sets whether column reordering with drag and drop is allowed or not.
 +     * 
 +     * @since 7.5.0
 +     * @param columnReorderingAllowed
 +     *            specifies whether column reordering is allowed
 +     */
 +    public void setColumnReorderingAllowed(boolean columnReorderingAllowed) {
 +        this.columnReorderingAllowed = columnReorderingAllowed;
 +    }
 +
 +    /**
 +     * Sets a new column order for the grid. All columns which are not ordered
 +     * here will remain in the order they were before as the last columns of
 +     * grid.
 +     * 
 +     * @param orderedColumns
 +     *            array of columns in wanted order
 +     */
 +    public void setColumnOrder(Column<?, T>... orderedColumns) {
 +        ColumnConfiguration conf = getEscalator().getColumnConfiguration();
 +
 +        // Trigger ComplexRenderer.destroy for old content
 +        conf.removeColumns(0, conf.getColumnCount());
 +
 +        List<Column<?, T>> newOrder = new ArrayList<Column<?, T>>();
 +        if (selectionColumn != null) {
 +            newOrder.add(selectionColumn);
 +        }
 +
 +        int i = 0;
 +        for (Column<?, T> column : orderedColumns) {
 +            if (columns.contains(column)) {
 +                newOrder.add(column);
 +                ++i;
 +            } else {
 +                throw new IllegalArgumentException("Given column at index " + i
 +                        + " does not exist in Grid");
 +            }
 +        }
 +
 +        if (columns.size() != newOrder.size()) {
 +            columns.removeAll(newOrder);
 +            newOrder.addAll(columns);
 +        }
 +        columns = newOrder;
 +
 +        List<Column<?, T>> visibleColumns = getVisibleColumns();
 +
 +        // Do ComplexRenderer.init and render new content
 +        conf.insertColumns(0, visibleColumns.size());
 +
 +        // Number of frozen columns should be kept same #16901
 +        updateFrozenColumns();
 +
 +        // Update column widths.
 +        for (Column<?, T> column : columns) {
 +            column.reapplyWidth();
 +        }
 +
 +        // Recalculate all the colspans
 +        for (HeaderRow row : header.getRows()) {
 +            row.calculateColspans();
 +        }
 +        for (FooterRow row : footer.getRows()) {
 +            row.calculateColspans();
 +        }
 +
 +        columnHider.updateTogglesOrder();
 +
 +        fireEvent(new ColumnReorderEvent<T>());
 +    }
 +
 +    /**
 +     * Sets the style generator that is used for generating styles for cells
 +     * 
 +     * @param cellStyleGenerator
 +     *            the cell style generator to set, or <code>null</code> to
 +     *            remove a previously set generator
 +     */
 +    public void setCellStyleGenerator(CellStyleGenerator<T> cellStyleGenerator) {
 +        this.cellStyleGenerator = cellStyleGenerator;
 +        refreshBody();
 +    }
 +
 +    /**
 +     * Gets the style generator that is used for generating styles for cells
 +     * 
 +     * @return the cell style generator, or <code>null</code> if no generator is
 +     *         set
 +     */
 +    public CellStyleGenerator<T> getCellStyleGenerator() {
 +        return cellStyleGenerator;
 +    }
 +
 +    /**
 +     * Sets the style generator that is used for generating styles for rows
 +     * 
 +     * @param rowStyleGenerator
 +     *            the row style generator to set, or <code>null</code> to remove
 +     *            a previously set generator
 +     */
 +    public void setRowStyleGenerator(RowStyleGenerator<T> rowStyleGenerator) {
 +        this.rowStyleGenerator = rowStyleGenerator;
 +        refreshBody();
 +    }
 +
 +    /**
 +     * Gets the style generator that is used for generating styles for rows
 +     * 
 +     * @return the row style generator, or <code>null</code> if no generator is
 +     *         set
 +     */
 +    public RowStyleGenerator<T> getRowStyleGenerator() {
 +        return rowStyleGenerator;
 +    }
 +
 +    private static void setCustomStyleName(Element element, String styleName) {
 +        assert element != null;
 +
 +        String oldStyleName = element
 +                .getPropertyString(CUSTOM_STYLE_PROPERTY_NAME);
 +
 +        if (!SharedUtil.equals(oldStyleName, styleName)) {
 +            if (oldStyleName != null && !oldStyleName.isEmpty()) {
 +                element.removeClassName(oldStyleName);
 +            }
 +            if (styleName != null && !styleName.isEmpty()) {
 +                element.addClassName(styleName);
 +            }
 +            element.setPropertyString(CUSTOM_STYLE_PROPERTY_NAME, styleName);
 +        }
 +
 +    }
 +
 +    /**
 +     * Opens the editor over the row with the given index.
 +     * 
 +     * @param rowIndex
 +     *            the index of the row to be edited
 +     * 
 +     * @throws IllegalStateException
 +     *             if the editor is not enabled
 +     * @throws IllegalStateException
 +     *             if the editor is already in edit mode
 +     */
 +    public void editRow(int rowIndex) {
 +        editor.editRow(rowIndex);
 +    }
 +
 +    /**
 +     * Returns whether the editor is currently open on some row.
 +     * 
 +     * @return {@code true} if the editor is active, {@code false} otherwise.
 +     */
 +    public boolean isEditorActive() {
 +        return editor.getState() != State.INACTIVE;
 +    }
 +
 +    /**
 +     * Saves any unsaved changes in the editor to the data source.
 +     * 
 +     * @throws IllegalStateException
 +     *             if the editor is not enabled
 +     * @throws IllegalStateException
 +     *             if the editor is not in edit mode
 +     */
 +    public void saveEditor() {
 +        editor.save();
 +    }
 +
 +    /**
 +     * Cancels the currently active edit and hides the editor. Any changes that
 +     * are not {@link #saveEditor() saved} are lost.
 +     * 
 +     * @throws IllegalStateException
 +     *             if the editor is not enabled
 +     * @throws IllegalStateException
 +     *             if the editor is not in edit mode
 +     */
 +    public void cancelEditor() {
 +        editor.cancel();
 +    }
 +
 +    /**
 +     * Returns the handler responsible for binding data and editor widgets to
 +     * the editor.
 +     * 
 +     * @return the editor handler or null if not set
 +     */
 +    public EditorHandler<T> getEditorHandler() {
 +        return editor.getHandler();
 +    }
 +
 +    /**
 +     * Sets the handler responsible for binding data and editor widgets to the
 +     * editor.
 +     * 
 +     * @param rowHandler
 +     *            the new editor handler
 +     * 
 +     * @throws IllegalStateException
 +     *             if the editor is currently in edit mode
 +     */
 +    public void setEditorHandler(EditorHandler<T> handler) {
 +        editor.setHandler(handler);
 +    }
 +
 +    /**
 +     * Returns the enabled state of the editor.
 +     * 
 +     * @return true if editing is enabled, false otherwise
 +     */
 +    public boolean isEditorEnabled() {
 +        return editor.isEnabled();
 +    }
 +
 +    /**
 +     * Sets the enabled state of the editor.
 +     * 
 +     * @param enabled
 +     *            true to enable editing, false to disable
 +     * 
 +     * @throws IllegalStateException
 +     *             if in edit mode and trying to disable
 +     * @throws IllegalStateException
 +     *             if the editor handler is not set
 +     */
 +    public void setEditorEnabled(boolean enabled) {
 +        editor.setEnabled(enabled);
 +    }
 +
 +    /**
 +     * Returns the editor widget associated with the given column. If the editor
 +     * is not active, returns null.
 +     * 
 +     * @param column
 +     *            the column
 +     * @return the widget if the editor is open, null otherwise
 +     */
 +    public Widget getEditorWidget(Column<?, T> column) {
 +        return editor.getWidget(column);
 +    }
 +
 +    /**
 +     * Sets the caption on the save button in the Grid editor.
 +     * 
 +     * @param saveCaption
 +     *            the caption to set
 +     * @throws IllegalArgumentException
 +     *             if {@code saveCaption} is {@code null}
 +     */
 +    public void setEditorSaveCaption(String saveCaption)
 +            throws IllegalArgumentException {
 +        editor.setSaveCaption(saveCaption);
 +    }
 +
 +    /**
 +     * Gets the current caption on the save button in the Grid editor.
 +     * 
 +     * @return the current caption on the save button
 +     */
 +    public String getEditorSaveCaption() {
 +        return editor.getSaveCaption();
 +    }
 +
 +    /**
 +     * Sets the caption on the cancel button in the Grid editor.
 +     * 
 +     * @param cancelCaption
 +     *            the caption to set
 +     * @throws IllegalArgumentException
 +     *             if {@code cancelCaption} is {@code null}
 +     */
 +    public void setEditorCancelCaption(String cancelCaption)
 +            throws IllegalArgumentException {
 +        editor.setCancelCaption(cancelCaption);
 +    }
 +
 +    /**
 +     * Gets the caption on the cancel button in the Grid editor.
 +     * 
 +     * @return the current caption on the cancel button
 +     */
 +    public String getEditorCancelCaption() {
 +        return editor.getCancelCaption();
 +    }
 +
 +    @Override
 +    protected void onAttach() {
 +        super.onAttach();
 +
 +        if (getEscalator().getBody().getRowCount() == 0 && dataSource != null) {
 +            setEscalatorSizeFromDataSource();
 +        }
 +
 +        // Grid was just attached to DOM. Column widths should be calculated.
 +        recalculateColumnWidths();
 +    }
 +
 +    @Override
 +    protected void onDetach() {
 +        Set<Integer> details = new HashSet<Integer>(visibleDetails);
 +        for (int row : details) {
 +            setDetailsVisible(row, false);
 +        }
 +
 +        super.onDetach();
 +    }
 +
 +    @Override
 +    public void onResize() {
 +        super.onResize();
 +
 +        /*
 +         * Delay calculation to be deferred so Escalator can do it's magic.
 +         */
 +        Scheduler.get().scheduleFinally(new ScheduledCommand() {
 +
 +            @Override
 +            public void execute() {
 +                if (escalator.getInnerWidth() != autoColumnWidthsRecalculator.lastCalculatedInnerWidth) {
 +                    recalculateColumnWidths();
 +                }
 +
 +                // Vertical resizing could make editor positioning invalid so it
 +                // needs to be recalculated on resize
 +                if (isEditorActive()) {
 +                    editor.updateVerticalScrollPosition();
 +                }
++
++                // if there is a resize, we need to refresh the body to avoid an
++                // off-by-one error which occurs when the user scrolls all the
++                // way to the bottom.
++                refreshBody();
 +            }
 +        });
 +    }
 +
 +    /**
 +     * Grid does not support adding Widgets this way.
 +     * <p>
 +     * This method is implemented only because removing widgets from Grid (added
 +     * via e.g. {@link Renderer}s) requires the {@link HasWidgets} interface.
 +     * 
 +     * @param w
 +     *            irrelevant
 +     * @throws UnsupportedOperationException
 +     *             always
 +     */
 +    @Override
 +    @Deprecated
 +    public void add(Widget w) {
 +        throw new UnsupportedOperationException(
 +                "Cannot add widgets to Grid with this method");
 +    }
 +
 +    /**
 +     * Grid does not support clearing Widgets this way.
 +     * <p>
 +     * This method is implemented only because removing widgets from Grid (added
 +     * via e.g. {@link Renderer}s) requires the {@link HasWidgets} interface.
 +     * 
 +     * @throws UnsupportedOperationException
 +     *             always
 +     */
 +    @Override
 +    @Deprecated
 +    public void clear() {
 +        throw new UnsupportedOperationException(
 +                "Cannot clear widgets from Grid this way");
 +    }
 +
 +    /**
 +     * Grid does not support iterating through Widgets this way.
 +     * <p>
 +     * This method is implemented only because removing widgets from Grid (added
 +     * via e.g. {@link Renderer}s) requires the {@link HasWidgets} interface.
 +     * 
 +     * @return never
 +     * @throws UnsupportedOperationException
 +     *             always
 +     */
 +    @Override
 +    @Deprecated
 +    public Iterator<Widget> iterator() {
 +        throw new UnsupportedOperationException(
 +                "Cannot iterate through widgets in Grid this way");
 +    }
 +
 +    /**
 +     * Grid does not support removing Widgets this way.
 +     * <p>
 +     * This method is implemented only because removing widgets from Grid (added
 +     * via e.g. {@link Renderer}s) requires the {@link HasWidgets} interface.
 +     * 
 +     * @return always <code>false</code>
 +     */
 +    @Override
 +    @Deprecated
 +    public boolean remove(Widget w) {
 +        /*
 +         * This is the method that is the sole reason to have Grid implement
 +         * HasWidget - when Vaadin removes a Component from the hierarchy, the
 +         * corresponding Widget will call removeFromParent() on itself. GWT will
 +         * check there that its parent (i.e. Grid) implements HasWidgets, and
 +         * will call this remove(Widget) method.
 +         * 
 +         * tl;dr: all this song and dance to make sure GWT's sanity checks
 +         * aren't triggered, even though they effectively do nothing interesting
 +         * from Grid's perspective.
 +         */
 +        return false;
 +    }
 +
 +    /**
 +     * Accesses the package private method Widget#setParent()
 +     * 
 +     * @param widget
 +     *            The widget to access
 +     * @param parent
 +     *            The parent to set
 +     */
 +    private static native final void setParent(Widget widget, Grid<?> parent)
 +    /*-{
 +        widget.@com.google.gwt.user.client.ui.Widget::setParent(Lcom/google/gwt/user/client/ui/Widget;)(parent);
 +    }-*/;
 +
 +    private static native final void onAttach(Widget widget)
 +    /*-{
 +        widget.@Widget::onAttach()();
 +    }-*/;
 +
 +    private static native final void onDetach(Widget widget)
 +    /*-{
 +        widget.@Widget::onDetach()();
 +    }-*/;
 +
 +    @Override
 +    protected void doAttachChildren() {
 +        if (sidebar.getParent() == this) {
 +            onAttach(sidebar);
 +        }
 +    }
 +
 +    @Override
 +    protected void doDetachChildren() {
 +        if (sidebar.getParent() == this) {
 +            onDetach(sidebar);
 +        }
 +    }
 +
 +    private void attachWidget(Widget w, Element parent) {
 +        assert w.getParent() == null;
 +
 +        parent.appendChild(w.getElement());
 +        setParent(w, this);
 +    }
 +
 +    private void detachWidget(Widget w) {
 +        assert w.getParent() == this;
 +
 +        setParent(w, null);
 +        w.getElement().removeFromParent();
 +    }
 +
 +    /**
 +     * Resets all cached pixel sizes and reads new values from the DOM. This
 +     * methods should be used e.g. when styles affecting the dimensions of
 +     * elements in this grid have been changed.
 +     */
 +    public void resetSizesFromDom() {
 +        getEscalator().resetSizesFromDom();
 +    }
 +
 +    /**
 +     * Sets a new details generator for row details.
 +     * <p>
 +     * The currently opened row details will be re-rendered.
 +     * 
 +     * @since 7.5.0
 +     * @param detailsGenerator
 +     *            the details generator to set
 +     * @throws IllegalArgumentException
 +     *             if detailsGenerator is <code>null</code>;
 +     */
 +    public void setDetailsGenerator(DetailsGenerator detailsGenerator)
 +            throws IllegalArgumentException {
 +
 +        if (detailsGenerator == null) {
 +            throw new IllegalArgumentException(
 +                    "Details generator may not be null");
 +        }
 +
 +        for (Integer index : visibleDetails) {
 +            setDetailsVisible(index, false);
 +        }
 +
 +        this.detailsGenerator = detailsGenerator;
 +
 +        // this will refresh all visible spacers
 +        escalator.getBody().setSpacerUpdater(gridSpacerUpdater);
 +    }
 +
 +    /**
 +     * Gets the current details generator for row details.
 +     * 
 +     * @since 7.5.0
 +     * @return the detailsGenerator the current details generator
 +     */
 +    public DetailsGenerator getDetailsGenerator() {
 +        return detailsGenerator;
 +    }
 +
 +    /**
 +     * Shows or hides the details for a specific row.
 +     * <p>
 +     * This method does nothing if trying to set show already-visible details,
 +     * or hide already-hidden details.
 +     * 
 +     * @since 7.5.0
 +     * @param rowIndex
 +     *            the index of the affected row
 +     * @param visible
 +     *            <code>true</code> to show the details, or <code>false</code>
 +     *            to hide them
 +     * @see #isDetailsVisible(int)
 +     */
 +    public void setDetailsVisible(int rowIndex, boolean visible) {
 +        if (DetailsGenerator.NULL.equals(detailsGenerator)) {
 +            return;
 +        }
 +
 +        Integer rowIndexInteger = Integer.valueOf(rowIndex);
 +
 +        /*
 +         * We want to prevent opening a details row twice, so any subsequent
 +         * openings (or closings) of details is a NOOP.
 +         * 
 +         * When a details row is opened, it is given an arbitrary height
 +         * (because Escalator requires a height upon opening). Only when it's
 +         * opened, Escalator will ask the generator to generate a widget, which
 +         * we then can measure. When measured, we correct the initial height by
 +         * the original height.
 +         * 
 +         * Without this check, we would override the measured height, and revert
 +         * back to the initial, arbitrary, height which would most probably be
 +         * wrong.
 +         * 
 +         * see GridSpacerUpdater.init for implementation details.
 +         */
 +
 +        boolean isVisible = isDetailsVisible(rowIndex);
 +        if (visible && !isVisible) {
 +            escalator.getBody().setSpacer(rowIndex, DETAILS_ROW_INITIAL_HEIGHT);
 +            visibleDetails.add(rowIndexInteger);
 +        }
 +
 +        else if (!visible && isVisible) {
 +            escalator.getBody().setSpacer(rowIndex, -1);
 +            visibleDetails.remove(rowIndexInteger);
 +        }
 +    }
 +
 +    /**
 +     * Check whether the details for a row is visible or not.
 +     * 
 +     * @since 7.5.0
 +     * @param rowIndex
 +     *            the index of the row for which to check details
 +     * @return <code>true</code> iff the details for the given row is visible
 +     * @see #setDetailsVisible(int, boolean)
 +     */
 +    public boolean isDetailsVisible(int rowIndex) {
 +        return visibleDetails.contains(Integer.valueOf(rowIndex));
 +    }
 +
 +    /**
 +     * Requests that the column widths should be recalculated.
 +     * <p>
 +     * The actual recalculation is not necessarily done immediately so you
 +     * cannot rely on the columns being the correct width after the call
 +     * returns.
 +     * 
 +     * @since 7.4.1
 +     */
 +    public void recalculateColumnWidths() {
 +        autoColumnWidthsRecalculator.schedule();
 +    }
 +
 +    /**
 +     * Gets the customizable menu bar that is by default used for toggling
 +     * column hidability. The application developer is allowed to add their
 +     * custom items to the end of the menu, but should try to avoid modifying
 +     * the items in the beginning of the menu that control the column hiding if
 +     * any columns are marked as hidable. A toggle for opening the menu will be
 +     * displayed whenever the menu contains at least one item.
 +     * 
 +     * @since 7.5.0
 +     * @return the menu bar
 +     */
 +    public MenuBar getSidebarMenu() {
 +        return sidebar.menuBar;
 +    }
 +
 +    /**
 +     * Tests whether the sidebar menu is currently open.
 +     * 
 +     * @since 7.5.0
 +     * @see #getSidebarMenu()
 +     * @return <code>true</code> if the sidebar is open; <code>false</code> if
 +     *         it is closed
 +     */
 +    public boolean isSidebarOpen() {
 +        return sidebar.isOpen();
 +    }
 +
 +    /**
 +     * Sets whether the sidebar menu is open.
 +     * 
 +     * 
 +     * @since 7.5.0
 +     * @see #getSidebarMenu()
 +     * @see #isSidebarOpen()
 +     * @param sidebarOpen
 +     *            <code>true</code> to open the sidebar; <code>false</code> to
 +     *            close it
 +     */
 +    public void setSidebarOpen(boolean sidebarOpen) {
 +        if (sidebarOpen) {
 +            sidebar.open();
 +        } else {
 +            sidebar.close();
 +        }
 +    }
 +
 +    @Override
 +    public int getTabIndex() {
 +        return FocusUtil.getTabIndex(this);
 +    }
 +
 +    @Override
 +    public void setAccessKey(char key) {
 +        FocusUtil.setAccessKey(this, key);
 +    }
 +
 +    @Override
 +    public void setFocus(boolean focused) {
 +        FocusUtil.setFocus(this, focused);
 +    }
 +
 +    @Override
 +    public void setTabIndex(int index) {
 +        FocusUtil.setTabIndex(this, index);
 +    }
 +
 +    @Override
 +    public void focus() {
 +        setFocus(true);
 +    }
 +
 +    /**
 +     * Sets the buffered editor mode.
 +     * 
 +     * @since 7.6
 +     * @param editorUnbuffered
 +     *            <code>true</code> to enable buffered editor,
 +     *            <code>false</code> to disable it
 +     */
 +    public void setEditorBuffered(boolean editorBuffered) {
 +        editor.setBuffered(editorBuffered);
 +    }
 +
 +    /**
 +     * Gets the buffered editor mode.
 +     * 
 +     * @since 7.6
 +     * @return <code>true</code> if buffered editor is enabled,
 +     *         <code>false</code> otherwise
 +     */
 +    public boolean isEditorBuffered() {
 +        return editor.isBuffered();
 +    }
 +
 +    /**
 +     * Returns the {@link EventCellReference} for the latest event fired from
 +     * this Grid.
 +     * <p>
 +     * Note: This cell reference will be updated when firing the next event.
 +     * 
 +     * @since 7.5
 +     * @return event cell reference
 +     */
 +    public EventCellReference<T> getEventCell() {
 +        return eventCell;
 +    }
 +
 +    /**
 +     * Returns a CellReference for the cell to which the given element belongs
 +     * to.
 +     * 
 +     * @since 7.6
 +     * @param element
 +     *            Element to find from the cell's content.
 +     * @return CellReference or <code>null</code> if cell was not found.
 +     */
 +    public CellReference<T> getCellReference(Element element) {
 +        RowContainer container = getEscalator().findRowContainer(element);
 +        if (container != null) {
 +            Cell cell = container.getCell(element);
 +            if (cell != null) {
 +                EventCellReference<T> cellRef = new EventCellReference<T>(this);
 +                cellRef.set(cell, getSectionFromContainer(container));
 +                return cellRef;
 +            }
 +        }
 +        return null;
 +    }
 +}
index 7f4ef3ffe56f28d1c2bd7c1b8c54c98f2bf0a5c2,0000000000000000000000000000000000000000..86723b97fcadef6ad317664e721b1c40d8df5f9b
mode 100644,000000..100644
--- /dev/null
@@@ -1,50 -1,0 +1,52 @@@
 +/*
 + * Copyright 2000-2014 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.annotations;
 +
 +import java.lang.annotation.ElementType;
++import java.lang.annotation.Inherited;
 +import java.lang.annotation.Retention;
 +import java.lang.annotation.RetentionPolicy;
 +import java.lang.annotation.Target;
 +
 +import com.vaadin.server.UIProvider;
 +import com.vaadin.ui.UI;
 +
 +/**
 + * Marks a UI that should be retained when the user refreshed the browser
 + * window. By default, a new UI instance is created when refreshing, causing any
 + * UI state not captured in the URL or the URI fragment to get discarded. By
 + * adding this annotation to a UI class, the framework will instead reuse the
 + * current UI instance when a reload is detected.
 + * <p>
 + * Whenever a request is received that reloads a preserved UI, the UI's
 + * {@link UI#refresh(com.vaadin.server.VaadinRequest) refresh} method is invoked
 + * by the framework.
 + * <p>
 + * By using
 + * {@link UIProvider#isPreservedOnRefresh(com.vaadin.server.UICreateEvent)}, the
 + * decision can also be made dynamically based on other parameters than only
 + * whether this annotation is present on the UI class.
 + * 
 + * @author Vaadin Ltd
 + * @since 7.0.0
 + */
 +@Target(ElementType.TYPE)
 +@Retention(RetentionPolicy.RUNTIME)
++@Inherited
 +public @interface PreserveOnRefresh {
 +    // Empty marker annotation
 +}
index b6a28c1560f83f7160697bdaf15c92c69947ec13,0000000000000000000000000000000000000000..736bc4488b2671cd4facffa6feefa4abcd4a60b7
mode 100644,000000..100644
--- /dev/null
@@@ -1,59 -1,0 +1,61 @@@
 +/*
 + * Copyright 2000-2014 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.annotations;
 +
 +import java.lang.annotation.ElementType;
++import java.lang.annotation.Inherited;
 +import java.lang.annotation.Retention;
 +import java.lang.annotation.RetentionPolicy;
 +import java.lang.annotation.Target;
 +
 +import com.vaadin.shared.communication.PushMode;
 +import com.vaadin.shared.ui.ui.Transport;
 +import com.vaadin.ui.UI;
 +
 +/**
 + * Configures server push for a {@link UI}. Adding <code>@Push</code> to a UI
 + * class configures the UI for automatic push. If some other push mode is
 + * desired, it can be passed as a parameter, e.g.
 + * <code>@Push(PushMode.MANUAL)</code>.
 + * 
 + * @see PushMode
 + * 
 + * @author Vaadin Ltd.
 + * @since 7.1
 + */
 +@Retention(RetentionPolicy.RUNTIME)
 +@Target(ElementType.TYPE)
++@Inherited
 +public @interface Push {
 +    /**
 +     * Returns the {@link PushMode} to use for the annotated UI. The default
 +     * push mode when this annotation is present is {@link PushMode#AUTOMATIC}.
 +     * 
 +     * @return the push mode to use
 +     */
 +    public PushMode value() default PushMode.AUTOMATIC;
 +
 +    /**
 +     * Returns the transport type used for the push for the annotated UI. The
 +     * default transport type when this annotation is present is
 +     * {@link Transport#WEBSOCKET}.
 +     * 
 +     * @return the transport type to use
 +     */
 +    public Transport transport() default Transport.WEBSOCKET;
 +
 +}
index 61c47389add430e6c4d960db79aba42fb94dc6ef,0000000000000000000000000000000000000000..03fa1179bc488077db1559333fc5b073391a0b1a
mode 100644,000000..100644
--- /dev/null
@@@ -1,36 -1,0 +1,38 @@@
 +/*
 + * Copyright 2000-2014 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.annotations;
 +
 +import java.lang.annotation.ElementType;
++import java.lang.annotation.Inherited;
 +import java.lang.annotation.Retention;
 +import java.lang.annotation.RetentionPolicy;
 +import java.lang.annotation.Target;
 +
 +import com.vaadin.ui.UI;
 +
 +/**
 + * Defines a specific theme for a {@link UI}.
 + */
 +@Retention(RetentionPolicy.RUNTIME)
 +@Target(ElementType.TYPE)
++@Inherited
 +public @interface Theme {
 +    /**
 +     * @return simple name of the theme
 +     */
 +    public String value();
 +}
index 38a3d75f178e24b4ba8746952fdae95751d4955a,0000000000000000000000000000000000000000..07eaf17e33840fe6711e4d30e9413ab60d5b71b0
mode 100644,000000..100644
--- /dev/null
@@@ -1,38 -1,0 +1,40 @@@
 +/*
 + * Copyright 2000-2014 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.annotations;
 +
 +import java.lang.annotation.ElementType;
++import java.lang.annotation.Inherited;
 +import java.lang.annotation.Retention;
 +import java.lang.annotation.RetentionPolicy;
 +import java.lang.annotation.Target;
 +
 +import com.vaadin.ui.UI;
 +
 +/**
 + * Defines the HTML page title for a {@link UI}.
 + */
 +@Retention(RetentionPolicy.RUNTIME)
 +@Target(ElementType.TYPE)
++@Inherited
 +public @interface Title {
 +    /**
 +     * Gets the HTML title that should be used if the UI is used on it's own.
 +     * 
 +     * @return a page title string
 +     */
 +    public String value();
 +}
index 00af38ecc1b99799931df955d712f80a9de2bdcd,0000000000000000000000000000000000000000..907f2c3a0cdd85a382556ddab5291f7588c98ea8
mode 100644,000000..100644
--- /dev/null
@@@ -1,142 -1,0 +1,144 @@@
 +/*
 + * Copyright 2000-2014 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.annotations;
 +
 +import java.lang.annotation.Documented;
 +import java.lang.annotation.ElementType;
++import java.lang.annotation.Inherited;
 +import java.lang.annotation.Retention;
 +import java.lang.annotation.RetentionPolicy;
 +import java.lang.annotation.Target;
 +
 +import com.vaadin.server.Constants;
 +import com.vaadin.server.DefaultDeploymentConfiguration;
 +import com.vaadin.server.DeploymentConfiguration;
 +import com.vaadin.server.DeploymentConfiguration.LegacyProperyToStringMode;
 +import com.vaadin.server.VaadinServlet;
 +import com.vaadin.server.VaadinSession;
 +import com.vaadin.ui.UI;
 +
 +/**
 + * Annotation for configuring subclasses of {@link VaadinServlet}. For a
 + * {@link VaadinServlet} class that has this annotation, the defined values are
 + * read during initialization and will be available using
 + * {@link DeploymentConfiguration#getApplicationOrSystemProperty(String, String)}
 + * as well as from specific methods in {@link DeploymentConfiguration}. Init
 + * params defined in <code>web.xml</code> or the <code>@WebServlet</code>
 + * annotation take precedence over values defined in this annotation.
 + * 
 + * @since 7.1
 + * @author Vaadin Ltd
 + */
 +@Retention(RetentionPolicy.RUNTIME)
 +@Target(ElementType.TYPE)
++@Inherited
 +public @interface VaadinServletConfiguration {
 +    /**
 +     * Defines the init parameter name for methods in
 +     * {@link VaadinServletConfiguration}.
 +     * 
 +     * @since 7.1
 +     * @author Vaadin Ltd
 +     */
 +    @Retention(RetentionPolicy.RUNTIME)
 +    @Target(ElementType.METHOD)
 +    @Documented
 +    public @interface InitParameterName {
 +        /**
 +         * The name of the init parameter that the annotated method controls.
 +         * 
 +         * @return the parameter name
 +         */
 +        public String value();
 +    }
 +
 +    /**
 +     * Whether Vaadin is in production mode.
 +     * 
 +     * @return true if in production mode, false otherwise.
 +     * 
 +     * @see DeploymentConfiguration#isProductionMode()
 +     */
 +    @InitParameterName(Constants.SERVLET_PARAMETER_PRODUCTION_MODE)
 +    public boolean productionMode();
 +
 +    /**
 +     * Gets the default UI class to use for the servlet.
 +     * 
 +     * @return the default UI class
 +     */
 +    @InitParameterName(VaadinSession.UI_PARAMETER)
 +    public Class<? extends UI> ui();
 +
 +    /**
 +     * The time resources can be cached in the browser, in seconds. The default
 +     * value is 3600 seconds, i.e. one hour.
 +     * 
 +     * @return the resource cache time
 +     * 
 +     * @see DeploymentConfiguration#getResourceCacheTime()
 +     */
 +    @InitParameterName(Constants.SERVLET_PARAMETER_RESOURCE_CACHE_TIME)
 +    public int resourceCacheTime() default DefaultDeploymentConfiguration.DEFAULT_RESOURCE_CACHE_TIME;
 +
 +    /**
 +     * The number of seconds between heartbeat requests of a UI, or a
 +     * non-positive number if heartbeat is disabled. The default value is 300
 +     * seconds, i.e. 5 minutes.
 +     * 
 +     * @return the time between heartbeats
 +     * 
 +     * @see DeploymentConfiguration#getHeartbeatInterval()
 +     */
 +    @InitParameterName(Constants.SERVLET_PARAMETER_HEARTBEAT_INTERVAL)
 +    public int heartbeatInterval() default DefaultDeploymentConfiguration.DEFAULT_HEARTBEAT_INTERVAL;
 +
 +    /**
 +     * Whether a session should be closed when all its open UIs have been idle
 +     * for longer than its configured maximum inactivity time. The default value
 +     * is <code>false</code>.
 +     * 
 +     * @return true if UIs and sessions receiving only heartbeat requests are
 +     *         eventually closed; false if heartbeat requests extend UI and
 +     *         session lifetime indefinitely
 +     * 
 +     * @see DeploymentConfiguration#isCloseIdleSessions()
 +     */
 +    @InitParameterName(Constants.SERVLET_PARAMETER_CLOSE_IDLE_SESSIONS)
 +    public boolean closeIdleSessions() default DefaultDeploymentConfiguration.DEFAULT_CLOSE_IDLE_SESSIONS;
 +
 +    /**
 +     * The default widgetset to use for the servlet. The default value is
 +     * <code>com.vaadin.DefaultWidgetSet</code>.
 +     * 
 +     * @return the default widgetset name
 +     */
 +    @InitParameterName(VaadinServlet.PARAMETER_WIDGETSET)
 +    public String widgetset() default VaadinServlet.DEFAULT_WIDGETSET;
 +
 +    /**
 +     * The legacy Property.toString() mode used. The default value is
 +     * {@link LegacyProperyToStringMode#DISABLED}
 +     * 
 +     * @return The Property.toString() mode in use.
 +     * 
 +     * @deprecated as of 7.1, should only be used to ease migration
 +     */
 +    @Deprecated
 +    @InitParameterName(Constants.SERVLET_PARAMETER_LEGACY_PROPERTY_TOSTRING)
 +    public LegacyProperyToStringMode legacyPropertyToStringMode() default LegacyProperyToStringMode.DISABLED;
 +}
index c6ef6a7194a42b0b15f0a09e82bdb2505f0cb4ce,0000000000000000000000000000000000000000..2dcf93af135f7ba846b327c57f8e407cd071dbc7
mode 100644,000000..100644
--- /dev/null
@@@ -1,37 -1,0 +1,39 @@@
 +/*
 + * Copyright 2000-2014 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.annotations;
 +
 +import java.lang.annotation.ElementType;
++import java.lang.annotation.Inherited;
 +import java.lang.annotation.Retention;
 +import java.lang.annotation.RetentionPolicy;
 +import java.lang.annotation.Target;
 +
 +import com.vaadin.ui.UI;
 +
 +/**
 + * Defines a specific widgetset for a {@link UI}.
 + */
 +@Retention(RetentionPolicy.RUNTIME)
 +@Target(ElementType.TYPE)
++@Inherited
 +public @interface Widgetset {
 +    /**
 +     * @return name of the widgetset
 +     */
 +    public String value();
 +
 +}
index d3d834cad79cceaf4233cf291d3aa61ff5efa827,0000000000000000000000000000000000000000..4ed86b9c31b0e8b61e4cf025469193d35b7eb628
mode 100644,000000..100644
--- /dev/null
@@@ -1,206 -1,0 +1,222 @@@
-      * on the target class, its super classes and implemented interfaces are
-      * also searched for the annotation.
 +/*
 + * Copyright 2000-2014 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.server;
 +
 +import java.io.Serializable;
 +import java.lang.annotation.Annotation;
++import java.lang.annotation.Inherited;
 +
 +import com.vaadin.annotations.PreserveOnRefresh;
 +import com.vaadin.annotations.Push;
 +import com.vaadin.annotations.Theme;
 +import com.vaadin.annotations.Title;
 +import com.vaadin.annotations.Widgetset;
 +import com.vaadin.shared.communication.PushMode;
 +import com.vaadin.shared.ui.ui.Transport;
 +import com.vaadin.ui.UI;
 +
 +public abstract class UIProvider implements Serializable {
 +    public abstract Class<? extends UI> getUIClass(UIClassSelectionEvent event);
 +
 +    public UI createInstance(UICreateEvent event) {
 +        try {
 +            return event.getUIClass().newInstance();
 +        } catch (InstantiationException e) {
 +            throw new RuntimeException("Could not instantiate UI class", e);
 +        } catch (IllegalAccessException e) {
 +            throw new RuntimeException("Could not access UI class", e);
 +        }
 +    }
 +
 +    /**
 +     * Helper to get an annotation for a class. If the annotation is not present
-         // Find from the class hierarchy
-         Class<?> currentType = clazz;
-         while (currentType != Object.class) {
-             T annotation = currentType.getAnnotation(annotationType);
++     * on the target class, its super classes and directly implemented
++     * interfaces are also searched for the annotation. Interfaces implemented
++     * by superclasses are not taken into account.
++     * <p>
++     * Note that searching implemented interfaces for {@code @Inherited}
++     * annotations and searching for superclasses for non-inherited annotations
++     * do not follow the standard semantics and are supported for backwards
++     * compatibility. Future versions of the framework might only support the
++     * standard semantics of {@code @Inherited}.
 +     * 
 +     * @param clazz
 +     *            the class from which the annotation should be found
 +     * @param annotationType
 +     *            the annotation type to look for
 +     * @return an annotation of the given type, or <code>null</code> if the
 +     *         annotation is not present on the class
 +     */
 +    protected static <T extends Annotation> T getAnnotationFor(Class<?> clazz,
 +            Class<T> annotationType) {
-             } else {
-                 currentType = currentType.getSuperclass();
++        // Don't discover hierarchy if annotation is inherited
++        if (annotationType.getAnnotation(Inherited.class) != null) {
++            T annotation = clazz.getAnnotation(annotationType);
 +            if (annotation != null) {
 +                return annotation;
-         // Find from an implemented interface
++            }
++        } else {
++            // Find from the class hierarchy
++            Class<?> currentType = clazz;
++            while (currentType != Object.class) {
++                T annotation = currentType.getAnnotation(annotationType);
++                if (annotation != null) {
++                    return annotation;
++                } else {
++                    currentType = currentType.getSuperclass();
++                }
 +            }
 +        }
 +
++        // Find from a directly implemented interface
 +        for (Class<?> iface : clazz.getInterfaces()) {
 +            T annotation = iface.getAnnotation(annotationType);
 +            if (annotation != null) {
 +                return annotation;
 +            }
 +        }
 +
 +        return null;
 +    }
 +
 +    /**
 +     * Finds the theme to use for a specific UI. If no specific theme is
 +     * required, <code>null</code> is returned.
 +     * <p>
 +     * The default implementation checks for a @{@link Theme} annotation on the
 +     * UI class.
 +     * 
 +     * @param event
 +     *            the UI create event with information about the UI and the
 +     *            current request.
 +     * @return the name of the theme, or <code>null</code> if the default theme
 +     *         should be used
 +     * 
 +     */
 +    public String getTheme(UICreateEvent event) {
 +        Theme uiTheme = getAnnotationFor(event.getUIClass(), Theme.class);
 +        if (uiTheme != null) {
 +            return uiTheme.value();
 +        } else {
 +            return null;
 +        }
 +    }
 +
 +    /**
 +     * Finds the widgetset to use for a specific UI. If no specific widgetset is
 +     * required, <code>null</code> is returned.
 +     * <p>
 +     * The default implementation uses the @{@link Widgetset} annotation if it's
 +     * defined for the UI class.
 +     * 
 +     * @param event
 +     *            the UI create event with information about the UI and the
 +     *            current request.
 +     * @return the name of the widgetset, or <code>null</code> if the default
 +     *         widgetset should be used
 +     * 
 +     */
 +    public String getWidgetset(UICreateEvent event) {
 +        Widgetset uiWidgetset = getAnnotationFor(event.getUIClass(),
 +                Widgetset.class);
 +        if (uiWidgetset != null) {
 +            return uiWidgetset.value();
 +        } else {
 +            return null;
 +        }
 +    }
 +
 +    /**
 +     * Checks whether the same UI state should be reused if the framework can
 +     * detect that the application is opened in a browser window where it has
 +     * previously been open. The framework attempts to discover this by checking
 +     * the value of window.name in the browser.
 +     * <p>
 +     * Whenever a preserved UI is reused, its
 +     * {@link UI#refresh(com.vaadin.server.VaadinRequest) refresh} method is
 +     * invoked by the framework first.
 +     * 
 +     * 
 +     * @param event
 +     *            the UI create event with information about the UI and the
 +     *            current request.
 +     * 
 +     * @return <code>true</code>if the same UI instance should be reused e.g.
 +     *         when the browser window is refreshed.
 +     */
 +    public boolean isPreservedOnRefresh(UICreateEvent event) {
 +        PreserveOnRefresh preserveOnRefresh = getAnnotationFor(
 +                event.getUIClass(), PreserveOnRefresh.class);
 +        return preserveOnRefresh != null;
 +    }
 +
 +    public String getPageTitle(UICreateEvent event) {
 +        Title titleAnnotation = getAnnotationFor(event.getUIClass(),
 +                Title.class);
 +        if (titleAnnotation == null) {
 +            return null;
 +        } else {
 +            return titleAnnotation.value();
 +        }
 +    }
 +
 +    /**
 +     * Finds the {@link PushMode} to use for a specific UI. If no specific push
 +     * mode is required, <code>null</code> is returned.
 +     * <p>
 +     * The default implementation uses the @{@link Push} annotation if it's
 +     * defined for the UI class.
 +     * 
 +     * @param event
 +     *            the UI create event with information about the UI and the
 +     *            current request.
 +     * @return the push mode to use, or <code>null</code> if the default push
 +     *         mode should be used
 +     * 
 +     */
 +    public PushMode getPushMode(UICreateEvent event) {
 +        Push push = getAnnotationFor(event.getUIClass(), Push.class);
 +        if (push == null) {
 +            return null;
 +        } else {
 +            return push.value();
 +        }
 +    }
 +
 +    /**
 +     * Finds the {@link Transport} to use for a specific UI. If no transport is
 +     * defined, <code>null</code> is returned.
 +     * <p>
 +     * The default implementation uses the @{@link Push} annotation if it's
 +     * defined for the UI class.
 +     * 
 +     * @param event
 +     *            the UI create event with information about the UI and the
 +     *            current request.
 +     * @return the transport type to use, or <code>null</code> if the default
 +     *         transport type should be used
 +     */
 +    public Transport getPushTransport(UICreateEvent event) {
 +        Push push = getAnnotationFor(event.getUIClass(), Push.class);
 +        if (push == null) {
 +            return null;
 +        } else {
 +            return push.transport();
 +        }
 +    }
 +
 +}
index 043f95fd7ba55ec7a4e2d6f4e6eafcc88c45c908,0000000000000000000000000000000000000000..5c91b6669cc7e0d8bdfe1fbacd0c9b145f6bb1ce
mode 100644,000000..100644
--- /dev/null
@@@ -1,648 -1,0 +1,665 @@@
-                         "com.liferay.portal.util.PortalUtil",
-                         "getHttpServletRequest", request,
 +/*
 + * Copyright 2000-2014 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.server;
 +
 +import java.io.IOException;
 +import java.io.Serializable;
 +import java.lang.reflect.InvocationTargetException;
 +import java.lang.reflect.Method;
 +import java.util.Enumeration;
 +import java.util.Map;
 +import java.util.Properties;
 +import java.util.logging.Level;
 +import java.util.logging.Logger;
 +
 +import javax.portlet.ActionRequest;
 +import javax.portlet.ActionResponse;
 +import javax.portlet.EventRequest;
 +import javax.portlet.EventResponse;
 +import javax.portlet.GenericPortlet;
 +import javax.portlet.PortalContext;
 +import javax.portlet.PortletConfig;
 +import javax.portlet.PortletContext;
 +import javax.portlet.PortletException;
 +import javax.portlet.PortletRequest;
 +import javax.portlet.PortletResponse;
 +import javax.portlet.RenderRequest;
 +import javax.portlet.RenderResponse;
 +import javax.portlet.ResourceRequest;
 +import javax.portlet.ResourceResponse;
 +import javax.servlet.http.HttpServletRequest;
 +import javax.servlet.http.HttpServletRequestWrapper;
 +
 +import com.liferay.portal.kernel.util.PortalClassLoaderUtil;
 +import com.liferay.portal.kernel.util.PropsUtil;
 +import com.vaadin.server.communication.PortletDummyRequestHandler;
 +import com.vaadin.server.communication.PortletUIInitHandler;
 +import com.vaadin.util.CurrentInstance;
 +
 +/**
 + * Portlet 2.0 base class. This replaces the servlet in servlet/portlet 1.0
 + * deployments and handles various portlet requests from the browser.
 + * 
 + * @author Vaadin Ltd
 + */
 +public class VaadinPortlet extends GenericPortlet implements Constants,
 +        Serializable {
 +
 +    /**
 +     * Base class for portlet requests that need access to HTTP servlet
 +     * requests.
 +     */
 +    public static abstract class VaadinHttpAndPortletRequest extends
 +            VaadinPortletRequest {
 +
 +        /**
 +         * Constructs a new {@link VaadinHttpAndPortletRequest}.
 +         * 
 +         * @since 7.2
 +         * @param request
 +         *            {@link PortletRequest} to be wrapped
 +         * @param vaadinService
 +         *            {@link VaadinPortletService} associated with this request
 +         */
 +        public VaadinHttpAndPortletRequest(PortletRequest request,
 +                VaadinPortletService vaadinService) {
 +            super(request, vaadinService);
 +        }
 +
 +        private HttpServletRequest originalRequest;
 +
 +        /**
 +         * Returns the original HTTP servlet request for this portlet request.
 +         * 
 +         * @since 7.2
 +         * @param request
 +         *            {@link PortletRequest} used to
 +         * @return the original HTTP servlet request
 +         */
 +        protected abstract HttpServletRequest getServletRequest(
 +                PortletRequest request);
 +
 +        private HttpServletRequest getOriginalRequest() {
 +            if (originalRequest == null) {
 +                PortletRequest request = getRequest();
 +                originalRequest = getServletRequest(request);
 +            }
 +
 +            return originalRequest;
 +        }
 +
 +        @Override
 +        public String getParameter(String name) {
 +            String parameter = super.getParameter(name);
 +            if (parameter == null && getOriginalRequest() != null) {
 +                parameter = getOriginalRequest().getParameter(name);
 +            }
 +            return parameter;
 +        }
 +
 +        @Override
 +        public String getRemoteAddr() {
 +            if (getOriginalRequest() != null) {
 +                return getOriginalRequest().getRemoteAddr();
 +            } else {
 +                return super.getRemoteAddr();
 +            }
 +
 +        }
 +
 +        @Override
 +        public String getRemoteHost() {
 +            if (getOriginalRequest() != null) {
 +                return getOriginalRequest().getRemoteHost();
 +            } else {
 +                return super.getRemoteHost();
 +            }
 +        }
 +
 +        @Override
 +        public int getRemotePort() {
 +            if (getOriginalRequest() != null) {
 +                return getOriginalRequest().getRemotePort();
 +            } else {
 +                return super.getRemotePort();
 +            }
 +        }
 +
 +        @Override
 +        public String getHeader(String name) {
 +            String header = super.getHeader(name);
 +            if (header == null && getOriginalRequest() != null) {
 +                header = getOriginalRequest().getHeader(name);
 +            }
 +            return header;
 +        }
 +
 +        @Override
 +        public Enumeration<String> getHeaderNames() {
 +            Enumeration<String> headerNames = super.getHeaderNames();
 +            if (headerNames == null && getOriginalRequest() != null) {
 +                headerNames = getOriginalRequest().getHeaderNames();
 +            }
 +            return headerNames;
 +        }
 +
 +        @Override
 +        public Enumeration<String> getHeaders(String name) {
 +            Enumeration<String> headers = super.getHeaders(name);
 +            if (headers == null && getOriginalRequest() != null) {
 +                headers = getOriginalRequest().getHeaders(name);
 +            }
 +            return headers;
 +        }
 +
 +        @Override
 +        public Map<String, String[]> getParameterMap() {
 +            Map<String, String[]> parameterMap = super.getParameterMap();
 +            if (parameterMap == null && getOriginalRequest() != null) {
 +                parameterMap = getOriginalRequest().getParameterMap();
 +            }
 +            return parameterMap;
 +        }
 +    }
 +
 +    /**
 +     * Portlet request for Liferay.
 +     */
 +    public static class VaadinLiferayRequest extends
 +            VaadinHttpAndPortletRequest {
++        /**
++         * The PortalUtil class to use. Set to either
++         * {@link #LIFERAY_6_PORTAL_UTIL} or {@link #LIFERAY_7_PORTAL_UTIL} the
++         * first time it is needed.
++         */
++        private static String portalUtilClass = null;
++        private static final String LIFERAY_6_PORTAL_UTIL = "com.liferay.portal.util.PortalUtil";
++        private static final String LIFERAY_7_PORTAL_UTIL = "com.liferay.portal.kernel.util.PortalUtil";
 +
 +        public VaadinLiferayRequest(PortletRequest request,
 +                VaadinPortletService vaadinService) {
 +            super(request, vaadinService);
 +        }
 +
 +        @Override
 +        public String getPortalProperty(String name) {
 +            return PropsUtil.get(name);
 +        }
 +
 +        /**
 +         * Simplified version of what Liferay PortalClassInvoker did. This is
 +         * used because the API of PortalClassInvoker has changed in Liferay
 +         * 6.2.
 +         * 
 +         * This simply uses reflection with Liferay class loader. Parameters are
 +         * Strings to avoid static dependencies and to load all classes with
 +         * Liferay's own class loader. Only static utility methods are
 +         * supported.
 +         * 
 +         * This method is for internal use only and may change in future
 +         * versions.
 +         * 
 +         * @param className
 +         *            name of the Liferay class to call
 +         * @param methodName
 +         *            name of the method to call
 +         * @param parameterClassName
 +         *            name of the parameter class of the method
 +         * @throws Exception
 +         * @return return value of the invoked method
 +         */
 +        private Object invokeStaticLiferayMethod(String className,
 +                String methodName, Object argument, String parameterClassName)
 +                throws Exception {
 +            Thread currentThread = Thread.currentThread();
 +
 +            ClassLoader contextClassLoader = currentThread
 +                    .getContextClassLoader();
 +
 +            try {
 +                // this should be available across all Liferay versions with no
 +                // problematic static dependencies
 +                ClassLoader portalClassLoader = PortalClassLoaderUtil
 +                        .getClassLoader();
 +                // this is in case the class loading triggers code that
 +                // explicitly
 +                // uses current thread class loader
 +                currentThread.setContextClassLoader(portalClassLoader);
 +
 +                Class<?> targetClass = portalClassLoader.loadClass(className);
 +                Class<?> parameterClass = portalClassLoader
 +                        .loadClass(parameterClassName);
 +                Method method = targetClass.getMethod(methodName,
 +                        parameterClass);
 +
 +                return method.invoke(null, new Object[] { argument });
 +            } catch (InvocationTargetException ite) {
 +                throw (Exception) ite.getCause();
 +            } finally {
 +                currentThread.setContextClassLoader(contextClassLoader);
 +            }
 +        }
 +
 +        @Override
 +        protected HttpServletRequest getServletRequest(PortletRequest request) {
++            if (portalUtilClass == null) {
++                try {
++                    invokeStaticLiferayMethod(LIFERAY_7_PORTAL_UTIL,
++                            "getHttpServletRequest", request,
++                            "javax.portlet.PortletRequest");
++                    portalUtilClass = LIFERAY_7_PORTAL_UTIL;
++                } catch (Exception e) {
++                    // Liferay 6 or older
++                    portalUtilClass = LIFERAY_6_PORTAL_UTIL;
++                }
++            }
 +            try {
 +                // httpRequest = PortalUtil.getHttpServletRequest(request);
 +                HttpServletRequest httpRequest = (HttpServletRequest) invokeStaticLiferayMethod(
-                         "com.liferay.portal.util.PortalUtil",
-                         "getOriginalServletRequest", httpRequest,
-                         "javax.servlet.http.HttpServletRequest");
++                        portalUtilClass, "getHttpServletRequest", request,
 +                        "javax.portlet.PortletRequest");
 +
 +                // httpRequest =
 +                // PortalUtil.getOriginalServletRequest(httpRequest);
 +                httpRequest = (HttpServletRequest) invokeStaticLiferayMethod(
++                        portalUtilClass, "getOriginalServletRequest",
++                        httpRequest, "javax.servlet.http.HttpServletRequest");
 +                return httpRequest;
 +            } catch (Exception e) {
 +                throw new IllegalStateException("Liferay request not detected",
 +                        e);
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Portlet request for GateIn.
 +     */
 +    public static class VaadinGateInRequest extends VaadinHttpAndPortletRequest {
 +        public VaadinGateInRequest(PortletRequest request,
 +                VaadinPortletService vaadinService) {
 +            super(request, vaadinService);
 +        }
 +
 +        @Override
 +        protected HttpServletRequest getServletRequest(PortletRequest request) {
 +            try {
 +                Method getRealReq = request.getClass().getMethod(
 +                        "getRealRequest");
 +                HttpServletRequestWrapper origRequest = (HttpServletRequestWrapper) getRealReq
 +                        .invoke(request);
 +                return origRequest;
 +            } catch (Exception e) {
 +                throw new IllegalStateException("GateIn request not detected",
 +                        e);
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Portlet request for WebSphere Portal.
 +     */
 +    public static class VaadinWebSpherePortalRequest extends
 +            VaadinHttpAndPortletRequest {
 +
 +        public VaadinWebSpherePortalRequest(PortletRequest request,
 +                VaadinPortletService vaadinService) {
 +            super(request, vaadinService);
 +        }
 +
 +        @Override
 +        protected HttpServletRequest getServletRequest(PortletRequest request) {
 +            try {
 +                Class<?> portletUtils = Class
 +                        .forName("com.ibm.ws.portletcontainer.portlet.PortletUtils");
 +                Method getHttpServletRequest = portletUtils.getMethod(
 +                        "getHttpServletRequest", PortletRequest.class);
 +
 +                return (HttpServletRequest) getHttpServletRequest.invoke(null,
 +                        request);
 +            } catch (Exception e) {
 +                throw new IllegalStateException(
 +                        "WebSphere Portal request not detected.");
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Portlet request for WebSphere Portal.
 +     */
 +    public static class VaadinWebLogicPortalRequest extends
 +            VaadinHttpAndPortletRequest {
 +        private static boolean warningLogged = false;
 +
 +        private static Method servletRequestMethod = null;
 +
 +        public VaadinWebLogicPortalRequest(PortletRequest request,
 +                VaadinPortletService vaadinService) {
 +            super(request, vaadinService);
 +        }
 +
 +        @Override
 +        protected HttpServletRequest getServletRequest(PortletRequest request) {
 +            try {
 +                if (servletRequestMethod == null) {
 +                    Class<?> portletRequestClass = Class
 +                            .forName("com.bea.portlet.container.PortletRequestImpl");
 +                    servletRequestMethod = portletRequestClass
 +                            .getDeclaredMethod("getInternalRequest",
 +                                    new Class[] {});
 +                    servletRequestMethod.setAccessible(true);
 +                }
 +
 +                return (HttpServletRequest) servletRequestMethod
 +                        .invoke(request);
 +            } catch (Exception e) {
 +                if (!warningLogged) {
 +                    warningLogged = true;
 +                    getLogger()
 +                            .log(Level.WARNING,
 +                                    "Could not determine underlying servlet request for WebLogic Portal portlet request",
 +                                    e);
 +                }
 +                return null;
 +            }
 +        }
 +    }
 +
 +    /**
 +     * @deprecated As of 7.0. Will likely change or be removed in a future
 +     *             version
 +     */
 +    @Deprecated
 +    public static final String RESOURCE_URL_ID = "APP";
 +
 +    /**
 +     * This portlet parameter is used to add styles to the main element. E.g
 +     * "height:500px" generates a style="height:500px" to the main element.
 +     * 
 +     * @deprecated As of 7.0. Will likely change or be removed in a future
 +     *             version
 +     */
 +    @Deprecated
 +    public static final String PORTLET_PARAMETER_STYLE = "style";
 +
 +    /**
 +     * This portal parameter is used to define the name of the Vaadin theme that
 +     * is used for all Vaadin applications in the portal.
 +     * 
 +     * @deprecated As of 7.0. Will likely change or be removed in a future
 +     *             version
 +     */
 +    @Deprecated
 +    public static final String PORTAL_PARAMETER_VAADIN_THEME = "vaadin.theme";
 +
 +    /**
 +     * @deprecated As of 7.0. Will likely change or be removed in a future
 +     *             version
 +     */
 +    @Deprecated
 +    public static final String WRITE_AJAX_PAGE_SCRIPT_WIDGETSET_SHOULD_WRITE = "writeAjaxPageScriptWidgetsetShouldWrite";
 +
 +    // TODO some parts could be shared with AbstractApplicationServlet
 +
 +    // TODO Can we close the application when the portlet is removed? Do we know
 +    // when the portlet is removed?
 +
 +    private VaadinPortletService vaadinService;
 +
 +    @Override
 +    public void init(PortletConfig config) throws PortletException {
 +        CurrentInstance.clearAll();
 +        super.init(config);
 +        Properties initParameters = new Properties();
 +
 +        // Read default parameters from the context
 +        final PortletContext context = config.getPortletContext();
 +        for (final Enumeration<String> e = context.getInitParameterNames(); e
 +                .hasMoreElements();) {
 +            final String name = e.nextElement();
 +            initParameters.setProperty(name, context.getInitParameter(name));
 +        }
 +
 +        // Override with application settings from portlet.xml
 +        for (final Enumeration<String> e = config.getInitParameterNames(); e
 +                .hasMoreElements();) {
 +            final String name = e.nextElement();
 +            initParameters.setProperty(name, config.getInitParameter(name));
 +        }
 +
 +        DeploymentConfiguration deploymentConfiguration = createDeploymentConfiguration(initParameters);
 +        try {
 +            vaadinService = createPortletService(deploymentConfiguration);
 +        } catch (ServiceException e) {
 +            throw new PortletException("Could not initialized VaadinPortlet", e);
 +        }
 +        // Sets current service even though there are no request and response
 +        vaadinService.setCurrentInstances(null, null);
 +
 +        portletInitialized();
 +
 +        CurrentInstance.clearAll();
 +    }
 +
 +    protected void portletInitialized() throws PortletException {
 +
 +    }
 +
 +    protected DeploymentConfiguration createDeploymentConfiguration(
 +            Properties initParameters) {
 +        return new DefaultDeploymentConfiguration(getClass(), initParameters);
 +    }
 +
 +    protected VaadinPortletService createPortletService(
 +            DeploymentConfiguration deploymentConfiguration)
 +            throws ServiceException {
 +        VaadinPortletService service = new VaadinPortletService(this,
 +                deploymentConfiguration);
 +        service.init();
 +        return service;
 +    }
 +
 +    /**
 +     * @author Vaadin Ltd
 +     * 
 +     * @deprecated As of 7.0. This is no longer used and only provided for
 +     *             backwards compatibility. Each {@link RequestHandler} can
 +     *             individually decide whether it wants to handle a request or
 +     *             not.
 +     */
 +    @Deprecated
 +    protected enum RequestType {
 +        FILE_UPLOAD, UIDL, RENDER, STATIC_FILE, APP, DUMMY, EVENT, ACTION, UNKNOWN, BROWSER_DETAILS, PUBLISHED_FILE, HEARTBEAT;
 +    }
 +
 +    /**
 +     * @param vaadinRequest
 +     * @return
 +     * 
 +     * @deprecated As of 7.0. This is no longer used and only provided for
 +     *             backwards compatibility. Each {@link RequestHandler} can
 +     *             individually decide whether it wants to handle a request or
 +     *             not.
 +     */
 +    @Deprecated
 +    protected RequestType getRequestType(VaadinPortletRequest vaadinRequest) {
 +        PortletRequest request = vaadinRequest.getPortletRequest();
 +        if (request instanceof RenderRequest) {
 +            return RequestType.RENDER;
 +        } else if (request instanceof ResourceRequest) {
 +            if (ServletPortletHelper.isUIDLRequest(vaadinRequest)) {
 +                return RequestType.UIDL;
 +            } else if (PortletUIInitHandler.isUIInitRequest(vaadinRequest)) {
 +                return RequestType.BROWSER_DETAILS;
 +            } else if (ServletPortletHelper.isFileUploadRequest(vaadinRequest)) {
 +                return RequestType.FILE_UPLOAD;
 +            } else if (ServletPortletHelper
 +                    .isPublishedFileRequest(vaadinRequest)) {
 +                return RequestType.PUBLISHED_FILE;
 +            } else if (ServletPortletHelper.isAppRequest(vaadinRequest)) {
 +                return RequestType.APP;
 +            } else if (ServletPortletHelper.isHeartbeatRequest(vaadinRequest)) {
 +                return RequestType.HEARTBEAT;
 +            } else if (PortletDummyRequestHandler.isDummyRequest(vaadinRequest)) {
 +                return RequestType.DUMMY;
 +            } else {
 +                return RequestType.STATIC_FILE;
 +            }
 +        } else if (request instanceof ActionRequest) {
 +            return RequestType.ACTION;
 +        } else if (request instanceof EventRequest) {
 +            return RequestType.EVENT;
 +        }
 +        return RequestType.UNKNOWN;
 +    }
 +
 +    /**
 +     * @param request
 +     * @param response
 +     * @throws PortletException
 +     * @throws IOException
 +     * 
 +     * @deprecated As of 7.0. Will likely change or be removed in a future
 +     *             version
 +     */
 +    @Deprecated
 +    protected void handleRequest(PortletRequest request,
 +            PortletResponse response) throws PortletException, IOException {
 +
 +        CurrentInstance.clearAll();
 +        try {
 +            getService().handleRequest(createVaadinRequest(request),
 +                    createVaadinResponse(response));
 +        } catch (ServiceException e) {
 +            throw new PortletException(e);
 +        }
 +    }
 +
 +    /**
 +     * Wraps the request in a (possibly portal specific) Vaadin portlet request.
 +     * 
 +     * @param request
 +     *            The original PortletRequest
 +     * @return A wrapped version of the PortletRequest
 +     */
 +    protected VaadinPortletRequest createVaadinRequest(PortletRequest request) {
 +        PortalContext portalContext = request.getPortalContext();
 +        String portalInfo = portalContext.getPortalInfo().toLowerCase().trim();
 +        VaadinPortletService service = getService();
 +
 +        if (portalInfo.contains("gatein")) {
 +            return new VaadinGateInRequest(request, service);
 +        }
 +
 +        if (portalInfo.contains("liferay")) {
 +            return new VaadinLiferayRequest(request, service);
 +        }
 +
 +        if (portalInfo.contains("websphere portal")) {
 +            return new VaadinWebSpherePortalRequest(request, service);
 +        }
 +        if (portalInfo.contains("weblogic portal")) {
 +            return new VaadinWebLogicPortalRequest(request, service);
 +        }
 +
 +        return new VaadinPortletRequest(request, service);
 +    }
 +
 +    private VaadinPortletResponse createVaadinResponse(PortletResponse response) {
 +        return new VaadinPortletResponse(response, getService());
 +    }
 +
 +    protected VaadinPortletService getService() {
 +        return vaadinService;
 +    }
 +
 +    @Override
 +    public void processEvent(EventRequest request, EventResponse response)
 +            throws PortletException, IOException {
 +        handleRequest(request, response);
 +    }
 +
 +    @Override
 +    public void processAction(ActionRequest request, ActionResponse response)
 +            throws PortletException, IOException {
 +        handleRequest(request, response);
 +    }
 +
 +    @Override
 +    protected void doDispatch(RenderRequest request, RenderResponse response)
 +            throws PortletException, IOException {
 +        try {
 +            // try to let super handle - it'll call methods annotated for
 +            // handling, the default doXYZ(), or throw if a handler for the mode
 +            // is not found
 +            super.doDispatch(request, response);
 +
 +        } catch (PortletException e) {
 +            if (e.getCause() == null) {
 +                // No cause interpreted as 'unknown mode' - pass that trough
 +                // so that the application can handle
 +                handleRequest(request, response);
 +
 +            } else {
 +                // Something else failed, pass on
 +                throw e;
 +            }
 +        }
 +    }
 +
 +    @Override
 +    public void serveResource(ResourceRequest request, ResourceResponse response)
 +            throws PortletException, IOException {
 +        handleRequest(request, response);
 +    }
 +
 +    @Override
 +    public void destroy() {
 +        super.destroy();
 +        getService().destroy();
 +    }
 +
 +    private static final Logger getLogger() {
 +        return Logger.getLogger(VaadinPortlet.class.getName());
 +    }
 +
 +    /**
 +     * Gets the currently used Vaadin portlet. The current portlet is
 +     * automatically defined when initializing the portlet and when processing
 +     * requests to the server and in threads started at a point when the current
 +     * portlet is defined (see {@link InheritableThreadLocal}). In other cases,
 +     * (e.g. from background threads started in some other way), the current
 +     * portlet is not automatically defined.
 +     * <p>
 +     * The current portlet is derived from the current service using
 +     * {@link VaadinService#getCurrent()}
 +     * 
 +     * @return the current vaadin portlet instance if available, otherwise
 +     *         <code>null</code>
 +     * 
 +     * @since 7.0
 +     */
 +    public static VaadinPortlet getCurrent() {
 +        VaadinService vaadinService = CurrentInstance.get(VaadinService.class);
 +        if (vaadinService instanceof VaadinPortletService) {
 +            VaadinPortletService vps = (VaadinPortletService) vaadinService;
 +            return vps.getPortlet();
 +        } else {
 +            return null;
 +        }
 +    }
 +
 +}
index 593b1f544f09d8029f90103c17bed8c2844f0ea4,0000000000000000000000000000000000000000..3503a2d8b136f7b49ac8ee8404e9c737a370989e
mode 100644,000000..100644
--- /dev/null
@@@ -1,647 -1,0 +1,681 @@@
-      * @param uI
 +/*
 + * Copyright 2000-2014 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.server.communication;
 +
 +import java.io.IOException;
 +import java.io.Reader;
 +import java.io.Serializable;
 +import java.lang.reflect.Type;
 +import java.util.ArrayList;
 +import java.util.HashSet;
 +import java.util.List;
 +import java.util.Map;
 +import java.util.Set;
 +import java.util.logging.Level;
 +import java.util.logging.Logger;
 +
 +import com.vaadin.server.ClientConnector;
 +import com.vaadin.server.Constants;
 +import com.vaadin.server.JsonCodec;
 +import com.vaadin.server.LegacyCommunicationManager;
 +import com.vaadin.server.LegacyCommunicationManager.InvalidUIDLSecurityKeyException;
 +import com.vaadin.server.ServerRpcManager;
 +import com.vaadin.server.ServerRpcManager.RpcInvocationException;
 +import com.vaadin.server.ServerRpcMethodInvocation;
 +import com.vaadin.server.VaadinRequest;
 +import com.vaadin.server.VaadinService;
 +import com.vaadin.server.VariableOwner;
 +import com.vaadin.shared.ApplicationConstants;
 +import com.vaadin.shared.Connector;
 +import com.vaadin.shared.Version;
 +import com.vaadin.shared.communication.LegacyChangeVariablesInvocation;
 +import com.vaadin.shared.communication.MethodInvocation;
 +import com.vaadin.shared.communication.ServerRpc;
 +import com.vaadin.shared.communication.UidlValue;
 +import com.vaadin.ui.Component;
 +import com.vaadin.ui.ConnectorTracker;
 +import com.vaadin.ui.UI;
 +
 +import elemental.json.JsonArray;
 +import elemental.json.JsonException;
 +import elemental.json.JsonObject;
 +import elemental.json.JsonValue;
 +import elemental.json.impl.JsonUtil;
 +
 +/**
 + * Handles a client-to-server message containing serialized {@link ServerRpc
 + * server RPC} invocations.
 + *
 + * @author Vaadin Ltd
 + * @since 7.1
 + */
 +public class ServerRpcHandler implements Serializable {
 +
 +    /**
 +     * A data transfer object representing an RPC request sent by the client
 +     * side.
 +     *
 +     * @since 7.2
 +     * @author Vaadin Ltd
 +     */
 +    public static class RpcRequest implements Serializable {
 +
 +        private final String csrfToken;
 +        private final JsonArray invocations;
 +        private final int syncId;
 +        private final JsonObject json;
 +        private final boolean resynchronize;
 +        private final int clientToServerMessageId;
 +        private String widgetsetVersion = null;
 +
 +        public RpcRequest(String jsonString, VaadinRequest request) {
 +            json = JsonUtil.parse(jsonString);
 +
 +            JsonValue token = json.get(ApplicationConstants.CSRF_TOKEN);
 +            if (token == null) {
 +                csrfToken = ApplicationConstants.CSRF_TOKEN_DEFAULT_VALUE;
 +            } else {
 +                String csrfToken = token.asString();
 +                if (csrfToken.equals("")) {
 +                    csrfToken = ApplicationConstants.CSRF_TOKEN_DEFAULT_VALUE;
 +                }
 +                this.csrfToken = csrfToken;
 +            }
 +
 +            if (request.getService().getDeploymentConfiguration()
 +                    .isSyncIdCheckEnabled()) {
 +                syncId = (int) json
 +                        .getNumber(ApplicationConstants.SERVER_SYNC_ID);
 +            } else {
 +                syncId = -1;
 +            }
 +
 +            if (json.hasKey(ApplicationConstants.RESYNCHRONIZE_ID)) {
 +                resynchronize = json
 +                        .getBoolean(ApplicationConstants.RESYNCHRONIZE_ID);
 +            } else {
 +                resynchronize = false;
 +            }
 +            if (json.hasKey(ApplicationConstants.WIDGETSET_VERSION_ID)) {
 +                widgetsetVersion = json
 +                        .getString(ApplicationConstants.WIDGETSET_VERSION_ID);
 +            }
 +
 +            if (json.hasKey(ApplicationConstants.CLIENT_TO_SERVER_ID)) {
 +                clientToServerMessageId = (int) json
 +                        .getNumber(ApplicationConstants.CLIENT_TO_SERVER_ID);
 +            } else {
 +                getLogger()
 +                        .warning("Server message without client id received");
 +                clientToServerMessageId = -1;
 +            }
 +            invocations = json.getArray(ApplicationConstants.RPC_INVOCATIONS);
 +        }
 +
 +        /**
 +         * Gets the CSRF security token (double submit cookie) for this request.
 +         *
 +         * @return the CSRF security token for this current change request
 +         */
 +        public String getCsrfToken() {
 +            return csrfToken;
 +        }
 +
 +        /**
 +         * Gets the data to recreate the RPC as requested by the client side.
 +         *
 +         * @return the data describing which RPC should be made, and all their
 +         *         data
 +         */
 +        public JsonArray getRpcInvocationsData() {
 +            return invocations;
 +        }
 +
 +        /**
 +         * Gets the sync id last seen by the client.
 +         *
 +         * @return the last sync id given by the server, according to the
 +         *         client's request
 +         */
 +        public int getSyncId() {
 +            return syncId;
 +        }
 +
 +        /**
 +         * Checks if this is a request to resynchronize the client side
 +         * 
 +         * @return true if this is a resynchronization request, false otherwise
 +         */
 +        public boolean isResynchronize() {
 +            return resynchronize;
 +        }
 +
 +        /**
 +         * Gets the id of the client to server message
 +         * 
 +         * @since 7.6
 +         * @return the server message id
 +         */
 +        public int getClientToServerId() {
 +            return clientToServerMessageId;
 +        }
 +
 +        /**
 +         * Gets the entire request in JSON format, as it was received from the
 +         * client.
 +         * <p>
 +         * <em>Note:</em> This is a shared reference - any modifications made
 +         * will be shared.
 +         *
 +         * @return the raw JSON object that was received from the client
 +         *
 +         */
 +        public JsonObject getRawJson() {
 +            return json;
 +        }
 +
 +        /**
 +         * Gets the widget set version reported by the client
 +         * 
 +         * @since 7.6
 +         * @return The widget set version reported by the client or null if the
 +         *         message did not contain a widget set version
 +         */
 +        public String getWidgetsetVersion() {
 +            return widgetsetVersion;
 +        }
 +    }
 +
 +    private static final int MAX_BUFFER_SIZE = 64 * 1024;
 +
 +    /**
 +     * Reads JSON containing zero or more serialized RPC calls (including legacy
 +     * variable changes) and executes the calls.
 +     *
 +     * @param ui
 +     *            The {@link UI} receiving the calls. Cannot be null.
 +     * @param reader
 +     *            The {@link Reader} used to read the JSON.
 +     * @param request
 +     * @throws IOException
 +     *             If reading the message fails.
 +     * @throws InvalidUIDLSecurityKeyException
 +     *             If the received security key does not match the one stored in
 +     *             the session.
 +     */
 +    public void handleRpc(UI ui, Reader reader, VaadinRequest request)
 +            throws IOException, InvalidUIDLSecurityKeyException {
 +        ui.getSession().setLastRequestTimestamp(System.currentTimeMillis());
 +
 +        String changeMessage = getMessage(reader);
 +
 +        if (changeMessage == null || changeMessage.equals("")) {
 +            // The client sometimes sends empty messages, this is probably a bug
 +            return;
 +        }
 +
 +        RpcRequest rpcRequest = new RpcRequest(changeMessage, request);
 +
 +        // Security: double cookie submission pattern unless disabled by
 +        // property
 +        if (!VaadinService.isCsrfTokenValid(ui.getSession(),
 +                rpcRequest.getCsrfToken())) {
 +            throw new InvalidUIDLSecurityKeyException("");
 +        }
 +
 +        checkWidgetsetVersion(rpcRequest.getWidgetsetVersion());
 +
 +        int expectedId = ui.getLastProcessedClientToServerId() + 1;
 +        if (rpcRequest.getClientToServerId() != -1
 +                && rpcRequest.getClientToServerId() != expectedId) {
 +            // Invalid message id, skip RPC processing but force a full
 +            // re-synchronization of the client as it might have not received
 +            // the previous response (e.g. due to a bad connection)
 +
 +            // Must resync also for duplicate messages because the server might
 +            // have generated a response for the first message but the response
 +            // did not reach the client. When the client re-sends the message,
 +            // it would only get an empty response (because the dirty flags have
 +            // been cleared on the server) and would be out of sync
 +            ui.getSession().getCommunicationManager().repaintAll(ui);
 +
 +            if (rpcRequest.getClientToServerId() < expectedId) {
 +                // Just a duplicate message due to a bad connection or similar
 +                // It has already been handled by the server so it is safe to
 +                // ignore
 +                getLogger().fine(
 +                        "Ignoring old message from the client. Expected: "
 +                                + expectedId + ", got: "
 +                                + rpcRequest.getClientToServerId());
 +            } else {
 +                getLogger().warning(
 +                        "Unexpected message id from the client. Expected: "
 +                                + expectedId + ", got: "
 +                                + rpcRequest.getClientToServerId());
 +            }
 +        } else {
 +            // Message id ok, process RPCs
 +            ui.setLastProcessedClientToServerId(expectedId);
 +            handleInvocations(ui, rpcRequest.getSyncId(),
 +                    rpcRequest.getRpcInvocationsData());
 +        }
 +
 +        ui.getConnectorTracker().cleanConcurrentlyRemovedConnectorIds(
 +                rpcRequest.getSyncId());
 +
 +        if (rpcRequest.isResynchronize()) {
 +            ui.getSession().getCommunicationManager().repaintAll(ui);
 +        }
 +
 +    }
 +
 +    /**
 +     * Checks that the version reported by the client (widgetset) matches that
 +     * of the server.
 +     * 
 +     * @param widgetsetVersion
 +     *            the widget set version reported by the client or null
 +     */
 +    private void checkWidgetsetVersion(String widgetsetVersion) {
 +        if (widgetsetVersion == null) {
 +            // Only check when the widgetset version is reported. It is reported
 +            // in the first UIDL request (not the initial request as it is a
 +            // plain GET /)
 +            return;
 +        }
 +
 +        if (!Version.getFullVersion().equals(widgetsetVersion)) {
 +            getLogger().warning(
 +                    String.format(Constants.WIDGETSET_MISMATCH_INFO,
 +                            Version.getFullVersion(), widgetsetVersion));
 +        }
 +    }
 +
 +    /**
 +     * Processes invocations data received from the client.
 +     * <p>
 +     * The invocations data can contain any number of RPC calls, including
 +     * legacy variable change calls that are processed separately.
 +     * <p>
 +     * Consecutive changes to the value of the same variable are combined and
 +     * changeVariables() is only called once for them. This preserves the Vaadin
 +     * 6 semantics for components and add-ons that do not use Vaadin 7 RPC
 +     * directly.
 +     *
-     private void handleInvocations(UI uI, int lastSyncIdSeenByClient,
++     * @param ui
 +     *            the UI receiving the invocations data
 +     * @param lastSyncIdSeenByClient
 +     *            the most recent sync id the client has seen at the time the
 +     *            request was sent
 +     * @param invocationsData
 +     *            JSON containing all information needed to execute all
 +     *            requested RPC calls.
++     * @since
 +     */
-         LegacyCommunicationManager manager = uI.getSession()
++    protected void handleInvocations(UI ui, int lastSyncIdSeenByClient,
 +            JsonArray invocationsData) {
 +        // TODO PUSH Refactor so that this is not needed
-             ConnectorTracker connectorTracker = uI.getConnectorTracker();
++        LegacyCommunicationManager manager = ui.getSession()
 +                .getCommunicationManager();
 +
 +        try {
-                     uI.getConnectorTracker(), invocationsData,
++            ConnectorTracker connectorTracker = ui.getConnectorTracker();
 +
 +            Set<Connector> enabledConnectors = new HashSet<Connector>();
 +
 +            List<MethodInvocation> invocations = parseInvocations(
-                     try {
-                         ServerRpcManager.applyInvocation(connector,
-                                 (ServerRpcMethodInvocation) invocation);
-                     } catch (RpcInvocationException e) {
-                         manager.handleConnectorRelatedException(connector, e);
-                     }
++                    ui.getConnectorTracker(), invocationsData,
 +                    lastSyncIdSeenByClient);
 +            for (MethodInvocation invocation : invocations) {
 +                final ClientConnector connector = connectorTracker
 +                        .getConnector(invocation.getConnectorId());
 +
 +                if (connector != null && connector.isConnectorEnabled()) {
 +                    enabledConnectors.add(connector);
 +                }
 +            }
 +
 +            for (int i = 0; i < invocations.size(); i++) {
 +                MethodInvocation invocation = invocations.get(i);
 +
 +                final ClientConnector connector = connectorTracker
 +                        .getConnector(invocation.getConnectorId());
 +                if (connector == null) {
 +                    getLogger()
 +                            .log(Level.WARNING,
 +                                    "Received RPC call for unknown connector with id {0} (tried to invoke {1}.{2})",
 +                                    new Object[] { invocation.getConnectorId(),
 +                                            invocation.getInterfaceName(),
 +                                            invocation.getMethodName() });
 +                    continue;
 +                }
 +
 +                if (!enabledConnectors.contains(connector)) {
 +
 +                    if (invocation instanceof LegacyChangeVariablesInvocation) {
 +                        LegacyChangeVariablesInvocation legacyInvocation = (LegacyChangeVariablesInvocation) invocation;
 +                        // TODO convert window close to a separate RPC call and
 +                        // handle above - not a variable change
 +
 +                        // Handle special case where window-close is called
 +                        // after the window has been removed from the
 +                        // application or the application has closed
 +                        Map<String, Object> changes = legacyInvocation
 +                                .getVariableChanges();
 +                        if (changes.size() == 1 && changes.containsKey("close")
 +                                && Boolean.TRUE.equals(changes.get("close"))) {
 +                            // Silently ignore this
 +                            continue;
 +                        }
 +                    }
 +
 +                    // Connector is disabled, log a warning and move to the next
 +                    getLogger().warning(
 +                            getIgnoredDisabledError("RPC call", connector));
 +                    continue;
 +                }
 +                // DragAndDropService has null UI
 +                if (connector.getUI() != null && connector.getUI().isClosing()) {
 +                    String msg = "Ignoring RPC call for connector "
 +                            + connector.getClass().getName();
 +                    if (connector instanceof Component) {
 +                        String caption = ((Component) connector).getCaption();
 +                        if (caption != null) {
 +                            msg += ", caption=" + caption;
 +                        }
 +                    }
 +                    msg += " in closed UI";
 +                    getLogger().warning(msg);
 +                    continue;
 +
 +                }
 +
 +                if (invocation instanceof ServerRpcMethodInvocation) {
-                     // All code below is for legacy variable changes
++                    handleInvocation(ui, connector,
++                            (ServerRpcMethodInvocation) invocation);
 +                } else {
-                     Map<String, Object> changes = legacyInvocation
-                             .getVariableChanges();
-                     try {
-                         if (connector instanceof VariableOwner) {
-                             // The source parameter is never used anywhere
-                             changeVariables(null, (VariableOwner) connector,
-                                     changes);
-                         } else {
-                             throw new IllegalStateException(
-                                     "Received legacy variable change for "
-                                             + connector.getClass().getName()
-                                             + " ("
-                                             + connector.getConnectorId()
-                                             + ") which is not a VariableOwner. The client-side connector sent these legacy varaibles: "
-                                             + changes.keySet());
-                         }
-                     } catch (Exception e) {
-                         manager.handleConnectorRelatedException(connector, e);
-                     }
 +                    LegacyChangeVariablesInvocation legacyInvocation = (LegacyChangeVariablesInvocation) invocation;
++                    handleInvocation(ui, connector, legacyInvocation);
 +                }
 +            }
 +        } catch (JsonException e) {
 +            getLogger().warning(
 +                    "Unable to parse RPC call from the client: "
 +                            + e.getMessage());
 +            throw new RuntimeException(e);
 +        }
 +    }
 +
++    /**
++     * Handles the given RPC method invocation for the given connector
++     * 
++     * @since
++     * @param ui
++     *            the UI containing the connector
++     * @param connector
++     *            the connector the RPC is targeted to
++     * @param invocation
++     *            information about the rpc to invoke
++     */
++    protected void handleInvocation(UI ui, ClientConnector connector,
++            ServerRpcMethodInvocation invocation) {
++        try {
++            ServerRpcManager.applyInvocation(connector, invocation);
++        } catch (RpcInvocationException e) {
++            ui.getSession().getCommunicationManager()
++                    .handleConnectorRelatedException(connector, e);
++        }
++
++    }
++
++    /**
++     * Handles the given Legacy variable change RPC method invocation for the
++     * given connector
++     * 
++     * @since
++     * @param ui
++     *            the UI containing the connector
++     * @param connector
++     *            the connector the RPC is targeted to
++     * @param invocation
++     *            information about the rpc to invoke
++     */
++    protected void handleInvocation(UI ui, ClientConnector connector,
++            LegacyChangeVariablesInvocation legacyInvocation) {
++        Map<String, Object> changes = legacyInvocation.getVariableChanges();
++        try {
++            if (connector instanceof VariableOwner) {
++                // The source parameter is never used anywhere
++                changeVariables(null, (VariableOwner) connector, changes);
++            } else {
++                throw new IllegalStateException(
++                        "Received legacy variable change for "
++                                + connector.getClass().getName()
++                                + " ("
++                                + connector.getConnectorId()
++                                + ") which is not a VariableOwner. The client-side connector sent these legacy varaibles: "
++                                + changes.keySet());
++            }
++        } catch (Exception e) {
++            ui.getSession().getCommunicationManager()
++                    .handleConnectorRelatedException(connector, e);
++        }
++
++    }
++
 +    /**
 +     * Parse JSON from the client into a list of MethodInvocation instances.
 +     *
 +     * @param connectorTracker
 +     *            The ConnectorTracker used to lookup connectors
 +     * @param invocationsJson
 +     *            JSON containing all information needed to execute all
 +     *            requested RPC calls.
 +     * @param lastSyncIdSeenByClient
 +     *            the most recent sync id the client has seen at the time the
 +     *            request was sent
 +     * @return list of MethodInvocation to perform
 +     */
 +    private List<MethodInvocation> parseInvocations(
 +            ConnectorTracker connectorTracker, JsonArray invocationsJson,
 +            int lastSyncIdSeenByClient) {
 +        int invocationCount = invocationsJson.length();
 +        ArrayList<MethodInvocation> invocations = new ArrayList<MethodInvocation>(
 +                invocationCount);
 +
 +        MethodInvocation previousInvocation = null;
 +        // parse JSON to MethodInvocations
 +        for (int i = 0; i < invocationCount; ++i) {
 +
 +            JsonArray invocationJson = invocationsJson.getArray(i);
 +
 +            MethodInvocation invocation = parseInvocation(invocationJson,
 +                    previousInvocation, connectorTracker,
 +                    lastSyncIdSeenByClient);
 +            if (invocation != null) {
 +                // Can be null if the invocation was a legacy invocation and it
 +                // was merged with the previous one or if the invocation was
 +                // rejected because of an error.
 +                invocations.add(invocation);
 +                previousInvocation = invocation;
 +            }
 +        }
 +        return invocations;
 +    }
 +
 +    private MethodInvocation parseInvocation(JsonArray invocationJson,
 +            MethodInvocation previousInvocation,
 +            ConnectorTracker connectorTracker, long lastSyncIdSeenByClient) {
 +        String connectorId = invocationJson.getString(0);
 +        String interfaceName = invocationJson.getString(1);
 +        String methodName = invocationJson.getString(2);
 +
 +        if (connectorTracker.getConnector(connectorId) == null
 +                && !connectorId
 +                        .equals(ApplicationConstants.DRAG_AND_DROP_CONNECTOR_ID)) {
 +
 +            if (!connectorTracker.connectorWasPresentAsRequestWasSent(
 +                    connectorId, lastSyncIdSeenByClient)) {
 +                getLogger()
 +                        .log(Level.WARNING,
 +                                "RPC call to "
 +                                        + interfaceName
 +                                        + "."
 +                                        + methodName
 +                                        + " received for connector "
 +                                        + connectorId
 +                                        + " but no such connector could be found. Resynchronizing client.");
 +                // This is likely an out of sync issue (client tries to update a
 +                // connector which is not present). Force resync.
 +                connectorTracker.markAllConnectorsDirty();
 +            }
 +            return null;
 +        }
 +
 +        JsonArray parametersJson = invocationJson.getArray(3);
 +
 +        if (LegacyChangeVariablesInvocation.isLegacyVariableChange(
 +                interfaceName, methodName)) {
 +            if (!(previousInvocation instanceof LegacyChangeVariablesInvocation)) {
 +                previousInvocation = null;
 +            }
 +
 +            return parseLegacyChangeVariablesInvocation(connectorId,
 +                    interfaceName, methodName,
 +                    (LegacyChangeVariablesInvocation) previousInvocation,
 +                    parametersJson, connectorTracker);
 +        } else {
 +            return parseServerRpcInvocation(connectorId, interfaceName,
 +                    methodName, parametersJson, connectorTracker);
 +        }
 +
 +    }
 +
 +    private LegacyChangeVariablesInvocation parseLegacyChangeVariablesInvocation(
 +            String connectorId, String interfaceName, String methodName,
 +            LegacyChangeVariablesInvocation previousInvocation,
 +            JsonArray parametersJson, ConnectorTracker connectorTracker) {
 +        if (parametersJson.length() != 2) {
 +            throw new JsonException(
 +                    "Invalid parameters in legacy change variables call. Expected 2, was "
 +                            + parametersJson.length());
 +        }
 +        String variableName = parametersJson.getString(0);
 +        UidlValue uidlValue = (UidlValue) JsonCodec.decodeInternalType(
 +                UidlValue.class, true, parametersJson.get(1), connectorTracker);
 +
 +        Object value = uidlValue.getValue();
 +
 +        if (previousInvocation != null
 +                && previousInvocation.getConnectorId().equals(connectorId)) {
 +            previousInvocation.setVariableChange(variableName, value);
 +            return null;
 +        } else {
 +            return new LegacyChangeVariablesInvocation(connectorId,
 +                    variableName, value);
 +        }
 +    }
 +
 +    private ServerRpcMethodInvocation parseServerRpcInvocation(
 +            String connectorId, String interfaceName, String methodName,
 +            JsonArray parametersJson, ConnectorTracker connectorTracker)
 +            throws JsonException {
 +        ClientConnector connector = connectorTracker.getConnector(connectorId);
 +
 +        ServerRpcManager<?> rpcManager = connector.getRpcManager(interfaceName);
 +        if (rpcManager == null) {
 +            /*
 +             * Security: Don't even decode the json parameters if no RpcManager
 +             * corresponding to the received method invocation has been
 +             * registered.
 +             */
 +            getLogger().warning(
 +                    "Ignoring RPC call to " + interfaceName + "." + methodName
 +                            + " in connector " + connector.getClass().getName()
 +                            + "(" + connectorId
 +                            + ") as no RPC implementation is registered");
 +            return null;
 +        }
 +
 +        // Use interface from RpcManager instead of loading the class based on
 +        // the string name to avoid problems with OSGi
 +        Class<? extends ServerRpc> rpcInterface = rpcManager.getRpcInterface();
 +
 +        ServerRpcMethodInvocation invocation = new ServerRpcMethodInvocation(
 +                connectorId, rpcInterface, methodName, parametersJson.length());
 +
 +        Object[] parameters = new Object[parametersJson.length()];
 +        Type[] declaredRpcMethodParameterTypes = invocation.getMethod()
 +                .getGenericParameterTypes();
 +
 +        for (int j = 0; j < parametersJson.length(); ++j) {
 +            JsonValue parameterValue = parametersJson.get(j);
 +            Type parameterType = declaredRpcMethodParameterTypes[j];
 +            parameters[j] = JsonCodec.decodeInternalOrCustomType(parameterType,
 +                    parameterValue, connectorTracker);
 +        }
 +        invocation.setParameters(parameters);
 +        return invocation;
 +    }
 +
 +    protected void changeVariables(Object source, VariableOwner owner,
 +            Map<String, Object> m) {
 +        owner.changeVariables(source, m);
 +    }
 +
 +    protected String getMessage(Reader reader) throws IOException {
 +
 +        StringBuilder sb = new StringBuilder(MAX_BUFFER_SIZE);
 +        char[] buffer = new char[MAX_BUFFER_SIZE];
 +
 +        while (true) {
 +            int read = reader.read(buffer);
 +            if (read == -1) {
 +                break;
 +            }
 +            sb.append(buffer, 0, read);
 +        }
 +
 +        return sb.toString();
 +    }
 +
 +    private static final Logger getLogger() {
 +        return Logger.getLogger(ServerRpcHandler.class.getName());
 +    }
 +
 +    /**
 +     * Generates an error message when the client is trying to to something
 +     * ('what') with a connector which is disabled or invisible.
 +     *
 +     * @since 7.1.8
 +     * @param connector
 +     *            the connector which is disabled (or invisible)
 +     * @return an error message
 +     */
 +    public static String getIgnoredDisabledError(String what,
 +            ClientConnector connector) {
 +        String msg = "Ignoring " + what + " for disabled connector "
 +                + connector.getClass().getName();
 +        if (connector instanceof Component) {
 +            String caption = ((Component) connector).getCaption();
 +            if (caption != null) {
 +                msg += ", caption=" + caption;
 +            }
 +        }
 +        return msg;
 +    }
 +}
index dda3d81453b370befba736b887803a3458c47a6e,0000000000000000000000000000000000000000..db18bb9e1e15ef9380fa4ab09c9386e3b49b0b2b
mode 100644,000000..100644
--- /dev/null
@@@ -1,191 -1,0 +1,202 @@@
-     private ServerRpcHandler rpcHandler = new ServerRpcHandler();
 +/*
 + * Copyright 2000-2014 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.server.communication;
 +
 +import java.io.IOException;
 +import java.io.StringWriter;
 +import java.io.Writer;
 +import java.util.logging.Level;
 +import java.util.logging.Logger;
 +
 +import com.vaadin.server.LegacyCommunicationManager.InvalidUIDLSecurityKeyException;
 +import com.vaadin.server.ServletPortletHelper;
 +import com.vaadin.server.SessionExpiredHandler;
 +import com.vaadin.server.SynchronizedRequestHandler;
 +import com.vaadin.server.SystemMessages;
 +import com.vaadin.server.VaadinRequest;
 +import com.vaadin.server.VaadinResponse;
 +import com.vaadin.server.VaadinService;
 +import com.vaadin.server.VaadinSession;
 +import com.vaadin.shared.JsonConstants;
 +import com.vaadin.ui.UI;
 +
 +import elemental.json.JsonException;
 +
 +/**
 + * Processes a UIDL request from the client.
 + * 
 + * Uses {@link ServerRpcHandler} to execute client-to-server RPC invocations and
 + * {@link UidlWriter} to write state changes and client RPC calls back to the
 + * client.
 + * 
 + * @author Vaadin Ltd
 + * @since 7.1
 + */
 +public class UidlRequestHandler extends SynchronizedRequestHandler implements
 +        SessionExpiredHandler {
 +
 +    public static final String UIDL_PATH = "UIDL/";
 +
++    private ServerRpcHandler rpcHandler;
 +
 +    public UidlRequestHandler() {
++        rpcHandler = createRpcHandler();
++    }
++
++    /**
++     * Creates the ServerRpcHandler to use
++     * 
++     * @since
++     * @return the ServerRpcHandler to use
++     */
++    protected ServerRpcHandler createRpcHandler() {
++        return new ServerRpcHandler();
 +    }
 +
 +    @Override
 +    protected boolean canHandleRequest(VaadinRequest request) {
 +        return ServletPortletHelper.isUIDLRequest(request);
 +    }
 +
 +    @Override
 +    public boolean synchronizedHandleRequest(VaadinSession session,
 +            VaadinRequest request, VaadinResponse response) throws IOException {
 +        UI uI = session.getService().findUI(request);
 +        if (uI == null) {
 +            // This should not happen but it will if the UI has been closed. We
 +            // really don't want to see it in the server logs though
 +            UIInitHandler.commitJsonResponse(request, response,
 +                    getUINotFoundErrorJSON(session.getService(), request));
 +            return true;
 +        }
 +
 +        StringWriter stringWriter = new StringWriter();
 +
 +        try {
 +            rpcHandler.handleRpc(uI, request.getReader(), request);
 +
 +            writeUidl(request, response, uI, stringWriter);
 +        } catch (JsonException e) {
 +            getLogger().log(Level.SEVERE, "Error writing JSON to response", e);
 +            // Refresh on client side
 +            writeRefresh(request, response);
 +            return true;
 +        } catch (InvalidUIDLSecurityKeyException e) {
 +            getLogger().log(Level.WARNING,
 +                    "Invalid security key received from {0}",
 +                    request.getRemoteHost());
 +            // Refresh on client side
 +            writeRefresh(request, response);
 +            return true;
 +        } finally {
 +            stringWriter.close();
 +        }
 +
 +        return UIInitHandler.commitJsonResponse(request, response,
 +                stringWriter.toString());
 +    }
 +
 +    private void writeRefresh(VaadinRequest request, VaadinResponse response)
 +            throws IOException {
 +        String json = VaadinService.createCriticalNotificationJSON(null, null,
 +                null, null);
 +        UIInitHandler.commitJsonResponse(request, response, json);
 +    }
 +
 +    private void writeUidl(VaadinRequest request, VaadinResponse response,
 +            UI ui, Writer writer) throws IOException {
 +        openJsonMessage(writer, response);
 +
 +        new UidlWriter().write(ui, writer, false);
 +
 +        closeJsonMessage(writer);
 +    }
 +
 +    protected void closeJsonMessage(Writer outWriter) throws IOException {
 +        outWriter.write("}]");
 +    }
 +
 +    /**
 +     * Writes the opening of JSON message to be sent to client.
 +     * 
 +     * @param outWriter
 +     * @param response
 +     * @throws IOException
 +     */
 +    protected void openJsonMessage(Writer outWriter, VaadinResponse response)
 +            throws IOException {
 +        // some dirt to prevent cross site scripting
 +        outWriter.write("for(;;);[{");
 +    }
 +
 +    private static final Logger getLogger() {
 +        return Logger.getLogger(UidlRequestHandler.class.getName());
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see
 +     * com.vaadin.server.SessionExpiredHandler#handleSessionExpired(com.vaadin
 +     * .server.VaadinRequest, com.vaadin.server.VaadinResponse)
 +     */
 +    @Override
 +    public boolean handleSessionExpired(VaadinRequest request,
 +            VaadinResponse response) throws IOException {
 +        if (!ServletPortletHelper.isUIDLRequest(request)) {
 +            return false;
 +        }
 +        VaadinService service = request.getService();
 +        SystemMessages systemMessages = service.getSystemMessages(
 +                ServletPortletHelper.findLocale(null, null, request), request);
 +
 +        service.writeStringResponse(response, JsonConstants.JSON_CONTENT_TYPE,
 +                VaadinService.createCriticalNotificationJSON(
 +                        systemMessages.getSessionExpiredCaption(),
 +                        systemMessages.getSessionExpiredMessage(), null,
 +                        systemMessages.getSessionExpiredURL()));
 +
 +        return true;
 +    }
 +
 +    /**
 +     * Returns the JSON which should be returned to the client when a request
 +     * for a non-existent UI arrives.
 +     * 
 +     * @param service
 +     *            The VaadinService
 +     * @param vaadinRequest
 +     *            The request which triggered this, or null if not available
 +     * @since 7.1
 +     * @return A JSON string
 +     */
 +    static String getUINotFoundErrorJSON(VaadinService service,
 +            VaadinRequest vaadinRequest) {
 +        SystemMessages ci = service.getSystemMessages(
 +                vaadinRequest.getLocale(), vaadinRequest);
 +        // Session Expired is not really the correct message as the
 +        // session exists but the requested UI does not.
 +        // Using Communication Error for now.
 +        String json = VaadinService.createCriticalNotificationJSON(
 +                ci.getCommunicationErrorCaption(),
 +                ci.getCommunicationErrorMessage(), null,
 +                ci.getCommunicationErrorURL());
 +
 +        return json;
 +    }
 +
 +}
index b117cb4b4d7cbd3873eed1e5edb80bbeab99382e,0000000000000000000000000000000000000000..8a4e62fb24bc5fae162295c7c01906c92115b58e
mode 100644,000000..100644
--- /dev/null
@@@ -1,363 -1,0 +1,366 @@@
-         writer.write(String.format(", \"timings\":[%d, %d]", ui.getSession()
-                 .getCumulativeRequestDuration(), ui.getSession()
-                 .getLastRequestDuration()));
 +/*
 + * Copyright 2000-2014 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.server.communication;
 +
 +import java.io.IOException;
 +import java.io.Serializable;
 +import java.io.Writer;
 +import java.util.ArrayList;
 +import java.util.Collection;
 +import java.util.Collections;
 +import java.util.Comparator;
 +import java.util.HashSet;
 +import java.util.List;
 +import java.util.Set;
 +import java.util.logging.Level;
 +import java.util.logging.Logger;
 +
 +import com.vaadin.annotations.JavaScript;
 +import com.vaadin.annotations.StyleSheet;
 +import com.vaadin.server.ClientConnector;
 +import com.vaadin.server.JsonPaintTarget;
 +import com.vaadin.server.LegacyCommunicationManager;
 +import com.vaadin.server.LegacyCommunicationManager.ClientCache;
 +import com.vaadin.server.SystemMessages;
 +import com.vaadin.server.VaadinService;
 +import com.vaadin.server.VaadinSession;
 +import com.vaadin.shared.ApplicationConstants;
 +import com.vaadin.ui.ConnectorTracker;
 +import com.vaadin.ui.UI;
 +
 +import elemental.json.Json;
 +import elemental.json.JsonArray;
 +import elemental.json.impl.JsonUtil;
 +
 +/**
 + * Serializes pending server-side changes to UI state to JSON. This includes
 + * shared state, client RPC invocations, connector hierarchy changes, connector
 + * type information among others.
 + * 
 + * @author Vaadin Ltd
 + * @since 7.1
 + */
 +public class UidlWriter implements Serializable {
 +
 +    /**
 +     * Writes a JSON object containing all pending changes to the given UI.
 +     * 
 +     * @param ui
 +     *            The {@link UI} whose changes to write
 +     * @param writer
 +     *            The writer to use
 +     * @param analyzeLayouts
 +     *            Whether detected layout problems should be logged.
 +     * @param async
 +     *            True if this message is sent by the server asynchronously,
 +     *            false if it is a response to a client message.
 +     * 
 +     * @throws IOException
 +     *             If the writing fails.
 +     */
 +    public void write(UI ui, Writer writer, boolean async) throws IOException {
 +        VaadinSession session = ui.getSession();
 +        VaadinService service = session.getService();
 +
 +        // Purge pending access calls as they might produce additional changes
 +        // to write out
 +        service.runPendingAccessTasks(session);
 +
 +        Set<ClientConnector> processedConnectors = new HashSet<ClientConnector>();
 +
 +        LegacyCommunicationManager manager = session.getCommunicationManager();
 +        ClientCache clientCache = manager.getClientCache(ui);
 +        boolean repaintAll = clientCache.isEmpty();
 +        // Paints components
 +        ConnectorTracker uiConnectorTracker = ui.getConnectorTracker();
 +        getLogger().log(Level.FINE, "* Creating response to client");
 +
 +        while (true) {
 +            ArrayList<ClientConnector> connectorsToProcess = new ArrayList<ClientConnector>();
 +            for (ClientConnector c : uiConnectorTracker.getDirtyConnectors()) {
 +                if (!processedConnectors.contains(c)
 +                        && LegacyCommunicationManager
 +                                .isConnectorVisibleToClient(c)) {
 +                    connectorsToProcess.add(c);
 +                }
 +            }
 +
 +            if (connectorsToProcess.isEmpty()) {
 +                break;
 +            }
 +
 +            for (ClientConnector connector : connectorsToProcess) {
 +                boolean initialized = uiConnectorTracker
 +                        .isClientSideInitialized(connector);
 +                processedConnectors.add(connector);
 +
 +                try {
 +                    connector.beforeClientResponse(!initialized);
 +                } catch (RuntimeException e) {
 +                    manager.handleConnectorRelatedException(connector, e);
 +                }
 +            }
 +        }
 +
 +        getLogger().log(
 +                Level.FINE,
 +                "Found " + processedConnectors.size()
 +                        + " dirty connectors to paint");
 +
 +        uiConnectorTracker.setWritingResponse(true);
 +        try {
 +
 +            int syncId = service.getDeploymentConfiguration()
 +                    .isSyncIdCheckEnabled() ? uiConnectorTracker
 +                    .getCurrentSyncId() : -1;
 +            writer.write("\"" + ApplicationConstants.SERVER_SYNC_ID + "\": "
 +                    + syncId + ", ");
 +            if (repaintAll) {
 +                writer.write("\"" + ApplicationConstants.RESYNCHRONIZE_ID
 +                        + "\": true, ");
 +            }
 +            int nextClientToServerMessageId = ui
 +                    .getLastProcessedClientToServerId() + 1;
 +            writer.write("\"" + ApplicationConstants.CLIENT_TO_SERVER_ID
 +                    + "\": " + nextClientToServerMessageId + ", ");
 +            writer.write("\"changes\" : ");
 +
 +            JsonPaintTarget paintTarget = new JsonPaintTarget(manager, writer,
 +                    !repaintAll);
 +
 +            new LegacyUidlWriter().write(ui, writer, paintTarget);
 +
 +            paintTarget.close();
 +            writer.write(", "); // close changes
 +
 +            // send shared state to client
 +
 +            // for now, send the complete state of all modified and new
 +            // components
 +
 +            // Ideally, all this would be sent before "changes", but that causes
 +            // complications with legacy components that create sub-components
 +            // in their paint phase. Nevertheless, this will be processed on the
 +            // client after component creation but before legacy UIDL
 +            // processing.
 +
 +            writer.write("\"state\":");
 +            Set<String> stateUpdateConnectors = new SharedStateWriter().write(
 +                    ui, writer);
 +            writer.write(", "); // close states
 +
 +            // TODO This should be optimized. The type only needs to be
 +            // sent once for each connector id + on refresh. Use the same cache
 +            // as
 +            // widget mapping
 +
 +            writer.write("\"types\":");
 +            new ConnectorTypeWriter().write(ui, writer, paintTarget);
 +            writer.write(", "); // close states
 +
 +            // Send update hierarchy information to the client.
 +
 +            // This could be optimized aswell to send only info if hierarchy has
 +            // actually changed. Much like with the shared state. Note though
 +            // that an empty hierarchy is information aswell (e.g. change from 1
 +            // child to 0 children)
 +
 +            writer.write("\"hierarchy\":");
 +            new ConnectorHierarchyWriter().write(ui, writer,
 +                    stateUpdateConnectors);
 +            writer.write(", "); // close hierarchy
 +
 +            // send server to client RPC calls for components in the UI, in call
 +            // order
 +
 +            // collect RPC calls from components in the UI in the order in
 +            // which they were performed, remove the calls from components
 +
 +            writer.write("\"rpc\" : ");
 +            new ClientRpcWriter().write(ui, writer);
 +            writer.write(", "); // close rpc
 +
 +            uiConnectorTracker.markAllConnectorsClean();
 +
 +            writer.write("\"meta\" : ");
 +
 +            SystemMessages messages = ui.getSession().getService()
 +                    .getSystemMessages(ui.getLocale(), null);
 +            // TODO hilightedConnector
 +            new MetadataWriter().write(ui, writer, repaintAll, async, messages);
 +            writer.write(", ");
 +
 +            writer.write("\"resources\" : ");
 +            new ResourceWriter().write(ui, writer, paintTarget);
 +
 +            Collection<Class<? extends ClientConnector>> usedClientConnectors = paintTarget
 +                    .getUsedClientConnectors();
 +            boolean typeMappingsOpen = false;
 +
 +            List<Class<? extends ClientConnector>> newConnectorTypes = new ArrayList<Class<? extends ClientConnector>>();
 +
 +            for (Class<? extends ClientConnector> class1 : usedClientConnectors) {
 +                if (clientCache.cache(class1)) {
 +                    // client does not know the mapping key for this type, send
 +                    // mapping to client
 +                    newConnectorTypes.add(class1);
 +
 +                    if (!typeMappingsOpen) {
 +                        typeMappingsOpen = true;
 +                        writer.write(", \"typeMappings\" : { ");
 +                    } else {
 +                        writer.write(" , ");
 +                    }
 +                    String canonicalName = class1.getCanonicalName();
 +                    writer.write("\"");
 +                    writer.write(canonicalName);
 +                    writer.write("\" : ");
 +                    writer.write(manager.getTagForType(class1));
 +                }
 +            }
 +            if (typeMappingsOpen) {
 +                writer.write(" }");
 +            }
 +
 +            // TODO PUSH Refactor to TypeInheritanceWriter or something
 +            boolean typeInheritanceMapOpen = false;
 +            if (typeMappingsOpen) {
 +                // send the whole type inheritance map if any new mappings
 +                for (Class<? extends ClientConnector> class1 : usedClientConnectors) {
 +                    if (!ClientConnector.class.isAssignableFrom(class1
 +                            .getSuperclass())) {
 +                        continue;
 +                    }
 +                    if (!typeInheritanceMapOpen) {
 +                        typeInheritanceMapOpen = true;
 +                        writer.write(", \"typeInheritanceMap\" : { ");
 +                    } else {
 +                        writer.write(" , ");
 +                    }
 +                    writer.write("\"");
 +                    writer.write(manager.getTagForType(class1));
 +                    writer.write("\" : ");
 +                    writer.write(manager
 +                            .getTagForType((Class<? extends ClientConnector>) class1
 +                                    .getSuperclass()));
 +                }
 +                if (typeInheritanceMapOpen) {
 +                    writer.write(" }");
 +                }
 +            }
 +
 +            // TODO Refactor to DependencyWriter or something
 +            /*
 +             * Ensure super classes come before sub classes to get script
 +             * dependency order right. Sub class @JavaScript might assume that
 +             * 
 +             * @JavaScript defined by super class is already loaded.
 +             */
 +            Collections.sort(newConnectorTypes, new Comparator<Class<?>>() {
 +                @Override
 +                public int compare(Class<?> o1, Class<?> o2) {
 +                    // TODO optimize using Class.isAssignableFrom?
 +                    return hierarchyDepth(o1) - hierarchyDepth(o2);
 +                }
 +
 +                private int hierarchyDepth(Class<?> type) {
 +                    if (type == Object.class) {
 +                        return 0;
 +                    } else {
 +                        return hierarchyDepth(type.getSuperclass()) + 1;
 +                    }
 +                }
 +            });
 +
 +            List<String> scriptDependencies = new ArrayList<String>();
 +            List<String> styleDependencies = new ArrayList<String>();
 +
 +            for (Class<? extends ClientConnector> class1 : newConnectorTypes) {
 +                JavaScript jsAnnotation = class1
 +                        .getAnnotation(JavaScript.class);
 +                if (jsAnnotation != null) {
 +                    for (String uri : jsAnnotation.value()) {
 +                        scriptDependencies.add(manager.registerDependency(uri,
 +                                class1));
 +                    }
 +                }
 +
 +                StyleSheet styleAnnotation = class1
 +                        .getAnnotation(StyleSheet.class);
 +                if (styleAnnotation != null) {
 +                    for (String uri : styleAnnotation.value()) {
 +                        styleDependencies.add(manager.registerDependency(uri,
 +                                class1));
 +                    }
 +                }
 +            }
 +
 +            // Include script dependencies in output if there are any
 +            if (!scriptDependencies.isEmpty()) {
 +                writer.write(", \"scriptDependencies\": "
 +                        + JsonUtil.stringify(toJsonArray(scriptDependencies)));
 +            }
 +
 +            // Include style dependencies in output if there are any
 +            if (!styleDependencies.isEmpty()) {
 +                writer.write(", \"styleDependencies\": "
 +                        + JsonUtil.stringify(toJsonArray(styleDependencies)));
 +            }
 +
 +            session.getDragAndDropService().printJSONResponse(writer);
 +
 +            for (ClientConnector connector : processedConnectors) {
 +                uiConnectorTracker.markClientSideInitialized(connector);
 +            }
 +
 +            assert (uiConnectorTracker.getDirtyConnectors().isEmpty()) : "Connectors have been marked as dirty during the end of the paint phase. This is most certainly not intended.";
 +
 +            writePerformanceData(ui, writer);
 +        } finally {
 +            uiConnectorTracker.setWritingResponse(false);
 +            uiConnectorTracker.cleanConnectorMap();
 +        }
 +    }
 +
 +    private JsonArray toJsonArray(List<String> list) {
 +        JsonArray result = Json.createArray();
 +        for (int i = 0; i < list.size(); i++) {
 +            result.set(i, list.get(i));
 +        }
 +
 +        return result;
 +    }
 +
 +    /**
 +     * Adds the performance timing data (used by TestBench 3) to the UIDL
 +     * response.
 +     * 
 +     * @throws IOException
 +     */
 +    private void writePerformanceData(UI ui, Writer writer) throws IOException {
++        if (!ui.getSession().getService().getDeploymentConfiguration()
++                .isProductionMode()) {
++            writer.write(String.format(", \"timings\":[%d, %d]", ui
++                    .getSession().getCumulativeRequestDuration(), ui
++                    .getSession().getLastRequestDuration()));
++        }
 +    }
 +
 +    private static final Logger getLogger() {
 +        return Logger.getLogger(UidlWriter.class.getName());
 +    }
 +}
index 063f4f5346ea4afd41397538714ea4eef8f2c3c8,0000000000000000000000000000000000000000..255f34d93653c8110b7685a633829ac8cdab1d10
mode 100644,000000..100644
--- /dev/null
@@@ -1,557 -1,0 +1,581 @@@
-                     return new URL("file://" + directory.getCanonicalPath());
-                 } catch (MalformedURLException e) {
-                     // ignore: continue to the next classpath entry
-                     if (debug) {
-                         e.printStackTrace();
 +/*
 + * Copyright 2000-2014 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.server.widgetsetutils;
 +
 +import java.io.File;
 +import java.io.FileFilter;
 +import java.io.IOException;
 +import java.net.JarURLConnection;
 +import java.net.MalformedURLException;
 +import java.net.URL;
 +import java.net.URLConnection;
 +import java.util.ArrayList;
 +import java.util.HashMap;
 +import java.util.Iterator;
 +import java.util.LinkedHashMap;
 +import java.util.List;
 +import java.util.Map;
 +import java.util.Set;
 +import java.util.jar.Attributes;
 +import java.util.jar.JarFile;
 +import java.util.jar.Manifest;
 +
 +/**
 + * Utility class to collect widgetset related information from classpath.
 + * Utility will seek all directories from classpaths, and jar files having
 + * "Vaadin-Widgetsets" key in their manifest file.
 + * <p>
 + * Used by WidgetMapGenerator and ide tools to implement some monkey coding for
 + * you.
 + * <p>
 + * Developer notice: If you end up reading this comment, I guess you have faced
 + * a sluggish performance of widget compilation or unreliable detection of
 + * components in your classpaths. The thing you might be able to do is to use
 + * annotation processing tool like apt to generate the needed information. Then
 + * either use that information in {@link WidgetMapGenerator} or create the
 + * appropriate monkey code for gwt directly in annotation processor and get rid
 + * of {@link WidgetMapGenerator}. Using annotation processor might be a good
 + * idea when dropping Java 1.5 support (integrated to javac in 6).
 + * 
 + */
 +public class ClassPathExplorer {
 +
 +    private static final String VAADIN_ADDON_VERSION_ATTRIBUTE = "Vaadin-Package-Version";
 +
 +    /**
 +     * File filter that only accepts directories.
 +     */
 +    private final static FileFilter DIRECTORIES_ONLY = new FileFilter() {
 +        @Override
 +        public boolean accept(File f) {
 +            if (f.exists() && f.isDirectory()) {
 +                return true;
 +            } else {
 +                return false;
 +            }
 +        }
 +    };
 +
 +    /**
 +     * Contains information about widgetsets and themes found on the classpath
 +     * 
 +     * @since 7.1
 +     */
 +    public static class LocationInfo {
 +
 +        private final Map<String, URL> widgetsets;
 +
 +        private final Map<String, URL> addonStyles;
 +
 +        public LocationInfo(Map<String, URL> widgetsets, Map<String, URL> themes) {
 +            this.widgetsets = widgetsets;
 +            addonStyles = themes;
 +        }
 +
 +        public Map<String, URL> getWidgetsets() {
 +            return widgetsets;
 +        }
 +
 +        public Map<String, URL> getAddonStyles() {
 +            return addonStyles;
 +        }
 +
 +    }
 +
 +    /**
 +     * Raw class path entries as given in the java class path string. Only
 +     * entries that could include widgets/widgetsets are listed (primarily
 +     * directories, Vaadin JARs and add-on JARs).
 +     */
 +    private static List<String> rawClasspathEntries = getRawClasspathEntries();
 +
 +    /**
 +     * Map from identifiers (either a package name preceded by the path and a
 +     * slash, or a URL for a JAR file) to the corresponding URLs. This is
 +     * constructed from the class path.
 +     */
 +    private static Map<String, URL> classpathLocations = getClasspathLocations(rawClasspathEntries);
 +
 +    private static boolean debug = false;
 +
 +    static {
 +        String debugProperty = System.getProperty("debug");
 +        if (debugProperty != null && !debugProperty.equals("")) {
 +            debug = true;
 +        }
 +    }
 +
 +    /**
 +     * No instantiation from outside, callable methods are static.
 +     */
 +    private ClassPathExplorer() {
 +    }
 +
 +    /**
 +     * Finds the names and locations of widgetsets available on the class path.
 +     * 
 +     * @return map from widgetset classname to widgetset location URL
 +     * @deprecated Use {@link #getAvailableWidgetSetsAndStylesheets()} instead
 +     */
 +    @Deprecated
 +    public static Map<String, URL> getAvailableWidgetSets() {
 +        return getAvailableWidgetSetsAndStylesheets().getWidgetsets();
 +    }
 +
 +    /**
 +     * Finds the names and locations of widgetsets and themes available on the
 +     * class path.
 +     * 
 +     * @return
 +     */
 +    public static LocationInfo getAvailableWidgetSetsAndStylesheets() {
 +        long start = System.currentTimeMillis();
 +        Map<String, URL> widgetsets = new HashMap<String, URL>();
 +        Map<String, URL> themes = new HashMap<String, URL>();
 +        Set<String> keySet = classpathLocations.keySet();
 +        for (String location : keySet) {
 +            searchForWidgetSetsAndAddonStyles(location, widgetsets, themes);
 +        }
 +        long end = System.currentTimeMillis();
 +
 +        StringBuilder sb = new StringBuilder();
 +        sb.append("Widgetsets found from classpath:\n");
 +        for (String ws : widgetsets.keySet()) {
 +            sb.append("\t");
 +            sb.append(ws);
 +            sb.append(" in ");
 +            sb.append(widgetsets.get(ws));
 +            sb.append("\n");
 +        }
 +
 +        sb.append("Addon styles found from classpath:\n");
 +        for (String theme : themes.keySet()) {
 +            sb.append("\t");
 +            sb.append(theme);
 +            sb.append(" in ");
 +            sb.append(themes.get(theme));
 +            sb.append("\n");
 +        }
 +
 +        log(sb.toString());
 +        log("Search took " + (end - start) + "ms");
 +        return new LocationInfo(widgetsets, themes);
 +    }
 +
 +    /**
 +     * Finds all GWT modules / Vaadin widgetsets and Addon styles in a valid
 +     * location.
 +     * 
 +     * If the location is a directory, all GWT modules (files with the
 +     * ".gwt.xml" extension) are added to widgetsets.
 +     * 
 +     * If the location is a JAR file, the comma-separated values of the
 +     * "Vaadin-Widgetsets" attribute in its manifest are added to widgetsets.
 +     * 
 +     * @param locationString
 +     *            an entry in {@link #classpathLocations}
 +     * @param widgetsets
 +     *            a map from widgetset name (including package, with dots as
 +     *            separators) to a URL (see {@link #classpathLocations}) - new
 +     *            entries are added to this map
 +     */
 +    private static void searchForWidgetSetsAndAddonStyles(
 +            String locationString, Map<String, URL> widgetsets,
 +            Map<String, URL> addonStyles) {
 +
 +        URL location = classpathLocations.get(locationString);
 +        File directory = new File(location.getFile());
 +
 +        if (directory.exists() && !directory.isHidden()) {
 +            // Get the list of the files contained in the directory
 +            String[] files = directory.list();
 +            for (int i = 0; i < files.length; i++) {
 +                // we are only interested in .gwt.xml files
 +                if (!files[i].endsWith(".gwt.xml")) {
 +                    continue;
 +                }
 +
 +                // remove the .gwt.xml extension
 +                String classname = files[i].substring(0, files[i].length() - 8);
 +                String packageName = locationString.substring(locationString
 +                        .lastIndexOf("/") + 1);
 +                classname = packageName + "." + classname;
 +
 +                if (!WidgetSetBuilder.isWidgetset(classname)) {
 +                    // Only return widgetsets and not GWT modules to avoid
 +                    // comparing modules and widgetsets
 +                    continue;
 +                }
 +
 +                if (!widgetsets.containsKey(classname)) {
 +                    String packagePath = packageName.replaceAll("\\.", "/");
 +                    String basePath = location.getFile().replaceAll(
 +                            "/" + packagePath + "$", "");
 +                    try {
 +                        URL url = new URL(location.getProtocol(),
 +                                location.getHost(), location.getPort(),
 +                                basePath);
 +                        widgetsets.put(classname, url);
 +                    } catch (MalformedURLException e) {
 +                        // should never happen as based on an existing URL,
 +                        // only changing end of file name/path part
 +                        error("Error locating the widgetset " + classname, e);
 +                    }
 +                }
 +            }
 +        } else {
 +
 +            try {
 +                // check files in jar file, entries will list all directories
 +                // and files in jar
 +
 +                URLConnection openConnection = location.openConnection();
 +                if (openConnection instanceof JarURLConnection) {
 +                    JarURLConnection conn = (JarURLConnection) openConnection;
 +
 +                    JarFile jarFile = conn.getJarFile();
 +
 +                    Manifest manifest = jarFile.getManifest();
 +                    if (manifest == null) {
 +                        // No manifest so this is not a Vaadin Add-on
 +                        return;
 +                    }
 +
 +                    // Check for widgetset attribute
 +                    String value = manifest.getMainAttributes().getValue(
 +                            "Vaadin-Widgetsets");
 +                    if (value != null) {
 +                        String[] widgetsetNames = value.split(",");
 +                        for (int i = 0; i < widgetsetNames.length; i++) {
 +                            String widgetsetname = widgetsetNames[i].trim();
 +                            if (!widgetsetname.equals("")) {
 +                                widgetsets.put(widgetsetname, location);
 +                            }
 +                        }
 +                    }
 +
 +                    // Check for theme attribute
 +                    value = manifest.getMainAttributes().getValue(
 +                            "Vaadin-Stylesheets");
 +                    if (value != null) {
 +                        String[] stylesheets = value.split(",");
 +                        for (int i = 0; i < stylesheets.length; i++) {
 +                            String stylesheet = stylesheets[i].trim();
 +                            if (!stylesheet.equals("")) {
 +                                addonStyles.put(stylesheet, location);
 +                            }
 +                        }
 +                    }
 +                }
 +            } catch (IOException e) {
 +                error("Error parsing jar file", e);
 +            }
 +
 +        }
 +    }
 +
 +    /**
 +     * Splits the current class path into entries, and filters them accepting
 +     * directories, Vaadin add-on JARs with widgetsets and Vaadin JARs.
 +     * 
 +     * Some other non-JAR entries may also be included in the result.
 +     * 
 +     * @return filtered list of class path entries
 +     */
 +    private final static List<String> getRawClasspathEntries() {
 +        // try to keep the order of the classpath
 +        List<String> locations = new ArrayList<String>();
 +
 +        String pathSep = System.getProperty("path.separator");
 +        String classpath = System.getProperty("java.class.path");
 +
 +        if (classpath.startsWith("\"")) {
 +            classpath = classpath.substring(1);
 +        }
 +        if (classpath.endsWith("\"")) {
 +            classpath = classpath.substring(0, classpath.length() - 1);
 +        }
 +
 +        debug("Classpath: " + classpath);
 +
 +        String[] split = classpath.split(pathSep);
 +        for (int i = 0; i < split.length; i++) {
 +            String classpathEntry = split[i];
 +            if (acceptClassPathEntry(classpathEntry)) {
 +                locations.add(classpathEntry);
 +            }
 +        }
 +
 +        return locations;
 +    }
 +
 +    /**
 +     * Determine every URL location defined by the current classpath, and it's
 +     * associated package name.
 +     * 
 +     * See {@link #classpathLocations} for information on output format.
 +     * 
 +     * @param rawClasspathEntries
 +     *            raw class path entries as split from the Java class path
 +     *            string
 +     * @return map of classpath locations, see {@link #classpathLocations}
 +     */
 +    private final static Map<String, URL> getClasspathLocations(
 +            List<String> rawClasspathEntries) {
 +        long start = System.currentTimeMillis();
 +        // try to keep the order of the classpath
 +        Map<String, URL> locations = new LinkedHashMap<String, URL>();
 +        for (String classpathEntry : rawClasspathEntries) {
 +            File file = new File(classpathEntry);
 +            include(null, file, locations);
 +        }
 +        long end = System.currentTimeMillis();
 +        if (debug) {
 +            debug("getClassPathLocations took " + (end - start) + "ms");
 +        }
 +        return locations;
 +    }
 +
 +    /**
 +     * Checks a class path entry to see whether it can contain widgets and
 +     * widgetsets.
 +     * 
 +     * All directories are automatically accepted. JARs are accepted if they
 +     * have the "Vaadin-Widgetsets" attribute in their manifest or the JAR file
 +     * name contains "vaadin-" or ".vaadin.".
 +     * 
 +     * Also other non-JAR entries may be accepted, the caller should be prepared
 +     * to handle them.
 +     * 
 +     * @param classpathEntry
 +     *            class path entry string as given in the Java class path
 +     * @return true if the entry should be considered when looking for widgets
 +     *         or widgetsets
 +     */
 +    private static boolean acceptClassPathEntry(String classpathEntry) {
 +        if (!classpathEntry.endsWith(".jar")) {
 +            // accept all non jars (practically directories)
 +            return true;
 +        } else {
 +            // accepts jars that comply with vaadin-component packaging
 +            // convention (.vaadin. or vaadin- as distribution packages),
 +            if (classpathEntry.contains("vaadin-")
 +                    || classpathEntry.contains(".vaadin.")) {
 +                return true;
 +            } else {
 +                URL url;
 +                try {
 +                    url = new URL("file:"
 +                            + new File(classpathEntry).getCanonicalPath());
 +                    url = new URL("jar:" + url.toExternalForm() + "!/");
 +                    JarURLConnection conn = (JarURLConnection) url
 +                            .openConnection();
 +                    debug(url.toString());
 +
 +                    JarFile jarFile = conn.getJarFile();
 +                    Manifest manifest = jarFile.getManifest();
 +                    if (manifest != null) {
 +                        Attributes mainAttributes = manifest
 +                                .getMainAttributes();
 +                        if (mainAttributes.getValue("Vaadin-Widgetsets") != null) {
 +                            return true;
 +                        }
 +                        if (mainAttributes.getValue("Vaadin-Stylesheets") != null) {
 +                            return true;
 +                        }
 +                    }
 +                } catch (MalformedURLException e) {
 +                    if (debug) {
 +                        error("Failed to inspect JAR file", e);
 +                    }
 +                } catch (IOException e) {
 +                    if (debug) {
 +                        error("Failed to inspect JAR file", e);
 +                    }
 +                }
 +
 +                return false;
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Recursively add subdirectories and jar files to locations - see
 +     * {@link #classpathLocations}.
 +     * 
 +     * @param name
 +     * @param file
 +     * @param locations
 +     */
 +    private final static void include(String name, File file,
 +            Map<String, URL> locations) {
 +        if (!file.exists()) {
 +            return;
 +        }
 +        if (!file.isDirectory()) {
 +            // could be a JAR file
 +            includeJar(file, locations);
 +            return;
 +        }
 +
 +        if (file.isHidden() || file.getPath().contains(File.separator + ".")) {
 +            return;
 +        }
 +
 +        if (name == null) {
 +            name = "";
 +        } else {
 +            name += ".";
 +        }
 +
 +        // add all directories recursively
 +        File[] dirs = file.listFiles(DIRECTORIES_ONLY);
 +        for (int i = 0; i < dirs.length; i++) {
 +            try {
 +                // add the present directory
 +                if (!dirs[i].isHidden()
 +                        && !dirs[i].getPath().contains(File.separator + ".")) {
 +                    String key = dirs[i].getCanonicalPath() + "/" + name
 +                            + dirs[i].getName();
 +                    locations.put(key,
 +                            new URL("file://" + dirs[i].getCanonicalPath()));
 +                }
 +            } catch (Exception ioe) {
 +                return;
 +            }
 +            include(name + dirs[i].getName(), dirs[i], locations);
 +        }
 +    }
 +
 +    /**
 +     * Add a jar file to locations - see {@link #classpathLocations}.
 +     * 
 +     * @param name
 +     * @param locations
 +     */
 +    private static void includeJar(File file, Map<String, URL> locations) {
 +        try {
 +            URL url = new URL("file:" + file.getCanonicalPath());
 +            url = new URL("jar:" + url.toExternalForm() + "!/");
 +            JarURLConnection conn = (JarURLConnection) url.openConnection();
 +            JarFile jarFile = conn.getJarFile();
 +            if (jarFile != null) {
 +                // the key does not matter here as long as it is unique
 +                locations.put(url.toString(), url);
 +            }
 +        } catch (Exception e) {
 +            // e.printStackTrace();
 +            return;
 +        }
 +
 +    }
 +
 +    /**
 +     * Find and return the default source directory where to create new
 +     * widgetsets.
 +     * 
 +     * Return the first directory (not a JAR file etc.) on the classpath by
 +     * default.
 +     * 
 +     * TODO this could be done better...
 +     * 
 +     * @return URL
 +     */
 +    public static URL getDefaultSourceDirectory() {
++        return getWidgetsetSourceDirectory(null);
++    }
 +
++    /**
++     * Find and return the source directory which contains the given widgetset
++     * file.
++     * 
++     * If not applicable or widgetsetFileName is null, return the first
++     * directory (not a JAR file etc.) on the classpath.
++     * 
++     * TODO this could be done better...
++     * 
++     * @param widgetsetFileName
++     *            relative path for the widgetset
++     * 
++     * @return URL
++     */
++    public static URL getWidgetsetSourceDirectory(String widgetsetFileName) {
 +        if (debug) {
 +            debug("classpathLocations values:");
 +            ArrayList<String> locations = new ArrayList<String>(
 +                    classpathLocations.keySet());
 +            for (String location : locations) {
 +                debug(String.valueOf(classpathLocations.get(location)));
 +            }
 +        }
 +
++        URL firstDirectory = null;
 +        Iterator<String> it = rawClasspathEntries.iterator();
 +        while (it.hasNext()) {
 +            String entry = it.next();
 +
 +            File directory = new File(entry);
 +            if (directory.exists() && !directory.isHidden()
 +                    && directory.isDirectory()) {
 +                try {
-                 } catch (IOException e) {
++                    URL directoryUrl = directory.toURI().toURL();
++
++                    // Store the first directory encountered.
++                    if (firstDirectory == null) {
++                        firstDirectory = directoryUrl;
 +                    }
-         return null;
++
++                    if (widgetsetFileName == null
++                            || new File(directory, widgetsetFileName).exists()) {
++                        return directoryUrl;
++                    }
++                } catch (MalformedURLException e) {
 +                    // ignore: continue to the next classpath entry
 +                    if (debug) {
 +                        e.printStackTrace();
 +                    }
 +                }
 +            }
 +        }
++
++        return firstDirectory;
 +    }
 +
 +    /**
 +     * Test method for helper tool
 +     */
 +    public static void main(String[] args) {
 +        log("Searching for available widgetsets and stylesheets...");
 +
 +        ClassPathExplorer.getAvailableWidgetSetsAndStylesheets();
 +    }
 +
 +    private static void log(String message) {
 +        System.out.println(message);
 +    }
 +
 +    private static void error(String message, Exception e) {
 +        System.err.println(message);
 +        e.printStackTrace();
 +    }
 +
 +    private static void debug(String message) {
 +        if (debug) {
 +            System.out.println(message);
 +        }
 +    }
 +
 +}
index f810a63a38d92c82634dfb172fa56b75c59c7a70,0000000000000000000000000000000000000000..b0d8cdd004f6d3c598e5eea6a9a4b085385b0e0a
mode 100644,000000..100644
--- /dev/null
@@@ -1,214 -1,0 +1,215 @@@
-             sourceUrl = ClassPathExplorer.getDefaultSourceDirectory();
 +/*
 + * Copyright 2000-2014 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.server.widgetsetutils;
 +
 +import java.io.BufferedReader;
 +import java.io.BufferedWriter;
 +import java.io.File;
 +import java.io.FileNotFoundException;
 +import java.io.FileOutputStream;
 +import java.io.FileReader;
 +import java.io.IOException;
 +import java.io.OutputStreamWriter;
 +import java.io.PrintStream;
 +import java.io.Reader;
 +import java.net.URL;
 +import java.util.Collection;
 +import java.util.HashSet;
 +import java.util.Iterator;
 +import java.util.Map;
 +import java.util.regex.Matcher;
 +import java.util.regex.Pattern;
 +
 +/**
 + * Helper class to update widgetsets GWT module configuration file. Can be used
 + * command line or via IDE tools.
 + * 
 + * <p>
 + * If module definition file contains text "WS Compiler: manually edited", tool
 + * will skip editing file.
 + * 
 + */
 +public class WidgetSetBuilder {
 +
 +    public static void main(String[] args) throws IOException {
 +        if (args.length == 0) {
 +            printUsage();
 +        } else {
 +            String widgetsetname = args[0];
 +            updateWidgetSet(widgetsetname);
 +
 +        }
 +    }
 +
 +    public static void updateWidgetSet(final String widgetset)
 +            throws IOException, FileNotFoundException {
 +        boolean changed = false;
 +
 +        Map<String, URL> availableWidgetSets = ClassPathExplorer
 +                .getAvailableWidgetSets();
 +
++        String widgetsetFileName = widgetset.replace(".", "/") + ".gwt.xml";
 +        URL sourceUrl = availableWidgetSets.get(widgetset);
 +        if (sourceUrl == null) {
 +            // find first/default source directory
-         String widgetsetfilename = sourceUrl.getFile() + "/"
-                 + widgetset.replace(".", "/") + ".gwt.xml";
++            sourceUrl = ClassPathExplorer
++                    .getWidgetsetSourceDirectory(widgetsetFileName);
 +        }
 +
-         File widgetsetFile = new File(widgetsetfilename);
++        String wsFullPath = sourceUrl.getFile() + "/" + widgetsetFileName;
 +
-                 commitChanges(widgetsetfilename, content);
++        File widgetsetFile = new File(wsFullPath);
 +        if (!widgetsetFile.exists()) {
 +            // create empty gwt module file
 +            File parent = widgetsetFile.getParentFile();
 +            if (parent != null && !parent.exists()) {
 +                if (!parent.mkdirs()) {
 +                    throw new IOException(
 +                            "Could not create directory for the widgetset: "
 +                                    + parent.getPath());
 +                }
 +            }
 +            widgetsetFile.createNewFile();
 +            PrintStream printStream = new PrintStream(new FileOutputStream(
 +                    widgetsetFile));
 +            printStream
 +                    .print("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
 +                            + "<!DOCTYPE module PUBLIC \"-//Google Inc.//DTD Google Web Toolkit 2.5.1//EN\" \"http://google-web-toolkit.googlecode.com/svn/tags/2.5.1/distro-source/core/src/gwt-module.dtd\">\n");
 +            printStream.print("<module>\n");
 +            printStream
 +                    .print("    <!--\n"
 +                            + "     Uncomment the following to compile the widgetset for one browser only.\n\n"
 +                            + "     Multiple browsers can be specified as a comma separated list. The\n"
 +                            + "     supported user agents at the moment of writing were:\n"
 +                            + "     ie8,ie9,gecko1_8,safari,opera\n\n"
 +                            + "     The value gecko1_8 is used for Firefox and safari is used for webkit\n"
 +                            + "     based browsers including Google Chrome.\n"
 +                            + "    -->\n"
 +                            + "    <!-- <set-property name=\"user.agent\" value=\"safari\"/> -->\n\n"
 +                            + "    <!--\n"
 +                            + "    To enable SuperDevMode, uncomment this line.\n\n"
 +                            + "    See https://vaadin.com/wiki/-/wiki/Main/Using%20SuperDevMode for more\n"
 +                            + "    information and instructions.\n"
 +                            + "    -->\n"
 +                            + "    <!-- <set-configuration-property name=\"devModeRedirectEnabled\" value=\"true\" /> -->\n\n");
 +            printStream.print("\n</module>\n");
 +            printStream.close();
 +            changed = true;
 +        }
 +
 +        String content = readFile(widgetsetFile);
 +        if (isEditable(content)) {
 +            String originalContent = content;
 +
 +            Collection<String> oldInheritedWidgetsets = getCurrentInheritedWidgetsets(content);
 +
 +            // add widgetsets that do not exist
 +            Iterator<String> i = availableWidgetSets.keySet().iterator();
 +            while (i.hasNext()) {
 +                String ws = i.next();
 +                if (ws.equals(widgetset)) {
 +                    // do not inherit the module itself
 +                    continue;
 +                }
 +                if (!oldInheritedWidgetsets.contains(ws)) {
 +                    content = addWidgetSet(ws, content);
 +                }
 +            }
 +
 +            for (String ws : oldInheritedWidgetsets) {
 +                if (!availableWidgetSets.containsKey(ws)) {
 +                    // widgetset not available in classpath
 +                    content = removeWidgetSet(ws, content);
 +                }
 +            }
 +
 +            changed = changed || !content.equals(originalContent);
 +            if (changed) {
++                commitChanges(wsFullPath, content);
 +            }
 +        } else {
 +            System.out
 +                    .println("Widgetset is manually edited. Skipping updates.");
 +        }
 +    }
 +
 +    private static boolean isEditable(String content) {
 +        return !content.contains("WS Compiler: manually edited");
 +    }
 +
 +    private static String removeWidgetSet(String ws, String content) {
 +        return content.replaceFirst("<inherits name=\"" + ws + "\"[^/]*/>", "");
 +    }
 +
 +    private static void commitChanges(String widgetsetfilename, String content)
 +            throws IOException {
 +        BufferedWriter bufferedWriter = new BufferedWriter(
 +                new OutputStreamWriter(new FileOutputStream(widgetsetfilename)));
 +        bufferedWriter.write(content);
 +        bufferedWriter.close();
 +    }
 +
 +    private static String addWidgetSet(String ws, String content) {
 +        return content.replace("</module>", "\n    <inherits name=\"" + ws
 +                + "\" />" + "\n</module>");
 +    }
 +
 +    private static Collection<String> getCurrentInheritedWidgetsets(
 +            String content) {
 +        HashSet<String> hashSet = new HashSet<String>();
 +        Pattern inheritsPattern = Pattern.compile(" name=\"([^\"]*)\"");
 +
 +        Matcher matcher = inheritsPattern.matcher(content);
 +
 +        while (matcher.find()) {
 +            String gwtModule = matcher.group(1);
 +            if (isWidgetset(gwtModule)) {
 +                hashSet.add(gwtModule);
 +            }
 +        }
 +        return hashSet;
 +    }
 +
 +    static boolean isWidgetset(String gwtModuleName) {
 +        return gwtModuleName.toLowerCase().contains("widgetset");
 +    }
 +
 +    private static String readFile(File widgetsetFile) throws IOException {
 +        Reader fi = new FileReader(widgetsetFile);
 +        BufferedReader bufferedReader = new BufferedReader(fi);
 +        StringBuilder sb = new StringBuilder();
 +        String line;
 +        while ((line = bufferedReader.readLine()) != null) {
 +            sb.append(line);
 +            sb.append("\n");
 +        }
 +        fi.close();
 +        return sb.toString();
 +    }
 +
 +    private static void printUsage() {
 +        PrintStream o = System.out;
 +        o.println(WidgetSetBuilder.class.getSimpleName() + " usage:");
 +        o.println("    1. Set the same classpath as you will "
 +                + "have for the GWT compiler.");
 +        o.println("    2. Give the widgetsetname (to be created or updated)"
 +                + " as first parameter");
 +        o.println();
 +        o.println("All found vaadin widgetsets will be inherited in given widgetset");
 +
 +    }
 +
 +}
index e0dbcb004b63c43f8aa9503f50f5556c6196727a,0000000000000000000000000000000000000000..f517ab0af5ff149eee9fb275ccb8eeb7ca96bde9
mode 100644,000000..100644
--- /dev/null
@@@ -1,589 -1,0 +1,574 @@@
- import com.vaadin.shared.ui.AlignmentInfo;
 +/*
 + * Copyright 2000-2014 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.util.Collection;
 +import java.util.Iterator;
 +import java.util.LinkedList;
 +import java.util.logging.Logger;
 +
 +import org.jsoup.nodes.Attributes;
 +import org.jsoup.nodes.Element;
 +
 +import com.vaadin.event.LayoutEvents.LayoutClickEvent;
 +import com.vaadin.event.LayoutEvents.LayoutClickListener;
 +import com.vaadin.event.LayoutEvents.LayoutClickNotifier;
 +import com.vaadin.server.Sizeable;
 +import com.vaadin.shared.Connector;
 +import com.vaadin.shared.EventId;
 +import com.vaadin.shared.MouseEventDetails;
-             int bitMask = 0;
-             if (attr.hasKey(":middle")) {
-                 bitMask += AlignmentInfo.Bits.ALIGNMENT_VERTICAL_CENTER;
-             } else if (attr.hasKey(":bottom")) {
-                 bitMask += AlignmentInfo.Bits.ALIGNMENT_BOTTOM;
-             } else {
-                 bitMask += AlignmentInfo.Bits.ALIGNMENT_TOP;
-             }
-             if (attr.hasKey(":center")) {
-                 bitMask += AlignmentInfo.Bits.ALIGNMENT_HORIZONTAL_CENTER;
-             } else if (attr.hasKey(":right")) {
-                 bitMask += AlignmentInfo.Bits.ALIGNMENT_RIGHT;
-             } else {
-                 bitMask += AlignmentInfo.Bits.ALIGNMENT_LEFT;
-             }
-             setComponentAlignment(newChild, new Alignment(bitMask));
 +import com.vaadin.shared.ui.MarginInfo;
 +import com.vaadin.shared.ui.orderedlayout.AbstractOrderedLayoutServerRpc;
 +import com.vaadin.shared.ui.orderedlayout.AbstractOrderedLayoutState;
 +import com.vaadin.shared.ui.orderedlayout.AbstractOrderedLayoutState.ChildComponentData;
 +import com.vaadin.ui.declarative.DesignAttributeHandler;
 +import com.vaadin.ui.declarative.DesignContext;
 +
 +@SuppressWarnings("serial")
 +public abstract class AbstractOrderedLayout extends AbstractLayout implements
 +        Layout.AlignmentHandler, Layout.SpacingHandler, LayoutClickNotifier,
 +        Layout.MarginHandler {
 +
 +    private AbstractOrderedLayoutServerRpc rpc = new AbstractOrderedLayoutServerRpc() {
 +
 +        @Override
 +        public void layoutClick(MouseEventDetails mouseDetails,
 +                Connector clickedConnector) {
 +            fireEvent(LayoutClickEvent.createEvent(AbstractOrderedLayout.this,
 +                    mouseDetails, clickedConnector));
 +        }
 +    };
 +
 +    public static final Alignment ALIGNMENT_DEFAULT = Alignment.TOP_LEFT;
 +
 +    /**
 +     * Custom layout slots containing the components.
 +     */
 +    protected LinkedList<Component> components = new LinkedList<Component>();
 +
 +    private Alignment defaultComponentAlignment = Alignment.TOP_LEFT;
 +
 +    /* Child component alignments */
 +
 +    /**
 +     * Constructs an empty AbstractOrderedLayout.
 +     */
 +    public AbstractOrderedLayout() {
 +        registerRpc(rpc);
 +    }
 +
 +    @Override
 +    protected AbstractOrderedLayoutState getState() {
 +        return (AbstractOrderedLayoutState) super.getState();
 +    }
 +
 +    @Override
 +    protected AbstractOrderedLayoutState getState(boolean markAsDirty) {
 +        return (AbstractOrderedLayoutState) super.getState(markAsDirty);
 +    }
 +
 +    /**
 +     * Add a component into this container. The component is added to the right
 +     * or under the previous component.
 +     * 
 +     * @param c
 +     *            the component to be added.
 +     */
 +    @Override
 +    public void addComponent(Component c) {
 +        // Add to components before calling super.addComponent
 +        // so that it is available to AttachListeners
 +        components.add(c);
 +        try {
 +            super.addComponent(c);
 +        } catch (IllegalArgumentException e) {
 +            components.remove(c);
 +            throw e;
 +        }
 +        componentAdded(c);
 +    }
 +
 +    /**
 +     * Adds a component into this container. The component is added to the left
 +     * or on top of the other components.
 +     * 
 +     * @param c
 +     *            the component to be added.
 +     */
 +    public void addComponentAsFirst(Component c) {
 +        // If c is already in this, we must remove it before proceeding
 +        // see ticket #7668
 +        if (equals(c.getParent())) {
 +            removeComponent(c);
 +        }
 +        components.addFirst(c);
 +        try {
 +            super.addComponent(c);
 +        } catch (IllegalArgumentException e) {
 +            components.remove(c);
 +            throw e;
 +        }
 +        componentAdded(c);
 +
 +    }
 +
 +    /**
 +     * Adds a component into indexed position in this container.
 +     * 
 +     * @param c
 +     *            the component to be added.
 +     * @param index
 +     *            the index of the component position. The components currently
 +     *            in and after the position are shifted forwards.
 +     */
 +    public void addComponent(Component c, int index) {
 +        // If c is already in this, we must remove it before proceeding
 +        // see ticket #7668
 +        if (equals(c.getParent())) {
 +            // When c is removed, all components after it are shifted down
 +            if (index > getComponentIndex(c)) {
 +                index--;
 +            }
 +            removeComponent(c);
 +        }
 +        components.add(index, c);
 +        try {
 +            super.addComponent(c);
 +        } catch (IllegalArgumentException e) {
 +            components.remove(c);
 +            throw e;
 +        }
 +
 +        componentAdded(c);
 +    }
 +
 +    private void componentRemoved(Component c) {
 +        getState().childData.remove(c);
 +    }
 +
 +    private void componentAdded(Component c) {
 +        ChildComponentData ccd = new ChildComponentData();
 +        ccd.alignmentBitmask = getDefaultComponentAlignment().getBitMask();
 +        getState().childData.put(c, ccd);
 +    }
 +
 +    /**
 +     * Removes the component from this container.
 +     * 
 +     * @param c
 +     *            the component to be removed.
 +     */
 +    @Override
 +    public void removeComponent(Component c) {
 +        components.remove(c);
 +        super.removeComponent(c);
 +        componentRemoved(c);
 +    }
 +
 +    /**
 +     * Gets the component container iterator for going trough all the components
 +     * in the container.
 +     * 
 +     * @return the Iterator of the components inside the container.
 +     */
 +    @Override
 +    public Iterator<Component> iterator() {
 +        return components.iterator();
 +    }
 +
 +    /**
 +     * Gets the number of contained components. Consistent with the iterator
 +     * returned by {@link #getComponentIterator()}.
 +     * 
 +     * @return the number of contained components
 +     */
 +    @Override
 +    public int getComponentCount() {
 +        return components.size();
 +    }
 +
 +    /* Documented in superclass */
 +    @Override
 +    public void replaceComponent(Component oldComponent, Component newComponent) {
 +
 +        // Gets the locations
 +        int oldLocation = -1;
 +        int newLocation = -1;
 +        int location = 0;
 +        for (final Iterator<Component> i = components.iterator(); i.hasNext();) {
 +            final Component component = i.next();
 +
 +            if (component == oldComponent) {
 +                oldLocation = location;
 +            }
 +            if (component == newComponent) {
 +                newLocation = location;
 +            }
 +
 +            location++;
 +        }
 +
 +        if (oldLocation == -1) {
 +            addComponent(newComponent);
 +        } else if (newLocation == -1) {
 +            Alignment alignment = getComponentAlignment(oldComponent);
 +            float expandRatio = getExpandRatio(oldComponent);
 +
 +            removeComponent(oldComponent);
 +            addComponent(newComponent, oldLocation);
 +            applyLayoutSettings(newComponent, alignment, expandRatio);
 +        } else {
 +            // Both old and new are in the layout
 +            if (oldLocation > newLocation) {
 +                components.remove(oldComponent);
 +                components.add(newLocation, oldComponent);
 +                components.remove(newComponent);
 +                components.add(oldLocation, newComponent);
 +            } else {
 +                components.remove(newComponent);
 +                components.add(oldLocation, newComponent);
 +                components.remove(oldComponent);
 +                components.add(newLocation, oldComponent);
 +            }
 +
 +            markAsDirty();
 +        }
 +    }
 +
 +    @Override
 +    public void setComponentAlignment(Component childComponent,
 +            Alignment alignment) {
 +        ChildComponentData childData = getState().childData.get(childComponent);
 +        if (childData != null) {
 +            // Alignments are bit masks
 +            childData.alignmentBitmask = alignment.getBitMask();
 +        } else {
 +            throw new IllegalArgumentException(
 +                    "Component must be added to layout before using setComponentAlignment()");
 +        }
 +
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see com.vaadin.ui.Layout.AlignmentHandler#getComponentAlignment(com
 +     * .vaadin.ui.Component)
 +     */
 +    @Override
 +    public Alignment getComponentAlignment(Component childComponent) {
 +        ChildComponentData childData = getState().childData.get(childComponent);
 +        if (childData == null) {
 +            throw new IllegalArgumentException(
 +                    "The given component is not a child of this layout");
 +        }
 +
 +        return new Alignment(childData.alignmentBitmask);
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see com.vaadin.ui.Layout.SpacingHandler#setSpacing(boolean)
 +     */
 +    @Override
 +    public void setSpacing(boolean spacing) {
 +        getState().spacing = spacing;
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see com.vaadin.ui.Layout.SpacingHandler#isSpacing()
 +     */
 +    @Override
 +    public boolean isSpacing() {
 +        return getState(false).spacing;
 +    }
 +
 +    /**
 +     * <p>
 +     * This method is used to control how excess space in layout is distributed
 +     * among components. Excess space may exist if layout is sized and contained
 +     * non relatively sized components don't consume all available space.
 +     * 
 +     * <p>
 +     * Example how to distribute 1:3 (33%) for component1 and 2:3 (67%) for
 +     * component2 :
 +     * 
 +     * <code>
 +     * layout.setExpandRatio(component1, 1);<br>
 +     * layout.setExpandRatio(component2, 2);
 +     * </code>
 +     * 
 +     * <p>
 +     * If no ratios have been set, the excess space is distributed evenly among
 +     * all components.
 +     * 
 +     * <p>
 +     * Note, that width or height (depending on orientation) needs to be defined
 +     * for this method to have any effect.
 +     * 
 +     * @see Sizeable
 +     * 
 +     * @param component
 +     *            the component in this layout which expand ratio is to be set
 +     * @param ratio
 +     */
 +    public void setExpandRatio(Component component, float ratio) {
 +        ChildComponentData childData = getState().childData.get(component);
 +        if (childData == null) {
 +            throw new IllegalArgumentException(
 +                    "The given component is not a child of this layout");
 +        }
 +
 +        childData.expandRatio = ratio;
 +    }
 +
 +    /**
 +     * Returns the expand ratio of given component.
 +     * 
 +     * @param component
 +     *            which expand ratios is requested
 +     * @return expand ratio of given component, 0.0f by default.
 +     */
 +    public float getExpandRatio(Component component) {
 +        ChildComponentData childData = getState(false).childData.get(component);
 +        if (childData == null) {
 +            throw new IllegalArgumentException(
 +                    "The given component is not a child of this layout");
 +        }
 +
 +        return childData.expandRatio;
 +    }
 +
 +    @Override
 +    public void addLayoutClickListener(LayoutClickListener listener) {
 +        addListener(EventId.LAYOUT_CLICK_EVENT_IDENTIFIER,
 +                LayoutClickEvent.class, listener,
 +                LayoutClickListener.clickMethod);
 +    }
 +
 +    /**
 +     * @deprecated As of 7.0, replaced by
 +     *             {@link #addLayoutClickListener(LayoutClickListener)}
 +     **/
 +    @Override
 +    @Deprecated
 +    public void addListener(LayoutClickListener listener) {
 +        addLayoutClickListener(listener);
 +    }
 +
 +    @Override
 +    public void removeLayoutClickListener(LayoutClickListener listener) {
 +        removeListener(EventId.LAYOUT_CLICK_EVENT_IDENTIFIER,
 +                LayoutClickEvent.class, listener);
 +    }
 +
 +    /**
 +     * @deprecated As of 7.0, replaced by
 +     *             {@link #removeLayoutClickListener(LayoutClickListener)}
 +     **/
 +    @Override
 +    @Deprecated
 +    public void removeListener(LayoutClickListener listener) {
 +        removeLayoutClickListener(listener);
 +    }
 +
 +    /**
 +     * Returns the index of the given component.
 +     * 
 +     * @param component
 +     *            The component to look up.
 +     * @return The index of the component or -1 if the component is not a child.
 +     */
 +    public int getComponentIndex(Component component) {
 +        return components.indexOf(component);
 +    }
 +
 +    /**
 +     * Returns the component at the given position.
 +     * 
 +     * @param index
 +     *            The position of the component.
 +     * @return The component at the given index.
 +     * @throws IndexOutOfBoundsException
 +     *             If the index is out of range.
 +     */
 +    public Component getComponent(int index) throws IndexOutOfBoundsException {
 +        return components.get(index);
 +    }
 +
 +    @Override
 +    public void setMargin(boolean enabled) {
 +        setMargin(new MarginInfo(enabled));
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see com.vaadin.ui.Layout.MarginHandler#getMargin()
 +     */
 +    @Override
 +    public MarginInfo getMargin() {
 +        return new MarginInfo(getState(false).marginsBitmask);
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see com.vaadin.ui.Layout.MarginHandler#setMargin(MarginInfo)
 +     */
 +    @Override
 +    public void setMargin(MarginInfo marginInfo) {
 +        getState().marginsBitmask = marginInfo.getBitMask();
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see com.vaadin.ui.Layout.AlignmentHandler#getDefaultComponentAlignment()
 +     */
 +    @Override
 +    public Alignment getDefaultComponentAlignment() {
 +        return defaultComponentAlignment;
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see
 +     * com.vaadin.ui.Layout.AlignmentHandler#setDefaultComponentAlignment(com
 +     * .vaadin.ui.Alignment)
 +     */
 +    @Override
 +    public void setDefaultComponentAlignment(Alignment defaultAlignment) {
 +        defaultComponentAlignment = defaultAlignment;
 +    }
 +
 +    private void applyLayoutSettings(Component target, Alignment alignment,
 +            float expandRatio) {
 +        setComponentAlignment(target, alignment);
 +        setExpandRatio(target, expandRatio);
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see com.vaadin.ui.AbstractComponent#readDesign(org.jsoup.nodes .Element,
 +     * com.vaadin.ui.declarative.DesignContext)
 +     */
 +    @Override
 +    public void readDesign(Element design, DesignContext designContext) {
 +        // process default attributes
 +        super.readDesign(design, designContext);
 +
 +        setMargin(readMargin(design, getMargin(), designContext));
 +
 +        // handle children
 +        for (Element childComponent : design.children()) {
 +            Attributes attr = childComponent.attributes();
 +            Component newChild = designContext.readDesign(childComponent);
 +            addComponent(newChild);
 +            // handle alignment
++            setComponentAlignment(newChild,
++                    DesignAttributeHandler.readAlignment(attr));
 +            // handle expand ratio
 +            if (attr.hasKey(":expand")) {
 +                String value = attr.get(":expand");
 +                if (value.length() > 0) {
 +                    try {
 +                        float ratio = Float.valueOf(value);
 +                        setExpandRatio(newChild, ratio);
 +                    } catch (NumberFormatException nfe) {
 +                        getLogger().info(
 +                                "Failed to parse expand ratio " + value);
 +                    }
 +                } else {
 +                    setExpandRatio(newChild, 1.0f);
 +                }
 +            }
 +        }
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see com.vaadin.ui.AbstractComponent#writeDesign(org.jsoup.nodes.Element
 +     * , com.vaadin.ui.declarative.DesignContext)
 +     */
 +    @Override
 +    public void writeDesign(Element design, DesignContext designContext) {
 +        // write default attributes
 +        super.writeDesign(design, designContext);
 +
 +        AbstractOrderedLayout def = (AbstractOrderedLayout) designContext
 +                .getDefaultInstance(this);
 +
 +        writeMargin(design, getMargin(), def.getMargin(), designContext);
 +
 +        // handle children
 +        if (!designContext.shouldWriteChildren(this, def)) {
 +            return;
 +        }
 +
 +        for (Component child : this) {
 +            Element childElement = designContext.createElement(child);
 +            design.appendChild(childElement);
 +            // handle alignment
 +            Alignment alignment = getComponentAlignment(child);
 +            if (alignment.isMiddle()) {
 +                childElement.attr(":middle", true);
 +            } else if (alignment.isBottom()) {
 +                childElement.attr(":bottom", true);
 +            }
 +            if (alignment.isCenter()) {
 +                childElement.attr(":center", true);
 +            } else if (alignment.isRight()) {
 +                childElement.attr(":right", true);
 +            }
 +            // handle expand ratio
 +            float expandRatio = getExpandRatio(child);
 +            if (expandRatio == 1.0f) {
 +                childElement.attr(":expand", true);
 +            } else if (expandRatio > 0) {
 +                childElement.attr(":expand", DesignAttributeHandler
 +                        .getFormatter().format(expandRatio));
 +            }
 +        }
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see com.vaadin.ui.AbstractComponent#getCustomAttributes()
 +     */
 +    @Override
 +    protected Collection<String> getCustomAttributes() {
 +        Collection<String> customAttributes = super.getCustomAttributes();
 +        customAttributes.add("margin");
 +        customAttributes.add("margin-left");
 +        customAttributes.add("margin-right");
 +        customAttributes.add("margin-top");
 +        customAttributes.add("margin-bottom");
 +        return customAttributes;
 +    }
 +
 +    private static Logger getLogger() {
 +        return Logger.getLogger(AbstractOrderedLayout.class.getName());
 +    }
 +}
index 9babf7e876c5f258ac637c3fd12a3ba24b5ba3d7,0000000000000000000000000000000000000000..2714e0cbf59843990b410355a4ba9fec05057a2a
mode 100644,000000..100644
--- /dev/null
@@@ -1,2338 -1,0 +1,2341 @@@
-                     if (!isNullSelectionAllowed() && id == null) {
-                         markAsDirty();
-                     } else if (id != null
-                             && id.equals(getNullSelectionItemId())) {
-                         setValue(null, true);
 +/*
 + * Copyright 2000-2014 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.util.ArrayList;
 +import java.util.Collection;
 +import java.util.Collections;
 +import java.util.EventObject;
 +import java.util.HashMap;
 +import java.util.HashSet;
 +import java.util.Iterator;
 +import java.util.LinkedHashSet;
 +import java.util.LinkedList;
 +import java.util.List;
 +import java.util.Map;
 +import java.util.Set;
 +
 +import org.jsoup.nodes.Element;
 +
 +import com.vaadin.data.Container;
 +import com.vaadin.data.Item;
 +import com.vaadin.data.Property;
 +import com.vaadin.data.Validator.InvalidValueException;
 +import com.vaadin.data.util.IndexedContainer;
 +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.DataBoundTransferable;
 +import com.vaadin.event.Transferable;
 +import com.vaadin.event.dd.DragAndDropEvent;
 +import com.vaadin.event.dd.DropTarget;
 +import com.vaadin.event.dd.TargetDetailsImpl;
 +import com.vaadin.event.dd.acceptcriteria.ClientSideCriterion;
 +import com.vaadin.event.dd.acceptcriteria.ContainsDataFlavor;
 +import com.vaadin.event.dd.acceptcriteria.TargetDetailIs;
 +import com.vaadin.server.KeyMapper;
 +import com.vaadin.server.PaintException;
 +import com.vaadin.server.PaintTarget;
 +import com.vaadin.server.Resource;
 +import com.vaadin.server.VaadinSession;
 +import com.vaadin.shared.ui.combobox.FilteringMode;
 +import com.vaadin.shared.ui.dd.VerticalDropLocation;
 +import com.vaadin.shared.ui.select.AbstractSelectState;
 +import com.vaadin.ui.declarative.DesignAttributeHandler;
 +import com.vaadin.ui.declarative.DesignContext;
 +import com.vaadin.ui.declarative.DesignException;
 +import com.vaadin.ui.declarative.DesignFormatter;
 +
 +/**
 + * <p>
 + * A class representing a selection of items the user has selected in a UI. The
 + * set of choices is presented as a set of {@link com.vaadin.data.Item}s in a
 + * {@link com.vaadin.data.Container}.
 + * </p>
 + * 
 + * <p>
 + * A <code>Select</code> component may be in single- or multiselect mode.
 + * Multiselect mode means that more than one item can be selected
 + * simultaneously.
 + * </p>
 + * 
 + * @author Vaadin Ltd.
 + * @since 5.0
 + */
 +@SuppressWarnings("serial")
 +// TODO currently cannot specify type more precisely in case of multi-select
 +public abstract class AbstractSelect extends AbstractField<Object> implements
 +        Container, Container.Viewer, Container.PropertySetChangeListener,
 +        Container.PropertySetChangeNotifier, Container.ItemSetChangeNotifier,
 +        Container.ItemSetChangeListener, LegacyComponent {
 +
 +    public enum ItemCaptionMode {
 +        /**
 +         * Item caption mode: Item's ID converted to a String using
 +         * {@link VaadinSession#getConverterFactory()} is used as caption.
 +         */
 +        ID,
 +        /**
 +         * Item caption mode: Item's ID's <code>String</code> representation is
 +         * used as caption.
 +         * 
 +         * @since 7.5.6
 +         */
 +        ID_TOSTRING,
 +        /**
 +         * Item caption mode: Item's <code>String</code> representation is used
 +         * as caption.
 +         */
 +        ITEM,
 +        /**
 +         * Item caption mode: Index of the item is used as caption. The index
 +         * mode can only be used with the containers implementing the
 +         * {@link com.vaadin.data.Container.Indexed} interface.
 +         */
 +        INDEX,
 +        /**
 +         * Item caption mode: If an Item has a caption it's used, if not, Item's
 +         * ID converted to a String using
 +         * {@link VaadinSession#getConverterFactory()} is used as caption.
 +         * <b>This is the default</b>.
 +         */
 +        EXPLICIT_DEFAULTS_ID,
 +        /**
 +         * Item caption mode: Captions must be explicitly specified.
 +         */
 +        EXPLICIT,
 +        /**
 +         * Item caption mode: Only icons are shown, captions are hidden.
 +         */
 +        ICON_ONLY,
 +        /**
 +         * Item caption mode: Item captions are read from property specified
 +         * with <code>setItemCaptionPropertyId</code>.
 +         */
 +        PROPERTY;
 +    }
 +
 +    /**
 +     * @deprecated As of 7.0, use {@link ItemCaptionMode#ID} instead
 +     */
 +    @Deprecated
 +    public static final ItemCaptionMode ITEM_CAPTION_MODE_ID = ItemCaptionMode.ID;
 +
 +    /**
 +     * @deprecated As of 7.0, use {@link ItemCaptionMode#ITEM} instead
 +     */
 +    @Deprecated
 +    public static final ItemCaptionMode ITEM_CAPTION_MODE_ITEM = ItemCaptionMode.ITEM;
 +
 +    /**
 +     * @deprecated As of 7.0, use {@link ItemCaptionMode#INDEX} instead
 +     */
 +    @Deprecated
 +    public static final ItemCaptionMode ITEM_CAPTION_MODE_INDEX = ItemCaptionMode.INDEX;
 +
 +    /**
 +     * @deprecated As of 7.0, use {@link ItemCaptionMode#EXPLICIT_DEFAULTS_ID}
 +     *             instead
 +     */
 +    @Deprecated
 +    public static final ItemCaptionMode ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID = ItemCaptionMode.EXPLICIT_DEFAULTS_ID;
 +
 +    /**
 +     * @deprecated As of 7.0, use {@link ItemCaptionMode#EXPLICIT} instead
 +     */
 +    @Deprecated
 +    public static final ItemCaptionMode ITEM_CAPTION_MODE_EXPLICIT = ItemCaptionMode.EXPLICIT;
 +
 +    /**
 +     * @deprecated As of 7.0, use {@link ItemCaptionMode#ICON_ONLY} instead
 +     */
 +    @Deprecated
 +    public static final ItemCaptionMode ITEM_CAPTION_MODE_ICON_ONLY = ItemCaptionMode.ICON_ONLY;
 +
 +    /**
 +     * @deprecated As of 7.0, use {@link ItemCaptionMode#PROPERTY} instead
 +     */
 +    @Deprecated
 +    public static final ItemCaptionMode ITEM_CAPTION_MODE_PROPERTY = ItemCaptionMode.PROPERTY;
 +
 +    /**
 +     * Interface for option filtering, used to filter options based on user
 +     * entered value. The value is matched to the item caption.
 +     * <code>FilteringMode.OFF</code> (0) turns the filtering off.
 +     * <code>FilteringMode.STARTSWITH</code> (1) matches from the start of the
 +     * caption. <code>FilteringMode.CONTAINS</code> (1) matches anywhere in the
 +     * caption.
 +     */
 +    public interface Filtering extends Serializable {
 +
 +        /**
 +         * @deprecated As of 7.0, use {@link FilteringMode#OFF} instead
 +         */
 +        @Deprecated
 +        public static final FilteringMode FILTERINGMODE_OFF = FilteringMode.OFF;
 +        /**
 +         * @deprecated As of 7.0, use {@link FilteringMode#STARTSWITH} instead
 +         */
 +        @Deprecated
 +        public static final FilteringMode FILTERINGMODE_STARTSWITH = FilteringMode.STARTSWITH;
 +        /**
 +         * @deprecated As of 7.0, use {@link FilteringMode#CONTAINS} instead
 +         */
 +        @Deprecated
 +        public static final FilteringMode FILTERINGMODE_CONTAINS = FilteringMode.CONTAINS;
 +
 +        /**
 +         * Sets the option filtering mode.
 +         * 
 +         * @param filteringMode
 +         *            the filtering mode to use
 +         */
 +        public void setFilteringMode(FilteringMode filteringMode);
 +
 +        /**
 +         * Gets the current filtering mode.
 +         * 
 +         * @return the filtering mode in use
 +         */
 +        public FilteringMode getFilteringMode();
 +
 +    }
 +
 +    /**
 +     * Is the select in multiselect mode?
 +     */
 +    private boolean multiSelect = false;
 +
 +    /**
 +     * Select options.
 +     */
 +    protected Container items;
 +
 +    /**
 +     * Is the user allowed to add new options?
 +     */
 +    private boolean allowNewOptions;
 +
 +    /**
 +     * Keymapper used to map key values.
 +     */
 +    protected KeyMapper<Object> itemIdMapper = new KeyMapper<Object>();
 +
 +    /**
 +     * Item icons.
 +     */
 +    private final HashMap<Object, Resource> itemIcons = new HashMap<Object, Resource>();
 +
 +    /**
 +     * Item captions.
 +     */
 +    private final HashMap<Object, String> itemCaptions = new HashMap<Object, String>();
 +
 +    /**
 +     * Item caption mode.
 +     */
 +    private ItemCaptionMode itemCaptionMode = ItemCaptionMode.EXPLICIT_DEFAULTS_ID;
 +
 +    /**
 +     * Item caption source property id.
 +     */
 +    private Object itemCaptionPropertyId = null;
 +
 +    /**
 +     * Item icon source property id.
 +     */
 +    private Object itemIconPropertyId = null;
 +
 +    /**
 +     * List of property set change event listeners.
 +     */
 +    private Set<Container.PropertySetChangeListener> propertySetEventListeners = null;
 +
 +    /**
 +     * List of item set change event listeners.
 +     */
 +    private Set<Container.ItemSetChangeListener> itemSetEventListeners = null;
 +
 +    /**
 +     * Item id that represents null selection of this select.
 +     * 
 +     * <p>
 +     * Data interface does not support nulls as item ids. Selecting the item
 +     * identified by this id is the same as selecting no items at all. This
 +     * setting only affects the single select mode.
 +     * </p>
 +     */
 +    private Object nullSelectionItemId = null;
 +
 +    // Null (empty) selection is enabled by default
 +    private boolean nullSelectionAllowed = true;
 +    private NewItemHandler newItemHandler;
 +
 +    // Caption (Item / Property) change listeners
 +    CaptionChangeListener captionChangeListener;
 +
 +    /* Constructors */
 +
 +    /**
 +     * Creates an empty Select. The caption is not used.
 +     */
 +    public AbstractSelect() {
 +        setContainerDataSource(new IndexedContainer());
 +    }
 +
 +    /**
 +     * Creates an empty Select with caption.
 +     */
 +    public AbstractSelect(String caption) {
 +        setContainerDataSource(new IndexedContainer());
 +        setCaption(caption);
 +    }
 +
 +    /**
 +     * Creates a new select that is connected to a data-source.
 +     * 
 +     * @param caption
 +     *            the Caption of the component.
 +     * @param dataSource
 +     *            the Container datasource to be selected from by this select.
 +     */
 +    public AbstractSelect(String caption, Container dataSource) {
 +        setCaption(caption);
 +        setContainerDataSource(dataSource);
 +    }
 +
 +    /**
 +     * Creates a new select that is filled from a collection of option values.
 +     * 
 +     * @param caption
 +     *            the Caption of this field.
 +     * @param options
 +     *            the Collection containing the options.
 +     */
 +    public AbstractSelect(String caption, Collection<?> options) {
 +
 +        // Creates the options container and add given options to it
 +        final Container c = new IndexedContainer();
 +        if (options != null) {
 +            for (final Iterator<?> i = options.iterator(); i.hasNext();) {
 +                c.addItem(i.next());
 +            }
 +        }
 +
 +        setCaption(caption);
 +        setContainerDataSource(c);
 +    }
 +
 +    /* Component methods */
 +
 +    /**
 +     * Paints the content of this component.
 +     * 
 +     * @param target
 +     *            the Paint Event.
 +     * @throws PaintException
 +     *             if the paint operation failed.
 +     */
 +    @Override
 +    public void paintContent(PaintTarget target) throws PaintException {
 +
 +        // Paints select attributes
 +        if (isMultiSelect()) {
 +            target.addAttribute("selectmode", "multi");
 +        }
 +        if (isNewItemsAllowed()) {
 +            target.addAttribute("allownewitem", true);
 +        }
 +        if (isNullSelectionAllowed()) {
 +            target.addAttribute("nullselect", true);
 +            if (getNullSelectionItemId() != null) {
 +                target.addAttribute("nullselectitem", true);
 +            }
 +        }
 +
 +        // Constructs selected keys array
 +        String[] selectedKeys;
 +        if (isMultiSelect()) {
 +            selectedKeys = new String[((Set<?>) getValue()).size()];
 +        } else {
 +            selectedKeys = new String[(getValue() == null
 +                    && getNullSelectionItemId() == null ? 0 : 1)];
 +        }
 +
 +        // ==
 +        // first remove all previous item/property listeners
 +        getCaptionChangeListener().clear();
 +        // Paints the options and create array of selected id keys
 +
 +        target.startTag("options");
 +        int keyIndex = 0;
 +        // Support for external null selection item id
 +        final Collection<?> ids = getItemIds();
 +        if (isNullSelectionAllowed() && getNullSelectionItemId() != null
 +                && !ids.contains(getNullSelectionItemId())) {
 +            final Object id = getNullSelectionItemId();
 +            // Paints option
 +            target.startTag("so");
 +            paintItem(target, id);
 +            if (isSelected(id)) {
 +                selectedKeys[keyIndex++] = itemIdMapper.key(id);
 +            }
 +            target.endTag("so");
 +        }
 +
 +        final Iterator<?> i = getItemIds().iterator();
 +        // Paints the available selection options from data source
 +        while (i.hasNext()) {
 +            // Gets the option attribute values
 +            final Object id = i.next();
 +            if (!isNullSelectionAllowed() && id != null
 +                    && id.equals(getNullSelectionItemId())) {
 +                // Remove item if it's the null selection item but null
 +                // selection is not allowed
 +                continue;
 +            }
 +            final String key = itemIdMapper.key(id);
 +            // add listener for each item, to cause repaint if an item changes
 +            getCaptionChangeListener().addNotifierForItem(id);
 +            target.startTag("so");
 +            paintItem(target, id);
 +            if (isSelected(id) && keyIndex < selectedKeys.length) {
 +                selectedKeys[keyIndex++] = key;
 +            }
 +            target.endTag("so");
 +        }
 +        target.endTag("options");
 +        // ==
 +
 +        // Paint variables
 +        target.addVariable(this, "selected", selectedKeys);
 +        if (isNewItemsAllowed()) {
 +            target.addVariable(this, "newitem", "");
 +        }
 +
 +    }
 +
 +    protected void paintItem(PaintTarget target, Object itemId)
 +            throws PaintException {
 +        final String key = itemIdMapper.key(itemId);
 +        final String caption = getItemCaption(itemId);
 +        final Resource icon = getItemIcon(itemId);
 +        if (icon != null) {
 +            target.addAttribute("icon", icon);
 +        }
 +        target.addAttribute("caption", caption);
 +        if (itemId != null && itemId.equals(getNullSelectionItemId())) {
 +            target.addAttribute("nullselection", true);
 +        }
 +        target.addAttribute("key", key);
 +        if (isSelected(itemId)) {
 +            target.addAttribute("selected", true);
 +        }
 +    }
 +
 +    /**
 +     * Invoked when the value of a variable has changed.
 +     * 
 +     * @see com.vaadin.ui.AbstractComponent#changeVariables(java.lang.Object,
 +     *      java.util.Map)
 +     */
 +    @Override
 +    public void changeVariables(Object source, Map<String, Object> variables) {
 +
 +        // New option entered (and it is allowed)
 +        if (isNewItemsAllowed()) {
 +            final String newitem = (String) variables.get("newitem");
 +            if (newitem != null && newitem.length() > 0) {
 +                getNewItemHandler().addNewItem(newitem);
 +            }
 +        }
 +
 +        // Selection change
 +        if (variables.containsKey("selected")) {
 +            final String[] clientSideSelectedKeys = (String[]) variables
 +                    .get("selected");
 +
 +            // Multiselect mode
 +            if (isMultiSelect()) {
 +
 +                // TODO Optimize by adding repaintNotNeeded when applicable
 +
 +                // Converts the key-array to id-set
 +                final LinkedList<Object> acceptedSelections = new LinkedList<Object>();
 +                for (int i = 0; i < clientSideSelectedKeys.length; i++) {
 +                    final Object id = itemIdMapper
 +                            .get(clientSideSelectedKeys[i]);
 +                    if (!isNullSelectionAllowed()
 +                            && (id == null || id == getNullSelectionItemId())) {
 +                        // skip empty selection if nullselection is not allowed
 +                        markAsDirty();
 +                    } else if (id != null && containsId(id)) {
 +                        acceptedSelections.add(id);
 +                    }
 +                }
 +
 +                if (!isNullSelectionAllowed() && acceptedSelections.size() < 1) {
 +                    // empty selection not allowed, keep old value
 +                    markAsDirty();
 +                    return;
 +                }
 +
 +                // Limits the deselection to the set of visible items
 +                // (non-visible items can not be deselected)
 +                Collection<?> visibleNotSelected = getVisibleItemIds();
 +                if (visibleNotSelected != null) {
 +                    visibleNotSelected = new HashSet<Object>(visibleNotSelected);
 +                    // Don't remove those that will be added to preserve order
 +                    visibleNotSelected.removeAll(acceptedSelections);
 +
 +                    @SuppressWarnings("unchecked")
 +                    Set<Object> newsel = (Set<Object>) getValue();
 +                    if (newsel == null) {
 +                        newsel = new LinkedHashSet<Object>();
 +                    } else {
 +                        newsel = new LinkedHashSet<Object>(newsel);
 +                    }
 +                    newsel.removeAll(visibleNotSelected);
 +                    newsel.addAll(acceptedSelections);
 +                    setValue(newsel, true);
 +                }
 +            } else {
 +                // Single select mode
 +                if (!isNullSelectionAllowed()
 +                        && (clientSideSelectedKeys.length == 0
 +                                || clientSideSelectedKeys[0] == null || clientSideSelectedKeys[0] == getNullSelectionItemId())) {
 +                    markAsDirty();
 +                    return;
 +                }
 +                if (clientSideSelectedKeys.length == 0) {
 +                    // Allows deselection only if the deselected item is
 +                    // visible
 +                    final Object current = getValue();
 +                    final Collection<?> visible = getVisibleItemIds();
 +                    if (visible != null && visible.contains(current)) {
 +                        setValue(null, true);
 +                    }
 +                } else {
 +                    final Object id = itemIdMapper
 +                            .get(clientSideSelectedKeys[0]);
-                         setValue(id, true);
++
++                    if (id != null) {
++                        if (isNullSelectionAllowed()
++                                && id.equals(getNullSelectionItemId())) {
++                            setValue(null, true);
++                        } else {
++                            setValue(id, true);
++                        }
 +                    } else {
++                        markAsDirty();
 +                    }
 +                }
 +            }
 +        }
 +    }
 +
 +    /**
 +     * TODO refine doc Setter for new item handler that is called when user adds
 +     * new item in newItemAllowed mode.
 +     * 
 +     * @param newItemHandler
 +     */
 +    public void setNewItemHandler(NewItemHandler newItemHandler) {
 +        this.newItemHandler = newItemHandler;
 +    }
 +
 +    /**
 +     * TODO refine doc
 +     * 
 +     * @return
 +     */
 +    public NewItemHandler getNewItemHandler() {
 +        if (newItemHandler == null) {
 +            newItemHandler = new DefaultNewItemHandler();
 +        }
 +        return newItemHandler;
 +    }
 +
 +    public interface NewItemHandler extends Serializable {
 +        void addNewItem(String newItemCaption);
 +    }
 +
 +    /**
 +     * TODO refine doc
 +     * 
 +     * This is a default class that handles adding new items that are typed by
 +     * user to selects container.
 +     * 
 +     * By extending this class one may implement some logic on new item addition
 +     * like database inserts.
 +     * 
 +     */
 +    public class DefaultNewItemHandler implements NewItemHandler {
 +        @Override
 +        public void addNewItem(String newItemCaption) {
 +            // Checks for readonly
 +            if (isReadOnly()) {
 +                throw new Property.ReadOnlyException();
 +            }
 +
 +            // Adds new option
 +            if (addItem(newItemCaption) != null) {
 +
 +                // Sets the caption property, if used
 +                if (getItemCaptionPropertyId() != null) {
 +                    getContainerProperty(newItemCaption,
 +                            getItemCaptionPropertyId())
 +                            .setValue(newItemCaption);
 +                }
 +                if (isMultiSelect()) {
 +                    Set values = new HashSet((Collection) getValue());
 +                    values.add(newItemCaption);
 +                    setValue(values);
 +                } else {
 +                    setValue(newItemCaption);
 +                }
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Gets the visible item ids. In Select, this returns list of all item ids,
 +     * but can be overriden in subclasses if they paint only part of the items
 +     * to the terminal or null if no items is visible.
 +     */
 +    public Collection<?> getVisibleItemIds() {
 +        return getItemIds();
 +    }
 +
 +    /* Property methods */
 +
 +    /**
 +     * Returns the type of the property. <code>getValue</code> and
 +     * <code>setValue</code> methods must be compatible with this type: one can
 +     * safely cast <code>getValue</code> to given type and pass any variable
 +     * assignable to this type as a parameter to <code>setValue</code>.
 +     * 
 +     * @return the Type of the property.
 +     */
 +    @Override
 +    public Class<?> getType() {
 +        if (isMultiSelect()) {
 +            return Set.class;
 +        } else {
 +            return Object.class;
 +        }
 +    }
 +
 +    /**
 +     * Gets the selected item id or in multiselect mode a set of selected ids.
 +     * 
 +     * @see com.vaadin.ui.AbstractField#getValue()
 +     */
 +    @Override
 +    public Object getValue() {
 +        final Object retValue = super.getValue();
 +
 +        if (isMultiSelect()) {
 +
 +            // If the return value is not a set
 +            if (retValue == null) {
 +                return new HashSet<Object>();
 +            }
 +            if (retValue instanceof Set) {
 +                return Collections.unmodifiableSet((Set<?>) retValue);
 +            } else if (retValue instanceof Collection) {
 +                return new HashSet<Object>((Collection<?>) retValue);
 +            } else {
 +                final Set<Object> s = new HashSet<Object>();
 +                if (items.containsId(retValue)) {
 +                    s.add(retValue);
 +                }
 +                return s;
 +            }
 +
 +        } else {
 +            return retValue;
 +        }
 +    }
 +
 +    /**
 +     * Sets the visible value of the property.
 +     * 
 +     * <p>
 +     * The value of the select is the selected item id. If the select is in
 +     * multiselect-mode, the value is a set of selected item keys. In
 +     * multiselect mode all collections of id:s can be assigned.
 +     * </p>
 +     * 
 +     * @param newValue
 +     *            the New selected item or collection of selected items.
 +     * @see com.vaadin.ui.AbstractField#setValue(java.lang.Object)
 +     */
 +    @Override
 +    public void setValue(Object newValue) throws Property.ReadOnlyException {
 +        if (newValue == getNullSelectionItemId()) {
 +            newValue = null;
 +        }
 +
 +        setValue(newValue, false);
 +    }
 +
 +    /**
 +     * Sets the visible value of the property.
 +     * 
 +     * <p>
 +     * The value of the select is the selected item id. If the select is in
 +     * multiselect-mode, the value is a set of selected item keys. In
 +     * multiselect mode all collections of id:s can be assigned.
 +     * </p>
 +     * 
 +     * @since 7.5.7
 +     * @param newValue
 +     *            the New selected item or collection of selected items.
 +     * @param repaintIsNotNeeded
 +     *            True if caller is sure that repaint is not needed.
 +     * @param ignoreReadOnly
 +     *            True if read-only check should be omitted.
 +     * @see com.vaadin.ui.AbstractField#setValue(java.lang.Object,
 +     *      java.lang.Boolean)
 +     */
 +    @Override
 +    protected void setValue(Object newFieldValue, boolean repaintIsNotNeeded,
 +            boolean ignoreReadOnly)
 +            throws com.vaadin.data.Property.ReadOnlyException,
 +            ConversionException, InvalidValueException {
 +        if (isMultiSelect()) {
 +            if (newFieldValue == null) {
 +                super.setValue(new LinkedHashSet<Object>(), repaintIsNotNeeded,
 +                        ignoreReadOnly);
 +            } else if (Collection.class.isAssignableFrom(newFieldValue
 +                    .getClass())) {
 +                super.setValue(new LinkedHashSet<Object>(
 +                        (Collection<?>) newFieldValue), repaintIsNotNeeded,
 +                        ignoreReadOnly);
 +            }
 +        } else if (newFieldValue == null || items.containsId(newFieldValue)) {
 +            super.setValue(newFieldValue, repaintIsNotNeeded, ignoreReadOnly);
 +        }
 +    }
 +
 +    /* Container methods */
 +
 +    /**
 +     * Gets the item from the container with given id. If the container does not
 +     * contain the requested item, null is returned.
 +     * 
 +     * @param itemId
 +     *            the item id.
 +     * @return the item from the container.
 +     */
 +    @Override
 +    public Item getItem(Object itemId) {
 +        return items.getItem(itemId);
 +    }
 +
 +    /**
 +     * Gets the item Id collection from the container.
 +     * 
 +     * @return the Collection of item ids.
 +     */
 +    @Override
 +    public Collection<?> getItemIds() {
 +        return items.getItemIds();
 +    }
 +
 +    /**
 +     * Gets the property Id collection from the container.
 +     * 
 +     * @return the Collection of property ids.
 +     */
 +    @Override
 +    public Collection<?> getContainerPropertyIds() {
 +        return items.getContainerPropertyIds();
 +    }
 +
 +    /**
 +     * Gets the property type.
 +     * 
 +     * @param propertyId
 +     *            the Id identifying the property.
 +     * @see com.vaadin.data.Container#getType(java.lang.Object)
 +     */
 +    @Override
 +    public Class<?> getType(Object propertyId) {
 +        return items.getType(propertyId);
 +    }
 +
 +    /*
 +     * Gets the number of items in the container.
 +     * 
 +     * @return the Number of items in the container.
 +     * 
 +     * @see com.vaadin.data.Container#size()
 +     */
 +    @Override
 +    public int size() {
 +        int size = items.size();
 +        assert size >= 0;
 +        return size;
 +    }
 +
 +    /**
 +     * Tests, if the collection contains an item with given id.
 +     * 
 +     * @param itemId
 +     *            the Id the of item to be tested.
 +     */
 +    @Override
 +    public boolean containsId(Object itemId) {
 +        if (itemId != null) {
 +            return items.containsId(itemId);
 +        } else {
 +            return false;
 +        }
 +    }
 +
 +    /**
 +     * Gets the Property identified by the given itemId and propertyId from the
 +     * Container
 +     * 
 +     * @see com.vaadin.data.Container#getContainerProperty(Object, Object)
 +     */
 +    @Override
 +    public Property getContainerProperty(Object itemId, Object propertyId) {
 +        return items.getContainerProperty(itemId, propertyId);
 +    }
 +
 +    /**
 +     * Adds the new property to all items. Adds a property with given id, type
 +     * and default value to all items in the container.
 +     * 
 +     * This functionality is optional. If the function is unsupported, it always
 +     * returns false.
 +     * 
 +     * @return True if the operation succeeded.
 +     * @see com.vaadin.data.Container#addContainerProperty(java.lang.Object,
 +     *      java.lang.Class, java.lang.Object)
 +     */
 +    @Override
 +    public boolean addContainerProperty(Object propertyId, Class<?> type,
 +            Object defaultValue) throws UnsupportedOperationException {
 +
 +        final boolean retval = items.addContainerProperty(propertyId, type,
 +                defaultValue);
 +        if (retval && !(items instanceof Container.PropertySetChangeNotifier)) {
 +            firePropertySetChange();
 +        }
 +        return retval;
 +    }
 +
 +    /**
 +     * Removes all items from the container.
 +     * 
 +     * This functionality is optional. If the function is unsupported, it always
 +     * returns false.
 +     * 
 +     * @return True if the operation succeeded.
 +     * @see com.vaadin.data.Container#removeAllItems()
 +     */
 +    @Override
 +    public boolean removeAllItems() throws UnsupportedOperationException {
 +
 +        final boolean retval = items.removeAllItems();
 +        itemIdMapper.removeAll();
 +        if (retval) {
 +            setValue(null);
 +            if (!(items instanceof Container.ItemSetChangeNotifier)) {
 +                fireItemSetChange();
 +            }
 +        }
 +        return retval;
 +    }
 +
 +    /**
 +     * Creates a new item into container with container managed id. The id of
 +     * the created new item is returned. The item can be fetched with getItem()
 +     * method. if the creation fails, null is returned.
 +     * 
 +     * @return the Id of the created item or null in case of failure.
 +     * @see com.vaadin.data.Container#addItem()
 +     */
 +    @Override
 +    public Object addItem() throws UnsupportedOperationException {
 +
 +        final Object retval = items.addItem();
 +        if (retval != null
 +                && !(items instanceof Container.ItemSetChangeNotifier)) {
 +            fireItemSetChange();
 +        }
 +        return retval;
 +    }
 +
 +    /**
 +     * Create a new item into container. The created new item is returned and
 +     * ready for setting property values. if the creation fails, null is
 +     * returned. In case the container already contains the item, null is
 +     * returned.
 +     * 
 +     * This functionality is optional. If the function is unsupported, it always
 +     * returns null.
 +     * 
 +     * @param itemId
 +     *            the Identification of the item to be created.
 +     * @return the Created item with the given id, or null in case of failure.
 +     * @see com.vaadin.data.Container#addItem(java.lang.Object)
 +     */
 +    @Override
 +    public Item addItem(Object itemId) throws UnsupportedOperationException {
 +
 +        final Item retval = items.addItem(itemId);
 +        if (retval != null
 +                && !(items instanceof Container.ItemSetChangeNotifier)) {
 +            fireItemSetChange();
 +        }
 +        return retval;
 +    }
 +
 +    /**
 +     * Adds given items with given item ids to container.
 +     * 
 +     * @since 7.2
 +     * @param itemId
 +     *            item identifiers to be added to underlying container
 +     * @throws UnsupportedOperationException
 +     *             if the underlying container don't support adding items with
 +     *             identifiers
 +     */
 +    public void addItems(Object... itemId) throws UnsupportedOperationException {
 +        for (Object id : itemId) {
 +            addItem(id);
 +        }
 +    }
 +
 +    /**
 +     * Adds given items with given item ids to container.
 +     * 
 +     * @since 7.2
 +     * @param itemIds
 +     *            item identifiers to be added to underlying container
 +     * @throws UnsupportedOperationException
 +     *             if the underlying container don't support adding items with
 +     *             identifiers
 +     */
 +    public void addItems(Collection<?> itemIds)
 +            throws UnsupportedOperationException {
 +        addItems(itemIds.toArray());
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see com.vaadin.data.Container#removeItem(java.lang.Object)
 +     */
 +    @Override
 +    public boolean removeItem(Object itemId)
 +            throws UnsupportedOperationException {
 +
 +        unselect(itemId);
 +        final boolean retval = items.removeItem(itemId);
 +        itemIdMapper.remove(itemId);
 +        if (retval && !(items instanceof Container.ItemSetChangeNotifier)) {
 +            fireItemSetChange();
 +        }
 +        return retval;
 +    }
 +
 +    /**
 +     * Checks that the current selection is valid, i.e. the selected item ids
 +     * exist in the container. Updates the selection if one or several selected
 +     * item ids are no longer available in the container.
 +     */
 +    @SuppressWarnings("unchecked")
 +    public void sanitizeSelection() {
 +        Object value = getValue();
 +        if (value == null) {
 +            return;
 +        }
 +
 +        boolean changed = false;
 +
 +        if (isMultiSelect()) {
 +            Collection<Object> valueAsCollection = (Collection<Object>) value;
 +            List<Object> newSelection = new ArrayList<Object>(
 +                    valueAsCollection.size());
 +            for (Object subValue : valueAsCollection) {
 +                if (containsId(subValue)) {
 +                    newSelection.add(subValue);
 +                } else {
 +                    changed = true;
 +                }
 +            }
 +            if (changed) {
 +                setValue(newSelection);
 +            }
 +        } else {
 +            if (!containsId(value)) {
 +                setValue(null);
 +            }
 +        }
 +
 +    }
 +
 +    /**
 +     * Removes the property from all items. Removes a property with given id
 +     * from all the items in the container.
 +     * 
 +     * This functionality is optional. If the function is unsupported, it always
 +     * returns false.
 +     * 
 +     * @return True if the operation succeeded.
 +     * @see com.vaadin.data.Container#removeContainerProperty(java.lang.Object)
 +     */
 +    @Override
 +    public boolean removeContainerProperty(Object propertyId)
 +            throws UnsupportedOperationException {
 +
 +        final boolean retval = items.removeContainerProperty(propertyId);
 +        if (retval && !(items instanceof Container.PropertySetChangeNotifier)) {
 +            firePropertySetChange();
 +        }
 +        return retval;
 +    }
 +
 +    /* Container.Viewer methods */
 +
 +    /**
 +     * Sets the Container that serves as the data source of the viewer.
 +     * 
 +     * As a side-effect the fields value (selection) is set to null due old
 +     * selection not necessary exists in new Container.
 +     * 
 +     * @see com.vaadin.data.Container.Viewer#setContainerDataSource(Container)
 +     * 
 +     * @param newDataSource
 +     *            the new data source.
 +     */
 +    @Override
 +    public void setContainerDataSource(Container newDataSource) {
 +        if (newDataSource == null) {
 +            newDataSource = new IndexedContainer();
 +        }
 +
 +        getCaptionChangeListener().clear();
 +
 +        if (items != newDataSource) {
 +
 +            // Removes listeners from the old datasource
 +            if (items != null) {
 +                if (items instanceof Container.ItemSetChangeNotifier) {
 +                    ((Container.ItemSetChangeNotifier) items)
 +                            .removeItemSetChangeListener(this);
 +                }
 +                if (items instanceof Container.PropertySetChangeNotifier) {
 +                    ((Container.PropertySetChangeNotifier) items)
 +                            .removePropertySetChangeListener(this);
 +                }
 +            }
 +
 +            // Assigns new data source
 +            items = newDataSource;
 +
 +            // Clears itemIdMapper also
 +            itemIdMapper.removeAll();
 +
 +            // Adds listeners
 +            if (items != null) {
 +                if (items instanceof Container.ItemSetChangeNotifier) {
 +                    ((Container.ItemSetChangeNotifier) items)
 +                            .addItemSetChangeListener(this);
 +                }
 +                if (items instanceof Container.PropertySetChangeNotifier) {
 +                    ((Container.PropertySetChangeNotifier) items)
 +                            .addPropertySetChangeListener(this);
 +                }
 +            }
 +
 +            /*
 +             * We expect changing the data source should also clean value. See
 +             * #810, #4607, #5281
 +             */
 +            setValue(null);
 +
 +            markAsDirty();
 +
 +        }
 +    }
 +
 +    /**
 +     * Gets the viewing data-source container.
 +     * 
 +     * @see com.vaadin.data.Container.Viewer#getContainerDataSource()
 +     */
 +    @Override
 +    public Container getContainerDataSource() {
 +        return items;
 +    }
 +
 +    /* Select attributes */
 +
 +    /**
 +     * Is the select in multiselect mode? In multiselect mode
 +     * 
 +     * @return the Value of property multiSelect.
 +     */
 +    public boolean isMultiSelect() {
 +        return multiSelect;
 +    }
 +
 +    /**
 +     * Sets the multiselect mode. Setting multiselect mode false may lose
 +     * selection information: if selected items set contains one or more
 +     * selected items, only one of the selected items is kept as selected.
 +     * 
 +     * Subclasses of AbstractSelect can choose not to support changing the
 +     * multiselect mode, and may throw {@link UnsupportedOperationException}.
 +     * 
 +     * @param multiSelect
 +     *            the New value of property multiSelect.
 +     */
 +    public void setMultiSelect(boolean multiSelect) {
 +        if (multiSelect && getNullSelectionItemId() != null) {
 +            throw new IllegalStateException(
 +                    "Multiselect and NullSelectionItemId can not be set at the same time.");
 +        }
 +        if (multiSelect != this.multiSelect) {
 +
 +            // Selection before mode change
 +            final Object oldValue = getValue();
 +
 +            this.multiSelect = multiSelect;
 +
 +            // Convert the value type
 +            if (multiSelect) {
 +                final Set<Object> s = new HashSet<Object>();
 +                if (oldValue != null) {
 +                    s.add(oldValue);
 +                }
 +                setValue(s);
 +            } else {
 +                final Set<?> s = (Set<?>) oldValue;
 +                if (s == null || s.isEmpty()) {
 +                    setValue(null);
 +                } else {
 +                    // Set the single select to contain only the first
 +                    // selected value in the multiselect
 +                    setValue(s.iterator().next());
 +                }
 +            }
 +
 +            markAsDirty();
 +        }
 +    }
 +
 +    /**
 +     * Does the select allow adding new options by the user. If true, the new
 +     * options can be added to the Container. The text entered by the user is
 +     * used as id. Note that data-source must allow adding new items.
 +     * 
 +     * @return True if additions are allowed.
 +     */
 +    public boolean isNewItemsAllowed() {
 +        return allowNewOptions;
 +    }
 +
 +    /**
 +     * Enables or disables possibility to add new options by the user.
 +     * 
 +     * @param allowNewOptions
 +     *            the New value of property allowNewOptions.
 +     */
 +    public void setNewItemsAllowed(boolean allowNewOptions) {
 +
 +        // Only handle change requests
 +        if (this.allowNewOptions != allowNewOptions) {
 +
 +            this.allowNewOptions = allowNewOptions;
 +
 +            markAsDirty();
 +        }
 +    }
 +
 +    /**
 +     * Override the caption of an item. Setting caption explicitly overrides id,
 +     * item and index captions.
 +     * 
 +     * @param itemId
 +     *            the id of the item to be recaptioned.
 +     * @param caption
 +     *            the New caption.
 +     */
 +    public void setItemCaption(Object itemId, String caption) {
 +        if (itemId != null) {
 +            itemCaptions.put(itemId, caption);
 +            markAsDirty();
 +        }
 +    }
 +
 +    /**
 +     * Gets the caption of an item. The caption is generated as specified by the
 +     * item caption mode. See <code>setItemCaptionMode()</code> for more
 +     * details.
 +     * 
 +     * @param itemId
 +     *            the id of the item to be queried.
 +     * @return the caption for specified item.
 +     */
 +    public String getItemCaption(Object itemId) {
 +
 +        // Null items can not be found
 +        if (itemId == null) {
 +            return null;
 +        }
 +
 +        String caption = null;
 +
 +        switch (getItemCaptionMode()) {
 +
 +        case ID:
 +            caption = idToCaption(itemId);
 +            break;
 +        case ID_TOSTRING:
 +            caption = itemId.toString();
 +            break;
 +        case INDEX:
 +            if (items instanceof Container.Indexed) {
 +                caption = String.valueOf(((Container.Indexed) items)
 +                        .indexOfId(itemId));
 +            } else {
 +                caption = "ERROR: Container is not indexed";
 +            }
 +            break;
 +
 +        case ITEM:
 +            final Item i = getItem(itemId);
 +            if (i != null) {
 +                caption = i.toString();
 +            }
 +            break;
 +
 +        case EXPLICIT:
 +            caption = itemCaptions.get(itemId);
 +            break;
 +
 +        case EXPLICIT_DEFAULTS_ID:
 +            caption = itemCaptions.get(itemId);
 +            if (caption == null) {
 +                caption = idToCaption(itemId);
 +            }
 +            break;
 +
 +        case PROPERTY:
 +            final Property<?> p = getContainerProperty(itemId,
 +                    getItemCaptionPropertyId());
 +            if (p != null) {
 +                Object value = p.getValue();
 +                if (value != null) {
 +                    caption = value.toString();
 +                }
 +            }
 +            break;
 +        }
 +
 +        // All items must have some captions
 +        return caption != null ? caption : "";
 +    }
 +
 +    private String idToCaption(Object itemId) {
 +        try {
 +            Converter<String, Object> c = (Converter<String, Object>) ConverterUtil
 +                    .getConverter(String.class, itemId.getClass(), getSession());
 +            return ConverterUtil.convertFromModel(itemId, String.class, c,
 +                    getLocale());
 +        } catch (Exception e) {
 +            return itemId.toString();
 +        }
 +    }
 +
 +    /**
 +     * Sets the icon for an item.
 +     * 
 +     * @param itemId
 +     *            the id of the item to be assigned an icon.
 +     * @param icon
 +     *            the icon to use or null.
 +     */
 +    public void setItemIcon(Object itemId, Resource icon) {
 +        if (itemId != null) {
 +            if (icon == null) {
 +                itemIcons.remove(itemId);
 +            } else {
 +                itemIcons.put(itemId, icon);
 +            }
 +            markAsDirty();
 +        }
 +    }
 +
 +    /**
 +     * Gets the item icon.
 +     * 
 +     * @param itemId
 +     *            the id of the item to be assigned an icon.
 +     * @return the icon for the item or null, if not specified.
 +     */
 +    public Resource getItemIcon(Object itemId) {
 +        final Resource explicit = itemIcons.get(itemId);
 +        if (explicit != null) {
 +            return explicit;
 +        }
 +
 +        if (getItemIconPropertyId() == null) {
 +            return null;
 +        }
 +
 +        final Property<?> ip = getContainerProperty(itemId,
 +                getItemIconPropertyId());
 +        if (ip == null) {
 +            return null;
 +        }
 +        final Object icon = ip.getValue();
 +        if (icon instanceof Resource) {
 +            return (Resource) icon;
 +        }
 +
 +        return null;
 +    }
 +
 +    /**
 +     * Sets the item caption mode.
 +     * 
 +     * See {@link ItemCaptionMode} for a description of the modes.
 +     * <p>
 +     * {@link ItemCaptionMode#EXPLICIT_DEFAULTS_ID} is the default mode.
 +     * </p>
 +     * 
 +     * @param mode
 +     *            the One of the modes listed above.
 +     */
 +    public void setItemCaptionMode(ItemCaptionMode mode) {
 +        if (mode != null) {
 +            itemCaptionMode = mode;
 +            markAsDirty();
 +        }
 +    }
 +
 +    /**
 +     * Gets the item caption mode.
 +     * 
 +     * <p>
 +     * The mode can be one of the following ones:
 +     * <ul>
 +     * <li><code>ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID</code> : Items
 +     * Id-objects <code>toString</code> is used as item caption. If caption is
 +     * explicitly specified, it overrides the id-caption.
 +     * <li><code>ITEM_CAPTION_MODE_ID</code> : Items Id-objects
 +     * <code>toString</code> is used as item caption.</li>
 +     * <li><code>ITEM_CAPTION_MODE_ITEM</code> : Item-objects
 +     * <code>toString</code> is used as item caption.</li>
 +     * <li><code>ITEM_CAPTION_MODE_INDEX</code> : The index of the item is used
 +     * as item caption. The index mode can only be used with the containers
 +     * implementing <code>Container.Indexed</code> interface.</li>
 +     * <li><code>ITEM_CAPTION_MODE_EXPLICIT</code> : The item captions must be
 +     * explicitly specified.</li>
 +     * <li><code>ITEM_CAPTION_MODE_PROPERTY</code> : The item captions are read
 +     * from property, that must be specified with
 +     * <code>setItemCaptionPropertyId</code>.</li>
 +     * </ul>
 +     * The <code>ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID</code> is the default
 +     * mode.
 +     * </p>
 +     * 
 +     * @return the One of the modes listed above.
 +     */
 +    public ItemCaptionMode getItemCaptionMode() {
 +        return itemCaptionMode;
 +    }
 +
 +    /**
 +     * Sets the item caption property.
 +     * 
 +     * <p>
 +     * Setting the id to a existing property implicitly sets the item caption
 +     * mode to <code>ITEM_CAPTION_MODE_PROPERTY</code>. If the object is in
 +     * <code>ITEM_CAPTION_MODE_PROPERTY</code> mode, setting caption property id
 +     * null resets the item caption mode to
 +     * <code>ITEM_CAPTION_EXPLICIT_DEFAULTS_ID</code>.
 +     * </p>
 +     * <p>
 +     * Note that the type of the property used for caption must be String
 +     * </p>
 +     * <p>
 +     * Setting the property id to null disables this feature. The id is null by
 +     * default
 +     * </p>
 +     * .
 +     * 
 +     * @param propertyId
 +     *            the id of the property.
 +     * 
 +     */
 +    public void setItemCaptionPropertyId(Object propertyId) {
 +        if (propertyId != null) {
 +            itemCaptionPropertyId = propertyId;
 +            setItemCaptionMode(ITEM_CAPTION_MODE_PROPERTY);
 +            markAsDirty();
 +        } else {
 +            itemCaptionPropertyId = null;
 +            if (getItemCaptionMode() == ITEM_CAPTION_MODE_PROPERTY) {
 +                setItemCaptionMode(ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID);
 +            }
 +            markAsDirty();
 +        }
 +    }
 +
 +    /**
 +     * Gets the item caption property.
 +     * 
 +     * @return the Id of the property used as item caption source.
 +     */
 +    public Object getItemCaptionPropertyId() {
 +        return itemCaptionPropertyId;
 +    }
 +
 +    /**
 +     * Sets the item icon property.
 +     * 
 +     * <p>
 +     * If the property id is set to a valid value, each item is given an icon
 +     * got from the given property of the items. The type of the property must
 +     * be assignable to Resource.
 +     * </p>
 +     * 
 +     * <p>
 +     * Note : The icons set with <code>setItemIcon</code> function override the
 +     * icons from the property.
 +     * </p>
 +     * 
 +     * <p>
 +     * Setting the property id to null disables this feature. The id is null by
 +     * default
 +     * </p>
 +     * .
 +     * 
 +     * @param propertyId
 +     *            the id of the property that specifies icons for items or null
 +     * @throws IllegalArgumentException
 +     *             If the propertyId is not in the container or is not of a
 +     *             valid type
 +     */
 +    public void setItemIconPropertyId(Object propertyId)
 +            throws IllegalArgumentException {
 +        if (propertyId == null) {
 +            itemIconPropertyId = null;
 +        } else if (!getContainerPropertyIds().contains(propertyId)) {
 +            throw new IllegalArgumentException(
 +                    "Property id not found in the container");
 +        } else if (Resource.class.isAssignableFrom(getType(propertyId))) {
 +            itemIconPropertyId = propertyId;
 +        } else {
 +            throw new IllegalArgumentException(
 +                    "Property type must be assignable to Resource");
 +        }
 +        markAsDirty();
 +    }
 +
 +    /**
 +     * Gets the item icon property.
 +     * 
 +     * <p>
 +     * If the property id is set to a valid value, each item is given an icon
 +     * got from the given property of the items. The type of the property must
 +     * be assignable to Icon.
 +     * </p>
 +     * 
 +     * <p>
 +     * Note : The icons set with <code>setItemIcon</code> function override the
 +     * icons from the property.
 +     * </p>
 +     * 
 +     * <p>
 +     * Setting the property id to null disables this feature. The id is null by
 +     * default
 +     * </p>
 +     * .
 +     * 
 +     * @return the Id of the property containing the item icons.
 +     */
 +    public Object getItemIconPropertyId() {
 +        return itemIconPropertyId;
 +    }
 +
 +    /**
 +     * Tests if an item is selected.
 +     * 
 +     * <p>
 +     * In single select mode testing selection status of the item identified by
 +     * {@link #getNullSelectionItemId()} returns true if the value of the
 +     * property is null.
 +     * </p>
 +     * 
 +     * @param itemId
 +     *            the Id the of the item to be tested.
 +     * @see #getNullSelectionItemId()
 +     * @see #setNullSelectionItemId(Object)
 +     * 
 +     */
 +    public boolean isSelected(Object itemId) {
 +        if (itemId == null) {
 +            return false;
 +        }
 +        if (isMultiSelect()) {
 +            return ((Set<?>) getValue()).contains(itemId);
 +        } else {
 +            final Object value = getValue();
 +            return itemId.equals(value == null ? getNullSelectionItemId()
 +                    : value);
 +        }
 +    }
 +
 +    /**
 +     * Selects an item.
 +     * 
 +     * <p>
 +     * In single select mode selecting item identified by
 +     * {@link #getNullSelectionItemId()} sets the value of the property to null.
 +     * </p>
 +     * 
 +     * @param itemId
 +     *            the identifier of Item to be selected.
 +     * @see #getNullSelectionItemId()
 +     * @see #setNullSelectionItemId(Object)
 +     * 
 +     */
 +    public void select(Object itemId) {
 +        if (!isMultiSelect()) {
 +            setValue(itemId);
 +        } else if (!isSelected(itemId) && itemId != null
 +                && items.containsId(itemId)) {
 +            final Set<Object> s = new HashSet<Object>((Set<?>) getValue());
 +            s.add(itemId);
 +            setValue(s);
 +        }
 +    }
 +
 +    /**
 +     * Unselects an item.
 +     * 
 +     * @param itemId
 +     *            the identifier of the Item to be unselected.
 +     * @see #getNullSelectionItemId()
 +     * @see #setNullSelectionItemId(Object)
 +     * 
 +     */
 +    public void unselect(Object itemId) {
 +        if (isSelected(itemId)) {
 +            if (isMultiSelect()) {
 +                final Set<Object> s = new HashSet<Object>((Set<?>) getValue());
 +                s.remove(itemId);
 +                setValue(s);
 +            } else {
 +                setValue(null);
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Notifies this listener that the Containers contents has changed.
 +     * 
 +     * @see com.vaadin.data.Container.PropertySetChangeListener#containerPropertySetChange(com.vaadin.data.Container.PropertySetChangeEvent)
 +     */
 +    @Override
 +    public void containerPropertySetChange(
 +            Container.PropertySetChangeEvent event) {
 +        firePropertySetChange();
 +    }
 +
 +    /**
 +     * Adds a new Property set change listener for this Container.
 +     * 
 +     * @see com.vaadin.data.Container.PropertySetChangeNotifier#addListener(com.vaadin.data.Container.PropertySetChangeListener)
 +     */
 +    @Override
 +    public void addPropertySetChangeListener(
 +            Container.PropertySetChangeListener listener) {
 +        if (propertySetEventListeners == null) {
 +            propertySetEventListeners = new LinkedHashSet<Container.PropertySetChangeListener>();
 +        }
 +        propertySetEventListeners.add(listener);
 +    }
 +
 +    /**
 +     * @deprecated As of 7.0, replaced by
 +     *             {@link #addPropertySetChangeListener(com.vaadin.data.Container.PropertySetChangeListener)}
 +     **/
 +    @Override
 +    @Deprecated
 +    public void addListener(Container.PropertySetChangeListener listener) {
 +        addPropertySetChangeListener(listener);
 +    }
 +
 +    /**
 +     * Removes a previously registered Property set change listener.
 +     * 
 +     * @see com.vaadin.data.Container.PropertySetChangeNotifier#removeListener(com.vaadin.data.Container.PropertySetChangeListener)
 +     */
 +    @Override
 +    public void removePropertySetChangeListener(
 +            Container.PropertySetChangeListener listener) {
 +        if (propertySetEventListeners != null) {
 +            propertySetEventListeners.remove(listener);
 +            if (propertySetEventListeners.isEmpty()) {
 +                propertySetEventListeners = null;
 +            }
 +        }
 +    }
 +
 +    /**
 +     * @deprecated As of 7.0, replaced by
 +     *             {@link #removePropertySetChangeListener(com.vaadin.data.Container.PropertySetChangeListener)}
 +     **/
 +    @Override
 +    @Deprecated
 +    public void removeListener(Container.PropertySetChangeListener listener) {
 +        removePropertySetChangeListener(listener);
 +    }
 +
 +    /**
 +     * Adds an Item set change listener for the object.
 +     * 
 +     * @see com.vaadin.data.Container.ItemSetChangeNotifier#addListener(com.vaadin.data.Container.ItemSetChangeListener)
 +     */
 +    @Override
 +    public void addItemSetChangeListener(
 +            Container.ItemSetChangeListener listener) {
 +        if (itemSetEventListeners == null) {
 +            itemSetEventListeners = new LinkedHashSet<Container.ItemSetChangeListener>();
 +        }
 +        itemSetEventListeners.add(listener);
 +    }
 +
 +    /**
 +     * @deprecated As of 7.0, replaced by
 +     *             {@link #addItemSetChangeListener(com.vaadin.data.Container.ItemSetChangeListener)}
 +     **/
 +    @Override
 +    @Deprecated
 +    public void addListener(Container.ItemSetChangeListener listener) {
 +        addItemSetChangeListener(listener);
 +    }
 +
 +    /**
 +     * Removes the Item set change listener from the object.
 +     * 
 +     * @see com.vaadin.data.Container.ItemSetChangeNotifier#removeListener(com.vaadin.data.Container.ItemSetChangeListener)
 +     */
 +    @Override
 +    public void removeItemSetChangeListener(
 +            Container.ItemSetChangeListener listener) {
 +        if (itemSetEventListeners != null) {
 +            itemSetEventListeners.remove(listener);
 +            if (itemSetEventListeners.isEmpty()) {
 +                itemSetEventListeners = null;
 +            }
 +        }
 +    }
 +
 +    /**
 +     * @deprecated As of 7.0, replaced by
 +     *             {@link #removeItemSetChangeListener(com.vaadin.data.Container.ItemSetChangeListener)}
 +     **/
 +    @Override
 +    @Deprecated
 +    public void removeListener(Container.ItemSetChangeListener listener) {
 +        removeItemSetChangeListener(listener);
 +    }
 +
 +    @Override
 +    public Collection<?> getListeners(Class<?> eventType) {
 +        if (Container.ItemSetChangeEvent.class.isAssignableFrom(eventType)) {
 +            if (itemSetEventListeners == null) {
 +                return Collections.EMPTY_LIST;
 +            } else {
 +                return Collections
 +                        .unmodifiableCollection(itemSetEventListeners);
 +            }
 +        } else if (Container.PropertySetChangeEvent.class
 +                .isAssignableFrom(eventType)) {
 +            if (propertySetEventListeners == null) {
 +                return Collections.EMPTY_LIST;
 +            } else {
 +                return Collections
 +                        .unmodifiableCollection(propertySetEventListeners);
 +            }
 +        }
 +
 +        return super.getListeners(eventType);
 +    }
 +
 +    /**
 +     * Lets the listener know a Containers Item set has changed.
 +     * 
 +     * @see com.vaadin.data.Container.ItemSetChangeListener#containerItemSetChange(com.vaadin.data.Container.ItemSetChangeEvent)
 +     */
 +    @Override
 +    public void containerItemSetChange(Container.ItemSetChangeEvent event) {
 +        // Clears the item id mapping table
 +        itemIdMapper.removeAll();
 +
 +        // Notify all listeners
 +        fireItemSetChange();
 +    }
 +
 +    /**
 +     * Fires the property set change event.
 +     */
 +    protected void firePropertySetChange() {
 +        if (propertySetEventListeners != null
 +                && !propertySetEventListeners.isEmpty()) {
 +            final Container.PropertySetChangeEvent event = new PropertySetChangeEvent(
 +                    this);
 +            final Object[] listeners = propertySetEventListeners.toArray();
 +            for (int i = 0; i < listeners.length; i++) {
 +                ((Container.PropertySetChangeListener) listeners[i])
 +                        .containerPropertySetChange(event);
 +            }
 +        }
 +        markAsDirty();
 +    }
 +
 +    /**
 +     * Fires the item set change event.
 +     */
 +    protected void fireItemSetChange() {
 +        if (itemSetEventListeners != null && !itemSetEventListeners.isEmpty()) {
 +            final Container.ItemSetChangeEvent event = new ItemSetChangeEvent(
 +                    this);
 +            final Object[] listeners = itemSetEventListeners.toArray();
 +            for (int i = 0; i < listeners.length; i++) {
 +                ((Container.ItemSetChangeListener) listeners[i])
 +                        .containerItemSetChange(event);
 +            }
 +        }
 +        markAsDirty();
 +    }
 +
 +    /**
 +     * Implementation of item set change event.
 +     */
 +    private static class ItemSetChangeEvent extends EventObject implements
 +            Serializable, Container.ItemSetChangeEvent {
 +
 +        private ItemSetChangeEvent(Container source) {
 +            super(source);
 +        }
 +
 +        /**
 +         * Gets the Property where the event occurred.
 +         * 
 +         * @see com.vaadin.data.Container.ItemSetChangeEvent#getContainer()
 +         */
 +        @Override
 +        public Container getContainer() {
 +            return (Container) getSource();
 +        }
 +
 +    }
 +
 +    /**
 +     * Implementation of property set change event.
 +     */
 +    private static class PropertySetChangeEvent extends EventObject implements
 +            Container.PropertySetChangeEvent, Serializable {
 +
 +        private PropertySetChangeEvent(Container source) {
 +            super(source);
 +        }
 +
 +        /**
 +         * Retrieves the Container whose contents have been modified.
 +         * 
 +         * @see com.vaadin.data.Container.PropertySetChangeEvent#getContainer()
 +         */
 +        @Override
 +        public Container getContainer() {
 +            return (Container) getSource();
 +        }
 +
 +    }
 +
 +    /**
 +     * For multi-selectable fields, also an empty collection of values is
 +     * considered to be an empty field.
 +     * 
 +     * @see AbstractField#isEmpty().
 +     */
 +    @Override
 +    public boolean isEmpty() {
 +        if (!multiSelect) {
 +            return super.isEmpty();
 +        } else {
 +            Object value = getValue();
 +            return super.isEmpty()
 +                    || (value instanceof Collection && ((Collection<?>) value)
 +                            .isEmpty());
 +        }
 +    }
 +
 +    /**
 +     * Allow or disallow empty selection by the user. If the select is in
 +     * single-select mode, you can make an item represent the empty selection by
 +     * calling <code>setNullSelectionItemId()</code>. This way you can for
 +     * instance set an icon and caption for the null selection item.
 +     * 
 +     * @param nullSelectionAllowed
 +     *            whether or not to allow empty selection
 +     * @see #setNullSelectionItemId(Object)
 +     * @see #isNullSelectionAllowed()
 +     */
 +    public void setNullSelectionAllowed(boolean nullSelectionAllowed) {
 +        if (nullSelectionAllowed != this.nullSelectionAllowed) {
 +            this.nullSelectionAllowed = nullSelectionAllowed;
 +            markAsDirty();
 +        }
 +    }
 +
 +    /**
 +     * Checks if null empty selection is allowed by the user.
 +     * 
 +     * @return whether or not empty selection is allowed
 +     * @see #setNullSelectionAllowed(boolean)
 +     */
 +    public boolean isNullSelectionAllowed() {
 +        return nullSelectionAllowed;
 +    }
 +
 +    /**
 +     * Returns the item id that represents null value of this select in single
 +     * select mode.
 +     * 
 +     * <p>
 +     * Data interface does not support nulls as item ids. Selecting the item
 +     * identified by this id is the same as selecting no items at all. This
 +     * setting only affects the single select mode.
 +     * </p>
 +     * 
 +     * @return the Object Null value item id.
 +     * @see #setNullSelectionItemId(Object)
 +     * @see #isSelected(Object)
 +     * @see #select(Object)
 +     */
 +    public Object getNullSelectionItemId() {
 +        return nullSelectionItemId;
 +    }
 +
 +    /**
 +     * Sets the item id that represents null value of this select.
 +     * 
 +     * <p>
 +     * Data interface does not support nulls as item ids. Selecting the item
 +     * identified by this id is the same as selecting no items at all. This
 +     * setting only affects the single select mode.
 +     * </p>
 +     * 
 +     * @param nullSelectionItemId
 +     *            the nullSelectionItemId to set.
 +     * @see #getNullSelectionItemId()
 +     * @see #isSelected(Object)
 +     * @see #select(Object)
 +     */
 +    public void setNullSelectionItemId(Object nullSelectionItemId) {
 +        if (nullSelectionItemId != null && isMultiSelect()) {
 +            throw new IllegalStateException(
 +                    "Multiselect and NullSelectionItemId can not be set at the same time.");
 +        }
 +        this.nullSelectionItemId = nullSelectionItemId;
 +    }
 +
 +    /**
 +     * Notifies the component that it is connected to an application.
 +     * 
 +     * @see com.vaadin.ui.AbstractField#attach()
 +     */
 +    @Override
 +    public void attach() {
 +        super.attach();
 +    }
 +
 +    /**
 +     * Detaches the component from application.
 +     * 
 +     * @see com.vaadin.ui.AbstractComponent#detach()
 +     */
 +    @Override
 +    public void detach() {
 +        getCaptionChangeListener().clear();
 +        super.detach();
 +    }
 +
 +    // Caption change listener
 +    protected CaptionChangeListener getCaptionChangeListener() {
 +        if (captionChangeListener == null) {
 +            captionChangeListener = new CaptionChangeListener();
 +        }
 +        return captionChangeListener;
 +    }
 +
 +    /**
 +     * This is a listener helper for Item and Property changes that should cause
 +     * a repaint. It should be attached to all items that are displayed, and the
 +     * default implementation does this in paintContent(). Especially
 +     * "lazyloading" components should take care to add and remove listeners as
 +     * appropriate. Call addNotifierForItem() for each painted item (and
 +     * remember to clear).
 +     * 
 +     * NOTE: singleton, use getCaptionChangeListener().
 +     * 
 +     */
 +    protected class CaptionChangeListener implements
 +            Item.PropertySetChangeListener, Property.ValueChangeListener {
 +
 +        // TODO clean this up - type is either Item.PropertySetChangeNotifier or
 +        // Property.ValueChangeNotifier
 +        HashSet<Object> captionChangeNotifiers = new HashSet<Object>();
 +
 +        public void addNotifierForItem(Object itemId) {
 +            switch (getItemCaptionMode()) {
 +            case ITEM:
 +                final Item i = getItem(itemId);
 +                if (i == null) {
 +                    return;
 +                }
 +                if (i instanceof Item.PropertySetChangeNotifier) {
 +                    ((Item.PropertySetChangeNotifier) i)
 +                            .addPropertySetChangeListener(getCaptionChangeListener());
 +                    captionChangeNotifiers.add(i);
 +                }
 +                Collection<?> pids = i.getItemPropertyIds();
 +                if (pids != null) {
 +                    for (Iterator<?> it = pids.iterator(); it.hasNext();) {
 +                        Property<?> p = i.getItemProperty(it.next());
 +                        if (p != null
 +                                && p instanceof Property.ValueChangeNotifier) {
 +                            ((Property.ValueChangeNotifier) p)
 +                                    .addValueChangeListener(getCaptionChangeListener());
 +                            captionChangeNotifiers.add(p);
 +                        }
 +                    }
 +
 +                }
 +                break;
 +            case PROPERTY:
 +                final Property<?> p = getContainerProperty(itemId,
 +                        getItemCaptionPropertyId());
 +                if (p != null && p instanceof Property.ValueChangeNotifier) {
 +                    ((Property.ValueChangeNotifier) p)
 +                            .addValueChangeListener(getCaptionChangeListener());
 +                    captionChangeNotifiers.add(p);
 +                }
 +                break;
 +
 +            }
 +            if (getItemIconPropertyId() != null) {
 +                final Property p = getContainerProperty(itemId,
 +                        getItemIconPropertyId());
 +                if (p != null && p instanceof Property.ValueChangeNotifier) {
 +                    ((Property.ValueChangeNotifier) p)
 +                            .addValueChangeListener(getCaptionChangeListener());
 +                    captionChangeNotifiers.add(p);
 +                }
 +            }
 +        }
 +
 +        public void clear() {
 +            for (Iterator<Object> it = captionChangeNotifiers.iterator(); it
 +                    .hasNext();) {
 +                Object notifier = it.next();
 +                if (notifier instanceof Item.PropertySetChangeNotifier) {
 +                    ((Item.PropertySetChangeNotifier) notifier)
 +                            .removePropertySetChangeListener(getCaptionChangeListener());
 +                } else {
 +                    ((Property.ValueChangeNotifier) notifier)
 +                            .removeValueChangeListener(getCaptionChangeListener());
 +                }
 +            }
 +            captionChangeNotifiers.clear();
 +        }
 +
 +        @Override
 +        public void valueChange(com.vaadin.data.Property.ValueChangeEvent event) {
 +            markAsDirty();
 +        }
 +
 +        @Override
 +        public void itemPropertySetChange(
 +                com.vaadin.data.Item.PropertySetChangeEvent event) {
 +            markAsDirty();
 +        }
 +
 +    }
 +
 +    /**
 +     * Criterion which accepts a drop only if the drop target is (one of) the
 +     * given Item identifier(s). Criterion can be used only on a drop targets
 +     * that extends AbstractSelect like {@link Table} and {@link Tree}. The
 +     * target and identifiers of valid Items are given in constructor.
 +     * 
 +     * @since 6.3
 +     */
 +    public static class TargetItemIs extends AbstractItemSetCriterion {
 +
 +        /**
 +         * @param select
 +         *            the select implementation that is used as a drop target
 +         * @param itemId
 +         *            the identifier(s) that are valid drop locations
 +         */
 +        public TargetItemIs(AbstractSelect select, Object... itemId) {
 +            super(select, itemId);
 +        }
 +
 +        @Override
 +        public boolean accept(DragAndDropEvent dragEvent) {
 +            AbstractSelectTargetDetails dropTargetData = (AbstractSelectTargetDetails) dragEvent
 +                    .getTargetDetails();
 +            if (dropTargetData.getTarget() != select) {
 +                return false;
 +            }
 +            return itemIds.contains(dropTargetData.getItemIdOver());
 +        }
 +
 +    }
 +
 +    /**
 +     * Abstract helper class to implement item id based criterion.
 +     * 
 +     * Note, inner class used not to open itemIdMapper for public access.
 +     * 
 +     * @since 6.3
 +     * 
 +     */
 +    private static abstract class AbstractItemSetCriterion extends
 +            ClientSideCriterion {
 +        protected final Collection<Object> itemIds = new HashSet<Object>();
 +        protected AbstractSelect select;
 +
 +        public AbstractItemSetCriterion(AbstractSelect select, Object... itemId) {
 +            if (itemIds == null || select == null) {
 +                throw new IllegalArgumentException(
 +                        "Accepted item identifiers must be accepted.");
 +            }
 +            Collections.addAll(itemIds, itemId);
 +            this.select = select;
 +        }
 +
 +        @Override
 +        public void paintContent(PaintTarget target) throws PaintException {
 +            super.paintContent(target);
 +            String[] keys = new String[itemIds.size()];
 +            int i = 0;
 +            for (Object itemId : itemIds) {
 +                String key = select.itemIdMapper.key(itemId);
 +                keys[i++] = key;
 +            }
 +            target.addAttribute("keys", keys);
 +            target.addAttribute("s", select);
 +        }
 +
 +    }
 +
 +    /**
 +     * This criterion accepts a only a {@link Transferable} that contains given
 +     * Item (practically its identifier) from a specific AbstractSelect.
 +     * 
 +     * @since 6.3
 +     */
 +    public static class AcceptItem extends AbstractItemSetCriterion {
 +
 +        /**
 +         * @param select
 +         *            the select from which the item id's are checked
 +         * @param itemId
 +         *            the item identifier(s) of the select that are accepted
 +         */
 +        public AcceptItem(AbstractSelect select, Object... itemId) {
 +            super(select, itemId);
 +        }
 +
 +        @Override
 +        public boolean accept(DragAndDropEvent dragEvent) {
 +            DataBoundTransferable transferable = (DataBoundTransferable) dragEvent
 +                    .getTransferable();
 +            if (transferable.getSourceComponent() != select) {
 +                return false;
 +            }
 +            return itemIds.contains(transferable.getItemId());
 +        }
 +
 +        /**
 +         * A simple accept criterion which ensures that {@link Transferable}
 +         * contains an {@link Item} (or actually its identifier). In other words
 +         * the criterion check that drag is coming from a {@link Container} like
 +         * {@link Tree} or {@link Table}.
 +         */
 +        public static final ClientSideCriterion ALL = new ContainsDataFlavor(
 +                "itemId");
 +
 +    }
 +
 +    /**
 +     * TargetDetails implementation for subclasses of {@link AbstractSelect}
 +     * that implement {@link DropTarget}.
 +     * 
 +     * @since 6.3
 +     */
 +    public class AbstractSelectTargetDetails extends TargetDetailsImpl {
 +
 +        /**
 +         * The item id over which the drag event happened.
 +         */
 +        protected Object idOver;
 +
 +        /**
 +         * Constructor that automatically converts itemIdOver key to
 +         * corresponding item Id
 +         * 
 +         */
 +        protected AbstractSelectTargetDetails(Map<String, Object> rawVariables) {
 +            super(rawVariables, (DropTarget) AbstractSelect.this);
 +            // eagar fetch itemid, mapper may be emptied
 +            String keyover = (String) getData("itemIdOver");
 +            if (keyover != null) {
 +                idOver = itemIdMapper.get(keyover);
 +            }
 +        }
 +
 +        /**
 +         * If the drag operation is currently over an {@link Item}, this method
 +         * returns the identifier of that {@link Item}.
 +         * 
 +         */
 +        public Object getItemIdOver() {
 +            return idOver;
 +        }
 +
 +        /**
 +         * Returns a detailed vertical location where the drop happened on Item.
 +         */
 +        public VerticalDropLocation getDropLocation() {
 +            String detail = (String) getData("detail");
 +            if (detail == null) {
 +                return null;
 +            }
 +            return VerticalDropLocation.valueOf(detail);
 +        }
 +
 +    }
 +
 +    /**
 +     * An accept criterion to accept drops only on a specific vertical location
 +     * of an item.
 +     * <p>
 +     * This accept criterion is currently usable in Tree and Table
 +     * implementations.
 +     */
 +    public static class VerticalLocationIs extends TargetDetailIs {
 +        public static VerticalLocationIs TOP = new VerticalLocationIs(
 +                VerticalDropLocation.TOP);
 +        public static VerticalLocationIs BOTTOM = new VerticalLocationIs(
 +                VerticalDropLocation.BOTTOM);
 +        public static VerticalLocationIs MIDDLE = new VerticalLocationIs(
 +                VerticalDropLocation.MIDDLE);
 +
 +        private VerticalLocationIs(VerticalDropLocation l) {
 +            super("detail", l.name());
 +        }
 +    }
 +
 +    /**
 +     * Implement this interface and pass it to Tree.setItemDescriptionGenerator
 +     * or Table.setItemDescriptionGenerator to generate mouse over descriptions
 +     * ("tooltips") for the rows and cells in Table or for the items in Tree.
 +     */
 +    public interface ItemDescriptionGenerator extends Serializable {
 +
 +        /**
 +         * Called by Table when a cell (and row) is painted or a item is painted
 +         * in Tree
 +         * 
 +         * @param source
 +         *            The source of the generator, the Tree or Table the
 +         *            generator is attached to
 +         * @param itemId
 +         *            The itemId of the painted cell
 +         * @param propertyId
 +         *            The propertyId of the cell, null when getting row
 +         *            description
 +         * @return The description or "tooltip" of the item.
 +         */
 +        public String generateDescription(Component source, Object itemId,
 +                Object propertyId);
 +    }
 +
 +    @Override
 +    public void readDesign(Element design, DesignContext context) {
 +        // handle default attributes
 +        super.readDesign(design, context);
 +        // handle children specifying selectable items (<option>)
 +        readItems(design, context);
 +    }
 +
 +    protected void readItems(Element design, DesignContext context) {
 +        Set<String> selected = new HashSet<String>();
 +        for (Element child : design.children()) {
 +            readItem(child, selected, context);
 +        }
 +        if (!selected.isEmpty()) {
 +            if (isMultiSelect()) {
 +                setValue(selected, false, true);
 +            } else if (selected.size() == 1) {
 +                setValue(selected.iterator().next(), false, true);
 +            } else {
 +                throw new DesignException(
 +                        "Multiple values selected for a single select component");
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Reads an Item from a design and inserts it into the data source.
 +     * Hierarchical select components should override this method to recursively
 +     * recursively read any child items as well.
 +     * 
 +     * @since 7.5.0
 +     * @param child
 +     *            a child element representing the item
 +     * @param selected
 +     *            A set accumulating selected items. If the item that is read is
 +     *            marked as selected, its item id should be added to this set.
 +     * @param context
 +     *            the DesignContext instance used in parsing
 +     * @return the item id of the new item
 +     * 
 +     * @throws DesignException
 +     *             if the tag name of the {@code child} element is not
 +     *             {@code option}.
 +     */
 +    protected Object readItem(Element child, Set<String> selected,
 +            DesignContext context) {
 +        if (!"option".equals(child.tagName())) {
 +            throw new DesignException("Unrecognized child element in "
 +                    + getClass().getSimpleName() + ": " + child.tagName());
 +        }
 +
 +        String itemId;
 +        String caption = DesignFormatter.decodeFromTextNode(child.html());
 +        if (child.hasAttr("item-id")) {
 +            itemId = child.attr("item-id");
 +            addItem(itemId);
 +            setItemCaption(itemId, caption);
 +        } else {
 +            addItem(itemId = caption);
 +        }
 +
 +        if (child.hasAttr("icon")) {
 +            setItemIcon(
 +                    itemId,
 +                    DesignAttributeHandler.readAttribute("icon",
 +                            child.attributes(), Resource.class));
 +        }
 +
 +        if (child.hasAttr("selected")) {
 +            selected.add(itemId);
 +        }
 +
 +        return itemId;
 +    }
 +
 +    @Override
 +    public void writeDesign(Element design, DesignContext context) {
 +        // Write default attributes
 +        super.writeDesign(design, context);
 +
 +        // Write options if warranted
 +        if (context.shouldWriteData(this)) {
 +            writeItems(design, context);
 +        }
 +    }
 +
 +    /**
 +     * Writes the data source items to a design. Hierarchical select components
 +     * should override this method to only write the root items.
 +     * 
 +     * @since 7.5.0
 +     * @param design
 +     *            the element into which to insert the items
 +     * @param context
 +     *            the DesignContext instance used in writing
 +     */
 +    protected void writeItems(Element design, DesignContext context) {
 +        for (Object itemId : getItemIds()) {
 +            writeItem(design, itemId, context);
 +        }
 +    }
 +
 +    /**
 +     * Writes a data source Item to a design. Hierarchical select components
 +     * should override this method to recursively write any child items as well.
 +     * 
 +     * @since 7.5.0
 +     * @param design
 +     *            the element into which to insert the item
 +     * @param itemId
 +     *            the id of the item to write
 +     * @param context
 +     *            the DesignContext instance used in writing
 +     * @return
 +     */
 +    protected Element writeItem(Element design, Object itemId,
 +            DesignContext context) {
 +        Element element = design.appendElement("option");
 +
 +        String caption = getItemCaption(itemId);
 +        if (caption != null && !caption.equals(itemId.toString())) {
 +            element.html(DesignFormatter.encodeForTextNode(caption));
 +            element.attr("item-id", itemId.toString());
 +        } else {
 +            element.html(DesignFormatter.encodeForTextNode(itemId.toString()));
 +        }
 +
 +        Resource icon = getItemIcon(itemId);
 +        if (icon != null) {
 +            DesignAttributeHandler.writeAttribute("icon", element.attributes(),
 +                    icon, null, Resource.class);
 +        }
 +
 +        if (isSelected(itemId)) {
 +            element.attr("selected", "");
 +        }
 +
 +        return element;
 +    }
 +
 +    @Override
 +    protected AbstractSelectState getState() {
 +        return (AbstractSelectState) super.getState();
 +    }
 +}
index 90b3b56ef61b10e1a613b2963746caf54be13419,0000000000000000000000000000000000000000..29179ddca72628422d01dc843e35ee4c517b670d
mode 100644,000000..100644
--- /dev/null
@@@ -1,498 -1,0 +1,508 @@@
 +/*
 + * Copyright 2000-2014 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.OutputStream;
 +import java.util.HashMap;
 +import java.util.HashSet;
 +import java.util.Iterator;
 +import java.util.LinkedHashMap;
 +import java.util.Map;
 +import java.util.Map.Entry;
 +import java.util.Set;
 +
 +import org.jsoup.nodes.Element;
 +
 +import com.vaadin.event.Transferable;
 +import com.vaadin.event.TransferableImpl;
 +import com.vaadin.event.dd.DragSource;
 +import com.vaadin.event.dd.DropHandler;
 +import com.vaadin.event.dd.DropTarget;
 +import com.vaadin.event.dd.TargetDetails;
 +import com.vaadin.event.dd.TargetDetailsImpl;
 +import com.vaadin.server.PaintException;
 +import com.vaadin.server.PaintTarget;
 +import com.vaadin.server.StreamVariable;
 +import com.vaadin.shared.MouseEventDetails;
 +import com.vaadin.shared.ui.dd.HorizontalDropLocation;
 +import com.vaadin.shared.ui.dd.VerticalDropLocation;
 +import com.vaadin.shared.ui.draganddropwrapper.DragAndDropWrapperConstants;
++import com.vaadin.shared.ui.draganddropwrapper.DragAndDropWrapperServerRpc;
 +import com.vaadin.ui.declarative.DesignContext;
 +
 +@SuppressWarnings("serial")
 +public class DragAndDropWrapper extends CustomComponent implements DropTarget,
 +        DragSource, LegacyComponent {
 +
 +    public class WrapperTransferable extends TransferableImpl {
 +
 +        private Html5File[] files;
 +
 +        public WrapperTransferable(Component sourceComponent,
 +                Map<String, Object> rawVariables) {
 +            super(sourceComponent, rawVariables);
 +            Integer fc = (Integer) rawVariables.get("filecount");
 +            if (fc != null) {
 +                files = new Html5File[fc];
 +                for (int i = 0; i < fc; i++) {
 +                    Html5File file = new Html5File(
 +                            (String) rawVariables.get("fn" + i), // name
 +                            ((Double) rawVariables.get("fs" + i)).longValue(), // size
 +                            (String) rawVariables.get("ft" + i)); // mime
 +                    String id = (String) rawVariables.get("fi" + i);
 +                    files[i] = file;
 +                    receivers.put(id, new ProxyReceiver(id, file));
 +                    markAsDirty(); // paint Receivers
 +                }
 +            }
 +        }
 +
 +        /**
 +         * The component in wrapper that is being dragged or null if the
 +         * transferable is not a component (most likely an html5 drag).
 +         * 
 +         * @return
 +         */
 +        public Component getDraggedComponent() {
 +            Component object = (Component) getData("component");
 +            return object;
 +        }
 +
 +        /**
 +         * @return the mouse down event that started the drag and drop operation
 +         */
 +        public MouseEventDetails getMouseDownEvent() {
 +            return MouseEventDetails.deSerialize((String) getData("mouseDown"));
 +        }
 +
 +        public Html5File[] getFiles() {
 +            return files;
 +        }
 +
 +        public String getText() {
 +            String data = (String) getData("Text"); // IE, html5
 +            if (data == null) {
 +                // check for "text/plain" (webkit)
 +                data = (String) getData("text/plain");
 +            }
 +            return data;
 +        }
 +
 +        public String getHtml() {
 +            String data = (String) getData("Html"); // IE, html5
 +            if (data == null) {
 +                // check for "text/plain" (webkit)
 +                data = (String) getData("text/html");
 +            }
 +            return data;
 +        }
 +
 +    }
 +
++    private final DragAndDropWrapperServerRpc rpc = new DragAndDropWrapperServerRpc() {
++
++        @Override
++        public void poll() {
++            // #19616 RPC to poll the server for changes
++        }
++    };
++
 +    private Map<String, ProxyReceiver> receivers = new HashMap<String, ProxyReceiver>();
 +
 +    public class WrapperTargetDetails extends TargetDetailsImpl {
 +
 +        public WrapperTargetDetails(Map<String, Object> rawDropData) {
 +            super(rawDropData, DragAndDropWrapper.this);
 +        }
 +
 +        /**
 +         * @return the absolute position of wrapper on the page
 +         */
 +        public Integer getAbsoluteLeft() {
 +            return (Integer) getData("absoluteLeft");
 +        }
 +
 +        /**
 +         * 
 +         * @return the absolute position of wrapper on the page
 +         */
 +        public Integer getAbsoluteTop() {
 +            return (Integer) getData("absoluteTop");
 +        }
 +
 +        /**
 +         * @return a detail about the drags vertical position over the wrapper.
 +         */
 +        public VerticalDropLocation getVerticalDropLocation() {
 +            return VerticalDropLocation
 +                    .valueOf((String) getData("verticalLocation"));
 +        }
 +
 +        /**
 +         * @return a detail about the drags horizontal position over the
 +         *         wrapper.
 +         */
 +        public HorizontalDropLocation getHorizontalDropLocation() {
 +            return HorizontalDropLocation
 +                    .valueOf((String) getData("horizontalLocation"));
 +        }
 +
 +    }
 +
 +    public enum DragStartMode {
 +        /**
 +         * {@link DragAndDropWrapper} does not start drag events at all
 +         */
 +        NONE,
 +        /**
 +         * The component on which the drag started will be shown as drag image.
 +         */
 +        COMPONENT,
 +        /**
 +         * The whole wrapper is used as a drag image when dragging.
 +         */
 +        WRAPPER,
 +        /**
 +         * The whole wrapper is used to start an HTML5 drag.
 +         * 
 +         * NOTE: In Internet Explorer 6 to 8, this prevents user interactions
 +         * with the wrapper's contents. For example, clicking a button inside
 +         * the wrapper will no longer work.
 +         */
 +        HTML5,
 +
 +        /**
 +         * Uses the component defined in
 +         * {@link #setDragImageComponent(Component)} as the drag image.
 +         */
 +        COMPONENT_OTHER,
 +    }
 +
 +    private final Map<String, Object> html5DataFlavors = new LinkedHashMap<String, Object>();
 +    private DragStartMode dragStartMode = DragStartMode.NONE;
 +    private Component dragImageComponent = null;
 +
 +    private Set<String> sentIds = new HashSet<String>();
 +
 +    /**
 +     * This is an internal constructor. Use
 +     * {@link DragAndDropWrapper#DragAndDropWrapper(Component)} instead.
 +     * 
 +     * @since 7.5.0
 +     */
 +    @Deprecated
 +    public DragAndDropWrapper() {
 +        super();
++        registerRpc(rpc);
 +    }
 +
 +    /**
 +     * Wraps given component in a {@link DragAndDropWrapper}.
 +     * 
 +     * @param root
 +     *            the component to be wrapped
 +     */
 +    public DragAndDropWrapper(Component root) {
 +        this();
 +        setCompositionRoot(root);
 +    }
 +
 +    /**
 +     * Sets data flavors available in the DragAndDropWrapper is used to start an
 +     * HTML5 style drags. Most commonly the "Text" flavor should be set.
 +     * Multiple data types can be set.
 +     * 
 +     * @param type
 +     *            the string identifier of the drag "payload". E.g. "Text" or
 +     *            "text/html"
 +     * @param value
 +     *            the value
 +     */
 +    public void setHTML5DataFlavor(String type, Object value) {
 +        html5DataFlavors.put(type, value);
 +        markAsDirty();
 +    }
 +
 +    @Override
 +    public void changeVariables(Object source, Map<String, Object> variables) {
 +        // TODO Remove once LegacyComponent is no longer implemented
 +    }
 +
 +    @Override
 +    public void paintContent(PaintTarget target) throws PaintException {
 +        target.addAttribute(DragAndDropWrapperConstants.DRAG_START_MODE,
 +                dragStartMode.ordinal());
 +
 +        if (dragStartMode.equals(DragStartMode.COMPONENT_OTHER)) {
 +            if (dragImageComponent != null) {
 +                target.addAttribute(
 +                        DragAndDropWrapperConstants.DRAG_START_COMPONENT_ATTRIBUTE,
 +                        dragImageComponent.getConnectorId());
 +            } else {
 +                throw new IllegalArgumentException(
 +                        "DragStartMode.COMPONENT_OTHER set but no component "
 +                                + "was defined. Please set a component using DragAnd"
 +                                + "DropWrapper.setDragStartComponent(Component).");
 +            }
 +        }
 +        if (getDropHandler() != null) {
 +            getDropHandler().getAcceptCriterion().paint(target);
 +        }
 +        if (receivers != null && receivers.size() > 0) {
 +            for (Iterator<Entry<String, ProxyReceiver>> it = receivers
 +                    .entrySet().iterator(); it.hasNext();) {
 +                Entry<String, ProxyReceiver> entry = it.next();
 +                String id = entry.getKey();
 +                ProxyReceiver proxyReceiver = entry.getValue();
 +                Html5File html5File = proxyReceiver.file;
 +                if (html5File.getStreamVariable() != null) {
 +                    if (!sentIds.contains(id)) {
 +                        target.addVariable(this, "rec-" + id,
 +                                new ProxyReceiver(id, html5File));
 +
 +                        /*
 +                         * if a new batch is requested to be uploaded before the
 +                         * last one is done, any remaining ids will be replayed.
 +                         * We want to avoid a new ProxyReceiver to be made since
 +                         * it'll get a new URL, so we need to keep extra track
 +                         * on what has been sent.
 +                         * 
 +                         * See #12330.
 +                         */
 +                        sentIds.add(id);
 +
 +                        // these are cleaned from receivers once the upload has
 +                        // started
 +                    }
 +                } else {
 +                    // instructs the client side not to send the file
 +                    target.addVariable(this, "rec-" + id, (String) null);
 +                    // forget the file from subsequent paints
 +                    it.remove();
 +                }
 +            }
 +        }
 +        target.addAttribute(DragAndDropWrapperConstants.HTML5_DATA_FLAVORS,
 +                html5DataFlavors);
 +    }
 +
 +    private DropHandler dropHandler;
 +
 +    @Override
 +    public DropHandler getDropHandler() {
 +        return dropHandler;
 +    }
 +
 +    public void setDropHandler(DropHandler dropHandler) {
 +        this.dropHandler = dropHandler;
 +        markAsDirty();
 +    }
 +
 +    @Override
 +    public TargetDetails translateDropTargetDetails(
 +            Map<String, Object> clientVariables) {
 +        return new WrapperTargetDetails(clientVariables);
 +    }
 +
 +    @Override
 +    public Transferable getTransferable(final Map<String, Object> rawVariables) {
 +        return new WrapperTransferable(this, rawVariables);
 +    }
 +
 +    public void setDragStartMode(DragStartMode dragStartMode) {
 +        this.dragStartMode = dragStartMode;
 +        markAsDirty();
 +    }
 +
 +    public DragStartMode getDragStartMode() {
 +        return dragStartMode;
 +    }
 +
 +    /**
 +     * Sets the component that will be used as the drag image. Only used when
 +     * wrapper is set to {@link DragStartMode#COMPONENT_OTHER}
 +     * 
 +     * @param dragImageComponent
 +     */
 +    public void setDragImageComponent(Component dragImageComponent) {
 +        this.dragImageComponent = dragImageComponent;
 +        markAsDirty();
 +    }
 +
 +    /**
 +     * Gets the component that will be used as the drag image. Only used when
 +     * wrapper is set to {@link DragStartMode#COMPONENT_OTHER}
 +     * 
 +     * @return <code>null</code> if no component is set.
 +     */
 +    public Component getDragImageComponent() {
 +        return dragImageComponent;
 +    }
 +
 +    final class ProxyReceiver implements StreamVariable {
 +
 +        private String id;
 +        private Html5File file;
 +
 +        public ProxyReceiver(String id, Html5File file) {
 +            this.id = id;
 +            this.file = file;
 +        }
 +
 +        private boolean listenProgressOfUploadedFile;
 +
 +        @Override
 +        public OutputStream getOutputStream() {
 +            if (file.getStreamVariable() == null) {
 +                return null;
 +            }
 +            return file.getStreamVariable().getOutputStream();
 +        }
 +
 +        @Override
 +        public boolean listenProgress() {
 +            return file.getStreamVariable().listenProgress();
 +        }
 +
 +        @Override
 +        public void onProgress(StreamingProgressEvent event) {
 +            file.getStreamVariable().onProgress(
 +                    new ReceivingEventWrapper(event));
 +        }
 +
 +        @Override
 +        public void streamingStarted(StreamingStartEvent event) {
 +            listenProgressOfUploadedFile = file.getStreamVariable() != null;
 +            if (listenProgressOfUploadedFile) {
 +                file.getStreamVariable().streamingStarted(
 +                        new ReceivingEventWrapper(event));
 +            }
 +            // no need tell to the client about this receiver on next paint
 +            receivers.remove(id);
 +            sentIds.remove(id);
 +            // let the terminal GC the streamvariable and not to accept other
 +            // file uploads to this variable
 +            event.disposeStreamVariable();
 +        }
 +
 +        @Override
 +        public void streamingFinished(StreamingEndEvent event) {
 +            if (listenProgressOfUploadedFile) {
 +                file.getStreamVariable().streamingFinished(
 +                        new ReceivingEventWrapper(event));
 +            }
 +        }
 +
 +        @Override
 +        public void streamingFailed(final StreamingErrorEvent event) {
 +            if (listenProgressOfUploadedFile) {
 +                file.getStreamVariable().streamingFailed(
 +                        new ReceivingEventWrapper(event));
 +            }
 +        }
 +
 +        @Override
 +        public boolean isInterrupted() {
 +            return file.getStreamVariable().isInterrupted();
 +        }
 +
 +        /*
 +         * With XHR2 file posts we can't provide as much information from the
 +         * terminal as with multipart request. This helper class wraps the
 +         * terminal event and provides the lacking information from the
 +         * Html5File.
 +         */
 +        class ReceivingEventWrapper implements StreamingErrorEvent,
 +                StreamingEndEvent, StreamingStartEvent, StreamingProgressEvent {
 +
 +            private StreamingEvent wrappedEvent;
 +
 +            ReceivingEventWrapper(StreamingEvent e) {
 +                wrappedEvent = e;
 +            }
 +
 +            @Override
 +            public String getMimeType() {
 +                return file.getType();
 +            }
 +
 +            @Override
 +            public String getFileName() {
 +                return file.getFileName();
 +            }
 +
 +            @Override
 +            public long getContentLength() {
 +                return file.getFileSize();
 +            }
 +
 +            public StreamVariable getReceiver() {
 +                return ProxyReceiver.this;
 +            }
 +
 +            @Override
 +            public Exception getException() {
 +                if (wrappedEvent instanceof StreamingErrorEvent) {
 +                    return ((StreamingErrorEvent) wrappedEvent).getException();
 +                }
 +                return null;
 +            }
 +
 +            @Override
 +            public long getBytesReceived() {
 +                return wrappedEvent.getBytesReceived();
 +            }
 +
 +            /**
 +             * Calling this method has no effect. DD files are receive only once
 +             * anyway.
 +             */
 +            @Override
 +            public void disposeStreamVariable() {
 +
 +            }
 +        }
 +
 +    }
 +
 +    @Override
 +    public void readDesign(Element design, DesignContext designContext) {
 +        super.readDesign(design, designContext);
 +
 +        for (Element child : design.children()) {
 +            Component component = designContext.readDesign(child);
 +            if (getDragStartMode() == DragStartMode.COMPONENT_OTHER
 +                    && child.hasAttr(":drag-image")) {
 +                setDragImageComponent(component);
 +            } else if (getCompositionRoot() == null) {
 +                setCompositionRoot(component);
 +            }
 +        }
 +    }
 +
 +    @Override
 +    public void writeDesign(Element design, DesignContext designContext) {
 +        super.writeDesign(design, designContext);
 +
 +        design.appendChild(designContext.createElement(getCompositionRoot()));
 +        if (getDragStartMode() == DragStartMode.COMPONENT_OTHER) {
 +            Element child = designContext
 +                    .createElement(getDragImageComponent());
 +            child.attr(":drag-image", true);
 +            design.appendChild(child);
 +        }
 +    }
 +}
index f6f711d658c7018c57090a3c0e5fe7bf17698acf,0000000000000000000000000000000000000000..c817300e53216ae61c213dc6a9253f2933cdcd43
mode 100644,000000..100644
--- /dev/null
@@@ -1,80 -1,0 +1,80 @@@
-         setMargin(new MarginInfo(true, false, true, false));
 +/*
 + * Copyright 2000-2014 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 com.vaadin.shared.ui.MarginInfo;
 +import com.vaadin.shared.ui.orderedlayout.FormLayoutState;
 +
 +/**
 + * FormLayout is used by {@link Form} to layout fields. It may also be used
 + * separately without {@link Form}.
 + * 
 + * FormLayout is a close relative of {@link VerticalLayout}, but in FormLayout
 + * captions are rendered to the left of their respective components. Required
 + * and validation indicators are shown between the captions and the fields.
 + * 
 + * FormLayout by default has component spacing on. Also margin top and margin
 + * bottom are by default on.
 + * 
 + */
 +public class FormLayout extends AbstractOrderedLayout {
 +
 +    public FormLayout() {
 +        super();
 +        setSpacing(true);
++        setMargin(new MarginInfo(true, false));
 +        setWidth(100, UNITS_PERCENTAGE);
 +    }
 +
 +    /**
 +     * Constructs a FormLayout and adds the given components to it.
 +     * 
 +     * @see AbstractOrderedLayout#addComponents(Component...)
 +     * 
 +     * @param children
 +     *            Components to add to the FormLayout
 +     */
 +    public FormLayout(Component... children) {
 +        this();
 +        addComponents(children);
 +    }
 +
 +    /**
 +     * @deprecated This method currently has no effect as expand ratios are not
 +     *             implemented in FormLayout
 +     */
 +    @Override
 +    @Deprecated
 +    public void setExpandRatio(Component component, float ratio) {
 +        super.setExpandRatio(component, ratio);
 +    }
 +
 +    /**
 +     * @deprecated This method currently has no effect as expand ratios are not
 +     *             implemented in FormLayout
 +     */
 +    @Override
 +    @Deprecated
 +    public float getExpandRatio(Component component) {
 +        return super.getExpandRatio(component);
 +    }
 +
 +    @Override
 +    protected FormLayoutState getState() {
 +        return (FormLayoutState) super.getState();
 +    }
 +}
index 40746726754743e3c5457730d1fd8bda29cbfee2,0000000000000000000000000000000000000000..036fe8b7567b4bc7bacae6f63cb30fa9cbab6ab6
mode 100644,000000..100644
--- /dev/null
@@@ -1,7334 -1,0 +1,7339 @@@
-      * Sets the grid data source.
-      * 
 +/*
 + * Copyright 2000-2014 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.lang.reflect.Type;
 +import java.util.ArrayList;
 +import java.util.Arrays;
 +import java.util.Collection;
 +import java.util.Collections;
 +import java.util.EnumSet;
 +import java.util.HashMap;
 +import java.util.HashSet;
 +import java.util.Iterator;
 +import java.util.LinkedHashMap;
 +import java.util.LinkedHashSet;
 +import java.util.LinkedList;
 +import java.util.List;
 +import java.util.Locale;
 +import java.util.Map;
 +import java.util.Map.Entry;
 +import java.util.Set;
 +import java.util.logging.Level;
 +import java.util.logging.Logger;
 +
 +import org.jsoup.nodes.Attributes;
 +import org.jsoup.nodes.Element;
 +import org.jsoup.select.Elements;
 +
 +import com.vaadin.data.Container;
 +import com.vaadin.data.Container.Indexed;
 +import com.vaadin.data.Container.ItemSetChangeEvent;
 +import com.vaadin.data.Container.ItemSetChangeListener;
 +import com.vaadin.data.Container.ItemSetChangeNotifier;
 +import com.vaadin.data.Container.PropertySetChangeEvent;
 +import com.vaadin.data.Container.PropertySetChangeListener;
 +import com.vaadin.data.Container.PropertySetChangeNotifier;
 +import com.vaadin.data.Container.Sortable;
 +import com.vaadin.data.Item;
 +import com.vaadin.data.Property;
 +import com.vaadin.data.Validator.InvalidValueException;
 +import com.vaadin.data.fieldgroup.DefaultFieldGroupFieldFactory;
 +import com.vaadin.data.fieldgroup.FieldGroup;
 +import com.vaadin.data.fieldgroup.FieldGroup.CommitException;
 +import com.vaadin.data.fieldgroup.FieldGroupFieldFactory;
 +import com.vaadin.data.sort.Sort;
 +import com.vaadin.data.sort.SortOrder;
 +import com.vaadin.data.util.IndexedContainer;
 +import com.vaadin.data.util.converter.Converter;
 +import com.vaadin.data.util.converter.ConverterUtil;
 +import com.vaadin.event.ContextClickEvent;
 +import com.vaadin.event.ItemClickEvent;
 +import com.vaadin.event.ItemClickEvent.ItemClickListener;
 +import com.vaadin.event.ItemClickEvent.ItemClickNotifier;
 +import com.vaadin.event.SelectionEvent;
 +import com.vaadin.event.SelectionEvent.SelectionListener;
 +import com.vaadin.event.SelectionEvent.SelectionNotifier;
 +import com.vaadin.event.SortEvent;
 +import com.vaadin.event.SortEvent.SortListener;
 +import com.vaadin.event.SortEvent.SortNotifier;
 +import com.vaadin.server.AbstractClientConnector;
 +import com.vaadin.server.AbstractExtension;
 +import com.vaadin.server.EncodeResult;
 +import com.vaadin.server.ErrorMessage;
 +import com.vaadin.server.Extension;
 +import com.vaadin.server.JsonCodec;
 +import com.vaadin.server.KeyMapper;
 +import com.vaadin.server.VaadinSession;
 +import com.vaadin.server.communication.data.DataGenerator;
 +import com.vaadin.server.communication.data.RpcDataProviderExtension;
 +import com.vaadin.shared.MouseEventDetails;
 +import com.vaadin.shared.data.sort.SortDirection;
 +import com.vaadin.shared.ui.grid.EditorClientRpc;
 +import com.vaadin.shared.ui.grid.EditorServerRpc;
 +import com.vaadin.shared.ui.grid.GridClientRpc;
 +import com.vaadin.shared.ui.grid.GridColumnState;
 +import com.vaadin.shared.ui.grid.GridConstants;
 +import com.vaadin.shared.ui.grid.GridConstants.Section;
 +import com.vaadin.shared.ui.grid.GridServerRpc;
 +import com.vaadin.shared.ui.grid.GridState;
 +import com.vaadin.shared.ui.grid.GridStaticCellType;
 +import com.vaadin.shared.ui.grid.GridStaticSectionState;
 +import com.vaadin.shared.ui.grid.GridStaticSectionState.CellState;
 +import com.vaadin.shared.ui.grid.GridStaticSectionState.RowState;
 +import com.vaadin.shared.ui.grid.HeightMode;
 +import com.vaadin.shared.ui.grid.ScrollDestination;
 +import com.vaadin.shared.ui.grid.selection.MultiSelectionModelServerRpc;
 +import com.vaadin.shared.ui.grid.selection.MultiSelectionModelState;
 +import com.vaadin.shared.ui.grid.selection.SingleSelectionModelServerRpc;
 +import com.vaadin.shared.ui.grid.selection.SingleSelectionModelState;
 +import com.vaadin.shared.util.SharedUtil;
 +import com.vaadin.ui.declarative.DesignAttributeHandler;
 +import com.vaadin.ui.declarative.DesignContext;
 +import com.vaadin.ui.declarative.DesignException;
 +import com.vaadin.ui.declarative.DesignFormatter;
 +import com.vaadin.ui.renderers.HtmlRenderer;
 +import com.vaadin.ui.renderers.Renderer;
 +import com.vaadin.ui.renderers.TextRenderer;
 +import com.vaadin.util.ReflectTools;
 +
 +import elemental.json.Json;
 +import elemental.json.JsonObject;
 +import elemental.json.JsonValue;
 +
 +/**
 + * A grid component for displaying tabular data.
 + * <p>
 + * Grid is always bound to a {@link Container.Indexed}, but is not a
 + * {@code Container} of any kind in of itself. The contents of the given
 + * Container is displayed with the help of {@link Renderer Renderers}.
 + * 
 + * <h3 id="grid-headers-and-footers">Headers and Footers</h3>
 + * <p>
 + * 
 + * 
 + * <h3 id="grid-converters-and-renderers">Converters and Renderers</h3>
 + * <p>
 + * Each column has its own {@link Renderer} that displays data into something
 + * that can be displayed in the browser. That data is first converted with a
 + * {@link com.vaadin.data.util.converter.Converter Converter} into something
 + * that the Renderer can process. This can also be an implicit step - if a
 + * column has a simple data type, like a String, no explicit assignment is
 + * needed.
 + * <p>
 + * Usually a renderer takes some kind of object, and converts it into a
 + * HTML-formatted string.
 + * <p>
 + * <code><pre>
 + * Grid grid = new Grid(myContainer);
 + * Column column = grid.getColumn(STRING_DATE_PROPERTY);
 + * column.setConverter(new StringToDateConverter());
 + * column.setRenderer(new MyColorfulDateRenderer());
 + * </pre></code>
 + * 
 + * <h3 id="grid-lazyloading">Lazy Loading</h3>
 + * <p>
 + * The data is accessed as it is needed by Grid and not any sooner. In other
 + * words, if the given Container is huge, but only the first few rows are
 + * displayed to the user, only those (and a few more, for caching purposes) are
 + * accessed.
 + * 
 + * <h3 id="grid-selection-modes-and-models">Selection Modes and Models</h3>
 + * <p>
 + * Grid supports three selection <em>{@link SelectionMode modes}</em> (single,
 + * multi, none), and comes bundled with one
 + * <em>{@link SelectionModel model}</em> for each of the modes. The distinction
 + * between a selection mode and selection model is as follows: a <em>mode</em>
 + * essentially says whether you can have one, many or no rows selected. The
 + * model, however, has the behavioral details of each. A single selection model
 + * may require that the user deselects one row before selecting another one. A
 + * variant of a multiselect might have a configurable maximum of rows that may
 + * be selected. And so on.
 + * <p>
 + * <code><pre>
 + * Grid grid = new Grid(myContainer);
 + * 
 + * // uses the bundled SingleSelectionModel class
 + * grid.setSelectionMode(SelectionMode.SINGLE);
 + * 
 + * // changes the behavior to a custom selection model
 + * grid.setSelectionModel(new MyTwoSelectionModel());
 + * </pre></code>
 + * 
 + * @since 7.4
 + * @author Vaadin Ltd
 + */
 +public class Grid extends AbstractFocusable implements SelectionNotifier,
 +        SortNotifier, SelectiveRenderer, ItemClickNotifier {
 +
 +    /**
 +     * An event listener for column visibility change events in the Grid.
 +     * 
 +     * @since 7.5.0
 +     */
 +    public interface ColumnVisibilityChangeListener extends Serializable {
 +        /**
 +         * Called when a column has become hidden or unhidden.
 +         * 
 +         * @param event
 +         */
 +        void columnVisibilityChanged(ColumnVisibilityChangeEvent event);
 +    }
 +
 +    /**
 +     * An event that is fired when a column's visibility changes.
 +     * 
 +     * @since 7.5.0
 +     */
 +    public static class ColumnVisibilityChangeEvent extends Component.Event {
 +
 +        private final Column column;
 +        private final boolean userOriginated;
 +        private final boolean hidden;
 +
 +        /**
 +         * Constructor for a column visibility change event.
 +         * 
 +         * @param source
 +         *            the grid from which this event originates
 +         * @param column
 +         *            the column that changed its visibility
 +         * @param hidden
 +         *            <code>true</code> if the column was hidden,
 +         *            <code>false</code> if it became visible
 +         * @param isUserOriginated
 +         *            <code>true</code> iff the event was triggered by an UI
 +         *            interaction
 +         */
 +        public ColumnVisibilityChangeEvent(Grid source, Column column,
 +                boolean hidden, boolean isUserOriginated) {
 +            super(source);
 +            this.column = column;
 +            this.hidden = hidden;
 +            userOriginated = isUserOriginated;
 +        }
 +
 +        /**
 +         * Gets the column that became hidden or visible.
 +         * 
 +         * @return the column that became hidden or visible.
 +         * @see Column#isHidden()
 +         */
 +        public Column getColumn() {
 +            return column;
 +        }
 +
 +        /**
 +         * Was the column set hidden or visible.
 +         * 
 +         * @return <code>true</code> if the column was hidden <code>false</code>
 +         *         if it was set visible
 +         */
 +        public boolean isHidden() {
 +            return hidden;
 +        }
 +
 +        /**
 +         * Returns <code>true</code> if the column reorder was done by the user,
 +         * <code>false</code> if not and it was triggered by server side code.
 +         * 
 +         * @return <code>true</code> if event is a result of user interaction
 +         */
 +        public boolean isUserOriginated() {
 +            return userOriginated;
 +        }
 +    }
 +
 +    /**
 +     * A callback interface for generating details for a particular row in Grid.
 +     * 
 +     * @since 7.5.0
 +     * @author Vaadin Ltd
 +     * @see DetailsGenerator#NULL
 +     */
 +    public interface DetailsGenerator extends Serializable {
 +
 +        /** A details generator that provides no details */
 +        public DetailsGenerator NULL = new DetailsGenerator() {
 +            @Override
 +            public Component getDetails(RowReference rowReference) {
 +                return null;
 +            }
 +        };
 +
 +        /**
 +         * This method is called for whenever a details row needs to be shown on
 +         * the client. Grid removes all of its references to details components
 +         * when they are no longer displayed on the client-side and will
 +         * re-request once needed again.
 +         * <p>
 +         * <em>Note:</em> If a component gets generated, it may not be manually
 +         * attached anywhere. The same details component can not be displayed
 +         * for multiple different rows.
 +         * 
 +         * @param rowReference
 +         *            the reference for the row for which to generate details
 +         * @return the details for the given row, or <code>null</code> to leave
 +         *         the details empty.
 +         */
 +        Component getDetails(RowReference rowReference);
 +    }
 +
 +    /**
 +     * A class that manages details components by calling
 +     * {@link DetailsGenerator} as needed. Details components are attached by
 +     * this class when the {@link RpcDataProviderExtension} is sending data to
 +     * the client. Details components are detached and forgotten when client
 +     * informs that it has dropped the corresponding item.
 +     * 
 +     * @since 7.6.1
 +     */
 +    public final static class DetailComponentManager extends
 +            AbstractGridExtension implements DataGenerator {
 +
 +        /**
 +         * The user-defined details generator.
 +         * 
 +         * @see #setDetailsGenerator(DetailsGenerator)
 +         */
 +        private DetailsGenerator detailsGenerator;
 +
 +        /**
 +         * This map represents all details that are currently visible on the
 +         * client. Details components get destroyed once they scroll out of
 +         * view.
 +         */
 +        private final Map<Object, Component> itemIdToDetailsComponent = new HashMap<Object, Component>();
 +
 +        /**
 +         * Set of item ids that got <code>null</code> from DetailsGenerator when
 +         * {@link DetailsGenerator#getDetails(RowReference)} was called.
 +         */
 +        private final Set<Object> emptyDetails = new HashSet<Object>();
 +
 +        /**
 +         * Set of item IDs for all open details rows. Contains even the ones
 +         * that are not currently visible on the client.
 +         */
 +        private final Set<Object> openDetails = new HashSet<Object>();
 +
 +        public DetailComponentManager(Grid grid) {
 +            this(grid, DetailsGenerator.NULL);
 +        }
 +
 +        public DetailComponentManager(Grid grid,
 +                DetailsGenerator detailsGenerator) {
 +            super(grid);
 +            setDetailsGenerator(detailsGenerator);
 +        }
 +
 +        /**
 +         * Creates a details component with the help of the user-defined
 +         * {@link DetailsGenerator}.
 +         * <p>
 +         * This method attaches created components to the parent {@link Grid}.
 +         * 
 +         * @param itemId
 +         *            the item id for which to create the details component.
 +         * @throws IllegalStateException
 +         *             if the current details generator provides a component
 +         *             that was manually attached.
 +         */
 +        private void createDetails(Object itemId) throws IllegalStateException {
 +            assert itemId != null : "itemId was null";
 +
 +            if (itemIdToDetailsComponent.containsKey(itemId)
 +                    || emptyDetails.contains(itemId)) {
 +                // Don't overwrite existing components
 +                return;
 +            }
 +
 +            RowReference rowReference = new RowReference(getParentGrid());
 +            rowReference.set(itemId);
 +
 +            DetailsGenerator detailsGenerator = getParentGrid()
 +                    .getDetailsGenerator();
 +            Component details = detailsGenerator.getDetails(rowReference);
 +            if (details != null) {
 +                if (details.getParent() != null) {
 +                    String name = detailsGenerator.getClass().getName();
 +                    throw new IllegalStateException(name
 +                            + " generated a details component that already "
 +                            + "was attached. (itemId: " + itemId
 +                            + ", component: " + details + ")");
 +                }
 +
 +                itemIdToDetailsComponent.put(itemId, details);
 +
 +                addComponentToGrid(details);
 +
 +                assert !emptyDetails.contains(itemId) : "Bookeeping thinks "
 +                        + "itemId is empty even though we just created a "
 +                        + "component for it (" + itemId + ")";
 +            } else {
 +                emptyDetails.add(itemId);
 +            }
 +
 +        }
 +
 +        /**
 +         * Destroys a details component correctly.
 +         * <p>
 +         * This method will detach the component from parent {@link Grid}.
 +         * 
 +         * @param itemId
 +         *            the item id for which to destroy the details component
 +         */
 +        private void destroyDetails(Object itemId) {
 +            emptyDetails.remove(itemId);
 +
 +            Component removedComponent = itemIdToDetailsComponent
 +                    .remove(itemId);
 +            if (removedComponent == null) {
 +                return;
 +            }
 +
 +            removeComponentFromGrid(removedComponent);
 +        }
 +
 +        /**
 +         * Recreates all visible details components.
 +         */
 +        public void refreshDetails() {
 +            Set<Object> visibleItemIds = new HashSet<Object>(
 +                    itemIdToDetailsComponent.keySet());
 +            for (Object itemId : visibleItemIds) {
 +                destroyDetails(itemId);
 +                createDetails(itemId);
 +                refreshRow(itemId);
 +            }
 +        }
 +
 +        /**
 +         * Sets details visiblity status of given item id.
 +         * 
 +         * @param itemId
 +         *            item id to set
 +         * @param visible
 +         *            <code>true</code> if visible; <code>false</code> if not
 +         */
 +        public void setDetailsVisible(Object itemId, boolean visible) {
 +            if ((visible && openDetails.contains(itemId))
 +                    || (!visible && !openDetails.contains(itemId))) {
 +                return;
 +            }
 +
 +            if (visible) {
 +                openDetails.add(itemId);
 +                refreshRow(itemId);
 +            } else {
 +                openDetails.remove(itemId);
 +                destroyDetails(itemId);
 +                refreshRow(itemId);
 +            }
 +        }
 +
 +        @Override
 +        public void generateData(Object itemId, Item item, JsonObject rowData) {
 +            // DetailComponentManager should not send anything if details
 +            // generator is the default null version.
 +            if (openDetails.contains(itemId)
 +                    && !detailsGenerator.equals(DetailsGenerator.NULL)) {
 +                // Double check to be sure details component exists.
 +                createDetails(itemId);
 +
 +                Component detailsComponent = itemIdToDetailsComponent
 +                        .get(itemId);
 +                rowData.put(
 +                        GridState.JSONKEY_DETAILS_VISIBLE,
 +                        (detailsComponent != null ? detailsComponent
 +                                .getConnectorId() : ""));
 +            }
 +        }
 +
 +        @Override
 +        public void destroyData(Object itemId) {
 +            if (openDetails.contains(itemId)) {
 +                destroyDetails(itemId);
 +            }
 +        }
 +
 +        /**
 +         * Sets a new details generator for row details.
 +         * <p>
 +         * The currently opened row details will be re-rendered.
 +         * 
 +         * @param detailsGenerator
 +         *            the details generator to set
 +         * @throws IllegalArgumentException
 +         *             if detailsGenerator is <code>null</code>;
 +         */
 +        public void setDetailsGenerator(DetailsGenerator detailsGenerator)
 +                throws IllegalArgumentException {
 +            if (detailsGenerator == null) {
 +                throw new IllegalArgumentException(
 +                        "Details generator may not be null");
 +            } else if (detailsGenerator == this.detailsGenerator) {
 +                return;
 +            }
 +
 +            this.detailsGenerator = detailsGenerator;
 +
 +            refreshDetails();
 +        }
 +
 +        /**
 +         * Gets the current details generator for row details.
 +         * 
 +         * @return the detailsGenerator the current details generator
 +         */
 +        public DetailsGenerator getDetailsGenerator() {
 +            return detailsGenerator;
 +        }
 +
 +        /**
 +         * Checks whether details are visible for the given item.
 +         * 
 +         * @param itemId
 +         *            the id of the item for which to check details visibility
 +         * @return <code>true</code> iff the details are visible
 +         */
 +        public boolean isDetailsVisible(Object itemId) {
 +            return openDetails.contains(itemId);
 +        }
 +    }
 +
 +    /**
 +     * Custom field group that allows finding property types before an item has
 +     * been bound.
 +     */
 +    private final class CustomFieldGroup extends FieldGroup {
 +
 +        public CustomFieldGroup() {
 +            setFieldFactory(EditorFieldFactory.get());
 +        }
 +
 +        @Override
 +        protected Class<?> getPropertyType(Object propertyId)
 +                throws BindException {
 +            if (getItemDataSource() == null) {
 +                return datasource.getType(propertyId);
 +            } else {
 +                return super.getPropertyType(propertyId);
 +            }
 +        }
 +
 +        @Override
 +        protected <T extends Field> T build(String caption, Class<?> dataType,
 +                Class<T> fieldType) throws BindException {
 +            T field = super.build(caption, dataType, fieldType);
 +            if (field instanceof CheckBox) {
 +                field.setCaption(null);
 +            }
 +            return field;
 +        }
 +    }
 +
 +    /**
 +     * Field factory used by default in the editor.
 +     * 
 +     * Aims to fields of suitable type and with suitable size for use in the
 +     * editor row.
 +     */
 +    public static class EditorFieldFactory extends
 +            DefaultFieldGroupFieldFactory {
 +        private static final EditorFieldFactory INSTANCE = new EditorFieldFactory();
 +
 +        protected EditorFieldFactory() {
 +        }
 +
 +        /**
 +         * Returns the singleton instance
 +         * 
 +         * @return the singleton instance
 +         */
 +        public static EditorFieldFactory get() {
 +            return INSTANCE;
 +        }
 +
 +        @Override
 +        public <T extends Field> T createField(Class<?> type, Class<T> fieldType) {
 +            T f = super.createField(type, fieldType);
 +            if (f != null) {
 +                f.setWidth("100%");
 +            }
 +            return f;
 +        }
 +
 +        @Override
 +        protected AbstractSelect createCompatibleSelect(
 +                Class<? extends AbstractSelect> fieldType) {
 +            if (anySelect(fieldType)) {
 +                return super.createCompatibleSelect(ComboBox.class);
 +            }
 +            return super.createCompatibleSelect(fieldType);
 +        }
 +
 +        @Override
 +        protected void populateWithEnumData(AbstractSelect select,
 +                Class<? extends Enum> enumClass) {
 +            // Use enums directly and the EnumToStringConverter to be consistent
 +            // with what is shown in the Grid
 +            @SuppressWarnings("unchecked")
 +            EnumSet<?> enumSet = EnumSet.allOf(enumClass);
 +            for (Object r : enumSet) {
 +                select.addItem(r);
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Error handler for the editor
 +     */
 +    public interface EditorErrorHandler extends Serializable {
 +
 +        /**
 +         * Called when an exception occurs while the editor row is being saved
 +         * 
 +         * @param event
 +         *            An event providing more information about the error
 +         */
 +        void commitError(CommitErrorEvent event);
 +    }
 +
 +    /**
 +     * ContextClickEvent for the Grid Component.
 +     * 
 +     * @since 7.6
 +     */
 +    public static class GridContextClickEvent extends ContextClickEvent {
 +
 +        private final Object itemId;
 +        private final int rowIndex;
 +        private final Object propertyId;
 +        private final Section section;
 +
 +        public GridContextClickEvent(Grid source,
 +                MouseEventDetails mouseEventDetails, Section section,
 +                int rowIndex, Object itemId, Object propertyId) {
 +            super(source, mouseEventDetails);
 +            this.itemId = itemId;
 +            this.propertyId = propertyId;
 +            this.section = section;
 +            this.rowIndex = rowIndex;
 +        }
 +
 +        /**
 +         * Returns the item id of context clicked row.
 +         * 
 +         * @return item id of clicked row; <code>null</code> if header or footer
 +         */
 +        public Object getItemId() {
 +            return itemId;
 +        }
 +
 +        /**
 +         * Returns property id of clicked column.
 +         * 
 +         * @return property id
 +         */
 +        public Object getPropertyId() {
 +            return propertyId;
 +        }
 +
 +        /**
 +         * Return the clicked section of Grid.
 +         * 
 +         * @return section of grid
 +         */
 +        public Section getSection() {
 +            return section;
 +        }
 +
 +        /**
 +         * Returns the clicked row index relative to Grid section. In the body
 +         * of the Grid the index is the item index in the Container. Header and
 +         * Footer rows for index can be fetched with
 +         * {@link Grid#getHeaderRow(int)} and {@link Grid#getFooterRow(int)}.
 +         * 
 +         * @return row index in section
 +         */
 +        public int getRowIndex() {
 +            return rowIndex;
 +        }
 +
 +        @Override
 +        public Grid getComponent() {
 +            return (Grid) super.getComponent();
 +        }
 +    }
 +
 +    /**
 +     * An event which is fired when saving the editor fails
 +     */
 +    public static class CommitErrorEvent extends Component.Event {
 +
 +        private CommitException cause;
 +
 +        private Set<Column> errorColumns = new HashSet<Column>();
 +
 +        private String userErrorMessage;
 +
 +        public CommitErrorEvent(Grid grid, CommitException cause) {
 +            super(grid);
 +            this.cause = cause;
 +            userErrorMessage = cause.getLocalizedMessage();
 +        }
 +
 +        /**
 +         * Retrieves the cause of the failure
 +         * 
 +         * @return the cause of the failure
 +         */
 +        public CommitException getCause() {
 +            return cause;
 +        }
 +
 +        @Override
 +        public Grid getComponent() {
 +            return (Grid) super.getComponent();
 +        }
 +
 +        /**
 +         * Checks if validation exceptions caused this error
 +         * 
 +         * @return true if the problem was caused by a validation error
 +         */
 +        public boolean isValidationFailure() {
 +            return cause.getCause() instanceof InvalidValueException;
 +        }
 +
 +        /**
 +         * Marks that an error indicator should be shown for the editor of a
 +         * column.
 +         * 
 +         * @param column
 +         *            the column to show an error for
 +         */
 +        public void addErrorColumn(Column column) {
 +            errorColumns.add(column);
 +        }
 +
 +        /**
 +         * Gets all the columns that have been marked as erroneous.
 +         * 
 +         * @return an umodifiable collection of erroneous columns
 +         */
 +        public Collection<Column> getErrorColumns() {
 +            return Collections.unmodifiableCollection(errorColumns);
 +        }
 +
 +        /**
 +         * Gets the error message to show to the user.
 +         * 
 +         * @return error message to show
 +         */
 +        public String getUserErrorMessage() {
 +            return userErrorMessage;
 +        }
 +
 +        /**
 +         * Sets the error message to show to the user.
 +         * 
 +         * @param userErrorMessage
 +         *            the user error message to set
 +         */
 +        public void setUserErrorMessage(String userErrorMessage) {
 +            this.userErrorMessage = userErrorMessage;
 +        }
 +
 +    }
 +
 +    /**
 +     * An event listener for column reorder events in the Grid.
 +     * 
 +     * @since 7.5.0
 +     */
 +    public interface ColumnReorderListener extends Serializable {
 +
 +        /**
 +         * Called when the columns of the grid have been reordered.
 +         * 
 +         * @param event
 +         *            An event providing more information
 +         */
 +        void columnReorder(ColumnReorderEvent event);
 +    }
 +
 +    /**
 +     * An event that is fired when the columns are reordered.
 +     * 
 +     * @since 7.5.0
 +     */
 +    public static class ColumnReorderEvent extends Component.Event {
 +
 +        private final boolean userOriginated;
 +
 +        /**
 +         * 
 +         * @param source
 +         *            the grid where the event originated from
 +         * @param userOriginated
 +         *            <code>true</code> if event is a result of user
 +         *            interaction, <code>false</code> if from API call
 +         */
 +        public ColumnReorderEvent(Grid source, boolean userOriginated) {
 +            super(source);
 +            this.userOriginated = userOriginated;
 +        }
 +
 +        /**
 +         * Returns <code>true</code> if the column reorder was done by the user,
 +         * <code>false</code> if not and it was triggered by server side code.
 +         * 
 +         * @return <code>true</code> if event is a result of user interaction
 +         */
 +        public boolean isUserOriginated() {
 +            return userOriginated;
 +        }
 +
 +    }
 +
 +    /**
 +     * An event listener for column resize events in the Grid.
 +     * 
 +     * @since 7.6
 +     */
 +    public interface ColumnResizeListener extends Serializable {
 +
 +        /**
 +         * Called when the columns of the grid have been resized.
 +         * 
 +         * @param event
 +         *            An event providing more information
 +         */
 +        void columnResize(ColumnResizeEvent event);
 +    }
 +
 +    /**
 +     * An event that is fired when a column is resized, either programmatically
 +     * or by the user.
 +     * 
 +     * @since 7.6
 +     */
 +    public static class ColumnResizeEvent extends Component.Event {
 +
 +        private final Column column;
 +        private final boolean userOriginated;
 +
 +        /**
 +         * 
 +         * @param source
 +         *            the grid where the event originated from
 +         * @param userOriginated
 +         *            <code>true</code> if event is a result of user
 +         *            interaction, <code>false</code> if from API call
 +         */
 +        public ColumnResizeEvent(Grid source, Column column,
 +                boolean userOriginated) {
 +            super(source);
 +            this.column = column;
 +            this.userOriginated = userOriginated;
 +        }
 +
 +        /**
 +         * Returns the column that was resized.
 +         * 
 +         * @return the resized column.
 +         */
 +        public Column getColumn() {
 +            return column;
 +        }
 +
 +        /**
 +         * Returns <code>true</code> if the column resize was done by the user,
 +         * <code>false</code> if not and it was triggered by server side code.
 +         * 
 +         * @return <code>true</code> if event is a result of user interaction
 +         */
 +        public boolean isUserOriginated() {
 +            return userOriginated;
 +        }
 +
 +    }
 +
 +    /**
 +     * Interface for an editor event listener
 +     */
 +    public interface EditorListener extends Serializable {
 +
 +        public static final Method EDITOR_OPEN_METHOD = ReflectTools
 +                .findMethod(EditorListener.class, "editorOpened",
 +                        EditorOpenEvent.class);
 +        public static final Method EDITOR_MOVE_METHOD = ReflectTools
 +                .findMethod(EditorListener.class, "editorMoved",
 +                        EditorMoveEvent.class);
 +        public static final Method EDITOR_CLOSE_METHOD = ReflectTools
 +                .findMethod(EditorListener.class, "editorClosed",
 +                        EditorCloseEvent.class);
 +
 +        /**
 +         * Called when an editor is opened
 +         * 
 +         * @param e
 +         *            an editor open event object
 +         */
 +        public void editorOpened(EditorOpenEvent e);
 +
 +        /**
 +         * Called when an editor is reopened without closing it first
 +         * 
 +         * @param e
 +         *            an editor move event object
 +         */
 +        public void editorMoved(EditorMoveEvent e);
 +
 +        /**
 +         * Called when an editor is closed
 +         * 
 +         * @param e
 +         *            an editor close event object
 +         */
 +        public void editorClosed(EditorCloseEvent e);
 +
 +    }
 +
 +    /**
 +     * Base class for editor related events
 +     */
 +    public static abstract class EditorEvent extends Component.Event {
 +
 +        private Object itemID;
 +
 +        protected EditorEvent(Grid source, Object itemID) {
 +            super(source);
 +            this.itemID = itemID;
 +        }
 +
 +        /**
 +         * Get the item (row) for which this editor was opened
 +         */
 +        public Object getItem() {
 +            return itemID;
 +        }
 +
 +    }
 +
 +    /**
 +     * This event gets fired when an editor is opened
 +     */
 +    public static class EditorOpenEvent extends EditorEvent {
 +
 +        public EditorOpenEvent(Grid source, Object itemID) {
 +            super(source, itemID);
 +        }
 +    }
 +
 +    /**
 +     * This event gets fired when an editor is opened while another row is being
 +     * edited (i.e. editor focus moves elsewhere)
 +     */
 +    public static class EditorMoveEvent extends EditorEvent {
 +
 +        public EditorMoveEvent(Grid source, Object itemID) {
 +            super(source, itemID);
 +        }
 +    }
 +
 +    /**
 +     * This event gets fired when an editor is dismissed or closed by other
 +     * means.
 +     */
 +    public static class EditorCloseEvent extends EditorEvent {
 +
 +        public EditorCloseEvent(Grid source, Object itemID) {
 +            super(source, itemID);
 +        }
 +    }
 +
 +    /**
 +     * Default error handler for the editor
 +     * 
 +     */
 +    public class DefaultEditorErrorHandler implements EditorErrorHandler {
 +
 +        @Override
 +        public void commitError(CommitErrorEvent event) {
 +            Map<Field<?>, InvalidValueException> invalidFields = event
 +                    .getCause().getInvalidFields();
 +
 +            if (!invalidFields.isEmpty()) {
 +                Object firstErrorPropertyId = null;
 +                Field<?> firstErrorField = null;
 +
 +                FieldGroup fieldGroup = event.getCause().getFieldGroup();
 +                for (Column column : getColumns()) {
 +                    Object propertyId = column.getPropertyId();
 +                    Field<?> field = fieldGroup.getField(propertyId);
 +                    if (invalidFields.keySet().contains(field)) {
 +                        event.addErrorColumn(column);
 +
 +                        if (firstErrorPropertyId == null) {
 +                            firstErrorPropertyId = propertyId;
 +                            firstErrorField = field;
 +                        }
 +                    }
 +                }
 +
 +                /*
 +                 * Validation error, show first failure as
 +                 * "<Column header>: <message>"
 +                 */
 +                String caption = getColumn(firstErrorPropertyId)
 +                        .getHeaderCaption();
 +                String message = invalidFields.get(firstErrorField)
 +                        .getLocalizedMessage();
 +
 +                event.setUserErrorMessage(caption + ": " + message);
 +            } else {
 +                com.vaadin.server.ErrorEvent.findErrorHandler(Grid.this).error(
 +                        new ConnectorErrorEvent(Grid.this, event.getCause()));
 +            }
 +        }
 +
 +        private Object getFirstPropertyId(FieldGroup fieldGroup,
 +                Set<Field<?>> keySet) {
 +            for (Column c : getColumns()) {
 +                Object propertyId = c.getPropertyId();
 +                Field<?> f = fieldGroup.getField(propertyId);
 +                if (keySet.contains(f)) {
 +                    return propertyId;
 +                }
 +            }
 +            return null;
 +        }
 +    }
 +
 +    /**
 +     * Selection modes representing built-in {@link SelectionModel
 +     * SelectionModels} that come bundled with {@link Grid}.
 +     * <p>
 +     * Passing one of these enums into
 +     * {@link Grid#setSelectionMode(SelectionMode)} is equivalent to calling
 +     * {@link Grid#setSelectionModel(SelectionModel)} with one of the built-in
 +     * implementations of {@link SelectionModel}.
 +     * 
 +     * @see Grid#setSelectionMode(SelectionMode)
 +     * @see Grid#setSelectionModel(SelectionModel)
 +     */
 +    public enum SelectionMode {
 +        /** A SelectionMode that maps to {@link SingleSelectionModel} */
 +        SINGLE {
 +            @Override
 +            protected SelectionModel createModel() {
 +                return new SingleSelectionModel();
 +            }
 +
 +        },
 +
 +        /** A SelectionMode that maps to {@link MultiSelectionModel} */
 +        MULTI {
 +            @Override
 +            protected SelectionModel createModel() {
 +                return new MultiSelectionModel();
 +            }
 +        },
 +
 +        /** A SelectionMode that maps to {@link NoSelectionModel} */
 +        NONE {
 +            @Override
 +            protected SelectionModel createModel() {
 +                return new NoSelectionModel();
 +            }
 +        };
 +
 +        protected abstract SelectionModel createModel();
 +    }
 +
 +    /**
 +     * The server-side interface that controls Grid's selection state.
 +     * SelectionModel should extend {@link AbstractGridExtension}.
 +     */
 +    public interface SelectionModel extends Serializable, Extension {
 +        /**
 +         * Checks whether an item is selected or not.
 +         * 
 +         * @param itemId
 +         *            the item id to check for
 +         * @return <code>true</code> iff the item is selected
 +         */
 +        boolean isSelected(Object itemId);
 +
 +        /**
 +         * Returns a collection of all the currently selected itemIds.
 +         * 
 +         * @return a collection of all the currently selected itemIds
 +         */
 +        Collection<Object> getSelectedRows();
 +
 +        /**
 +         * Injects the current {@link Grid} instance into the SelectionModel.
 +         * This method should usually call the extend method of
 +         * {@link AbstractExtension}.
 +         * <p>
 +         * <em>Note:</em> This method should not be called manually.
 +         * 
 +         * @param grid
 +         *            the Grid in which the SelectionModel currently is, or
 +         *            <code>null</code> when a selection model is being detached
 +         *            from a Grid.
 +         */
 +        void setGrid(Grid grid);
 +
 +        /**
 +         * Resets the SelectiomModel to an initial state.
 +         * <p>
 +         * Most often this means that the selection state is cleared, but
 +         * implementations are free to interpret the "initial state" as they
 +         * wish. Some, for example, may want to keep the first selected item as
 +         * selected.
 +         */
 +        void reset();
 +
 +        /**
 +         * A SelectionModel that supports multiple selections to be made.
 +         * <p>
 +         * This interface has a contract of having the same behavior, no matter
 +         * how the selection model is interacted with. In other words, if
 +         * something is forbidden to do in e.g. the user interface, it must also
 +         * be forbidden to do in the server-side and client-side APIs.
 +         */
 +        public interface Multi extends SelectionModel {
 +
 +            /**
 +             * Marks items as selected.
 +             * <p>
 +             * This method does not clear any previous selection state, only
 +             * adds to it.
 +             * 
 +             * @param itemIds
 +             *            the itemId(s) to mark as selected
 +             * @return <code>true</code> if the selection state changed.
 +             *         <code>false</code> if all the given itemIds already were
 +             *         selected
 +             * @throws IllegalArgumentException
 +             *             if the <code>itemIds</code> varargs array is
 +             *             <code>null</code> or given itemIds don't exist in the
 +             *             container of Grid
 +             * @see #deselect(Object...)
 +             */
 +            boolean select(Object... itemIds) throws IllegalArgumentException;
 +
 +            /**
 +             * Marks items as selected.
 +             * <p>
 +             * This method does not clear any previous selection state, only
 +             * adds to it.
 +             * 
 +             * @param itemIds
 +             *            the itemIds to mark as selected
 +             * @return <code>true</code> if the selection state changed.
 +             *         <code>false</code> if all the given itemIds already were
 +             *         selected
 +             * @throws IllegalArgumentException
 +             *             if <code>itemIds</code> is <code>null</code> or given
 +             *             itemIds don't exist in the container of Grid
 +             * @see #deselect(Collection)
 +             */
 +            boolean select(Collection<?> itemIds)
 +                    throws IllegalArgumentException;
 +
 +            /**
 +             * Marks items as deselected.
 +             * 
 +             * @param itemIds
 +             *            the itemId(s) to remove from being selected
 +             * @return <code>true</code> if the selection state changed.
 +             *         <code>false</code> if none the given itemIds were
 +             *         selected previously
 +             * @throws IllegalArgumentException
 +             *             if the <code>itemIds</code> varargs array is
 +             *             <code>null</code>
 +             * @see #select(Object...)
 +             */
 +            boolean deselect(Object... itemIds) throws IllegalArgumentException;
 +
 +            /**
 +             * Marks items as deselected.
 +             * 
 +             * @param itemIds
 +             *            the itemId(s) to remove from being selected
 +             * @return <code>true</code> if the selection state changed.
 +             *         <code>false</code> if none the given itemIds were
 +             *         selected previously
 +             * @throws IllegalArgumentException
 +             *             if <code>itemIds</code> is <code>null</code>
 +             * @see #select(Collection)
 +             */
 +            boolean deselect(Collection<?> itemIds)
 +                    throws IllegalArgumentException;
 +
 +            /**
 +             * Marks all the items in the current Container as selected
 +             * 
 +             * @return <code>true</code> iff some items were previously not
 +             *         selected
 +             * @see #deselectAll()
 +             */
 +            boolean selectAll();
 +
 +            /**
 +             * Marks all the items in the current Container as deselected
 +             * 
 +             * @return <code>true</code> iff some items were previously selected
 +             * @see #selectAll()
 +             */
 +            boolean deselectAll();
 +
 +            /**
 +             * Marks items as selected while deselecting all items not in the
 +             * given Collection.
 +             * 
 +             * @param itemIds
 +             *            the itemIds to mark as selected
 +             * @return <code>true</code> if the selection state changed.
 +             *         <code>false</code> if all the given itemIds already were
 +             *         selected
 +             * @throws IllegalArgumentException
 +             *             if <code>itemIds</code> is <code>null</code> or given
 +             *             itemIds don't exist in the container of Grid
 +             */
 +            boolean setSelected(Collection<?> itemIds)
 +                    throws IllegalArgumentException;
 +
 +            /**
 +             * Marks items as selected while deselecting all items not in the
 +             * varargs array.
 +             * 
 +             * @param itemIds
 +             *            the itemIds to mark as selected
 +             * @return <code>true</code> if the selection state changed.
 +             *         <code>false</code> if all the given itemIds already were
 +             *         selected
 +             * @throws IllegalArgumentException
 +             *             if the <code>itemIds</code> varargs array is
 +             *             <code>null</code> or given itemIds don't exist in the
 +             *             container of Grid
 +             */
 +            boolean setSelected(Object... itemIds)
 +                    throws IllegalArgumentException;
 +        }
 +
 +        /**
 +         * A SelectionModel that supports for only single rows to be selected at
 +         * a time.
 +         * <p>
 +         * This interface has a contract of having the same behavior, no matter
 +         * how the selection model is interacted with. In other words, if
 +         * something is forbidden to do in e.g. the user interface, it must also
 +         * be forbidden to do in the server-side and client-side APIs.
 +         */
 +        public interface Single extends SelectionModel {
 +
 +            /**
 +             * Marks an item as selected.
 +             * 
 +             * @param itemId
 +             *            the itemId to mark as selected; <code>null</code> for
 +             *            deselect
 +             * @return <code>true</code> if the selection state changed.
 +             *         <code>false</code> if the itemId already was selected
 +             * @throws IllegalStateException
 +             *             if the selection was illegal. One such reason might
 +             *             be that the given id was null, indicating a deselect,
 +             *             but implementation doesn't allow deselecting.
 +             *             re-selecting something
 +             * @throws IllegalArgumentException
 +             *             if given itemId does not exist in the container of
 +             *             Grid
 +             */
 +            boolean select(Object itemId) throws IllegalStateException,
 +                    IllegalArgumentException;
 +
 +            /**
 +             * Gets the item id of the currently selected item.
 +             * 
 +             * @return the item id of the currently selected item, or
 +             *         <code>null</code> if nothing is selected
 +             */
 +            Object getSelectedRow();
 +
 +            /**
 +             * Sets whether it's allowed to deselect the selected row through
 +             * the UI. Deselection is allowed by default.
 +             * 
 +             * @param deselectAllowed
 +             *            <code>true</code> if the selected row can be
 +             *            deselected without selecting another row instead;
 +             *            otherwise <code>false</code>.
 +             */
 +            public void setDeselectAllowed(boolean deselectAllowed);
 +
 +            /**
 +             * Sets whether it's allowed to deselect the selected row through
 +             * the UI.
 +             * 
 +             * @return <code>true</code> if deselection is allowed; otherwise
 +             *         <code>false</code>
 +             */
 +            public boolean isDeselectAllowed();
 +        }
 +
 +        /**
 +         * A SelectionModel that does not allow for rows to be selected.
 +         * <p>
 +         * This interface has a contract of having the same behavior, no matter
 +         * how the selection model is interacted with. In other words, if the
 +         * developer is unable to select something programmatically, it is not
 +         * allowed for the end-user to select anything, either.
 +         */
 +        public interface None extends SelectionModel {
 +
 +            /**
 +             * {@inheritDoc}
 +             * 
 +             * @return always <code>false</code>.
 +             */
 +            @Override
 +            public boolean isSelected(Object itemId);
 +
 +            /**
 +             * {@inheritDoc}
 +             * 
 +             * @return always an empty collection.
 +             */
 +            @Override
 +            public Collection<Object> getSelectedRows();
 +        }
 +    }
 +
 +    /**
 +     * A base class for SelectionModels that contains some of the logic that is
 +     * reusable.
 +     */
 +    public static abstract class AbstractSelectionModel extends
 +            AbstractGridExtension implements SelectionModel, DataGenerator {
 +        protected final LinkedHashSet<Object> selection = new LinkedHashSet<Object>();
 +
 +        @Override
 +        public boolean isSelected(final Object itemId) {
 +            return selection.contains(itemId);
 +        }
 +
 +        @Override
 +        public Collection<Object> getSelectedRows() {
 +            return new ArrayList<Object>(selection);
 +        }
 +
 +        @Override
 +        public void setGrid(final Grid grid) {
 +            if (grid != null) {
 +                extend(grid);
 +            }
 +        }
 +
 +        /**
 +         * Sanity check for existence of item id.
 +         * 
 +         * @param itemId
 +         *            item id to be selected / deselected
 +         * 
 +         * @throws IllegalArgumentException
 +         *             if item Id doesn't exist in the container of Grid
 +         */
 +        protected void checkItemIdExists(Object itemId)
 +                throws IllegalArgumentException {
 +            if (!getParentGrid().getContainerDataSource().containsId(itemId)) {
 +                throw new IllegalArgumentException("Given item id (" + itemId
 +                        + ") does not exist in the container");
 +            }
 +        }
 +
 +        /**
 +         * Sanity check for existence of item ids in given collection.
 +         * 
 +         * @param itemIds
 +         *            item id collection to be selected / deselected
 +         * 
 +         * @throws IllegalArgumentException
 +         *             if at least one item id doesn't exist in the container of
 +         *             Grid
 +         */
 +        protected void checkItemIdsExist(Collection<?> itemIds)
 +                throws IllegalArgumentException {
 +            for (Object itemId : itemIds) {
 +                checkItemIdExists(itemId);
 +            }
 +        }
 +
 +        /**
 +         * Fires a {@link SelectionEvent} to all the {@link SelectionListener
 +         * SelectionListeners} currently added to the Grid in which this
 +         * SelectionModel is.
 +         * <p>
 +         * Note that this is only a helper method, and routes the call all the
 +         * way to Grid. A {@link SelectionModel} is not a
 +         * {@link SelectionNotifier}
 +         * 
 +         * @param oldSelection
 +         *            the complete {@link Collection} of the itemIds that were
 +         *            selected <em>before</em> this event happened
 +         * @param newSelection
 +         *            the complete {@link Collection} of the itemIds that are
 +         *            selected <em>after</em> this event happened
 +         */
 +        protected void fireSelectionEvent(
 +                final Collection<Object> oldSelection,
 +                final Collection<Object> newSelection) {
 +            getParentGrid().fireSelectionEvent(oldSelection, newSelection);
 +        }
 +
 +        @Override
 +        public void generateData(Object itemId, Item item, JsonObject rowData) {
 +            if (isSelected(itemId)) {
 +                rowData.put(GridState.JSONKEY_SELECTED, true);
 +            }
 +        }
 +
 +        @Override
 +        public void destroyData(Object itemId) {
 +            // NO-OP
 +        }
 +
 +        @Override
 +        protected Object getItemId(String rowKey) {
 +            return rowKey != null ? super.getItemId(rowKey) : null;
 +        }
 +    }
 +
 +    /**
 +     * A default implementation of a {@link SelectionModel.Single}
 +     */
 +    public static class SingleSelectionModel extends AbstractSelectionModel
 +            implements SelectionModel.Single {
 +
 +        @Override
 +        protected void extend(AbstractClientConnector target) {
 +            super.extend(target);
 +            registerRpc(new SingleSelectionModelServerRpc() {
 +
 +                @Override
 +                public void select(String rowKey) {
 +                    SingleSelectionModel.this.select(getItemId(rowKey), false);
 +                }
 +            });
 +        }
 +
 +        @Override
 +        public boolean select(final Object itemId) {
 +            return select(itemId, true);
 +        }
 +
 +        protected boolean select(final Object itemId, boolean refresh) {
 +            if (itemId == null) {
 +                return deselect(getSelectedRow());
 +            }
 +
 +            checkItemIdExists(itemId);
 +
 +            final Object selectedRow = getSelectedRow();
 +            final boolean modified = selection.add(itemId);
 +            if (modified) {
 +                final Collection<Object> deselected;
 +                if (selectedRow != null) {
 +                    deselectInternal(selectedRow, false, true);
 +                    deselected = Collections.singleton(selectedRow);
 +                } else {
 +                    deselected = Collections.emptySet();
 +                }
 +
 +                fireSelectionEvent(deselected, selection);
 +            }
 +
 +            if (refresh) {
 +                refreshRow(itemId);
 +            }
 +
 +            return modified;
 +        }
 +
 +        private boolean deselect(final Object itemId) {
 +            return deselectInternal(itemId, true, true);
 +        }
 +
 +        private boolean deselectInternal(final Object itemId,
 +                boolean fireEventIfNeeded, boolean refresh) {
 +            final boolean modified = selection.remove(itemId);
 +            if (modified) {
 +                if (refresh) {
 +                    refreshRow(itemId);
 +                }
 +                if (fireEventIfNeeded) {
 +                    fireSelectionEvent(Collections.singleton(itemId),
 +                            Collections.emptySet());
 +                }
 +            }
 +            return modified;
 +        }
 +
 +        @Override
 +        public Object getSelectedRow() {
 +            if (selection.isEmpty()) {
 +                return null;
 +            } else {
 +                return selection.iterator().next();
 +            }
 +        }
 +
 +        /**
 +         * Resets the selection state.
 +         * <p>
 +         * If an item is selected, it will become deselected.
 +         */
 +        @Override
 +        public void reset() {
 +            deselect(getSelectedRow());
 +        }
 +
 +        @Override
 +        public void setDeselectAllowed(boolean deselectAllowed) {
 +            getState().deselectAllowed = deselectAllowed;
 +        }
 +
 +        @Override
 +        public boolean isDeselectAllowed() {
 +            return getState().deselectAllowed;
 +        }
 +
 +        @Override
 +        protected SingleSelectionModelState getState() {
 +            return (SingleSelectionModelState) super.getState();
 +        }
 +    }
 +
 +    /**
 +     * A default implementation for a {@link SelectionModel.None}
 +     */
 +    public static class NoSelectionModel extends AbstractSelectionModel
 +            implements SelectionModel.None {
 +
 +        @Override
 +        public boolean isSelected(final Object itemId) {
 +            return false;
 +        }
 +
 +        @Override
 +        public Collection<Object> getSelectedRows() {
 +            return Collections.emptyList();
 +        }
 +
 +        /**
 +         * Semantically resets the selection model.
 +         * <p>
 +         * Effectively a no-op.
 +         */
 +        @Override
 +        public void reset() {
 +            // NOOP
 +        }
 +    }
 +
 +    /**
 +     * A default implementation of a {@link SelectionModel.Multi}
 +     */
 +    public static class MultiSelectionModel extends AbstractSelectionModel
 +            implements SelectionModel.Multi {
 +
 +        /**
 +         * The default selection size limit.
 +         * 
 +         * @see #setSelectionLimit(int)
 +         */
 +        public static final int DEFAULT_MAX_SELECTIONS = 1000;
 +
 +        private int selectionLimit = DEFAULT_MAX_SELECTIONS;
 +
 +        @Override
 +        protected void extend(AbstractClientConnector target) {
 +            super.extend(target);
 +            registerRpc(new MultiSelectionModelServerRpc() {
 +
 +                @Override
 +                public void select(List<String> rowKeys) {
 +                    List<Object> items = new ArrayList<Object>();
 +                    for (String rowKey : rowKeys) {
 +                        items.add(getItemId(rowKey));
 +                    }
 +                    MultiSelectionModel.this.select(items, false);
 +                }
 +
 +                @Override
 +                public void deselect(List<String> rowKeys) {
 +                    List<Object> items = new ArrayList<Object>();
 +                    for (String rowKey : rowKeys) {
 +                        items.add(getItemId(rowKey));
 +                    }
 +                    MultiSelectionModel.this.deselect(items, false);
 +                }
 +
 +                @Override
 +                public void selectAll() {
 +                    MultiSelectionModel.this.selectAll(false);
 +                }
 +
 +                @Override
 +                public void deselectAll() {
 +                    MultiSelectionModel.this.deselectAll(false);
 +                }
 +            });
 +        }
 +
 +        @Override
 +        public boolean select(final Object... itemIds)
 +                throws IllegalArgumentException {
 +            if (itemIds != null) {
 +                // select will fire the event
 +                return select(Arrays.asList(itemIds));
 +            } else {
 +                throw new IllegalArgumentException(
 +                        "Vararg array of itemIds may not be null");
 +            }
 +        }
 +
 +        /**
 +         * {@inheritDoc}
 +         * <p>
 +         * All items might not be selected if the limit set using
 +         * {@link #setSelectionLimit(int)} is exceeded.
 +         */
 +        @Override
 +        public boolean select(final Collection<?> itemIds)
 +                throws IllegalArgumentException {
 +            return select(itemIds, true);
 +        }
 +
 +        protected boolean select(final Collection<?> itemIds, boolean refresh) {
 +            if (itemIds == null) {
 +                throw new IllegalArgumentException("itemIds may not be null");
 +            }
 +
 +            // Sanity check
 +            checkItemIdsExist(itemIds);
 +
 +            final boolean selectionWillChange = !selection.containsAll(itemIds)
 +                    && selection.size() < selectionLimit;
 +            if (selectionWillChange) {
 +                final HashSet<Object> oldSelection = new HashSet<Object>(
 +                        selection);
 +                if (selection.size() + itemIds.size() >= selectionLimit) {
 +                    // Add one at a time if there's a risk of overflow
 +                    Iterator<?> iterator = itemIds.iterator();
 +                    while (iterator.hasNext()
 +                            && selection.size() < selectionLimit) {
 +                        selection.add(iterator.next());
 +                    }
 +                } else {
 +                    selection.addAll(itemIds);
 +                }
 +                fireSelectionEvent(oldSelection, selection);
 +            }
 +
 +            updateAllSelectedState();
 +
 +            if (refresh) {
 +                for (Object itemId : itemIds) {
 +                    refreshRow(itemId);
 +                }
 +            }
 +
 +            return selectionWillChange;
 +        }
 +
 +        /**
 +         * Sets the maximum number of rows that can be selected at once. This is
 +         * a mechanism to prevent exhausting server memory in situations where
 +         * users select lots of rows. If the limit is reached, newly selected
 +         * rows will not become recorded.
 +         * <p>
 +         * Old selections are not discarded if the current number of selected
 +         * row exceeds the new limit.
 +         * <p>
 +         * The default limit is {@value #DEFAULT_MAX_SELECTIONS} rows.
 +         * 
 +         * @param selectionLimit
 +         *            the non-negative selection limit to set
 +         * @throws IllegalArgumentException
 +         *             if the limit is negative
 +         */
 +        public void setSelectionLimit(int selectionLimit) {
 +            if (selectionLimit < 0) {
 +                throw new IllegalArgumentException(
 +                        "The selection limit must be non-negative");
 +            }
 +            this.selectionLimit = selectionLimit;
 +        }
 +
 +        /**
 +         * Gets the selection limit.
 +         * 
 +         * @see #setSelectionLimit(int)
 +         * 
 +         * @return the selection limit
 +         */
 +        public int getSelectionLimit() {
 +            return selectionLimit;
 +        }
 +
 +        @Override
 +        public boolean deselect(final Object... itemIds)
 +                throws IllegalArgumentException {
 +            if (itemIds != null) {
 +                // deselect will fire the event
 +                return deselect(Arrays.asList(itemIds));
 +            } else {
 +                throw new IllegalArgumentException(
 +                        "Vararg array of itemIds may not be null");
 +            }
 +        }
 +
 +        @Override
 +        public boolean deselect(final Collection<?> itemIds)
 +                throws IllegalArgumentException {
 +            return deselect(itemIds, true);
 +        }
 +
 +        protected boolean deselect(final Collection<?> itemIds, boolean refresh) {
 +            if (itemIds == null) {
 +                throw new IllegalArgumentException("itemIds may not be null");
 +            }
 +
 +            final boolean hasCommonElements = !Collections.disjoint(itemIds,
 +                    selection);
 +            if (hasCommonElements) {
 +                final HashSet<Object> oldSelection = new HashSet<Object>(
 +                        selection);
 +                selection.removeAll(itemIds);
 +                fireSelectionEvent(oldSelection, selection);
 +            }
 +
 +            updateAllSelectedState();
 +
 +            if (refresh) {
 +                for (Object itemId : itemIds) {
 +                    refreshRow(itemId);
 +                }
 +            }
 +
 +            return hasCommonElements;
 +        }
 +
 +        @Override
 +        public boolean selectAll() {
 +            return selectAll(true);
 +        }
 +
 +        protected boolean selectAll(boolean refresh) {
 +            // select will fire the event
 +            final Indexed container = getParentGrid().getContainerDataSource();
 +            if (container != null) {
 +                return select(container.getItemIds(), refresh);
 +            } else if (selection.isEmpty()) {
 +                return false;
 +            } else {
 +                /*
 +                 * this should never happen (no container but has a selection),
 +                 * but I guess the only theoretically correct course of
 +                 * action...
 +                 */
 +                return deselectAll(false);
 +            }
 +        }
 +
 +        @Override
 +        public boolean deselectAll() {
 +            return deselectAll(true);
 +        }
 +
 +        protected boolean deselectAll(boolean refresh) {
 +            // deselect will fire the event
 +            return deselect(getSelectedRows(), refresh);
 +        }
 +
 +        /**
 +         * {@inheritDoc}
 +         * <p>
 +         * The returned Collection is in <strong>order of selection</strong>
 +         * &ndash; the item that was first selected will be first in the
 +         * collection, and so on. Should an item have been selected twice
 +         * without being deselected in between, it will have remained in its
 +         * original position.
 +         */
 +        @Override
 +        public Collection<Object> getSelectedRows() {
 +            // overridden only for JavaDoc
 +            return super.getSelectedRows();
 +        }
 +
 +        /**
 +         * Resets the selection model.
 +         * <p>
 +         * Equivalent to calling {@link #deselectAll()}
 +         */
 +        @Override
 +        public void reset() {
 +            deselectAll();
 +        }
 +
 +        @Override
 +        public boolean setSelected(Collection<?> itemIds)
 +                throws IllegalArgumentException {
 +            if (itemIds == null) {
 +                throw new IllegalArgumentException("itemIds may not be null");
 +            }
 +
 +            checkItemIdsExist(itemIds);
 +
 +            boolean changed = false;
 +            Set<Object> selectedRows = new HashSet<Object>(itemIds);
 +            final Collection<Object> oldSelection = getSelectedRows();
 +            Set<Object> added = getDifference(selectedRows, selection);
 +            if (!added.isEmpty()) {
 +                changed = true;
 +                selection.addAll(added);
 +                for (Object id : added) {
 +                    refreshRow(id);
 +                }
 +            }
 +
 +            Set<Object> removed = getDifference(selection, selectedRows);
 +            if (!removed.isEmpty()) {
 +                changed = true;
 +                selection.removeAll(removed);
 +                for (Object id : removed) {
 +                    refreshRow(id);
 +                }
 +            }
 +
 +            if (changed) {
 +                fireSelectionEvent(oldSelection, selection);
 +            }
 +
 +            updateAllSelectedState();
 +
 +            return changed;
 +        }
 +
 +        /**
 +         * Compares two sets and returns a set containing all values that are
 +         * present in the first, but not in the second.
 +         * 
 +         * @param set1
 +         *            first item set
 +         * @param set2
 +         *            second item set
 +         * @return all values from set1 which are not present in set2
 +         */
 +        private static Set<Object> getDifference(Set<Object> set1,
 +                Set<Object> set2) {
 +            Set<Object> diff = new HashSet<Object>(set1);
 +            diff.removeAll(set2);
 +            return diff;
 +        }
 +
 +        @Override
 +        public boolean setSelected(Object... itemIds)
 +                throws IllegalArgumentException {
 +            if (itemIds != null) {
 +                return setSelected(Arrays.asList(itemIds));
 +            } else {
 +                throw new IllegalArgumentException(
 +                        "Vararg array of itemIds may not be null");
 +            }
 +        }
 +
 +        private void updateAllSelectedState() {
 +            int totalRowCount = getParentGrid().datasource.size();
 +            int rows = Math.min(totalRowCount, selectionLimit);
 +            if (getState().allSelected != selection.size() >= rows) {
 +                getState().allSelected = selection.size() >= rows;
 +            }
 +        }
 +
 +        @Override
 +        protected MultiSelectionModelState getState() {
 +            return (MultiSelectionModelState) super.getState();
 +        }
 +    }
 +
 +    /**
 +     * A data class which contains information which identifies a row in a
 +     * {@link Grid}.
 +     * <p>
 +     * Since this class follows the <code>Flyweight</code>-pattern any instance
 +     * of this object is subject to change without the user knowing it and so
 +     * should not be stored anywhere outside of the method providing these
 +     * instances.
 +     */
 +    public static class RowReference implements Serializable {
 +        private final Grid grid;
 +
 +        private Object itemId;
 +
 +        /**
 +         * Creates a new row reference for the given grid.
 +         * 
 +         * @param grid
 +         *            the grid that the row belongs to
 +         */
 +        public RowReference(Grid grid) {
 +            this.grid = grid;
 +        }
 +
 +        /**
 +         * Sets the identifying information for this row
 +         * 
 +         * @param itemId
 +         *            the item id of the row
 +         */
 +        public void set(Object itemId) {
 +            this.itemId = itemId;
 +        }
 +
 +        /**
 +         * Gets the grid that contains the referenced row.
 +         * 
 +         * @return the grid that contains referenced row
 +         */
 +        public Grid getGrid() {
 +            return grid;
 +        }
 +
 +        /**
 +         * Gets the item id of the row.
 +         * 
 +         * @return the item id of the row
 +         */
 +        public Object getItemId() {
 +            return itemId;
 +        }
 +
 +        /**
 +         * Gets the item for the row.
 +         * 
 +         * @return the item for the row
 +         */
 +        public Item getItem() {
 +            return grid.getContainerDataSource().getItem(itemId);
 +        }
 +    }
 +
 +    /**
 +     * A data class which contains information which identifies a cell in a
 +     * {@link Grid}.
 +     * <p>
 +     * Since this class follows the <code>Flyweight</code>-pattern any instance
 +     * of this object is subject to change without the user knowing it and so
 +     * should not be stored anywhere outside of the method providing these
 +     * instances.
 +     */
 +    public static class CellReference implements Serializable {
 +        private final RowReference rowReference;
 +
 +        private Object propertyId;
 +
 +        public CellReference(RowReference rowReference) {
 +            this.rowReference = rowReference;
 +        }
 +
 +        /**
 +         * Sets the identifying information for this cell
 +         * 
 +         * @param propertyId
 +         *            the property id of the column
 +         */
 +        public void set(Object propertyId) {
 +            this.propertyId = propertyId;
 +        }
 +
 +        /**
 +         * Gets the grid that contains the referenced cell.
 +         * 
 +         * @return the grid that contains referenced cell
 +         */
 +        public Grid getGrid() {
 +            return rowReference.getGrid();
 +        }
 +
 +        /**
 +         * @return the property id of the column
 +         */
 +        public Object getPropertyId() {
 +            return propertyId;
 +        }
 +
 +        /**
 +         * @return the property for the cell
 +         */
 +        public Property<?> getProperty() {
 +            return getItem().getItemProperty(propertyId);
 +        }
 +
 +        /**
 +         * Gets the item id of the row of the cell.
 +         * 
 +         * @return the item id of the row
 +         */
 +        public Object getItemId() {
 +            return rowReference.getItemId();
 +        }
 +
 +        /**
 +         * Gets the item for the row of the cell.
 +         * 
 +         * @return the item for the row
 +         */
 +        public Item getItem() {
 +            return rowReference.getItem();
 +        }
 +
 +        /**
 +         * Gets the value of the cell.
 +         * 
 +         * @return the value of the cell
 +         */
 +        public Object getValue() {
 +            return getProperty().getValue();
 +        }
 +    }
 +
 +    /**
 +     * A callback interface for generating custom style names for Grid rows.
 +     * 
 +     * @see Grid#setRowStyleGenerator(RowStyleGenerator)
 +     */
 +    public interface RowStyleGenerator extends Serializable {
 +
 +        /**
 +         * Called by Grid to generate a style name for a row.
 +         * 
 +         * @param row
 +         *            the row to generate a style for
 +         * @return the style name to add to this row, or {@code null} to not set
 +         *         any style
 +         */
 +        public String getStyle(RowReference row);
 +    }
 +
 +    /**
 +     * A callback interface for generating custom style names for Grid cells.
 +     * 
 +     * @see Grid#setCellStyleGenerator(CellStyleGenerator)
 +     */
 +    public interface CellStyleGenerator extends Serializable {
 +
 +        /**
 +         * Called by Grid to generate a style name for a column.
 +         * 
 +         * @param cell
 +         *            the cell to generate a style for
 +         * @return the style name to add to this cell, or {@code null} to not
 +         *         set any style
 +         */
 +        public String getStyle(CellReference cell);
 +    }
 +
 +    /**
 +     * A callback interface for generating optional descriptions (tooltips) for
 +     * Grid rows. If a description is generated for a row, it is used for all
 +     * the cells in the row for which a {@link CellDescriptionGenerator cell
 +     * description} is not generated.
 +     * 
 +     * @see Grid#setRowDescriptionGenerator
 +     * 
 +     * @since 7.6
 +     */
 +    public interface RowDescriptionGenerator extends Serializable {
 +
 +        /**
 +         * Called by Grid to generate a description (tooltip) for a row. The
 +         * description may contain HTML which is rendered directly; if this is
 +         * not desired the returned string must be escaped by the implementing
 +         * method.
 +         * 
 +         * @param row
 +         *            the row to generate a description for
 +         * @return the row description or {@code null} for no description
 +         */
 +        public String getDescription(RowReference row);
 +    }
 +
 +    /**
 +     * A callback interface for generating optional descriptions (tooltips) for
 +     * Grid cells. If a cell has both a {@link RowDescriptionGenerator row
 +     * description} and a cell description, the latter has precedence.
 +     * 
 +     * @see Grid#setCellDescriptionGenerator(CellDescriptionGenerator)
 +     * 
 +     * @since 7.6
 +     */
 +    public interface CellDescriptionGenerator extends Serializable {
 +
 +        /**
 +         * Called by Grid to generate a description (tooltip) for a cell. The
 +         * description may contain HTML which is rendered directly; if this is
 +         * not desired the returned string must be escaped by the implementing
 +         * method.
 +         * 
 +         * @param cell
 +         *            the cell to generate a description for
 +         * @return the cell description or {@code null} for no description
 +         */
 +        public String getDescription(CellReference cell);
 +    }
 +
 +    /**
 +     * Class for generating all row and cell related data for the essential
 +     * parts of Grid.
 +     */
 +    private class RowDataGenerator implements DataGenerator {
 +
 +        private void put(String key, String value, JsonObject object) {
 +            if (value != null && !value.isEmpty()) {
 +                object.put(key, value);
 +            }
 +        }
 +
 +        @Override
 +        public void generateData(Object itemId, Item item, JsonObject rowData) {
 +            RowReference row = new RowReference(Grid.this);
 +            row.set(itemId);
 +
 +            if (rowStyleGenerator != null) {
 +                String style = rowStyleGenerator.getStyle(row);
 +                put(GridState.JSONKEY_ROWSTYLE, style, rowData);
 +            }
 +
 +            if (rowDescriptionGenerator != null) {
 +                String description = rowDescriptionGenerator
 +                        .getDescription(row);
 +                put(GridState.JSONKEY_ROWDESCRIPTION, description, rowData);
 +
 +            }
 +
 +            JsonObject cellStyles = Json.createObject();
 +            JsonObject cellData = Json.createObject();
 +            JsonObject cellDescriptions = Json.createObject();
 +
 +            CellReference cell = new CellReference(row);
 +
 +            for (Column column : getColumns()) {
 +                cell.set(column.getPropertyId());
 +
 +                writeData(cell, cellData);
 +                writeStyles(cell, cellStyles);
 +                writeDescriptions(cell, cellDescriptions);
 +            }
 +
 +            if (cellDescriptionGenerator != null
 +                    && cellDescriptions.keys().length > 0) {
 +                rowData.put(GridState.JSONKEY_CELLDESCRIPTION, cellDescriptions);
 +            }
 +
 +            if (cellStyleGenerator != null && cellStyles.keys().length > 0) {
 +                rowData.put(GridState.JSONKEY_CELLSTYLES, cellStyles);
 +            }
 +
 +            rowData.put(GridState.JSONKEY_DATA, cellData);
 +        }
 +
 +        private void writeStyles(CellReference cell, JsonObject styles) {
 +            if (cellStyleGenerator != null) {
 +                String style = cellStyleGenerator.getStyle(cell);
 +                put(columnKeys.key(cell.getPropertyId()), style, styles);
 +            }
 +        }
 +
 +        private void writeDescriptions(CellReference cell,
 +                JsonObject descriptions) {
 +            if (cellDescriptionGenerator != null) {
 +                String description = cellDescriptionGenerator
 +                        .getDescription(cell);
 +                put(columnKeys.key(cell.getPropertyId()), description,
 +                        descriptions);
 +            }
 +        }
 +
 +        private void writeData(CellReference cell, JsonObject data) {
 +            Column column = getColumn(cell.getPropertyId());
 +            Converter<?, ?> converter = column.getConverter();
 +            Renderer<?> renderer = column.getRenderer();
 +
 +            Item item = cell.getItem();
 +            Object modelValue = item.getItemProperty(cell.getPropertyId())
 +                    .getValue();
 +
 +            data.put(columnKeys.key(cell.getPropertyId()), AbstractRenderer
 +                    .encodeValue(modelValue, renderer, converter, getLocale()));
 +        }
 +
 +        @Override
 +        public void destroyData(Object itemId) {
 +            // NO-OP
 +        }
 +    }
 +
 +    /**
 +     * Abstract base class for Grid header and footer sections.
 +     * 
 +     * @since 7.6
 +     * @param <ROWTYPE>
 +     *            the type of the rows in the section
 +     */
 +    public abstract static class StaticSection<ROWTYPE extends StaticSection.StaticRow<?>>
 +            implements Serializable {
 +
 +        /**
 +         * Abstract base class for Grid header and footer rows.
 +         * 
 +         * @param <CELLTYPE>
 +         *            the type of the cells in the row
 +         */
 +        public abstract static class StaticRow<CELLTYPE extends StaticCell>
 +                implements Serializable {
 +
 +            private RowState rowState = new RowState();
 +            protected StaticSection<?> section;
 +            private Map<Object, CELLTYPE> cells = new LinkedHashMap<Object, CELLTYPE>();
 +            private Map<Set<CELLTYPE>, CELLTYPE> cellGroups = new HashMap<Set<CELLTYPE>, CELLTYPE>();
 +
 +            protected StaticRow(StaticSection<?> section) {
 +                this.section = section;
 +            }
 +
 +            protected void addCell(Object propertyId) {
 +                CELLTYPE cell = createCell();
 +                cell.setColumnId(section.grid.getColumn(propertyId).getState().id);
 +                cells.put(propertyId, cell);
 +                rowState.cells.add(cell.getCellState());
 +            }
 +
 +            protected void removeCell(Object propertyId) {
 +                CELLTYPE cell = cells.remove(propertyId);
 +                if (cell != null) {
 +                    Set<CELLTYPE> cellGroupForCell = getCellGroupForCell(cell);
 +                    if (cellGroupForCell != null) {
 +                        removeCellFromGroup(cell, cellGroupForCell);
 +                    }
 +                    rowState.cells.remove(cell.getCellState());
 +                }
 +            }
 +
 +            private void removeCellFromGroup(CELLTYPE cell,
 +                    Set<CELLTYPE> cellGroup) {
 +                String columnId = cell.getColumnId();
 +                for (Set<String> group : rowState.cellGroups.keySet()) {
 +                    if (group.contains(columnId)) {
 +                        if (group.size() > 2) {
 +                            // Update map key correctly
 +                            CELLTYPE mergedCell = cellGroups.remove(cellGroup);
 +                            cellGroup.remove(cell);
 +                            cellGroups.put(cellGroup, mergedCell);
 +
 +                            group.remove(columnId);
 +                        } else {
 +                            rowState.cellGroups.remove(group);
 +                            cellGroups.remove(cellGroup);
 +                        }
 +                        return;
 +                    }
 +                }
 +            }
 +
 +            /**
 +             * Creates and returns a new instance of the cell type.
 +             * 
 +             * @return the created cell
 +             */
 +            protected abstract CELLTYPE createCell();
 +
 +            protected RowState getRowState() {
 +                return rowState;
 +            }
 +
 +            /**
 +             * Returns the cell for the given property id on this row. If the
 +             * column is merged returned cell is the cell for the whole group.
 +             * 
 +             * @param propertyId
 +             *            the property id of the column
 +             * @return the cell for the given property, merged cell for merged
 +             *         properties, null if not found
 +             */
 +            public CELLTYPE getCell(Object propertyId) {
 +                CELLTYPE cell = cells.get(propertyId);
 +                Set<CELLTYPE> cellGroup = getCellGroupForCell(cell);
 +                if (cellGroup != null) {
 +                    cell = cellGroups.get(cellGroup);
 +                }
 +                return cell;
 +            }
 +
 +            /**
 +             * Merges columns cells in a row
 +             * 
 +             * @param propertyIds
 +             *            The property ids of columns to merge
 +             * @return The remaining visible cell after the merge
 +             */
 +            public CELLTYPE join(Object... propertyIds) {
 +                assert propertyIds.length > 1 : "You need to merge at least 2 properties";
 +
 +                Set<CELLTYPE> cells = new HashSet<CELLTYPE>();
 +                for (int i = 0; i < propertyIds.length; ++i) {
 +                    cells.add(getCell(propertyIds[i]));
 +                }
 +
 +                return join(cells);
 +            }
 +
 +            /**
 +             * Merges columns cells in a row
 +             * 
 +             * @param cells
 +             *            The cells to merge. Must be from the same row.
 +             * @return The remaining visible cell after the merge
 +             */
 +            public CELLTYPE join(CELLTYPE... cells) {
 +                assert cells.length > 1 : "You need to merge at least 2 cells";
 +
 +                return join(new HashSet<CELLTYPE>(Arrays.asList(cells)));
 +            }
 +
 +            protected CELLTYPE join(Set<CELLTYPE> cells) {
 +                for (CELLTYPE cell : cells) {
 +                    if (getCellGroupForCell(cell) != null) {
 +                        throw new IllegalArgumentException(
 +                                "Cell already merged");
 +                    } else if (!this.cells.containsValue(cell)) {
 +                        throw new IllegalArgumentException(
 +                                "Cell does not exist on this row");
 +                    }
 +                }
 +
 +                // Create new cell data for the group
 +                CELLTYPE newCell = createCell();
 +
 +                Set<String> columnGroup = new HashSet<String>();
 +                for (CELLTYPE cell : cells) {
 +                    columnGroup.add(cell.getColumnId());
 +                }
 +                rowState.cellGroups.put(columnGroup, newCell.getCellState());
 +                cellGroups.put(cells, newCell);
 +                return newCell;
 +            }
 +
 +            private Set<CELLTYPE> getCellGroupForCell(CELLTYPE cell) {
 +                for (Set<CELLTYPE> group : cellGroups.keySet()) {
 +                    if (group.contains(cell)) {
 +                        return group;
 +                    }
 +                }
 +                return null;
 +            }
 +
 +            /**
 +             * Returns the custom style name for this row.
 +             * 
 +             * @return the style name or null if no style name has been set
 +             */
 +            public String getStyleName() {
 +                return getRowState().styleName;
 +            }
 +
 +            /**
 +             * Sets a custom style name for this row.
 +             * 
 +             * @param styleName
 +             *            the style name to set or null to not use any style
 +             *            name
 +             */
 +            public void setStyleName(String styleName) {
 +                getRowState().styleName = styleName;
 +            }
 +
 +            /**
 +             * Writes the declarative design to the given table row element.
 +             * 
 +             * @since 7.5.0
 +             * @param trElement
 +             *            Element to write design to
 +             * @param designContext
 +             *            the design context
 +             */
 +            protected void writeDesign(Element trElement,
 +                    DesignContext designContext) {
 +                Set<CELLTYPE> visited = new HashSet<CELLTYPE>();
 +                for (Grid.Column column : section.grid.getColumns()) {
 +                    CELLTYPE cell = getCell(column.getPropertyId());
 +                    if (visited.contains(cell)) {
 +                        continue;
 +                    }
 +                    visited.add(cell);
 +
 +                    Element cellElement = trElement
 +                            .appendElement(getCellTagName());
 +                    cell.writeDesign(cellElement, designContext);
 +
 +                    for (Entry<Set<CELLTYPE>, CELLTYPE> entry : cellGroups
 +                            .entrySet()) {
 +                        if (entry.getValue() == cell) {
 +                            cellElement.attr("colspan", ""
 +                                    + entry.getKey().size());
 +                            break;
 +                        }
 +                    }
 +                }
 +            }
 +
 +            /**
 +             * Reads the declarative design from the given table row element.
 +             * 
 +             * @since 7.5.0
 +             * @param trElement
 +             *            Element to read design from
 +             * @param designContext
 +             *            the design context
 +             * @throws DesignException
 +             *             if the given table row contains unexpected children
 +             */
 +            protected void readDesign(Element trElement,
 +                    DesignContext designContext) throws DesignException {
 +                Elements cellElements = trElement.children();
 +                int totalColSpans = 0;
 +                for (int i = 0; i < cellElements.size(); ++i) {
 +                    Element element = cellElements.get(i);
 +                    if (!element.tagName().equals(getCellTagName())) {
 +                        throw new DesignException(
 +                                "Unexpected element in tr while expecting "
 +                                        + getCellTagName() + ": "
 +                                        + element.tagName());
 +                    }
 +
 +                    int columnIndex = i + totalColSpans;
 +
 +                    int colspan = DesignAttributeHandler.readAttribute(
 +                            "colspan", element.attributes(), 1, int.class);
 +
 +                    Set<CELLTYPE> cells = new HashSet<CELLTYPE>();
 +                    for (int c = 0; c < colspan; ++c) {
 +                        cells.add(getCell(section.grid.getColumns()
 +                                .get(columnIndex + c).getPropertyId()));
 +                    }
 +
 +                    if (colspan > 1) {
 +                        totalColSpans += colspan - 1;
 +                        join(cells).readDesign(element, designContext);
 +                    } else {
 +                        cells.iterator().next()
 +                                .readDesign(element, designContext);
 +                    }
 +                }
 +            }
 +
 +            abstract protected String getCellTagName();
 +
 +            void detach() {
 +                for (CELLTYPE cell : cells.values()) {
 +                    cell.detach();
 +                }
 +            }
 +        }
 +
 +        /**
 +         * A header or footer cell. Has a simple textual caption.
 +         */
 +        abstract static class StaticCell implements Serializable {
 +
 +            private CellState cellState = new CellState();
 +            private StaticRow<?> row;
 +
 +            protected StaticCell(StaticRow<?> row) {
 +                this.row = row;
 +            }
 +
 +            void setColumnId(String id) {
 +                cellState.columnId = id;
 +            }
 +
 +            String getColumnId() {
 +                return cellState.columnId;
 +            }
 +
 +            /**
 +             * Gets the row where this cell is.
 +             * 
 +             * @return row for this cell
 +             */
 +            public StaticRow<?> getRow() {
 +                return row;
 +            }
 +
 +            protected CellState getCellState() {
 +                return cellState;
 +            }
 +
 +            /**
 +             * Sets the text displayed in this cell.
 +             * 
 +             * @param text
 +             *            a plain text caption
 +             */
 +            public void setText(String text) {
 +                removeComponentIfPresent();
 +                cellState.text = text;
 +                cellState.type = GridStaticCellType.TEXT;
 +                row.section.markAsDirty();
 +            }
 +
 +            /**
 +             * Returns the text displayed in this cell.
 +             * 
 +             * @return the plain text caption
 +             */
 +            public String getText() {
 +                if (cellState.type != GridStaticCellType.TEXT) {
 +                    throw new IllegalStateException(
 +                            "Cannot fetch Text from a cell with type "
 +                                    + cellState.type);
 +                }
 +                return cellState.text;
 +            }
 +
 +            /**
 +             * Returns the HTML content displayed in this cell.
 +             * 
 +             * @return the html
 +             * 
 +             */
 +            public String getHtml() {
 +                if (cellState.type != GridStaticCellType.HTML) {
 +                    throw new IllegalStateException(
 +                            "Cannot fetch HTML from a cell with type "
 +                                    + cellState.type);
 +                }
 +                return cellState.html;
 +            }
 +
 +            /**
 +             * Sets the HTML content displayed in this cell.
 +             * 
 +             * @param html
 +             *            the html to set
 +             */
 +            public void setHtml(String html) {
 +                removeComponentIfPresent();
 +                cellState.html = html;
 +                cellState.type = GridStaticCellType.HTML;
 +                row.section.markAsDirty();
 +            }
 +
 +            /**
 +             * Returns the component displayed in this cell.
 +             * 
 +             * @return the component
 +             */
 +            public Component getComponent() {
 +                if (cellState.type != GridStaticCellType.WIDGET) {
 +                    throw new IllegalStateException(
 +                            "Cannot fetch Component from a cell with type "
 +                                    + cellState.type);
 +                }
 +                return (Component) cellState.connector;
 +            }
 +
 +            /**
 +             * Sets the component displayed in this cell.
 +             * 
 +             * @param component
 +             *            the component to set
 +             */
 +            public void setComponent(Component component) {
 +                removeComponentIfPresent();
 +                component.setParent(row.section.grid);
 +                cellState.connector = component;
 +                cellState.type = GridStaticCellType.WIDGET;
 +                row.section.markAsDirty();
 +            }
 +
 +            /**
 +             * Returns the type of content stored in this cell.
 +             * 
 +             * @return cell content type
 +             */
 +            public GridStaticCellType getCellType() {
 +                return cellState.type;
 +            }
 +
 +            /**
 +             * Returns the custom style name for this cell.
 +             * 
 +             * @return the style name or null if no style name has been set
 +             */
 +            public String getStyleName() {
 +                return cellState.styleName;
 +            }
 +
 +            /**
 +             * Sets a custom style name for this cell.
 +             * 
 +             * @param styleName
 +             *            the style name to set or null to not use any style
 +             *            name
 +             */
 +            public void setStyleName(String styleName) {
 +                cellState.styleName = styleName;
 +                row.section.markAsDirty();
 +            }
 +
 +            private void removeComponentIfPresent() {
 +                Component component = (Component) cellState.connector;
 +                if (component != null) {
 +                    component.setParent(null);
 +                    cellState.connector = null;
 +                }
 +            }
 +
 +            /**
 +             * Writes the declarative design to the given table cell element.
 +             * 
 +             * @since 7.5.0
 +             * @param cellElement
 +             *            Element to write design to
 +             * @param designContext
 +             *            the design context
 +             */
 +            protected void writeDesign(Element cellElement,
 +                    DesignContext designContext) {
 +                switch (cellState.type) {
 +                case TEXT:
 +                    cellElement.attr("plain-text", true);
 +                    cellElement.appendText(getText());
 +                    break;
 +                case HTML:
 +                    cellElement.append(getHtml());
 +                    break;
 +                case WIDGET:
 +                    cellElement.appendChild(designContext
 +                            .createElement(getComponent()));
 +                    break;
 +                }
 +            }
 +
 +            /**
 +             * Reads the declarative design from the given table cell element.
 +             * 
 +             * @since 7.5.0
 +             * @param cellElement
 +             *            Element to read design from
 +             * @param designContext
 +             *            the design context
 +             */
 +            protected void readDesign(Element cellElement,
 +                    DesignContext designContext) {
 +                if (!cellElement.hasAttr("plain-text")) {
 +                    if (cellElement.children().size() > 0
 +                            && cellElement.child(0).tagName().contains("-")) {
 +                        setComponent(designContext.readDesign(cellElement
 +                                .child(0)));
 +                    } else {
 +                        setHtml(cellElement.html());
 +                    }
 +                } else {
 +                    // text â€“ need to unescape HTML entities
 +                    setText(DesignFormatter.decodeFromTextNode(cellElement
 +                            .html()));
 +                }
 +            }
 +
 +            void detach() {
 +                removeComponentIfPresent();
 +            }
 +        }
 +
 +        protected Grid grid;
 +        protected List<ROWTYPE> rows = new ArrayList<ROWTYPE>();
 +
 +        /**
 +         * Sets the visibility of the whole section.
 +         * 
 +         * @param visible
 +         *            true to show this section, false to hide
 +         */
 +        public void setVisible(boolean visible) {
 +            if (getSectionState().visible != visible) {
 +                getSectionState().visible = visible;
 +                markAsDirty();
 +            }
 +        }
 +
 +        /**
 +         * Returns the visibility of this section.
 +         * 
 +         * @return true if visible, false otherwise.
 +         */
 +        public boolean isVisible() {
 +            return getSectionState().visible;
 +        }
 +
 +        /**
 +         * Removes the row at the given position.
 +         * 
 +         * @param rowIndex
 +         *            the position of the row
 +         * 
 +         * @throws IllegalArgumentException
 +         *             if no row exists at given index
 +         * @see #removeRow(StaticRow)
 +         * @see #addRowAt(int)
 +         * @see #appendRow()
 +         * @see #prependRow()
 +         */
 +        public ROWTYPE removeRow(int rowIndex) {
 +            if (rowIndex >= rows.size() || rowIndex < 0) {
 +                throw new IllegalArgumentException("No row at given index "
 +                        + rowIndex);
 +            }
 +            ROWTYPE row = rows.remove(rowIndex);
 +            row.detach();
 +            getSectionState().rows.remove(rowIndex);
 +
 +            markAsDirty();
 +            return row;
 +        }
 +
 +        /**
 +         * Removes the given row from the section.
 +         * 
 +         * @param row
 +         *            the row to be removed
 +         * 
 +         * @throws IllegalArgumentException
 +         *             if the row does not exist in this section
 +         * @see #removeRow(int)
 +         * @see #addRowAt(int)
 +         * @see #appendRow()
 +         * @see #prependRow()
 +         */
 +        public void removeRow(ROWTYPE row) {
 +            try {
 +                removeRow(rows.indexOf(row));
 +            } catch (IndexOutOfBoundsException e) {
 +                throw new IllegalArgumentException(
 +                        "Section does not contain the given row");
 +            }
 +        }
 +
 +        /**
 +         * Gets row at given index.
 +         * 
 +         * @param rowIndex
 +         *            0 based index for row. Counted from top to bottom
 +         * @return row at given index
 +         */
 +        public ROWTYPE getRow(int rowIndex) {
 +            if (rowIndex >= rows.size() || rowIndex < 0) {
 +                throw new IllegalArgumentException("No row at given index "
 +                        + rowIndex);
 +            }
 +            return rows.get(rowIndex);
 +        }
 +
 +        /**
 +         * Adds a new row at the top of this section.
 +         * 
 +         * @return the new row
 +         * @see #appendRow()
 +         * @see #addRowAt(int)
 +         * @see #removeRow(StaticRow)
 +         * @see #removeRow(int)
 +         */
 +        public ROWTYPE prependRow() {
 +            return addRowAt(0);
 +        }
 +
 +        /**
 +         * Adds a new row at the bottom of this section.
 +         * 
 +         * @return the new row
 +         * @see #prependRow()
 +         * @see #addRowAt(int)
 +         * @see #removeRow(StaticRow)
 +         * @see #removeRow(int)
 +         */
 +        public ROWTYPE appendRow() {
 +            return addRowAt(rows.size());
 +        }
 +
 +        /**
 +         * Inserts a new row at the given position.
 +         * 
 +         * @param index
 +         *            the position at which to insert the row
 +         * @return the new row
 +         * 
 +         * @throws IndexOutOfBoundsException
 +         *             if the index is out of bounds
 +         * @see #appendRow()
 +         * @see #prependRow()
 +         * @see #removeRow(StaticRow)
 +         * @see #removeRow(int)
 +         */
 +        public ROWTYPE addRowAt(int index) {
 +            if (index > rows.size() || index < 0) {
 +                throw new IllegalArgumentException(
 +                        "Unable to add row at index " + index);
 +            }
 +            ROWTYPE row = createRow();
 +            rows.add(index, row);
 +            getSectionState().rows.add(index, row.getRowState());
 +
 +            for (Object id : grid.columns.keySet()) {
 +                row.addCell(id);
 +            }
 +
 +            markAsDirty();
 +            return row;
 +        }
 +
 +        /**
 +         * Gets the amount of rows in this section.
 +         * 
 +         * @return row count
 +         */
 +        public int getRowCount() {
 +            return rows.size();
 +        }
 +
 +        protected abstract GridStaticSectionState getSectionState();
 +
 +        protected abstract ROWTYPE createRow();
 +
 +        /**
 +         * Informs the grid that state has changed and it should be redrawn.
 +         */
 +        protected void markAsDirty() {
 +            grid.markAsDirty();
 +        }
 +
 +        /**
 +         * Removes a column for given property id from the section.
 +         * 
 +         * @param propertyId
 +         *            property to be removed
 +         */
 +        protected void removeColumn(Object propertyId) {
 +            for (ROWTYPE row : rows) {
 +                row.removeCell(propertyId);
 +            }
 +        }
 +
 +        /**
 +         * Adds a column for given property id to the section.
 +         * 
 +         * @param propertyId
 +         *            property to be added
 +         */
 +        protected void addColumn(Object propertyId) {
 +            for (ROWTYPE row : rows) {
 +                row.addCell(propertyId);
 +            }
 +        }
 +
 +        /**
 +         * Performs a sanity check that section is in correct state.
 +         * 
 +         * @throws IllegalStateException
 +         *             if merged cells are not i n continuous range
 +         */
 +        protected void sanityCheck() throws IllegalStateException {
 +            List<String> columnOrder = grid.getState().columnOrder;
 +            for (ROWTYPE row : rows) {
 +                for (Set<String> cellGroup : row.getRowState().cellGroups
 +                        .keySet()) {
 +                    if (!checkCellGroupAndOrder(columnOrder, cellGroup)) {
 +                        throw new IllegalStateException(
 +                                "Not all merged cells were in a continuous range.");
 +                    }
 +                }
 +            }
 +        }
 +
 +        private boolean checkCellGroupAndOrder(List<String> columnOrder,
 +                Set<String> cellGroup) {
 +            if (!columnOrder.containsAll(cellGroup)) {
 +                return false;
 +            }
 +
 +            for (int i = 0; i < columnOrder.size(); ++i) {
 +                if (!cellGroup.contains(columnOrder.get(i))) {
 +                    continue;
 +                }
 +
 +                for (int j = 1; j < cellGroup.size(); ++j) {
 +                    if (!cellGroup.contains(columnOrder.get(i + j))) {
 +                        return false;
 +                    }
 +                }
 +                return true;
 +            }
 +            return false;
 +        }
 +
 +        /**
 +         * Writes the declarative design to the given table section element.
 +         * 
 +         * @since 7.5.0
 +         * @param tableSectionElement
 +         *            Element to write design to
 +         * @param designContext
 +         *            the design context
 +         */
 +        protected void writeDesign(Element tableSectionElement,
 +                DesignContext designContext) {
 +            for (ROWTYPE row : rows) {
 +                row.writeDesign(tableSectionElement.appendElement("tr"),
 +                        designContext);
 +            }
 +        }
 +
 +        /**
 +         * Writes the declarative design from the given table section element.
 +         * 
 +         * @since 7.5.0
 +         * @param tableSectionElement
 +         *            Element to read design from
 +         * @param designContext
 +         *            the design context
 +         * @throws DesignException
 +         *             if the table section contains unexpected children
 +         */
 +        protected void readDesign(Element tableSectionElement,
 +                DesignContext designContext) throws DesignException {
 +            while (rows.size() > 0) {
 +                removeRow(0);
 +            }
 +
 +            for (Element row : tableSectionElement.children()) {
 +                if (!row.tagName().equals("tr")) {
 +                    throw new DesignException("Unexpected element in "
 +                            + tableSectionElement.tagName() + ": "
 +                            + row.tagName());
 +                }
 +                appendRow().readDesign(row, designContext);
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Represents the header section of a Grid.
 +     */
 +    protected static class Header extends StaticSection<HeaderRow> {
 +
 +        private HeaderRow defaultRow = null;
 +        private final GridStaticSectionState headerState = new GridStaticSectionState();
 +
 +        protected Header(Grid grid) {
 +            this.grid = grid;
 +            grid.getState(true).header = headerState;
 +            HeaderRow row = createRow();
 +            rows.add(row);
 +            setDefaultRow(row);
 +            getSectionState().rows.add(row.getRowState());
 +        }
 +
 +        /**
 +         * Sets the default row of this header. The default row is a special
 +         * header row providing a user interface for sorting columns.
 +         * 
 +         * @param row
 +         *            the new default row, or null for no default row
 +         * 
 +         * @throws IllegalArgumentException
 +         *             this header does not contain the row
 +         */
 +        public void setDefaultRow(HeaderRow row) {
 +            if (row == defaultRow) {
 +                return;
 +            }
 +
 +            if (row != null && !rows.contains(row)) {
 +                throw new IllegalArgumentException(
 +                        "Cannot set a default row that does not exist in the section");
 +            }
 +
 +            if (defaultRow != null) {
 +                defaultRow.setDefaultRow(false);
 +            }
 +
 +            if (row != null) {
 +                row.setDefaultRow(true);
 +            }
 +
 +            defaultRow = row;
 +            markAsDirty();
 +        }
 +
 +        /**
 +         * Returns the current default row of this header. The default row is a
 +         * special header row providing a user interface for sorting columns.
 +         * 
 +         * @return the default row or null if no default row set
 +         */
 +        public HeaderRow getDefaultRow() {
 +            return defaultRow;
 +        }
 +
 +        @Override
 +        protected GridStaticSectionState getSectionState() {
 +            return headerState;
 +        }
 +
 +        @Override
 +        protected HeaderRow createRow() {
 +            return new HeaderRow(this);
 +        }
 +
 +        @Override
 +        public HeaderRow removeRow(int rowIndex) {
 +            HeaderRow row = super.removeRow(rowIndex);
 +            if (row == defaultRow) {
 +                // Default Header Row was just removed.
 +                setDefaultRow(null);
 +            }
 +            return row;
 +        }
 +
 +        @Override
 +        protected void sanityCheck() throws IllegalStateException {
 +            super.sanityCheck();
 +
 +            boolean hasDefaultRow = false;
 +            for (HeaderRow row : rows) {
 +                if (row.getRowState().defaultRow) {
 +                    if (!hasDefaultRow) {
 +                        hasDefaultRow = true;
 +                    } else {
 +                        throw new IllegalStateException(
 +                                "Multiple default rows in header");
 +                    }
 +                }
 +            }
 +        }
 +
 +        @Override
 +        protected void readDesign(Element tableSectionElement,
 +                DesignContext designContext) {
 +            super.readDesign(tableSectionElement, designContext);
 +
 +            if (defaultRow == null && !rows.isEmpty()) {
 +                grid.setDefaultHeaderRow(rows.get(0));
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Represents a header row in Grid.
 +     */
 +    public static class HeaderRow extends StaticSection.StaticRow<HeaderCell> {
 +
 +        protected HeaderRow(StaticSection<?> section) {
 +            super(section);
 +        }
 +
 +        private void setDefaultRow(boolean value) {
 +            getRowState().defaultRow = value;
 +        }
 +
 +        private boolean isDefaultRow() {
 +            return getRowState().defaultRow;
 +        }
 +
 +        @Override
 +        protected HeaderCell createCell() {
 +            return new HeaderCell(this);
 +        }
 +
 +        @Override
 +        protected String getCellTagName() {
 +            return "th";
 +        }
 +
 +        @Override
 +        protected void writeDesign(Element trElement,
 +                DesignContext designContext) {
 +            super.writeDesign(trElement, designContext);
 +
 +            if (section.grid.getDefaultHeaderRow() == this) {
 +                DesignAttributeHandler.writeAttribute("default",
 +                        trElement.attributes(), true, null, boolean.class);
 +            }
 +        }
 +
 +        @Override
 +        protected void readDesign(Element trElement, DesignContext designContext) {
 +            super.readDesign(trElement, designContext);
 +
 +            boolean defaultRow = DesignAttributeHandler.readAttribute(
 +                    "default", trElement.attributes(), false, boolean.class);
 +            if (defaultRow) {
 +                section.grid.setDefaultHeaderRow(this);
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Represents a header cell in Grid. Can be a merged cell for multiple
 +     * columns.
 +     */
 +    public static class HeaderCell extends StaticSection.StaticCell {
 +
 +        protected HeaderCell(HeaderRow row) {
 +            super(row);
 +        }
 +    }
 +
 +    /**
 +     * Represents the footer section of a Grid. By default Footer is not
 +     * visible.
 +     */
 +    protected static class Footer extends StaticSection<FooterRow> {
 +
 +        private final GridStaticSectionState footerState = new GridStaticSectionState();
 +
 +        protected Footer(Grid grid) {
 +            this.grid = grid;
 +            grid.getState(true).footer = footerState;
 +        }
 +
 +        @Override
 +        protected GridStaticSectionState getSectionState() {
 +            return footerState;
 +        }
 +
 +        @Override
 +        protected FooterRow createRow() {
 +            return new FooterRow(this);
 +        }
 +
 +        @Override
 +        protected void sanityCheck() throws IllegalStateException {
 +            super.sanityCheck();
 +        }
 +    }
 +
 +    /**
 +     * Represents a footer row in Grid.
 +     */
 +    public static class FooterRow extends StaticSection.StaticRow<FooterCell> {
 +
 +        protected FooterRow(StaticSection<?> section) {
 +            super(section);
 +        }
 +
 +        @Override
 +        protected FooterCell createCell() {
 +            return new FooterCell(this);
 +        }
 +
 +        @Override
 +        protected String getCellTagName() {
 +            return "td";
 +        }
 +
 +    }
 +
 +    /**
 +     * Represents a footer cell in Grid.
 +     */
 +    public static class FooterCell extends StaticSection.StaticCell {
 +
 +        protected FooterCell(FooterRow row) {
 +            super(row);
 +        }
 +    }
 +
 +    /**
 +     * A column in the grid. Can be obtained by calling
 +     * {@link Grid#getColumn(Object propertyId)}.
 +     */
 +    public static class Column implements Serializable {
 +
 +        /**
 +         * The state of the column shared to the client
 +         */
 +        private final GridColumnState state;
 +
 +        /**
 +         * The grid this column is associated with
 +         */
 +        private final Grid grid;
 +
 +        /**
 +         * Backing property for column
 +         */
 +        private final Object propertyId;
 +
 +        private Converter<?, Object> converter;
 +
 +        /**
 +         * A check for allowing the
 +         * {@link #Column(Grid, GridColumnState, Object) constructor} to call
 +         * {@link #setConverter(Converter)} with a <code>null</code>, even if
 +         * model and renderer aren't compatible.
 +         */
 +        private boolean isFirstConverterAssignment = true;
 +
 +        /**
 +         * Internally used constructor.
 +         * 
 +         * @param grid
 +         *            The grid this column belongs to. Should not be null.
 +         * @param state
 +         *            the shared state of this column
 +         * @param propertyId
 +         *            the backing property id for this column
 +         */
 +        Column(Grid grid, GridColumnState state, Object propertyId) {
 +            this.grid = grid;
 +            this.state = state;
 +            this.propertyId = propertyId;
 +            internalSetRenderer(new TextRenderer());
 +        }
 +
 +        /**
 +         * Returns the serializable state of this column that is sent to the
 +         * client side connector.
 +         * 
 +         * @return the internal state of the column
 +         */
 +        GridColumnState getState() {
 +            return state;
 +        }
 +
 +        /**
 +         * Returns the property id for the backing property of this Column
 +         * 
 +         * @return property id
 +         */
 +        public Object getPropertyId() {
 +            return propertyId;
 +        }
 +
 +        /**
 +         * Returns the caption of the header. By default the header caption is
 +         * the property id of the column.
 +         * 
 +         * @return the text in the default row of header.
 +         * 
 +         * @throws IllegalStateException
 +         *             if the column no longer is attached to the grid
 +         */
 +        public String getHeaderCaption() throws IllegalStateException {
 +            checkColumnIsAttached();
 +
 +            return state.headerCaption;
 +        }
 +
 +        /**
 +         * Sets the caption of the header. This caption is also used as the
 +         * hiding toggle caption, unless it is explicitly set via
 +         * {@link #setHidingToggleCaption(String)}.
 +         * 
 +         * @param caption
 +         *            the text to show in the caption
 +         * @return the column itself
 +         * 
 +         * @throws IllegalStateException
 +         *             if the column is no longer attached to any grid
 +         */
 +        public Column setHeaderCaption(String caption)
 +                throws IllegalStateException {
 +            checkColumnIsAttached();
 +            if (caption == null) {
 +                caption = ""; // Render null as empty
 +            }
 +            state.headerCaption = caption;
 +
 +            HeaderRow row = grid.getHeader().getDefaultRow();
 +            if (row != null) {
 +                row.getCell(grid.getPropertyIdByColumnId(state.id)).setText(
 +                        caption);
 +            }
 +            return this;
 +        }
 +
 +        /**
 +         * Gets the caption of the hiding toggle for this column.
 +         * 
 +         * @since 7.5.0
 +         * @see #setHidingToggleCaption(String)
 +         * @return the caption for the hiding toggle for this column
 +         * @throws IllegalStateException
 +         *             if the column is no longer attached to any grid
 +         */
 +        public String getHidingToggleCaption() throws IllegalStateException {
 +            checkColumnIsAttached();
 +            return state.hidingToggleCaption;
 +        }
 +
 +        /**
 +         * Sets the caption of the hiding toggle for this column. Shown in the
 +         * toggle for this column in the grid's sidebar when the column is
 +         * {@link #isHidable() hidable}.
 +         * <p>
 +         * The default value is <code>null</code>, and in that case the column's
 +         * {@link #getHeaderCaption() header caption} is used.
 +         * <p>
 +         * <em>NOTE:</em> setting this to empty string might cause the hiding
 +         * toggle to not render correctly.
 +         * 
 +         * @since 7.5.0
 +         * @param hidingToggleCaption
 +         *            the text to show in the column hiding toggle
 +         * @return the column itself
 +         * @throws IllegalStateException
 +         *             if the column is no longer attached to any grid
 +         */
 +        public Column setHidingToggleCaption(String hidingToggleCaption)
 +                throws IllegalStateException {
 +            checkColumnIsAttached();
 +            state.hidingToggleCaption = hidingToggleCaption;
 +            grid.markAsDirty();
 +            return this;
 +        }
 +
 +        /**
 +         * Returns the width (in pixels). By default a column is 100px wide.
 +         * 
 +         * @return the width in pixels of the column
 +         * @throws IllegalStateException
 +         *             if the column is no longer attached to any grid
 +         */
 +        public double getWidth() throws IllegalStateException {
 +            checkColumnIsAttached();
 +            return state.width;
 +        }
 +
 +        /**
 +         * Sets the width (in pixels).
 +         * <p>
 +         * This overrides any configuration set by any of
 +         * {@link #setExpandRatio(int)}, {@link #setMinimumWidth(double)} or
 +         * {@link #setMaximumWidth(double)}.
 +         * 
 +         * @param pixelWidth
 +         *            the new pixel width of the column
 +         * @return the column itself
 +         * 
 +         * @throws IllegalStateException
 +         *             if the column is no longer attached to any grid
 +         * @throws IllegalArgumentException
 +         *             thrown if pixel width is less than zero
 +         */
 +        public Column setWidth(double pixelWidth) throws IllegalStateException,
 +                IllegalArgumentException {
 +            checkColumnIsAttached();
 +            if (pixelWidth < 0) {
 +                throw new IllegalArgumentException(
 +                        "Pixel width should be greated than 0 (in "
 +                                + toString() + ")");
 +            }
 +            if (state.width != pixelWidth) {
 +                state.width = pixelWidth;
 +                grid.markAsDirty();
 +                grid.fireColumnResizeEvent(this, false);
 +            }
 +            return this;
 +        }
 +
 +        /**
 +         * Returns whether this column has an undefined width.
 +         * 
 +         * @since 7.6
 +         * @return whether the width is undefined
 +         * @throws IllegalStateException
 +         *             if the column is no longer attached to any grid
 +         */
 +        public boolean isWidthUndefined() {
 +            checkColumnIsAttached();
 +            return state.width < 0;
 +        }
 +
 +        /**
 +         * Marks the column width as undefined. An undefined width means the
 +         * grid is free to resize the column based on the cell contents and
 +         * available space in the grid.
 +         * 
 +         * @return the column itself
 +         */
 +        public Column setWidthUndefined() {
 +            checkColumnIsAttached();
 +            if (!isWidthUndefined()) {
 +                state.width = -1;
 +                grid.markAsDirty();
 +                grid.fireColumnResizeEvent(this, false);
 +            }
 +            return this;
 +        }
 +
 +        /**
 +         * Checks if column is attached and throws an
 +         * {@link IllegalStateException} if it is not
 +         * 
 +         * @throws IllegalStateException
 +         *             if the column is no longer attached to any grid
 +         */
 +        protected void checkColumnIsAttached() throws IllegalStateException {
 +            if (grid.getColumnByColumnId(state.id) == null) {
 +                throw new IllegalStateException("Column no longer exists.");
 +            }
 +        }
 +
 +        /**
 +         * Sets this column as the last frozen column in its grid.
 +         * 
 +         * @return the column itself
 +         * 
 +         * @throws IllegalArgumentException
 +         *             if the column is no longer attached to any grid
 +         * @see Grid#setFrozenColumnCount(int)
 +         */
 +        public Column setLastFrozenColumn() {
 +            checkColumnIsAttached();
 +            grid.setFrozenColumnCount(grid.getState(false).columnOrder
 +                    .indexOf(getState().id) + 1);
 +            return this;
 +        }
 +
 +        /**
 +         * Sets the renderer for this column.
 +         * <p>
 +         * If a suitable converter isn't defined explicitly, the session
 +         * converter factory is used to find a compatible converter.
 +         * 
 +         * @param renderer
 +         *            the renderer to use
 +         * @return the column itself
 +         * 
 +         * @throws IllegalArgumentException
 +         *             if no compatible converter could be found
 +         * 
 +         * @see VaadinSession#getConverterFactory()
 +         * @see ConverterUtil#getConverter(Class, Class, VaadinSession)
 +         * @see #setConverter(Converter)
 +         */
 +        public Column setRenderer(Renderer<?> renderer) {
 +            if (!internalSetRenderer(renderer)) {
 +                throw new IllegalArgumentException(
 +                        "Could not find a converter for converting from the model type "
 +                                + getModelType()
 +                                + " to the renderer presentation type "
 +                                + renderer.getPresentationType() + " (in "
 +                                + toString() + ")");
 +            }
 +            return this;
 +        }
 +
 +        /**
 +         * Sets the renderer for this column and the converter used to convert
 +         * from the property value type to the renderer presentation type.
 +         * 
 +         * @param renderer
 +         *            the renderer to use, cannot be null
 +         * @param converter
 +         *            the converter to use
 +         * @return the column itself
 +         * 
 +         * @throws IllegalArgumentException
 +         *             if the renderer is already associated with a grid column
 +         */
 +        public <T> Column setRenderer(Renderer<T> renderer,
 +                Converter<? extends T, ?> converter) {
 +            if (renderer.getParent() != null) {
 +                throw new IllegalArgumentException(
 +                        "Cannot set a renderer that is already connected to a grid column (in "
 +                                + toString() + ")");
 +            }
 +
 +            if (getRenderer() != null) {
 +                grid.removeExtension(getRenderer());
 +            }
 +
 +            grid.addRenderer(renderer);
 +            state.rendererConnector = renderer;
 +            setConverter(converter);
 +            return this;
 +        }
 +
 +        /**
 +         * Sets the converter used to convert from the property value type to
 +         * the renderer presentation type.
 +         * 
 +         * @param converter
 +         *            the converter to use, or {@code null} to not use any
 +         *            converters
 +         * @return the column itself
 +         * 
 +         * @throws IllegalArgumentException
 +         *             if the types are not compatible
 +         */
 +        public Column setConverter(Converter<?, ?> converter)
 +                throws IllegalArgumentException {
 +            Class<?> modelType = getModelType();
 +            if (converter != null) {
 +                if (!converter.getModelType().isAssignableFrom(modelType)) {
 +                    throw new IllegalArgumentException(
 +                            "The converter model type "
 +                                    + converter.getModelType()
 +                                    + " is not compatible with the property type "
 +                                    + modelType + " (in " + toString() + ")");
 +
 +                } else if (!getRenderer().getPresentationType()
 +                        .isAssignableFrom(converter.getPresentationType())) {
 +                    throw new IllegalArgumentException(
 +                            "The converter presentation type "
 +                                    + converter.getPresentationType()
 +                                    + " is not compatible with the renderer presentation type "
 +                                    + getRenderer().getPresentationType()
 +                                    + " (in " + toString() + ")");
 +                }
 +            }
 +
 +            else {
 +                /*
 +                 * Since the converter is null (i.e. will be removed), we need
 +                 * to know that the renderer and model are compatible. If not,
 +                 * we can't allow for this to happen.
 +                 * 
 +                 * The constructor is allowed to call this method with null
 +                 * without any compatibility checks, therefore we have a special
 +                 * case for it.
 +                 */
 +
 +                Class<?> rendererPresentationType = getRenderer()
 +                        .getPresentationType();
 +                if (!isFirstConverterAssignment
 +                        && !rendererPresentationType
 +                                .isAssignableFrom(modelType)) {
 +                    throw new IllegalArgumentException(
 +                            "Cannot remove converter, "
 +                                    + "as renderer's presentation type "
 +                                    + rendererPresentationType.getName()
 +                                    + " and column's "
 +                                    + "model "
 +                                    + modelType.getName()
 +                                    + " type aren't "
 +                                    + "directly compatible with each other (in "
 +                                    + toString() + ")");
 +                }
 +            }
 +
 +            isFirstConverterAssignment = false;
 +
 +            @SuppressWarnings("unchecked")
 +            Converter<?, Object> castConverter = (Converter<?, Object>) converter;
 +            this.converter = castConverter;
 +
 +            return this;
 +        }
 +
 +        /**
 +         * Returns the renderer instance used by this column.
 +         * 
 +         * @return the renderer
 +         */
 +        public Renderer<?> getRenderer() {
 +            return (Renderer<?>) getState().rendererConnector;
 +        }
 +
 +        /**
 +         * Returns the converter instance used by this column.
 +         * 
 +         * @return the converter
 +         */
 +        public Converter<?, ?> getConverter() {
 +            return converter;
 +        }
 +
 +        private <T> boolean internalSetRenderer(Renderer<T> renderer) {
 +
 +            Converter<? extends T, ?> converter;
 +            if (isCompatibleWithProperty(renderer, getConverter())) {
 +                // Use the existing converter (possibly none) if types
 +                // compatible
 +                converter = (Converter<? extends T, ?>) getConverter();
 +            } else {
 +                converter = ConverterUtil.getConverter(
 +                        renderer.getPresentationType(), getModelType(),
 +                        getSession());
 +            }
 +            setRenderer(renderer, converter);
 +            return isCompatibleWithProperty(renderer, converter);
 +        }
 +
 +        private VaadinSession getSession() {
 +            UI ui = grid.getUI();
 +            return ui != null ? ui.getSession() : null;
 +        }
 +
 +        private boolean isCompatibleWithProperty(Renderer<?> renderer,
 +                Converter<?, ?> converter) {
 +            Class<?> type;
 +            if (converter == null) {
 +                type = getModelType();
 +            } else {
 +                type = converter.getPresentationType();
 +            }
 +            return renderer.getPresentationType().isAssignableFrom(type);
 +        }
 +
 +        private Class<?> getModelType() {
 +            return grid.getContainerDataSource().getType(
 +                    grid.getPropertyIdByColumnId(state.id));
 +        }
 +
 +        /**
 +         * Sets whether this column is sortable by the user. The grid can be
 +         * sorted by a sortable column by clicking or tapping the column's
 +         * default header. Programmatic sorting using the Grid#sort methods is
 +         * not affected by this setting.
 +         * 
 +         * @param sortable
 +         *            {@code true} if the user should be able to sort the
 +         *            column, {@code false} otherwise
 +         * @return the column itself
 +         * 
 +         * @throws IllegalStateException
 +         *             if the data source of the Grid does not implement
 +         *             {@link Sortable}
 +         * @throws IllegalStateException
 +         *             if the data source does not support sorting by the
 +         *             property associated with this column
 +         */
 +        public Column setSortable(boolean sortable) {
 +            checkColumnIsAttached();
 +
 +            if (sortable) {
 +                if (!(grid.datasource instanceof Sortable)) {
 +                    throw new IllegalStateException(
 +                            "Can't set column "
 +                                    + toString()
 +                                    + " sortable. The Container of Grid does not implement Sortable");
 +                } else if (!((Sortable) grid.datasource)
 +                        .getSortableContainerPropertyIds().contains(propertyId)) {
 +                    throw new IllegalStateException(
 +                            "Can't set column "
 +                                    + toString()
 +                                    + " sortable. Container doesn't support sorting by property "
 +                                    + propertyId);
 +                }
 +            }
 +
 +            state.sortable = sortable;
 +            grid.markAsDirty();
 +            return this;
 +        }
 +
 +        /**
 +         * Returns whether the user can sort the grid by this column.
 +         * <p>
 +         * <em>Note:</em> it is possible to sort by this column programmatically
 +         * using the Grid#sort methods regardless of the returned value.
 +         * 
 +         * @return {@code true} if the column is sortable by the user,
 +         *         {@code false} otherwise
 +         */
 +        public boolean isSortable() {
 +            return state.sortable;
 +        }
 +
 +        @Override
 +        public String toString() {
 +            return getClass().getSimpleName() + "[propertyId:"
 +                    + grid.getPropertyIdByColumnId(state.id) + "]";
 +        }
 +
 +        /**
 +         * Sets the ratio with which the column expands.
 +         * <p>
 +         * By default, all columns expand equally (treated as if all of them had
 +         * an expand ratio of 1). Once at least one column gets a defined expand
 +         * ratio, the implicit expand ratio is removed, and only the defined
 +         * expand ratios are taken into account.
 +         * <p>
 +         * If a column has a defined width ({@link #setWidth(double)}), it
 +         * overrides this method's effects.
 +         * <p>
 +         * <em>Example:</em> A grid with three columns, with expand ratios 0, 1
 +         * and 2, respectively. The column with a <strong>ratio of 0 is exactly
 +         * as wide as its contents requires</strong>. The column with a ratio of
 +         * 1 is as wide as it needs, <strong>plus a third of any excess
 +         * space</strong>, because we have 3 parts total, and this column
 +         * reserves only one of those. The column with a ratio of 2, is as wide
 +         * as it needs to be, <strong>plus two thirds</strong> of the excess
 +         * width.
 +         * 
 +         * @param expandRatio
 +         *            the expand ratio of this column. {@code 0} to not have it
 +         *            expand at all. A negative number to clear the expand
 +         *            value.
 +         * @throws IllegalStateException
 +         *             if the column is no longer attached to any grid
 +         * @see #setWidth(double)
 +         */
 +        public Column setExpandRatio(int expandRatio)
 +                throws IllegalStateException {
 +            checkColumnIsAttached();
 +
 +            getState().expandRatio = expandRatio;
 +            grid.markAsDirty();
 +            return this;
 +        }
 +
 +        /**
 +         * Returns the column's expand ratio.
 +         * 
 +         * @return the column's expand ratio
 +         * @see #setExpandRatio(int)
 +         */
 +        public int getExpandRatio() {
 +            return getState().expandRatio;
 +        }
 +
 +        /**
 +         * Clears the expand ratio for this column.
 +         * <p>
 +         * Equal to calling {@link #setExpandRatio(int) setExpandRatio(-1)}
 +         * 
 +         * @throws IllegalStateException
 +         *             if the column is no longer attached to any grid
 +         */
 +        public Column clearExpandRatio() throws IllegalStateException {
 +            return setExpandRatio(-1);
 +        }
 +
 +        /**
 +         * Sets the minimum width for this column.
 +         * <p>
 +         * This defines the minimum guaranteed pixel width of the column
 +         * <em>when it is set to expand</em>.
 +         * 
 +         * @throws IllegalStateException
 +         *             if the column is no longer attached to any grid
 +         * @see #setExpandRatio(int)
 +         */
 +        public Column setMinimumWidth(double pixels)
 +                throws IllegalStateException {
 +            checkColumnIsAttached();
 +
 +            final double maxwidth = getMaximumWidth();
 +            if (pixels >= 0 && pixels > maxwidth && maxwidth >= 0) {
 +                throw new IllegalArgumentException("New minimum width ("
 +                        + pixels + ") was greater than maximum width ("
 +                        + maxwidth + ")");
 +            }
 +            getState().minWidth = pixels;
 +            grid.markAsDirty();
 +            return this;
 +        }
 +
 +        /**
 +         * Return the minimum width for this column.
 +         * 
 +         * @return the minimum width for this column
 +         * @see #setMinimumWidth(double)
 +         */
 +        public double getMinimumWidth() {
 +            return getState().minWidth;
 +        }
 +
 +        /**
 +         * Sets the maximum width for this column.
 +         * <p>
 +         * This defines the maximum allowed pixel width of the column
 +         * <em>when it is set to expand</em>.
 +         * 
 +         * @param pixels
 +         *            the maximum width
 +         * @throws IllegalStateException
 +         *             if the column is no longer attached to any grid
 +         * @see #setExpandRatio(int)
 +         */
 +        public Column setMaximumWidth(double pixels) {
 +            checkColumnIsAttached();
 +
 +            final double minwidth = getMinimumWidth();
 +            if (pixels >= 0 && pixels < minwidth && minwidth >= 0) {
 +                throw new IllegalArgumentException("New maximum width ("
 +                        + pixels + ") was less than minimum width (" + minwidth
 +                        + ")");
 +            }
 +
 +            getState().maxWidth = pixels;
 +            grid.markAsDirty();
 +            return this;
 +        }
 +
 +        /**
 +         * Returns the maximum width for this column.
 +         * 
 +         * @return the maximum width for this column
 +         * @see #setMaximumWidth(double)
 +         */
 +        public double getMaximumWidth() {
 +            return getState().maxWidth;
 +        }
 +
 +        /**
 +         * Sets whether the properties corresponding to this column should be
 +         * editable when the item editor is active. By default columns are
 +         * editable.
 +         * <p>
 +         * Values in non-editable columns are currently not displayed when the
 +         * editor is active, but this will probably change in the future. They
 +         * are not automatically assigned an editor field and, if one is
 +         * manually assigned, it is not used. Columns that cannot (or should
 +         * not) be edited even in principle should be set non-editable.
 +         * 
 +         * @param editable
 +         *            {@code true} if this column should be editable,
 +         *            {@code false} otherwise
 +         * @return this column
 +         * 
 +         * @throws IllegalStateException
 +         *             if the editor is currently active
 +         * 
 +         * @see Grid#editItem(Object)
 +         * @see Grid#isEditorActive()
 +         */
 +        public Column setEditable(boolean editable) {
 +            checkColumnIsAttached();
 +            if (grid.isEditorActive()) {
 +                throw new IllegalStateException(
 +                        "Cannot change column editable status while the editor is active");
 +            }
 +            getState().editable = editable;
 +            grid.markAsDirty();
 +            return this;
 +        }
 +
 +        /**
 +         * Returns whether the properties corresponding to this column should be
 +         * editable when the item editor is active.
 +         * 
 +         * @return {@code true} if this column is editable, {@code false}
 +         *         otherwise
 +         * 
 +         * @see Grid#editItem(Object)
 +         * @see #setEditable(boolean)
 +         */
 +
 +        public boolean isEditable() {
 +            return getState().editable;
 +        }
 +
 +        /**
 +         * Sets the field component used to edit the properties in this column
 +         * when the item editor is active. If an item has not been set, then the
 +         * binding is postponed until the item is set using
 +         * {@link #editItem(Object)}.
 +         * <p>
 +         * Setting the field to <code>null</code> clears any previously set
 +         * field, causing a new field to be created the next time the item
 +         * editor is opened.
 +         * 
 +         * @param editor
 +         *            the editor field
 +         * @return this column
 +         */
 +        public Column setEditorField(Field<?> editor) {
 +            grid.setEditorField(getPropertyId(), editor);
 +            return this;
 +        }
 +
 +        /**
 +         * Returns the editor field used to edit the properties in this column
 +         * when the item editor is active. Returns null if the column is not
 +         * {@link Column#isEditable() editable}.
 +         * <p>
 +         * When {@link #editItem(Object) editItem} is called, fields are
 +         * automatically created and bound for any unbound properties.
 +         * <p>
 +         * Getting a field before the editor has been opened depends on special
 +         * support from the {@link FieldGroup} in use. Using this method with a
 +         * user-provided <code>FieldGroup</code> might cause
 +         * {@link com.vaadin.data.fieldgroup.FieldGroup.BindException
 +         * BindException} to be thrown.
 +         * 
 +         * @return the bound field; or <code>null</code> if the respective
 +         *         column is not editable
 +         * 
 +         * @throws IllegalArgumentException
 +         *             if there is no column for the provided property id
 +         * @throws FieldGroup.BindException
 +         *             if no field has been configured and there is a problem
 +         *             building or binding
 +         */
 +        public Field<?> getEditorField() {
 +            return grid.getEditorField(getPropertyId());
 +        }
 +
 +        /**
 +         * Hides or shows the column. By default columns are visible before
 +         * explicitly hiding them.
 +         * 
 +         * @since 7.5.0
 +         * @param hidden
 +         *            <code>true</code> to hide the column, <code>false</code>
 +         *            to show
 +         * @return this column
 +         */
 +        public Column setHidden(boolean hidden) {
 +            if (hidden != getState().hidden) {
 +                getState().hidden = hidden;
 +                grid.markAsDirty();
 +                grid.fireColumnVisibilityChangeEvent(this, hidden, false);
 +            }
 +            return this;
 +        }
 +
 +        /**
 +         * Returns whether this column is hidden. Default is {@code false}.
 +         * 
 +         * @since 7.5.0
 +         * @return <code>true</code> if the column is currently hidden,
 +         *         <code>false</code> otherwise
 +         */
 +        public boolean isHidden() {
 +            return getState().hidden;
 +        }
 +
 +        /**
 +         * Sets whether this column can be hidden by the user. Hidable columns
 +         * can be hidden and shown via the sidebar menu.
 +         * 
 +         * @since 7.5.0
 +         * @param hidable
 +         *            <code>true</code> iff the column may be hidable by the
 +         *            user via UI interaction
 +         * @return this column
 +         */
 +        public Column setHidable(boolean hidable) {
 +            if (hidable != getState().hidable) {
 +                getState().hidable = hidable;
 +                grid.markAsDirty();
 +            }
 +            return this;
 +        }
 +
 +        /**
 +         * Returns whether this column can be hidden by the user. Default is
 +         * {@code false}.
 +         * <p>
 +         * <em>Note:</em> the column can be programmatically hidden using
 +         * {@link #setHidden(boolean)} regardless of the returned value.
 +         * 
 +         * @since 7.5.0
 +         * @return <code>true</code> if the user can hide the column,
 +         *         <code>false</code> if not
 +         */
 +        public boolean isHidable() {
 +            return getState().hidable;
 +        }
 +
 +        /**
 +         * Sets whether this column can be resized by the user.
 +         * 
 +         * @since 7.6
 +         * @param resizable
 +         *            {@code true} if this column should be resizable,
 +         *            {@code false} otherwise
 +         */
 +        public Column setResizable(boolean resizable) {
 +            if (resizable != getState().resizable) {
 +                getState().resizable = resizable;
 +                grid.markAsDirty();
 +            }
 +            return this;
 +        }
 +
 +        /**
 +         * Returns whether this column can be resized by the user. Default is
 +         * {@code true}.
 +         * <p>
 +         * <em>Note:</em> the column can be programmatically resized using
 +         * {@link #setWidth(double)} and {@link #setWidthUndefined()} regardless
 +         * of the returned value.
 +         * 
 +         * @since 7.6
 +         * @return {@code true} if this column is resizable, {@code false}
 +         *         otherwise
 +         */
 +        public boolean isResizable() {
 +            return getState().resizable;
 +        }
 +
 +        /**
 +         * Writes the design attributes for this column into given element.
 +         * 
 +         * @since 7.5.0
 +         * 
 +         * @param design
 +         *            Element to write attributes into
 +         * 
 +         * @param designContext
 +         *            the design context
 +         */
 +        protected void writeDesign(Element design, DesignContext designContext) {
 +            Attributes attributes = design.attributes();
 +            GridColumnState def = new GridColumnState();
 +
 +            DesignAttributeHandler.writeAttribute("property-id", attributes,
 +                    getPropertyId(), null, Object.class);
 +
 +            // Sortable is a special attribute that depends on the container.
 +            DesignAttributeHandler.writeAttribute("sortable", attributes,
 +                    isSortable(), null, boolean.class);
 +            DesignAttributeHandler.writeAttribute("editable", attributes,
 +                    isEditable(), def.editable, boolean.class);
 +            DesignAttributeHandler.writeAttribute("resizable", attributes,
 +                    isResizable(), def.resizable, boolean.class);
 +
 +            DesignAttributeHandler.writeAttribute("hidable", attributes,
 +                    isHidable(), def.hidable, boolean.class);
 +            DesignAttributeHandler.writeAttribute("hidden", attributes,
 +                    isHidden(), def.hidden, boolean.class);
 +            DesignAttributeHandler.writeAttribute("hiding-toggle-caption",
 +                    attributes, getHidingToggleCaption(), null, String.class);
 +
 +            DesignAttributeHandler.writeAttribute("width", attributes,
 +                    getWidth(), def.width, Double.class);
 +            DesignAttributeHandler.writeAttribute("min-width", attributes,
 +                    getMinimumWidth(), def.minWidth, Double.class);
 +            DesignAttributeHandler.writeAttribute("max-width", attributes,
 +                    getMaximumWidth(), def.maxWidth, Double.class);
 +            DesignAttributeHandler.writeAttribute("expand", attributes,
 +                    getExpandRatio(), def.expandRatio, Integer.class);
 +        }
 +
 +        /**
 +         * Reads the design attributes for this column from given element.
 +         * 
 +         * @since 7.5.0
 +         * @param design
 +         *            Element to read attributes from
 +         * @param designContext
 +         *            the design context
 +         */
 +        protected void readDesign(Element design, DesignContext designContext) {
 +            Attributes attributes = design.attributes();
 +
 +            if (design.hasAttr("sortable")) {
 +                setSortable(DesignAttributeHandler.readAttribute("sortable",
 +                        attributes, boolean.class));
 +            }
 +            if (design.hasAttr("editable")) {
 +                setEditable(DesignAttributeHandler.readAttribute("editable",
 +                        attributes, boolean.class));
 +            }
 +            if (design.hasAttr("resizable")) {
 +                setResizable(DesignAttributeHandler.readAttribute("resizable",
 +                        attributes, boolean.class));
 +            }
 +
 +            if (design.hasAttr("hidable")) {
 +                setHidable(DesignAttributeHandler.readAttribute("hidable",
 +                        attributes, boolean.class));
 +            }
 +            if (design.hasAttr("hidden")) {
 +                setHidden(DesignAttributeHandler.readAttribute("hidden",
 +                        attributes, boolean.class));
 +            }
 +            if (design.hasAttr("hiding-toggle-caption")) {
 +                setHidingToggleCaption(DesignAttributeHandler.readAttribute(
 +                        "hiding-toggle-caption", attributes, String.class));
 +            }
 +
 +            // Read size info where necessary.
 +            if (design.hasAttr("width")) {
 +                setWidth(DesignAttributeHandler.readAttribute("width",
 +                        attributes, Double.class));
 +            }
 +            if (design.hasAttr("min-width")) {
 +                setMinimumWidth(DesignAttributeHandler.readAttribute(
 +                        "min-width", attributes, Double.class));
 +            }
 +            if (design.hasAttr("max-width")) {
 +                setMaximumWidth(DesignAttributeHandler.readAttribute(
 +                        "max-width", attributes, Double.class));
 +            }
 +            if (design.hasAttr("expand")) {
 +                if (design.attr("expand").isEmpty()) {
 +                    setExpandRatio(1);
 +                } else {
 +                    setExpandRatio(DesignAttributeHandler.readAttribute(
 +                            "expand", attributes, Integer.class));
 +                }
 +            }
 +        }
 +    }
 +
 +    /**
 +     * An abstract base class for server-side
 +     * {@link com.vaadin.ui.renderers.Renderer Grid renderers}. This class
 +     * currently extends the AbstractExtension superclass, but this fact should
 +     * be regarded as an implementation detail and subject to change in a future
 +     * major or minor Vaadin revision.
 +     * 
 +     * @param <T>
 +     *            the type this renderer knows how to present
 +     */
 +    public static abstract class AbstractRenderer<T> extends
 +            AbstractGridExtension implements Renderer<T> {
 +
 +        private final Class<T> presentationType;
 +
 +        private final String nullRepresentation;
 +
 +        protected AbstractRenderer(Class<T> presentationType,
 +                String nullRepresentation) {
 +            this.presentationType = presentationType;
 +            this.nullRepresentation = nullRepresentation;
 +        }
 +
 +        protected AbstractRenderer(Class<T> presentationType) {
 +            this(presentationType, null);
 +        }
 +
 +        /**
 +         * This method is inherited from AbstractExtension but should never be
 +         * called directly with an AbstractRenderer.
 +         */
 +        @Deprecated
 +        @Override
 +        protected Class<Grid> getSupportedParentType() {
 +            return Grid.class;
 +        }
 +
 +        /**
 +         * This method is inherited from AbstractExtension but should never be
 +         * called directly with an AbstractRenderer.
 +         */
 +        @Deprecated
 +        @Override
 +        protected void extend(AbstractClientConnector target) {
 +            super.extend(target);
 +        }
 +
 +        @Override
 +        public Class<T> getPresentationType() {
 +            return presentationType;
 +        }
 +
 +        @Override
 +        public JsonValue encode(T value) {
 +            if (value == null) {
 +                return encode(getNullRepresentation(), String.class);
 +            } else {
 +                return encode(value, getPresentationType());
 +            }
 +        }
 +
 +        /**
 +         * Null representation for the renderer
 +         * 
 +         * @return a textual representation of {@code null}
 +         */
 +        protected String getNullRepresentation() {
 +            return nullRepresentation;
 +        }
 +
 +        /**
 +         * Encodes the given value to JSON.
 +         * <p>
 +         * This is a helper method that can be invoked by an
 +         * {@link #encode(Object) encode(T)} override if serializing a value of
 +         * type other than {@link #getPresentationType() the presentation type}
 +         * is desired. For instance, a {@code Renderer<Date>} could first turn a
 +         * date value into a formatted string and return
 +         * {@code encode(dateString, String.class)}.
 +         * 
 +         * @param value
 +         *            the value to be encoded
 +         * @param type
 +         *            the type of the value
 +         * @return a JSON representation of the given value
 +         */
 +        protected <U> JsonValue encode(U value, Class<U> type) {
 +            return JsonCodec.encode(value, null, type,
 +                    getUI().getConnectorTracker()).getEncodedValue();
 +        }
 +
 +        /**
 +         * Converts and encodes the given data model property value using the
 +         * given converter and renderer. This method is public only for testing
 +         * purposes.
 +         * 
 +         * @since 7.6
 +         * @param renderer
 +         *            the renderer to use
 +         * @param converter
 +         *            the converter to use
 +         * @param modelValue
 +         *            the value to convert and encode
 +         * @param locale
 +         *            the locale to use in conversion
 +         * @return an encoded value ready to be sent to the client
 +         */
 +        public static <T> JsonValue encodeValue(Object modelValue,
 +                Renderer<T> renderer, Converter<?, ?> converter, Locale locale) {
 +            Class<T> presentationType = renderer.getPresentationType();
 +            T presentationValue;
 +
 +            if (converter == null) {
 +                try {
 +                    presentationValue = presentationType.cast(modelValue);
 +                } catch (ClassCastException e) {
 +                    if (presentationType == String.class) {
 +                        // If there is no converter, just fallback to using
 +                        // toString(). modelValue can't be null as
 +                        // Class.cast(null) will always succeed
 +                        presentationValue = (T) modelValue.toString();
 +                    } else {
 +                        throw new Converter.ConversionException(
 +                                "Unable to convert value of type "
 +                                        + modelValue.getClass().getName()
 +                                        + " to presentation type "
 +                                        + presentationType.getName()
 +                                        + ". No converter is set and the types are not compatible.");
 +                    }
 +                }
 +            } else {
 +                assert presentationType.isAssignableFrom(converter
 +                        .getPresentationType());
 +                @SuppressWarnings("unchecked")
 +                Converter<T, Object> safeConverter = (Converter<T, Object>) converter;
 +                presentationValue = safeConverter
 +                        .convertToPresentation(modelValue,
 +                                safeConverter.getPresentationType(), locale);
 +            }
 +
 +            JsonValue encodedValue;
 +            try {
 +                encodedValue = renderer.encode(presentationValue);
 +            } catch (Exception e) {
 +                getLogger().log(Level.SEVERE, "Unable to encode data", e);
 +                encodedValue = renderer.encode(null);
 +            }
 +
 +            return encodedValue;
 +        }
 +
 +        private static Logger getLogger() {
 +            return Logger.getLogger(AbstractRenderer.class.getName());
 +        }
 +
 +    }
 +
 +    /**
 +     * An abstract base class for server-side Grid extensions.
 +     * <p>
 +     * Note: If the extension is an instance of {@link DataGenerator} it will
 +     * automatically register itself to {@link RpcDataProviderExtension} of
 +     * extended Grid. On remove this registration is automatically removed.
 +     * 
 +     * @since 7.5
 +     */
 +    public static abstract class AbstractGridExtension extends
 +            AbstractExtension {
 +
 +        /**
 +         * Constructs a new Grid extension.
 +         */
 +        public AbstractGridExtension() {
 +            super();
 +        }
 +
 +        /**
 +         * Constructs a new Grid extension and extends given Grid.
 +         * 
 +         * @param grid
 +         *            a grid instance
 +         */
 +        public AbstractGridExtension(Grid grid) {
 +            super();
 +            extend(grid);
 +        }
 +
 +        @Override
 +        protected void extend(AbstractClientConnector target) {
 +            super.extend(target);
 +
 +            if (this instanceof DataGenerator) {
 +                getParentGrid().datasourceExtension
 +                        .addDataGenerator((DataGenerator) this);
 +            }
 +        }
 +
 +        @Override
 +        public void remove() {
 +            if (this instanceof DataGenerator) {
 +                getParentGrid().datasourceExtension
 +                        .removeDataGenerator((DataGenerator) this);
 +            }
 +
 +            super.remove();
 +        }
 +
 +        /**
 +         * Gets the item id for a row key.
 +         * <p>
 +         * A key is used to identify a particular row on both a server and a
 +         * client. This method can be used to get the item id for the row key
 +         * that the client has sent.
 +         * 
 +         * @param rowKey
 +         *            the row key for which to retrieve an item id
 +         * @return the item id corresponding to {@code key}
 +         */
 +        protected Object getItemId(String rowKey) {
 +            return getParentGrid().getKeyMapper().get(rowKey);
 +        }
 +
 +        /**
 +         * Gets the column for a column id.
 +         * <p>
 +         * An id is used to identify a particular column on both a server and a
 +         * client. This method can be used to get the column for the column id
 +         * that the client has sent.
 +         * 
 +         * @param columnId
 +         *            the column id for which to retrieve a column
 +         * @return the column corresponding to {@code columnId}
 +         */
 +        protected Column getColumn(String columnId) {
 +            return getParentGrid().getColumnByColumnId(columnId);
 +        }
 +
 +        /**
 +         * Gets the parent Grid of the renderer.
 +         * 
 +         * @return parent grid
 +         * @throws IllegalStateException
 +         *             if parent is not Grid
 +         */
 +        protected Grid getParentGrid() {
 +            if (getParent() instanceof Grid) {
 +                Grid grid = (Grid) getParent();
 +                return grid;
 +            } else if (getParent() == null) {
 +                throw new IllegalStateException(
 +                        "Renderer is not attached to any parent");
 +            } else {
 +                throw new IllegalStateException(
 +                        "Renderers can be used only with Grid. Extended "
 +                                + getParent().getClass().getSimpleName()
 +                                + " instead");
 +            }
 +        }
 +
 +        /**
 +         * Resends the row data for given item id to the client.
 +         * 
 +         * @since 7.6
 +         * @param itemId
 +         *            row to refresh
 +         */
 +        protected void refreshRow(Object itemId) {
 +            getParentGrid().datasourceExtension.updateRowData(itemId);
 +        }
 +
 +        /**
 +         * Informs the parent Grid that this Extension wants to add a child
 +         * component to it.
 +         * 
 +         * @since 7.6
 +         * @param c
 +         *            component
 +         */
 +        protected void addComponentToGrid(Component c) {
 +            getParentGrid().addComponent(c);
 +        }
 +
 +        /**
 +         * Informs the parent Grid that this Extension wants to remove a child
 +         * component from it.
 +         * 
 +         * @since 7.6
 +         * @param c
 +         *            component
 +         */
 +        protected void removeComponentFromGrid(Component c) {
 +            getParentGrid().removeComponent(c);
 +        }
 +    }
 +
 +    /**
 +     * The data source attached to the grid
 +     */
 +    private Container.Indexed datasource;
 +
 +    /**
 +     * Property id to column instance mapping
 +     */
 +    private final Map<Object, Column> columns = new HashMap<Object, Column>();
 +
 +    /**
 +     * Key generator for column server-to-client communication
 +     */
 +    private final KeyMapper<Object> columnKeys = new KeyMapper<Object>();
 +
 +    /**
 +     * The current sort order
 +     */
 +    private final List<SortOrder> sortOrder = new ArrayList<SortOrder>();
 +
 +    /**
 +     * Property listener for listening to changes in data source properties.
 +     */
 +    private final PropertySetChangeListener propertyListener = new PropertySetChangeListener() {
 +
 +        @Override
 +        public void containerPropertySetChange(PropertySetChangeEvent event) {
 +            Collection<?> properties = new HashSet<Object>(event.getContainer()
 +                    .getContainerPropertyIds());
 +
 +            // Find columns that need to be removed.
 +            List<Column> removedColumns = new LinkedList<Column>();
 +            for (Object propertyId : columns.keySet()) {
 +                if (!properties.contains(propertyId)) {
 +                    removedColumns.add(getColumn(propertyId));
 +                }
 +            }
 +
 +            // Actually remove columns.
 +            for (Column column : removedColumns) {
 +                Object propertyId = column.getPropertyId();
 +                internalRemoveColumn(propertyId);
 +                columnKeys.remove(propertyId);
 +            }
 +            datasourceExtension.columnsRemoved(removedColumns);
 +
 +            // Add new columns
 +            List<Column> addedColumns = new LinkedList<Column>();
 +            for (Object propertyId : properties) {
 +                if (!columns.containsKey(propertyId)) {
 +                    addedColumns.add(appendColumn(propertyId));
 +                }
 +            }
 +            datasourceExtension.columnsAdded(addedColumns);
 +
 +            if (getFrozenColumnCount() > columns.size()) {
 +                setFrozenColumnCount(columns.size());
 +            }
 +
 +            // Unset sortable for non-sortable columns.
 +            if (datasource instanceof Sortable) {
 +                Collection<?> sortables = ((Sortable) datasource)
 +                        .getSortableContainerPropertyIds();
 +                for (Object propertyId : columns.keySet()) {
 +                    Column column = columns.get(propertyId);
 +                    if (!sortables.contains(propertyId) && column.isSortable()) {
 +                        column.setSortable(false);
 +                    }
 +                }
 +            }
 +        }
 +    };
 +
 +    private final ItemSetChangeListener editorClosingItemSetListener = new ItemSetChangeListener() {
 +        @Override
 +        public void containerItemSetChange(ItemSetChangeEvent event) {
 +            cancelEditor();
 +        }
 +    };
 +
 +    private RpcDataProviderExtension datasourceExtension;
 +
 +    /**
 +     * The selection model that is currently in use. Never <code>null</code>
 +     * after the constructor has been run.
 +     */
 +    private SelectionModel selectionModel;
 +
 +    /**
 +     * Used to know whether selection change events originate from the server or
 +     * the client so the selection change handler knows whether the changes
 +     * should be sent to the client.
 +     */
 +    private boolean applyingSelectionFromClient;
 +
 +    private final Header header = new Header(this);
 +    private final Footer footer = new Footer(this);
 +
 +    private Object editedItemId = null;
 +    private boolean editorActive = false;
 +    private FieldGroup editorFieldGroup = new CustomFieldGroup();
 +
 +    private CellStyleGenerator cellStyleGenerator;
 +    private RowStyleGenerator rowStyleGenerator;
 +
 +    private CellDescriptionGenerator cellDescriptionGenerator;
 +    private RowDescriptionGenerator rowDescriptionGenerator;
 +
 +    /**
 +     * <code>true</code> if Grid is using the internal IndexedContainer created
 +     * in Grid() constructor, or <code>false</code> if the user has set their
 +     * own Container.
 +     * 
 +     * @see #setContainerDataSource(Indexed)
 +     * @see #Grid()
 +     */
 +    private boolean defaultContainer = true;
 +
 +    private EditorErrorHandler editorErrorHandler = new DefaultEditorErrorHandler();
 +
 +    private DetailComponentManager detailComponentManager = null;
 +
 +    private Set<Component> extensionComponents = new HashSet<Component>();
 +
 +    private static final Method SELECTION_CHANGE_METHOD = ReflectTools
 +            .findMethod(SelectionListener.class, "select", SelectionEvent.class);
 +
 +    private static final Method SORT_ORDER_CHANGE_METHOD = ReflectTools
 +            .findMethod(SortListener.class, "sort", SortEvent.class);
 +
 +    private static final Method COLUMN_REORDER_METHOD = ReflectTools
 +            .findMethod(ColumnReorderListener.class, "columnReorder",
 +                    ColumnReorderEvent.class);
 +
 +    private static final Method COLUMN_RESIZE_METHOD = ReflectTools
 +            .findMethod(ColumnResizeListener.class, "columnResize",
 +                    ColumnResizeEvent.class);
 +
 +    private static final Method COLUMN_VISIBILITY_METHOD = ReflectTools
 +            .findMethod(ColumnVisibilityChangeListener.class,
 +                    "columnVisibilityChanged",
 +                    ColumnVisibilityChangeEvent.class);
 +
 +    /**
 +     * Creates a new Grid with a new {@link IndexedContainer} as the data
 +     * source.
 +     */
 +    public Grid() {
 +        this(null, null);
 +    }
 +
 +    /**
 +     * Creates a new Grid using the given data source.
 +     * 
 +     * @param dataSource
 +     *            the indexed container to use as a data source
 +     */
 +    public Grid(final Container.Indexed dataSource) {
 +        this(null, dataSource);
 +    }
 +
 +    /**
 +     * Creates a new Grid with the given caption and a new
 +     * {@link IndexedContainer} data source.
 +     * 
 +     * @param caption
 +     *            the caption of the grid
 +     */
 +    public Grid(String caption) {
 +        this(caption, null);
 +    }
 +
 +    /**
 +     * Creates a new Grid with the given caption and data source. If the data
 +     * source is null, a new {@link IndexedContainer} will be used.
 +     * 
 +     * @param caption
 +     *            the caption of the grid
 +     * @param dataSource
 +     *            the indexed container to use as a data source
 +     */
 +    public Grid(String caption, Container.Indexed dataSource) {
 +        if (dataSource == null) {
 +            internalSetContainerDataSource(new IndexedContainer());
 +        } else {
 +            setContainerDataSource(dataSource);
 +        }
 +        setCaption(caption);
 +        initGrid();
 +    }
 +
 +    /**
 +     * Grid initial setup
 +     */
 +    private void initGrid() {
 +        setSelectionMode(getDefaultSelectionMode());
 +
 +        registerRpc(new GridServerRpc() {
 +
 +            @Override
 +            public void sort(String[] columnIds, SortDirection[] directions,
 +                    boolean userOriginated) {
 +                assert columnIds.length == directions.length;
 +
 +                List<SortOrder> order = new ArrayList<SortOrder>(
 +                        columnIds.length);
 +                for (int i = 0; i < columnIds.length; i++) {
 +                    Object propertyId = getPropertyIdByColumnId(columnIds[i]);
 +                    order.add(new SortOrder(propertyId, directions[i]));
 +                }
 +                setSortOrder(order, userOriginated);
 +                if (!order.equals(getSortOrder())) {
 +                    /*
 +                     * Actual sort order is not what the client expects. Make
 +                     * sure the client gets a state change event by clearing the
 +                     * diffstate and marking as dirty
 +                     */
 +                    ConnectorTracker connectorTracker = getUI()
 +                            .getConnectorTracker();
 +                    JsonObject diffState = connectorTracker
 +                            .getDiffState(Grid.this);
 +                    diffState.remove("sortColumns");
 +                    diffState.remove("sortDirs");
 +                    markAsDirty();
 +                }
 +            }
 +
 +            @Override
 +            public void itemClick(String rowKey, String columnId,
 +                    MouseEventDetails details) {
 +                Object itemId = getKeyMapper().get(rowKey);
 +                Item item = datasource.getItem(itemId);
 +                Object propertyId = getPropertyIdByColumnId(columnId);
 +                fireEvent(new ItemClickEvent(Grid.this, item, itemId,
 +                        propertyId, details));
 +            }
 +
 +            @Override
 +            public void columnsReordered(List<String> newColumnOrder,
 +                    List<String> oldColumnOrder) {
 +                final String diffStateKey = "columnOrder";
 +                ConnectorTracker connectorTracker = getUI()
 +                        .getConnectorTracker();
 +                JsonObject diffState = connectorTracker.getDiffState(Grid.this);
 +                // discard the change if the columns have been reordered from
 +                // the server side, as the server side is always right
 +                if (getState(false).columnOrder.equals(oldColumnOrder)) {
 +                    // Don't mark as dirty since client has the state already
 +                    getState(false).columnOrder = newColumnOrder;
 +                    // write changes to diffState so that possible reverting the
 +                    // column order is sent to client
 +                    assert diffState.hasKey(diffStateKey) : "Field name has changed";
 +                    Type type = null;
 +                    try {
 +                        type = (getState(false).getClass().getDeclaredField(
 +                                diffStateKey).getGenericType());
 +                    } catch (NoSuchFieldException e) {
 +                        e.printStackTrace();
 +                    } catch (SecurityException e) {
 +                        e.printStackTrace();
 +                    }
 +                    EncodeResult encodeResult = JsonCodec.encode(
 +                            getState(false).columnOrder, diffState, type,
 +                            connectorTracker);
 +
 +                    diffState.put(diffStateKey, encodeResult.getEncodedValue());
 +                    fireColumnReorderEvent(true);
 +                } else {
 +                    // make sure the client is reverted to the order that the
 +                    // server thinks it is
 +                    diffState.remove(diffStateKey);
 +                    markAsDirty();
 +                }
 +            }
 +
 +            @Override
 +            public void columnVisibilityChanged(String id, boolean hidden,
 +                    boolean userOriginated) {
 +                final Column column = getColumnByColumnId(id);
 +                final GridColumnState columnState = column.getState();
 +
 +                if (columnState.hidden != hidden) {
 +                    columnState.hidden = hidden;
 +
 +                    final String diffStateKey = "columns";
 +                    ConnectorTracker connectorTracker = getUI()
 +                            .getConnectorTracker();
 +                    JsonObject diffState = connectorTracker
 +                            .getDiffState(Grid.this);
 +
 +                    assert diffState.hasKey(diffStateKey) : "Field name has changed";
 +                    Type type = null;
 +                    try {
 +                        type = (getState(false).getClass().getDeclaredField(
 +                                diffStateKey).getGenericType());
 +                    } catch (NoSuchFieldException e) {
 +                        e.printStackTrace();
 +                    } catch (SecurityException e) {
 +                        e.printStackTrace();
 +                    }
 +                    EncodeResult encodeResult = JsonCodec.encode(
 +                            getState(false).columns, diffState, type,
 +                            connectorTracker);
 +
 +                    diffState.put(diffStateKey, encodeResult.getEncodedValue());
 +
 +                    fireColumnVisibilityChangeEvent(column, hidden,
 +                            userOriginated);
 +                }
 +            }
 +
 +            @Override
 +            public void contextClick(int rowIndex, String rowKey,
 +                    String columnId, Section section, MouseEventDetails details) {
 +                Object itemId = null;
 +                if (rowKey != null) {
 +                    itemId = getKeyMapper().get(rowKey);
 +                }
 +                fireEvent(new GridContextClickEvent(Grid.this, details,
 +                        section, rowIndex, itemId,
 +                        getPropertyIdByColumnId(columnId)));
 +            }
 +
 +            @Override
 +            public void columnResized(String id, double pixels) {
 +                final Column column = getColumnByColumnId(id);
 +                if (column != null && column.isResizable()) {
 +                    column.getState().width = pixels;
 +                    fireColumnResizeEvent(column, true);
 +                    markAsDirty();
 +                }
 +            }
 +        });
 +
 +        registerRpc(new EditorServerRpc() {
 +
 +            @Override
 +            public void bind(int rowIndex) {
 +                try {
 +                    Object id = getContainerDataSource().getIdByIndex(rowIndex);
 +
 +                    final boolean opening = editedItemId == null;
 +
 +                    final boolean moving = !opening && !editedItemId.equals(id);
 +
 +                    final boolean allowMove = !isEditorBuffered()
 +                            && getEditorFieldGroup().isValid();
 +
 +                    if (opening || !moving || allowMove) {
 +                        doBind(id);
 +                    } else {
 +                        failBind(null);
 +                    }
 +                } catch (Exception e) {
 +                    failBind(e);
 +                }
 +            }
 +
 +            private void doBind(Object id) {
 +                editedItemId = id;
 +                doEditItem();
 +                getEditorRpc().confirmBind(true);
 +            }
 +
 +            private void failBind(Exception e) {
 +                if (e != null) {
 +                    handleError(e);
 +                }
 +                getEditorRpc().confirmBind(false);
 +            }
 +
 +            @Override
 +            public void cancel(int rowIndex) {
 +                try {
 +                    // For future proofing even though cannot currently fail
 +                    doCancelEditor();
 +                } catch (Exception e) {
 +                    handleError(e);
 +                }
 +            }
 +
 +            @Override
 +            public void save(int rowIndex) {
 +                List<String> errorColumnIds = null;
 +                String errorMessage = null;
 +                boolean success = false;
 +                try {
 +                    saveEditor();
 +                    success = true;
 +                } catch (CommitException e) {
 +                    try {
 +                        CommitErrorEvent event = new CommitErrorEvent(
 +                                Grid.this, e);
 +                        getEditorErrorHandler().commitError(event);
 +
 +                        errorMessage = event.getUserErrorMessage();
 +
 +                        errorColumnIds = new ArrayList<String>();
 +                        for (Column column : event.getErrorColumns()) {
 +                            errorColumnIds.add(column.state.id);
 +                        }
 +                    } catch (Exception ee) {
 +                        // A badly written error handler can throw an exception,
 +                        // which would lock up the Grid
 +                        handleError(ee);
 +                    }
 +                } catch (Exception e) {
 +                    handleError(e);
 +                }
 +                getEditorRpc().confirmSave(success, errorMessage,
 +                        errorColumnIds);
 +            }
 +
 +            private void handleError(Exception e) {
 +                com.vaadin.server.ErrorEvent.findErrorHandler(Grid.this).error(
 +                        new ConnectorErrorEvent(Grid.this, e));
 +            }
 +        });
 +    }
 +
 +    @Override
 +    public void beforeClientResponse(boolean initial) {
 +        try {
 +            header.sanityCheck();
 +            footer.sanityCheck();
 +        } catch (Exception e) {
 +            e.printStackTrace();
 +            setComponentError(new ErrorMessage() {
 +
 +                @Override
 +                public ErrorLevel getErrorLevel() {
 +                    return ErrorLevel.CRITICAL;
 +                }
 +
 +                @Override
 +                public String getFormattedHtmlMessage() {
 +                    return "Incorrectly merged cells";
 +                }
 +
 +            });
 +        }
 +
 +        super.beforeClientResponse(initial);
 +    }
 +
 +    /**
++     * Sets the grid data source.<p>
++     *
++     * <strong>Note</strong> Grid columns are based on properties and try to detect a correct converter for
++     * the data type. The columns are not reinitialized automatically if the container is changed, and if the same
++     * properties are present after container change, the columns are reused.
++     * Properties with same names, but different data types will lead to unpredictable behaviour.
++     *
 +     * @param container
 +     *            The container data source. Cannot be null.
 +     * @throws IllegalArgumentException
 +     *             if the data source is null
 +     */
 +    public void setContainerDataSource(Container.Indexed container) {
 +        defaultContainer = false;
 +        internalSetContainerDataSource(container);
 +    }
 +
 +    private void internalSetContainerDataSource(Container.Indexed container) {
 +        if (container == null) {
 +            throw new IllegalArgumentException(
 +                    "Cannot set the datasource to null");
 +        }
 +        if (datasource == container) {
 +            return;
 +        }
 +
 +        // Remove old listeners
 +        if (datasource instanceof PropertySetChangeNotifier) {
 +            ((PropertySetChangeNotifier) datasource)
 +                    .removePropertySetChangeListener(propertyListener);
 +        }
 +
 +        if (datasourceExtension != null) {
 +            removeExtension(datasourceExtension);
 +        }
 +
 +        // Remove old DetailComponentManager
 +        if (detailComponentManager != null) {
 +            detailComponentManager.remove();
 +        }
 +
 +        resetEditor();
 +
 +        datasource = container;
 +
 +        //
 +        // Adjust sort order
 +        //
 +
 +        if (container instanceof Container.Sortable) {
 +
 +            // If the container is sortable, go through the current sort order
 +            // and match each item to the sortable properties of the new
 +            // container. If the new container does not support an item in the
 +            // current sort order, that item is removed from the current sort
 +            // order list.
 +            Collection<?> sortableProps = ((Container.Sortable) getContainerDataSource())
 +                    .getSortableContainerPropertyIds();
 +
 +            Iterator<SortOrder> i = sortOrder.iterator();
 +            while (i.hasNext()) {
 +                if (!sortableProps.contains(i.next().getPropertyId())) {
 +                    i.remove();
 +                }
 +            }
 +
 +            sort(false);
 +        } else {
 +            // Clear sorting order. Don't sort.
 +            sortOrder.clear();
 +        }
 +
 +        datasourceExtension = new RpcDataProviderExtension(container);
 +        datasourceExtension.extend(this);
 +        datasourceExtension.addDataGenerator(new RowDataGenerator());
 +        for (Extension e : getExtensions()) {
 +            if (e instanceof DataGenerator) {
 +                datasourceExtension.addDataGenerator((DataGenerator) e);
 +            }
 +        }
 +
 +        if (detailComponentManager != null) {
 +            detailComponentManager = new DetailComponentManager(this,
 +                    detailComponentManager.getDetailsGenerator());
 +        } else {
 +            detailComponentManager = new DetailComponentManager(this);
 +        }
 +
 +        /*
 +         * selectionModel == null when the invocation comes from the
 +         * constructor.
 +         */
 +        if (selectionModel != null) {
 +            selectionModel.reset();
 +        }
 +
 +        // Listen to changes in properties and remove columns if needed
 +        if (datasource instanceof PropertySetChangeNotifier) {
 +            ((PropertySetChangeNotifier) datasource)
 +                    .addPropertySetChangeListener(propertyListener);
 +        }
 +
 +        /*
 +         * activeRowHandler will be updated by the client-side request that
 +         * occurs on container change - no need to actively re-insert any
 +         * ValueChangeListeners at this point.
 +         */
 +
 +        setFrozenColumnCount(0);
 +
 +        if (columns.isEmpty()) {
 +            // Add columns
 +            for (Object propertyId : datasource.getContainerPropertyIds()) {
 +                Column column = appendColumn(propertyId);
 +
 +                // Initial sorting is defined by container
 +                if (datasource instanceof Sortable) {
 +                    column.setSortable(((Sortable) datasource)
 +                            .getSortableContainerPropertyIds().contains(
 +                                    propertyId));
 +                } else {
 +                    column.setSortable(false);
 +                }
 +            }
 +        } else {
 +            Collection<?> properties = datasource.getContainerPropertyIds();
 +            for (Object property : columns.keySet()) {
 +                if (!properties.contains(property)) {
 +                    throw new IllegalStateException(
 +                            "Found at least one column in Grid that does not exist in the given container: "
 +                                    + property
 +                                    + " with the header \""
 +                                    + getColumn(property).getHeaderCaption()
 +                                    + "\"");
 +                }
 +
 +                if (!(datasource instanceof Sortable)
 +                        || !((Sortable) datasource)
 +                                .getSortableContainerPropertyIds().contains(
 +                                        property)) {
 +                    columns.get(property).setSortable(false);
 +                }
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Returns the grid data source.
 +     * 
 +     * @return the container data source of the grid
 +     */
 +    public Container.Indexed getContainerDataSource() {
 +        return datasource;
 +    }
 +
 +    /**
 +     * Returns a column based on the property id
 +     * 
 +     * @param propertyId
 +     *            the property id of the column
 +     * @return the column or <code>null</code> if not found
 +     */
 +    public Column getColumn(Object propertyId) {
 +        return columns.get(propertyId);
 +    }
 +
 +    /**
 +     * Returns a copy of currently configures columns in their current visual
 +     * order in this Grid.
 +     * 
 +     * @return unmodifiable copy of current columns in visual order
 +     */
 +    public List<Column> getColumns() {
 +        List<Column> columns = new ArrayList<Grid.Column>();
 +        for (String columnId : getState(false).columnOrder) {
 +            columns.add(getColumnByColumnId(columnId));
 +        }
 +        return Collections.unmodifiableList(columns);
 +    }
 +
 +    /**
 +     * Adds a new Column to Grid. Also adds the property to container with data
 +     * type String, if property for column does not exist in it. Default value
 +     * for the new property is an empty String.
 +     * <p>
 +     * Note that adding a new property is only done for the default container
 +     * that Grid sets up with the default constructor.
 +     * 
 +     * @param propertyId
 +     *            the property id of the new column
 +     * @return the new column
 +     * 
 +     * @throws IllegalStateException
 +     *             if column for given property already exists in this grid
 +     */
 +
 +    public Column addColumn(Object propertyId) throws IllegalStateException {
 +        if (datasource.getContainerPropertyIds().contains(propertyId)
 +                && !columns.containsKey(propertyId)) {
 +            appendColumn(propertyId);
 +        } else if (defaultContainer) {
 +            addColumnProperty(propertyId, String.class, "");
 +        } else {
 +            if (columns.containsKey(propertyId)) {
 +                throw new IllegalStateException("A column for property id '"
 +                        + propertyId.toString()
 +                        + "' already exists in this grid");
 +            } else {
 +                throw new IllegalStateException("Property id '"
 +                        + propertyId.toString()
 +                        + "' does not exist in the container");
 +            }
 +        }
 +
 +        // Inform the data provider of this new column.
 +        Column column = getColumn(propertyId);
 +        List<Column> addedColumns = new ArrayList<Column>();
 +        addedColumns.add(column);
 +        datasourceExtension.columnsAdded(addedColumns);
 +
 +        return column;
 +    }
 +
 +    /**
 +     * Adds a new Column to Grid. This function makes sure that the property
 +     * with the given id and data type exists in the container. If property does
 +     * not exists, it will be created.
 +     * <p>
 +     * Default value for the new property is 0 if type is Integer, Double and
 +     * Float. If type is String, default value is an empty string. For all other
 +     * types the default value is null.
 +     * <p>
 +     * Note that adding a new property is only done for the default container
 +     * that Grid sets up with the default constructor.
 +     * 
 +     * @param propertyId
 +     *            the property id of the new column
 +     * @param type
 +     *            the data type for the new property
 +     * @return the new column
 +     * 
 +     * @throws IllegalStateException
 +     *             if column for given property already exists in this grid or
 +     *             property already exists in the container with wrong type
 +     */
 +    public Column addColumn(Object propertyId, Class<?> type) {
 +        addColumnProperty(propertyId, type, null);
 +        return getColumn(propertyId);
 +    }
 +
 +    protected void addColumnProperty(Object propertyId, Class<?> type,
 +            Object defaultValue) throws IllegalStateException {
 +        if (!defaultContainer) {
 +            throw new IllegalStateException(
 +                    "Container for this Grid is not a default container from Grid() constructor");
 +        }
 +
 +        if (!columns.containsKey(propertyId)) {
 +            if (!datasource.getContainerPropertyIds().contains(propertyId)) {
 +                datasource.addContainerProperty(propertyId, type, defaultValue);
 +            } else {
 +                Property<?> containerProperty = datasource
 +                        .getContainerProperty(datasource.firstItemId(),
 +                                propertyId);
 +                if (containerProperty.getType() == type) {
 +                    appendColumn(propertyId);
 +                } else {
 +                    throw new IllegalStateException(
 +                            "DataSource already has the given property "
 +                                    + propertyId + " with a different type");
 +                }
 +            }
 +        } else {
 +            throw new IllegalStateException(
 +                    "Grid already has a column for property " + propertyId);
 +        }
 +    }
 +
 +    /**
 +     * Removes all columns from this Grid.
 +     */
 +    public void removeAllColumns() {
 +        List<Column> removed = new ArrayList<Column>(columns.values());
 +        Set<Object> properties = new HashSet<Object>(columns.keySet());
 +        for (Object propertyId : properties) {
 +            removeColumn(propertyId);
 +        }
 +        datasourceExtension.columnsRemoved(removed);
 +    }
 +
 +    /**
 +     * Used internally by the {@link Grid} to get a {@link Column} by
 +     * referencing its generated state id. Also used by {@link Column} to verify
 +     * if it has been detached from the {@link Grid}.
 +     * 
 +     * @param columnId
 +     *            the client id generated for the column when the column is
 +     *            added to the grid
 +     * @return the column with the id or <code>null</code> if not found
 +     */
 +    Column getColumnByColumnId(String columnId) {
 +        Object propertyId = getPropertyIdByColumnId(columnId);
 +        return getColumn(propertyId);
 +    }
 +
 +    /**
 +     * Used internally by the {@link Grid} to get a property id by referencing
 +     * the columns generated state id.
 +     * 
 +     * @param columnId
 +     *            The state id of the column
 +     * @return The column instance or null if not found
 +     */
 +    Object getPropertyIdByColumnId(String columnId) {
 +        return columnKeys.get(columnId);
 +    }
 +
 +    /**
 +     * Returns whether column reordering is allowed. Default value is
 +     * <code>false</code>.
 +     * 
 +     * @since 7.5.0
 +     * @return true if reordering is allowed
 +     */
 +    public boolean isColumnReorderingAllowed() {
 +        return getState(false).columnReorderingAllowed;
 +    }
 +
 +    /**
 +     * Sets whether or not column reordering is allowed. Default value is
 +     * <code>false</code>.
 +     * 
 +     * @since 7.5.0
 +     * @param columnReorderingAllowed
 +     *            specifies whether column reordering is allowed
 +     */
 +    public void setColumnReorderingAllowed(boolean columnReorderingAllowed) {
 +        if (isColumnReorderingAllowed() != columnReorderingAllowed) {
 +            getState().columnReorderingAllowed = columnReorderingAllowed;
 +        }
 +    }
 +
 +    @Override
 +    protected GridState getState() {
 +        return (GridState) super.getState();
 +    }
 +
 +    @Override
 +    protected GridState getState(boolean markAsDirty) {
 +        return (GridState) super.getState(markAsDirty);
 +    }
 +
 +    /**
 +     * Creates a new column based on a property id and appends it as the last
 +     * column.
 +     * 
 +     * @param datasourcePropertyId
 +     *            The property id of a property in the datasource
 +     */
 +    private Column appendColumn(Object datasourcePropertyId) {
 +        if (datasourcePropertyId == null) {
 +            throw new IllegalArgumentException("Property id cannot be null");
 +        }
 +        assert datasource.getContainerPropertyIds().contains(
 +                datasourcePropertyId) : "Datasource should contain the property id";
 +
 +        GridColumnState columnState = new GridColumnState();
 +        columnState.id = columnKeys.key(datasourcePropertyId);
 +
 +        Column column = new Column(this, columnState, datasourcePropertyId);
 +        columns.put(datasourcePropertyId, column);
 +
 +        getState().columns.add(columnState);
 +        getState().columnOrder.add(columnState.id);
 +        header.addColumn(datasourcePropertyId);
 +        footer.addColumn(datasourcePropertyId);
 +
 +        String humanFriendlyPropertyId = SharedUtil
 +                .propertyIdToHumanFriendly(String.valueOf(datasourcePropertyId));
 +        column.setHeaderCaption(humanFriendlyPropertyId);
 +
 +        if (datasource instanceof Sortable
 +                && ((Sortable) datasource).getSortableContainerPropertyIds()
 +                        .contains(datasourcePropertyId)) {
 +            column.setSortable(true);
 +        }
 +
 +        return column;
 +    }
 +
 +    /**
 +     * Removes a column from Grid based on a property id.
 +     * 
 +     * @param propertyId
 +     *            The property id of column to be removed
 +     * 
 +     * @throws IllegalArgumentException
 +     *             if there is no column for given property id in this grid
 +     */
 +    public void removeColumn(Object propertyId) throws IllegalArgumentException {
 +        if (!columns.keySet().contains(propertyId)) {
 +            throw new IllegalArgumentException(
 +                    "There is no column for given property id " + propertyId);
 +        }
 +
 +        List<Column> removed = new ArrayList<Column>();
 +        removed.add(getColumn(propertyId));
 +        internalRemoveColumn(propertyId);
 +        datasourceExtension.columnsRemoved(removed);
 +    }
 +
 +    private void internalRemoveColumn(Object propertyId) {
 +        setEditorField(propertyId, null);
 +        header.removeColumn(propertyId);
 +        footer.removeColumn(propertyId);
 +        Column column = columns.remove(propertyId);
 +        getState().columnOrder.remove(columnKeys.key(propertyId));
 +        getState().columns.remove(column.getState());
 +        removeExtension(column.getRenderer());
 +    }
 +
 +    /**
 +     * Sets the columns and their order for the grid. Current columns whose
 +     * property id is not in propertyIds are removed. Similarly, a column is
 +     * added for any property id in propertyIds that has no corresponding column
 +     * in this Grid.
 +     * 
 +     * @since 7.5.0
 +     * 
 +     * @param propertyIds
 +     *            properties in the desired column order
 +     */
 +    public void setColumns(Object... propertyIds) {
 +        Set<?> removePids = new HashSet<Object>(columns.keySet());
 +        removePids.removeAll(Arrays.asList(propertyIds));
 +        for (Object removePid : removePids) {
 +            removeColumn(removePid);
 +        }
 +        Set<?> addPids = new HashSet<Object>(Arrays.asList(propertyIds));
 +        addPids.removeAll(columns.keySet());
 +        for (Object propertyId : addPids) {
 +            addColumn(propertyId);
 +        }
 +        setColumnOrder(propertyIds);
 +    }
 +
 +    /**
 +     * Sets a new column order for the grid. All columns which are not ordered
 +     * here will remain in the order they were before as the last columns of
 +     * grid.
 +     * 
 +     * @param propertyIds
 +     *            properties in the order columns should be
 +     */
 +    public void setColumnOrder(Object... propertyIds) {
 +        List<String> columnOrder = new ArrayList<String>();
 +        for (Object propertyId : propertyIds) {
 +            if (columns.containsKey(propertyId)) {
 +                columnOrder.add(columnKeys.key(propertyId));
 +            } else {
 +                throw new IllegalArgumentException(
 +                        "Grid does not contain column for property "
 +                                + String.valueOf(propertyId));
 +            }
 +        }
 +
 +        List<String> stateColumnOrder = getState().columnOrder;
 +        if (stateColumnOrder.size() != columnOrder.size()) {
 +            stateColumnOrder.removeAll(columnOrder);
 +            columnOrder.addAll(stateColumnOrder);
 +        }
 +        getState().columnOrder = columnOrder;
 +        fireColumnReorderEvent(false);
 +    }
 +
 +    /**
 +     * Sets the number of frozen columns in this grid. Setting the count to 0
 +     * means that no data columns will be frozen, but the built-in selection
 +     * checkbox column will still be frozen if it's in use. Setting the count to
 +     * -1 will also disable the selection column.
 +     * <p>
 +     * The default value is 0.
 +     * 
 +     * @param numberOfColumns
 +     *            the number of columns that should be frozen
 +     * 
 +     * @throws IllegalArgumentException
 +     *             if the column count is < 0 or > the number of visible columns
 +     */
 +    public void setFrozenColumnCount(int numberOfColumns) {
 +        if (numberOfColumns < -1 || numberOfColumns > columns.size()) {
 +            throw new IllegalArgumentException(
 +                    "count must be between -1 and the current number of columns ("
 +                            + columns.size() + "): " + numberOfColumns);
 +        }
 +
 +        getState().frozenColumnCount = numberOfColumns;
 +    }
 +
 +    /**
 +     * Gets the number of frozen columns in this grid. 0 means that no data
 +     * columns will be frozen, but the built-in selection checkbox column will
 +     * still be frozen if it's in use. -1 means that not even the selection
 +     * column is frozen.
 +     * <p>
 +     * <em>NOTE:</em> this count includes {@link Column#isHidden() hidden
 +     * columns} in the count.
 +     * 
 +     * @see #setFrozenColumnCount(int)
 +     * 
 +     * @return the number of frozen columns
 +     */
 +    public int getFrozenColumnCount() {
 +        return getState(false).frozenColumnCount;
 +    }
 +
 +    /**
 +     * Scrolls to a certain item, using {@link ScrollDestination#ANY}.
 +     * <p>
 +     * If the item has visible details, its size will also be taken into
 +     * account.
 +     * 
 +     * @param itemId
 +     *            id of item to scroll to.
 +     * @throws IllegalArgumentException
 +     *             if the provided id is not recognized by the data source.
 +     */
 +    public void scrollTo(Object itemId) throws IllegalArgumentException {
 +        scrollTo(itemId, ScrollDestination.ANY);
 +    }
 +
 +    /**
 +     * Scrolls to a certain item, using user-specified scroll destination.
 +     * <p>
 +     * If the item has visible details, its size will also be taken into
 +     * account.
 +     * 
 +     * @param itemId
 +     *            id of item to scroll to.
 +     * @param destination
 +     *            value specifying desired position of scrolled-to row.
 +     * @throws IllegalArgumentException
 +     *             if the provided id is not recognized by the data source.
 +     */
 +    public void scrollTo(Object itemId, ScrollDestination destination)
 +            throws IllegalArgumentException {
 +
 +        int row = datasource.indexOfId(itemId);
 +
 +        if (row == -1) {
 +            throw new IllegalArgumentException(
 +                    "Item with specified ID does not exist in data source");
 +        }
 +
 +        GridClientRpc clientRPC = getRpcProxy(GridClientRpc.class);
 +        clientRPC.scrollToRow(row, destination);
 +    }
 +
 +    /**
 +     * Scrolls to the beginning of the first data row.
 +     */
 +    public void scrollToStart() {
 +        GridClientRpc clientRPC = getRpcProxy(GridClientRpc.class);
 +        clientRPC.scrollToStart();
 +    }
 +
 +    /**
 +     * Scrolls to the end of the last data row.
 +     */
 +    public void scrollToEnd() {
 +        GridClientRpc clientRPC = getRpcProxy(GridClientRpc.class);
 +        clientRPC.scrollToEnd();
 +    }
 +
 +    /**
 +     * Sets the number of rows that should be visible in Grid's body, while
 +     * {@link #getHeightMode()} is {@link HeightMode#ROW}.
 +     * <p>
 +     * If Grid is currently not in {@link HeightMode#ROW}, the given value is
 +     * remembered, and applied once the mode is applied.
 +     * 
 +     * @param rows
 +     *            The height in terms of number of rows displayed in Grid's
 +     *            body. If Grid doesn't contain enough rows, white space is
 +     *            displayed instead. If <code>null</code> is given, then Grid's
 +     *            height is undefined
 +     * @throws IllegalArgumentException
 +     *             if {@code rows} is zero or less
 +     * @throws IllegalArgumentException
 +     *             if {@code rows} is {@link Double#isInfinite(double) infinite}
 +     * @throws IllegalArgumentException
 +     *             if {@code rows} is {@link Double#isNaN(double) NaN}
 +     */
 +    public void setHeightByRows(double rows) {
 +        if (rows <= 0.0d) {
 +            throw new IllegalArgumentException(
 +                    "More than zero rows must be shown.");
 +        } else if (Double.isInfinite(rows)) {
 +            throw new IllegalArgumentException(
 +                    "Grid doesn't support infinite heights");
 +        } else if (Double.isNaN(rows)) {
 +            throw new IllegalArgumentException("NaN is not a valid row count");
 +        }
 +
 +        getState().heightByRows = rows;
 +    }
 +
 +    /**
 +     * Gets the amount of rows in Grid's body that are shown, while
 +     * {@link #getHeightMode()} is {@link HeightMode#ROW}.
 +     * 
 +     * @return the amount of rows that are being shown in Grid's body
 +     * @see #setHeightByRows(double)
 +     */
 +    public double getHeightByRows() {
 +        return getState(false).heightByRows;
 +    }
 +
 +    /**
 +     * {@inheritDoc}
 +     * <p>
 +     * <em>Note:</em> This method will change the widget's size in the browser
 +     * only if {@link #getHeightMode()} returns {@link HeightMode#CSS}.
 +     * 
 +     * @see #setHeightMode(HeightMode)
 +     */
 +    @Override
 +    public void setHeight(float height, Unit unit) {
 +        super.setHeight(height, unit);
 +    }
 +
 +    /**
 +     * Defines the mode in which the Grid widget's height is calculated.
 +     * <p>
 +     * If {@link HeightMode#CSS} is given, Grid will respect the values given
 +     * via a {@code setHeight}-method, and behave as a traditional Component.
 +     * <p>
 +     * If {@link HeightMode#ROW} is given, Grid will make sure that the body
 +     * will display as many rows as {@link #getHeightByRows()} defines.
 +     * <em>Note:</em> If headers/footers are inserted or removed, the widget
 +     * will resize itself to still display the required amount of rows in its
 +     * body. It also takes the horizontal scrollbar into account.
 +     * 
 +     * @param heightMode
 +     *            the mode in to which Grid should be set
 +     */
 +    public void setHeightMode(HeightMode heightMode) {
 +        /*
 +         * This method is a workaround for the fact that Vaadin re-applies
 +         * widget dimensions (height/width) on each state change event. The
 +         * original design was to have setHeight and setHeightByRow be equals,
 +         * and whichever was called the latest was considered in effect.
 +         * 
 +         * But, because of Vaadin always calling setHeight on the widget, this
 +         * approach doesn't work.
 +         */
 +
 +        getState().heightMode = heightMode;
 +    }
 +
 +    /**
 +     * Returns the current {@link HeightMode} the Grid is in.
 +     * <p>
 +     * Defaults to {@link HeightMode#CSS}.
 +     * 
 +     * @return the current HeightMode
 +     */
 +    public HeightMode getHeightMode() {
 +        return getState(false).heightMode;
 +    }
 +
 +    /* Selection related methods: */
 +
 +    /**
 +     * Takes a new {@link SelectionModel} into use.
 +     * <p>
 +     * The SelectionModel that is previously in use will have all its items
 +     * deselected.
 +     * <p>
 +     * If the given SelectionModel is already in use, this method does nothing.
 +     * 
 +     * @param selectionModel
 +     *            the new SelectionModel to use
 +     * @throws IllegalArgumentException
 +     *             if {@code selectionModel} is <code>null</code>
 +     */
 +    public void setSelectionModel(SelectionModel selectionModel)
 +            throws IllegalArgumentException {
 +        if (selectionModel == null) {
 +            throw new IllegalArgumentException(
 +                    "Selection model may not be null");
 +        }
 +
 +        if (this.selectionModel != selectionModel) {
 +            // this.selectionModel is null on init
 +            if (this.selectionModel != null) {
 +                this.selectionModel.remove();
 +            }
 +
 +            this.selectionModel = selectionModel;
 +            selectionModel.setGrid(this);
 +        }
 +    }
 +
 +    /**
 +     * Returns the currently used {@link SelectionModel}.
 +     * 
 +     * @return the currently used SelectionModel
 +     */
 +    public SelectionModel getSelectionModel() {
 +        return selectionModel;
 +    }
 +
 +    /**
 +     * Sets the Grid's selection mode.
 +     * <p>
 +     * Grid supports three selection modes: multiselect, single select and no
 +     * selection, and this is a convenience method for choosing between one of
 +     * them.
 +     * <p>
 +     * Technically, this method is a shortcut that can be used instead of
 +     * calling {@code setSelectionModel} with a specific SelectionModel
 +     * instance. Grid comes with three built-in SelectionModel classes, and the
 +     * {@link SelectionMode} enum represents each of them.
 +     * <p>
 +     * Essentially, the two following method calls are equivalent:
 +     * <p>
 +     * <code><pre>
 +     * grid.setSelectionMode(SelectionMode.MULTI);
 +     * grid.setSelectionModel(new MultiSelectionMode());
 +     * </pre></code>
 +     * 
 +     * 
 +     * @param selectionMode
 +     *            the selection mode to switch to
 +     * @return The {@link SelectionModel} instance that was taken into use
 +     * @throws IllegalArgumentException
 +     *             if {@code selectionMode} is <code>null</code>
 +     * @see SelectionModel
 +     */
 +    public SelectionModel setSelectionMode(final SelectionMode selectionMode)
 +            throws IllegalArgumentException {
 +        if (selectionMode == null) {
 +            throw new IllegalArgumentException("selection mode may not be null");
 +        }
 +        final SelectionModel newSelectionModel = selectionMode.createModel();
 +        setSelectionModel(newSelectionModel);
 +        return newSelectionModel;
 +    }
 +
 +    /**
 +     * Checks whether an item is selected or not.
 +     * 
 +     * @param itemId
 +     *            the item id to check for
 +     * @return <code>true</code> iff the item is selected
 +     */
 +    // keep this javadoc in sync with SelectionModel.isSelected
 +    public boolean isSelected(Object itemId) {
 +        return selectionModel.isSelected(itemId);
 +    }
 +
 +    /**
 +     * Returns a collection of all the currently selected itemIds.
 +     * <p>
 +     * This method is a shorthand that delegates to the
 +     * {@link #getSelectionModel() selection model}.
 +     * 
 +     * @return a collection of all the currently selected itemIds
 +     */
 +    // keep this javadoc in sync with SelectionModel.getSelectedRows
 +    public Collection<Object> getSelectedRows() {
 +        return getSelectionModel().getSelectedRows();
 +    }
 +
 +    /**
 +     * Gets the item id of the currently selected item.
 +     * <p>
 +     * This method is a shorthand that delegates to the
 +     * {@link #getSelectionModel() selection model}. Only
 +     * {@link SelectionModel.Single} is supported.
 +     * 
 +     * @return the item id of the currently selected item, or <code>null</code>
 +     *         if nothing is selected
 +     * @throws IllegalStateException
 +     *             if the selection model does not implement
 +     *             {@code SelectionModel.Single}
 +     */
 +    // keep this javadoc in sync with SelectionModel.Single.getSelectedRow
 +    public Object getSelectedRow() throws IllegalStateException {
 +        if (selectionModel instanceof SelectionModel.Single) {
 +            return ((SelectionModel.Single) selectionModel).getSelectedRow();
 +        } else if (selectionModel instanceof SelectionModel.Multi) {
 +            throw new IllegalStateException("Cannot get unique selected row: "
 +                    + "Grid is in multiselect mode "
 +                    + "(the current selection model is "
 +                    + selectionModel.getClass().getName() + ").");
 +        } else if (selectionModel instanceof SelectionModel.None) {
 +            throw new IllegalStateException("Cannot get selected row: "
 +                    + "Grid selection is disabled "
 +                    + "(the current selection model is "
 +                    + selectionModel.getClass().getName() + ").");
 +        } else {
 +            throw new IllegalStateException("Cannot get selected row: "
 +                    + "Grid selection model does not implement "
 +                    + SelectionModel.Single.class.getName() + " or "
 +                    + SelectionModel.Multi.class.getName()
 +                    + "(the current model is "
 +                    + selectionModel.getClass().getName() + ").");
 +        }
 +    }
 +
 +    /**
 +     * Marks an item as selected.
 +     * <p>
 +     * This method is a shorthand that delegates to the
 +     * {@link #getSelectionModel() selection model}. Only
 +     * {@link SelectionModel.Single} and {@link SelectionModel.Multi} are
 +     * supported.
 +     * 
 +     * @param itemId
 +     *            the itemId to mark as selected
 +     * @return <code>true</code> if the selection state changed,
 +     *         <code>false</code> if the itemId already was selected
 +     * @throws IllegalArgumentException
 +     *             if the {@code itemId} doesn't exist in the currently active
 +     *             Container
 +     * @throws IllegalStateException
 +     *             if the selection was illegal. One such reason might be that
 +     *             the implementation already had an item selected, and that
 +     *             needs to be explicitly deselected before re-selecting
 +     *             something.
 +     * @throws IllegalStateException
 +     *             if the selection model does not implement
 +     *             {@code SelectionModel.Single} or {@code SelectionModel.Multi}
 +     */
 +    // keep this javadoc in sync with SelectionModel.Single.select
 +    public boolean select(Object itemId) throws IllegalArgumentException,
 +            IllegalStateException {
 +        if (selectionModel instanceof SelectionModel.Single) {
 +            return ((SelectionModel.Single) selectionModel).select(itemId);
 +        } else if (selectionModel instanceof SelectionModel.Multi) {
 +            return ((SelectionModel.Multi) selectionModel).select(itemId);
 +        } else if (selectionModel instanceof SelectionModel.None) {
 +            throw new IllegalStateException("Cannot select row '" + itemId
 +                    + "': Grid selection is disabled "
 +                    + "(the current selection model is "
 +                    + selectionModel.getClass().getName() + ").");
 +        } else {
 +            throw new IllegalStateException("Cannot select row '" + itemId
 +                    + "': Grid selection model does not implement "
 +                    + SelectionModel.Single.class.getName() + " or "
 +                    + SelectionModel.Multi.class.getName()
 +                    + "(the current model is "
 +                    + selectionModel.getClass().getName() + ").");
 +        }
 +    }
 +
 +    /**
 +     * Marks an item as unselected.
 +     * <p>
 +     * This method is a shorthand that delegates to the
 +     * {@link #getSelectionModel() selection model}. Only
 +     * {@link SelectionModel.Single} and {@link SelectionModel.Multi} are
 +     * supported.
 +     * 
 +     * @param itemId
 +     *            the itemId to remove from being selected
 +     * @return <code>true</code> if the selection state changed,
 +     *         <code>false</code> if the itemId was already selected
 +     * @throws IllegalArgumentException
 +     *             if the {@code itemId} doesn't exist in the currently active
 +     *             Container
 +     * @throws IllegalStateException
 +     *             if the deselection was illegal. One such reason might be that
 +     *             the implementation requires one or more items to be selected
 +     *             at all times.
 +     * @throws IllegalStateException
 +     *             if the selection model does not implement
 +     *             {@code SelectionModel.Single} or {code SelectionModel.Multi}
 +     */
 +    // keep this javadoc in sync with SelectionModel.Single.deselect
 +    public boolean deselect(Object itemId) throws IllegalStateException {
 +        if (selectionModel instanceof SelectionModel.Single) {
 +            if (isSelected(itemId)) {
 +                return ((SelectionModel.Single) selectionModel).select(null);
 +            }
 +            return false;
 +        } else if (selectionModel instanceof SelectionModel.Multi) {
 +            return ((SelectionModel.Multi) selectionModel).deselect(itemId);
 +        } else if (selectionModel instanceof SelectionModel.None) {
 +            throw new IllegalStateException("Cannot deselect row '" + itemId
 +                    + "': Grid selection is disabled "
 +                    + "(the current selection model is "
 +                    + selectionModel.getClass().getName() + ").");
 +        } else {
 +            throw new IllegalStateException("Cannot deselect row '" + itemId
 +                    + "': Grid selection model does not implement "
 +                    + SelectionModel.Single.class.getName() + " or "
 +                    + SelectionModel.Multi.class.getName()
 +                    + "(the current model is "
 +                    + selectionModel.getClass().getName() + ").");
 +        }
 +    }
 +
 +    /**
 +     * Marks all items as unselected.
 +     * <p>
 +     * This method is a shorthand that delegates to the
 +     * {@link #getSelectionModel() selection model}. Only
 +     * {@link SelectionModel.Single} and {@link SelectionModel.Multi} are
 +     * supported.
 +     * 
 +     * @return <code>true</code> if the selection state changed,
 +     *         <code>false</code> if the itemId was already selected
 +     * @throws IllegalStateException
 +     *             if the deselection was illegal. One such reason might be that
 +     *             the implementation requires one or more items to be selected
 +     *             at all times.
 +     * @throws IllegalStateException
 +     *             if the selection model does not implement
 +     *             {@code SelectionModel.Single} or {code SelectionModel.Multi}
 +     */
 +    public boolean deselectAll() throws IllegalStateException {
 +        if (selectionModel instanceof SelectionModel.Single) {
 +            if (getSelectedRow() != null) {
 +                return deselect(getSelectedRow());
 +            }
 +            return false;
 +        } else if (selectionModel instanceof SelectionModel.Multi) {
 +            return ((SelectionModel.Multi) selectionModel).deselectAll();
 +        } else if (selectionModel instanceof SelectionModel.None) {
 +            throw new IllegalStateException("Cannot deselect all rows"
 +                    + ": Grid selection is disabled "
 +                    + "(the current selection model is "
 +                    + selectionModel.getClass().getName() + ").");
 +        } else {
 +            throw new IllegalStateException("Cannot deselect all rows:"
 +                    + " Grid selection model does not implement "
 +                    + SelectionModel.Single.class.getName() + " or "
 +                    + SelectionModel.Multi.class.getName()
 +                    + "(the current model is "
 +                    + selectionModel.getClass().getName() + ").");
 +        }
 +    }
 +
 +    /**
 +     * Fires a selection change event.
 +     * <p>
 +     * <strong>Note:</strong> This is not a method that should be called by
 +     * application logic. This method is publicly accessible only so that
 +     * {@link SelectionModel SelectionModels} would be able to inform Grid of
 +     * these events.
 +     * 
 +     * @param newSelection
 +     *            the selection that was added by this event
 +     * @param oldSelection
 +     *            the selection that was removed by this event
 +     */
 +    public void fireSelectionEvent(Collection<Object> oldSelection,
 +            Collection<Object> newSelection) {
 +        fireEvent(new SelectionEvent(this, oldSelection, newSelection));
 +    }
 +
 +    @Override
 +    public void addSelectionListener(SelectionListener listener) {
 +        addListener(SelectionEvent.class, listener, SELECTION_CHANGE_METHOD);
 +    }
 +
 +    @Override
 +    public void removeSelectionListener(SelectionListener listener) {
 +        removeListener(SelectionEvent.class, listener, SELECTION_CHANGE_METHOD);
 +    }
 +
 +    private void fireColumnReorderEvent(boolean userOriginated) {
 +        fireEvent(new ColumnReorderEvent(this, userOriginated));
 +    }
 +
 +    /**
 +     * Registers a new column reorder listener.
 +     * 
 +     * @since 7.5.0
 +     * @param listener
 +     *            the listener to register
 +     */
 +    public void addColumnReorderListener(ColumnReorderListener listener) {
 +        addListener(ColumnReorderEvent.class, listener, COLUMN_REORDER_METHOD);
 +    }
 +
 +    /**
 +     * Removes a previously registered column reorder listener.
 +     * 
 +     * @since 7.5.0
 +     * @param listener
 +     *            the listener to remove
 +     */
 +    public void removeColumnReorderListener(ColumnReorderListener listener) {
 +        removeListener(ColumnReorderEvent.class, listener,
 +                COLUMN_REORDER_METHOD);
 +    }
 +
 +    private void fireColumnResizeEvent(Column column, boolean userOriginated) {
 +        fireEvent(new ColumnResizeEvent(this, column, userOriginated));
 +    }
 +
 +    /**
 +     * Registers a new column resize listener.
 +     * 
 +     * @param listener
 +     *            the listener to register
 +     */
 +    public void addColumnResizeListener(ColumnResizeListener listener) {
 +        addListener(ColumnResizeEvent.class, listener, COLUMN_RESIZE_METHOD);
 +    }
 +
 +    /**
 +     * Removes a previously registered column resize listener.
 +     * 
 +     * @param listener
 +     *            the listener to remove
 +     */
 +    public void removeColumnResizeListener(ColumnResizeListener listener) {
 +        removeListener(ColumnResizeEvent.class, listener, COLUMN_RESIZE_METHOD);
 +    }
 +
 +    /**
 +     * Gets the {@link KeyMapper } being used by the data source.
 +     * 
 +     * @return the key mapper being used by the data source
 +     */
 +    KeyMapper<Object> getKeyMapper() {
 +        return datasourceExtension.getKeyMapper();
 +    }
 +
 +    /**
 +     * Adds a renderer to this grid's connector hierarchy.
 +     * 
 +     * @param renderer
 +     *            the renderer to add
 +     */
 +    void addRenderer(Renderer<?> renderer) {
 +        addExtension(renderer);
 +    }
 +
 +    /**
 +     * Sets the current sort order using the fluid Sort API. Read the
 +     * documentation for {@link Sort} for more information.
 +     * <p>
 +     * <em>Note:</em> Sorting by a property that has no column in Grid will hide
 +     * all possible sorting indicators.
 +     * 
 +     * @param s
 +     *            a sort instance
 +     * 
 +     * @throws IllegalStateException
 +     *             if container is not sortable (does not implement
 +     *             Container.Sortable)
 +     * @throws IllegalArgumentException
 +     *             if trying to sort by non-existing property
 +     */
 +    public void sort(Sort s) {
 +        setSortOrder(s.build());
 +    }
 +
 +    /**
 +     * Sort this Grid in ascending order by a specified property.
 +     * <p>
 +     * <em>Note:</em> Sorting by a property that has no column in Grid will hide
 +     * all possible sorting indicators.
 +     * 
 +     * @param propertyId
 +     *            a property ID
 +     * 
 +     * @throws IllegalStateException
 +     *             if container is not sortable (does not implement
 +     *             Container.Sortable)
 +     * @throws IllegalArgumentException
 +     *             if trying to sort by non-existing property
 +     */
 +    public void sort(Object propertyId) {
 +        sort(propertyId, SortDirection.ASCENDING);
 +    }
 +
 +    /**
 +     * Sort this Grid in user-specified {@link SortOrder} by a property.
 +     * <p>
 +     * <em>Note:</em> Sorting by a property that has no column in Grid will hide
 +     * all possible sorting indicators.
 +     * 
 +     * @param propertyId
 +     *            a property ID
 +     * @param direction
 +     *            a sort order value (ascending/descending)
 +     * 
 +     * @throws IllegalStateException
 +     *             if container is not sortable (does not implement
 +     *             Container.Sortable)
 +     * @throws IllegalArgumentException
 +     *             if trying to sort by non-existing property
 +     */
 +    public void sort(Object propertyId, SortDirection direction) {
 +        sort(Sort.by(propertyId, direction));
 +    }
 +
 +    /**
 +     * Clear the current sort order, and re-sort the grid.
 +     */
 +    public void clearSortOrder() {
 +        sortOrder.clear();
 +        sort(false);
 +    }
 +
 +    /**
 +     * Sets the sort order to use.
 +     * <p>
 +     * <em>Note:</em> Sorting by a property that has no column in Grid will hide
 +     * all possible sorting indicators.
 +     * 
 +     * @param order
 +     *            a sort order list.
 +     * 
 +     * @throws IllegalStateException
 +     *             if container is not sortable (does not implement
 +     *             Container.Sortable)
 +     * @throws IllegalArgumentException
 +     *             if order is null or trying to sort by non-existing property
 +     */
 +    public void setSortOrder(List<SortOrder> order) {
 +        setSortOrder(order, false);
 +    }
 +
 +    private void setSortOrder(List<SortOrder> order, boolean userOriginated)
 +            throws IllegalStateException, IllegalArgumentException {
 +        if (!(getContainerDataSource() instanceof Container.Sortable)) {
 +            throw new IllegalStateException(
 +                    "Attached container is not sortable (does not implement Container.Sortable)");
 +        }
 +
 +        if (order == null) {
 +            throw new IllegalArgumentException("Order list may not be null!");
 +        }
 +
 +        sortOrder.clear();
 +
 +        Collection<?> sortableProps = ((Container.Sortable) getContainerDataSource())
 +                .getSortableContainerPropertyIds();
 +
 +        for (SortOrder o : order) {
 +            if (!sortableProps.contains(o.getPropertyId())) {
 +                throw new IllegalArgumentException(
 +                        "Property "
 +                                + o.getPropertyId()
 +                                + " does not exist or is not sortable in the current container");
 +            }
 +        }
 +
 +        sortOrder.addAll(order);
 +        sort(userOriginated);
 +    }
 +
 +    /**
 +     * Get the current sort order list.
 +     * 
 +     * @return a sort order list
 +     */
 +    public List<SortOrder> getSortOrder() {
 +        return Collections.unmodifiableList(sortOrder);
 +    }
 +
 +    /**
 +     * Apply sorting to data source.
 +     */
 +    private void sort(boolean userOriginated) {
 +
 +        Container c = getContainerDataSource();
 +        if (c instanceof Container.Sortable) {
 +            Container.Sortable cs = (Container.Sortable) c;
 +
 +            final int items = sortOrder.size();
 +            Object[] propertyIds = new Object[items];
 +            boolean[] directions = new boolean[items];
 +
 +            SortDirection[] stateDirs = new SortDirection[items];
 +
 +            for (int i = 0; i < items; ++i) {
 +                SortOrder order = sortOrder.get(i);
 +
 +                stateDirs[i] = order.getDirection();
 +                propertyIds[i] = order.getPropertyId();
 +                switch (order.getDirection()) {
 +                case ASCENDING:
 +                    directions[i] = true;
 +                    break;
 +                case DESCENDING:
 +                    directions[i] = false;
 +                    break;
 +                default:
 +                    throw new IllegalArgumentException("getDirection() of "
 +                            + order + " returned an unexpected value");
 +                }
 +            }
 +
 +            cs.sort(propertyIds, directions);
 +
 +            if (columns.keySet().containsAll(Arrays.asList(propertyIds))) {
 +                String[] columnKeys = new String[items];
 +                for (int i = 0; i < items; ++i) {
 +                    columnKeys[i] = this.columnKeys.key(propertyIds[i]);
 +                }
 +                getState().sortColumns = columnKeys;
 +                getState(false).sortDirs = stateDirs;
 +            } else {
 +                // Not all sorted properties are in Grid. Remove any indicators.
 +                getState().sortColumns = new String[] {};
 +                getState(false).sortDirs = new SortDirection[] {};
 +            }
 +            fireEvent(new SortEvent(this, new ArrayList<SortOrder>(sortOrder),
 +                    userOriginated));
 +        } else {
 +            throw new IllegalStateException(
 +                    "Container is not sortable (does not implement Container.Sortable)");
 +        }
 +    }
 +
 +    /**
 +     * Adds a sort order change listener that gets notified when the sort order
 +     * changes.
 +     * 
 +     * @param listener
 +     *            the sort order change listener to add
 +     */
 +    @Override
 +    public void addSortListener(SortListener listener) {
 +        addListener(SortEvent.class, listener, SORT_ORDER_CHANGE_METHOD);
 +    }
 +
 +    /**
 +     * Removes a sort order change listener previously added using
 +     * {@link #addSortListener(SortListener)}.
 +     * 
 +     * @param listener
 +     *            the sort order change listener to remove
 +     */
 +    @Override
 +    public void removeSortListener(SortListener listener) {
 +        removeListener(SortEvent.class, listener, SORT_ORDER_CHANGE_METHOD);
 +    }
 +
 +    /* Grid Headers */
 +
 +    /**
 +     * Returns the header section of this grid. The default header contains a
 +     * single row displaying the column captions.
 +     * 
 +     * @return the header
 +     */
 +    protected Header getHeader() {
 +        return header;
 +    }
 +
 +    /**
 +     * Gets the header row at given index.
 +     * 
 +     * @param rowIndex
 +     *            0 based index for row. Counted from top to bottom
 +     * @return header row at given index
 +     * @throws IllegalArgumentException
 +     *             if no row exists at given index
 +     */
 +    public HeaderRow getHeaderRow(int rowIndex) {
 +        return header.getRow(rowIndex);
 +    }
 +
 +    /**
 +     * Inserts a new row at the given position to the header section. Shifts the
 +     * row currently at that position and any subsequent rows down (adds one to
 +     * their indices).
 +     * 
 +     * @param index
 +     *            the position at which to insert the row
 +     * @return the new row
 +     * 
 +     * @throws IllegalArgumentException
 +     *             if the index is less than 0 or greater than row count
 +     * @see #appendHeaderRow()
 +     * @see #prependHeaderRow()
 +     * @see #removeHeaderRow(HeaderRow)
 +     * @see #removeHeaderRow(int)
 +     */
 +    public HeaderRow addHeaderRowAt(int index) {
 +        return header.addRowAt(index);
 +    }
 +
 +    /**
 +     * Adds a new row at the bottom of the header section.
 +     * 
 +     * @return the new row
 +     * @see #prependHeaderRow()
 +     * @see #addHeaderRowAt(int)
 +     * @see #removeHeaderRow(HeaderRow)
 +     * @see #removeHeaderRow(int)
 +     */
 +    public HeaderRow appendHeaderRow() {
 +        return header.appendRow();
 +    }
 +
 +    /**
 +     * Returns the current default row of the header section. The default row is
 +     * a special header row providing a user interface for sorting columns.
 +     * Setting a header text for column updates cells in the default header.
 +     * 
 +     * @return the default row or null if no default row set
 +     */
 +    public HeaderRow getDefaultHeaderRow() {
 +        return header.getDefaultRow();
 +    }
 +
 +    /**
 +     * Gets the row count for the header section.
 +     * 
 +     * @return row count
 +     */
 +    public int getHeaderRowCount() {
 +        return header.getRowCount();
 +    }
 +
 +    /**
 +     * Adds a new row at the top of the header section.
 +     * 
 +     * @return the new row
 +     * @see #appendHeaderRow()
 +     * @see #addHeaderRowAt(int)
 +     * @see #removeHeaderRow(HeaderRow)
 +     * @see #removeHeaderRow(int)
 +     */
 +    public HeaderRow prependHeaderRow() {
 +        return header.prependRow();
 +    }
 +
 +    /**
 +     * Removes the given row from the header section.
 +     * 
 +     * @param row
 +     *            the row to be removed
 +     * 
 +     * @throws IllegalArgumentException
 +     *             if the row does not exist in this section
 +     * @see #removeHeaderRow(int)
 +     * @see #addHeaderRowAt(int)
 +     * @see #appendHeaderRow()
 +     * @see #prependHeaderRow()
 +     */
 +    public void removeHeaderRow(HeaderRow row) {
 +        header.removeRow(row);
 +    }
 +
 +    /**
 +     * Removes the row at the given position from the header section.
 +     * 
 +     * @param rowIndex
 +     *            the position of the row
 +     * 
 +     * @throws IllegalArgumentException
 +     *             if no row exists at given index
 +     * @see #removeHeaderRow(HeaderRow)
 +     * @see #addHeaderRowAt(int)
 +     * @see #appendHeaderRow()
 +     * @see #prependHeaderRow()
 +     */
 +    public void removeHeaderRow(int rowIndex) {
 +        header.removeRow(rowIndex);
 +    }
 +
 +    /**
 +     * Sets the default row of the header. The default row is a special header
 +     * row providing a user interface for sorting columns.
 +     * 
 +     * @param row
 +     *            the new default row, or null for no default row
 +     * 
 +     * @throws IllegalArgumentException
 +     *             header does not contain the row
 +     */
 +    public void setDefaultHeaderRow(HeaderRow row) {
 +        header.setDefaultRow(row);
 +    }
 +
 +    /**
 +     * Sets the visibility of the header section.
 +     * 
 +     * @param visible
 +     *            true to show header section, false to hide
 +     */
 +    public void setHeaderVisible(boolean visible) {
 +        header.setVisible(visible);
 +    }
 +
 +    /**
 +     * Returns the visibility of the header section.
 +     * 
 +     * @return true if visible, false otherwise.
 +     */
 +    public boolean isHeaderVisible() {
 +        return header.isVisible();
 +    }
 +
 +    /* Grid Footers */
 +
 +    /**
 +     * Returns the footer section of this grid. The default header contains a
 +     * single row displaying the column captions.
 +     * 
 +     * @return the footer
 +     */
 +    protected Footer getFooter() {
 +        return footer;
 +    }
 +
 +    /**
 +     * Gets the footer row at given index.
 +     * 
 +     * @param rowIndex
 +     *            0 based index for row. Counted from top to bottom
 +     * @return footer row at given index
 +     * @throws IllegalArgumentException
 +     *             if no row exists at given index
 +     */
 +    public FooterRow getFooterRow(int rowIndex) {
 +        return footer.getRow(rowIndex);
 +    }
 +
 +    /**
 +     * Inserts a new row at the given position to the footer section. Shifts the
 +     * row currently at that position and any subsequent rows down (adds one to
 +     * their indices).
 +     * 
 +     * @param index
 +     *            the position at which to insert the row
 +     * @return the new row
 +     * 
 +     * @throws IllegalArgumentException
 +     *             if the index is less than 0 or greater than row count
 +     * @see #appendFooterRow()
 +     * @see #prependFooterRow()
 +     * @see #removeFooterRow(FooterRow)
 +     * @see #removeFooterRow(int)
 +     */
 +    public FooterRow addFooterRowAt(int index) {
 +        return footer.addRowAt(index);
 +    }
 +
 +    /**
 +     * Adds a new row at the bottom of the footer section.
 +     * 
 +     * @return the new row
 +     * @see #prependFooterRow()
 +     * @see #addFooterRowAt(int)
 +     * @see #removeFooterRow(FooterRow)
 +     * @see #removeFooterRow(int)
 +     */
 +    public FooterRow appendFooterRow() {
 +        return footer.appendRow();
 +    }
 +
 +    /**
 +     * Gets the row count for the footer.
 +     * 
 +     * @return row count
 +     */
 +    public int getFooterRowCount() {
 +        return footer.getRowCount();
 +    }
 +
 +    /**
 +     * Adds a new row at the top of the footer section.
 +     * 
 +     * @return the new row
 +     * @see #appendFooterRow()
 +     * @see #addFooterRowAt(int)
 +     * @see #removeFooterRow(FooterRow)
 +     * @see #removeFooterRow(int)
 +     */
 +    public FooterRow prependFooterRow() {
 +        return footer.prependRow();
 +    }
 +
 +    /**
 +     * Removes the given row from the footer section.
 +     * 
 +     * @param row
 +     *            the row to be removed
 +     * 
 +     * @throws IllegalArgumentException
 +     *             if the row does not exist in this section
 +     * @see #removeFooterRow(int)
 +     * @see #addFooterRowAt(int)
 +     * @see #appendFooterRow()
 +     * @see #prependFooterRow()
 +     */
 +    public void removeFooterRow(FooterRow row) {
 +        footer.removeRow(row);
 +    }
 +
 +    /**
 +     * Removes the row at the given position from the footer section.
 +     * 
 +     * @param rowIndex
 +     *            the position of the row
 +     * 
 +     * @throws IllegalArgumentException
 +     *             if no row exists at given index
 +     * @see #removeFooterRow(FooterRow)
 +     * @see #addFooterRowAt(int)
 +     * @see #appendFooterRow()
 +     * @see #prependFooterRow()
 +     */
 +    public void removeFooterRow(int rowIndex) {
 +        footer.removeRow(rowIndex);
 +    }
 +
 +    /**
 +     * Sets the visibility of the footer section.
 +     * 
 +     * @param visible
 +     *            true to show footer section, false to hide
 +     */
 +    public void setFooterVisible(boolean visible) {
 +        footer.setVisible(visible);
 +    }
 +
 +    /**
 +     * Returns the visibility of the footer section.
 +     * 
 +     * @return true if visible, false otherwise.
 +     */
 +    public boolean isFooterVisible() {
 +        return footer.isVisible();
 +    }
 +
 +    private void addComponent(Component c) {
 +        extensionComponents.add(c);
 +        c.setParent(this);
 +        markAsDirty();
 +    }
 +
 +    private void removeComponent(Component c) {
 +        extensionComponents.remove(c);
 +        c.setParent(null);
 +        markAsDirty();
 +    }
 +
 +    @Override
 +    public Iterator<Component> iterator() {
 +        // This is a hash set to avoid adding header/footer components inside
 +        // merged cells multiple times
 +        LinkedHashSet<Component> componentList = new LinkedHashSet<Component>();
 +
 +        Header header = getHeader();
 +        for (int i = 0; i < header.getRowCount(); ++i) {
 +            HeaderRow row = header.getRow(i);
 +            for (Object propId : columns.keySet()) {
 +                HeaderCell cell = row.getCell(propId);
 +                if (cell.getCellState().type == GridStaticCellType.WIDGET) {
 +                    componentList.add(cell.getComponent());
 +                }
 +            }
 +        }
 +
 +        Footer footer = getFooter();
 +        for (int i = 0; i < footer.getRowCount(); ++i) {
 +            FooterRow row = footer.getRow(i);
 +            for (Object propId : columns.keySet()) {
 +                FooterCell cell = row.getCell(propId);
 +                if (cell.getCellState().type == GridStaticCellType.WIDGET) {
 +                    componentList.add(cell.getComponent());
 +                }
 +            }
 +        }
 +
 +        componentList.addAll(getEditorFields());
 +
 +        componentList.addAll(extensionComponents);
 +
 +        return componentList.iterator();
 +    }
 +
 +    @Override
 +    public boolean isRendered(Component childComponent) {
 +        if (getEditorFields().contains(childComponent)) {
 +            // Only render editor fields if the editor is open
 +            return isEditorActive();
 +        } else {
 +            // TODO Header and footer components should also only be rendered if
 +            // the header/footer is visible
 +            return true;
 +        }
 +    }
 +
 +    EditorClientRpc getEditorRpc() {
 +        return getRpcProxy(EditorClientRpc.class);
 +    }
 +
 +    /**
 +     * Sets the {@code CellDescriptionGenerator} instance for generating
 +     * optional descriptions (tooltips) for individual Grid cells. If a
 +     * {@link RowDescriptionGenerator} is also set, the row description it
 +     * generates is displayed for cells for which {@code generator} returns
 +     * null.
 +     * 
 +     * @param generator
 +     *            the description generator to use or {@code null} to remove a
 +     *            previously set generator if any
 +     * 
 +     * @see #setRowDescriptionGenerator(RowDescriptionGenerator)
 +     * 
 +     * @since 7.6
 +     */
 +    public void setCellDescriptionGenerator(CellDescriptionGenerator generator) {
 +        cellDescriptionGenerator = generator;
 +        getState().hasDescriptions = (generator != null || rowDescriptionGenerator != null);
 +        datasourceExtension.refreshCache();
 +    }
 +
 +    /**
 +     * Returns the {@code CellDescriptionGenerator} instance used to generate
 +     * descriptions (tooltips) for Grid cells.
 +     * 
 +     * @return the description generator or {@code null} if no generator is set
 +     * 
 +     * @since 7.6
 +     */
 +    public CellDescriptionGenerator getCellDescriptionGenerator() {
 +        return cellDescriptionGenerator;
 +    }
 +
 +    /**
 +     * Sets the {@code RowDescriptionGenerator} instance for generating optional
 +     * descriptions (tooltips) for Grid rows. If a
 +     * {@link CellDescriptionGenerator} is also set, the row description
 +     * generated by {@code generator} is used for cells for which the cell
 +     * description generator returns null.
 +     * 
 +     * 
 +     * @param generator
 +     *            the description generator to use or {@code null} to remove a
 +     *            previously set generator if any
 +     * 
 +     * @see #setCellDescriptionGenerator(CellDescriptionGenerator)
 +     * 
 +     * @since 7.6
 +     */
 +    public void setRowDescriptionGenerator(RowDescriptionGenerator generator) {
 +        rowDescriptionGenerator = generator;
 +        getState().hasDescriptions = (generator != null || cellDescriptionGenerator != null);
 +        datasourceExtension.refreshCache();
 +    }
 +
 +    /**
 +     * Returns the {@code RowDescriptionGenerator} instance used to generate
 +     * descriptions (tooltips) for Grid rows
 +     * 
 +     * @return the description generator or {@code} null if no generator is set
 +     * 
 +     * @since 7.6
 +     */
 +    public RowDescriptionGenerator getRowDescriptionGenerator() {
 +        return rowDescriptionGenerator;
 +    }
 +
 +    /**
 +     * Sets the style generator that is used for generating styles for cells
 +     * 
 +     * @param cellStyleGenerator
 +     *            the cell style generator to set, or <code>null</code> to
 +     *            remove a previously set generator
 +     */
 +    public void setCellStyleGenerator(CellStyleGenerator cellStyleGenerator) {
 +        this.cellStyleGenerator = cellStyleGenerator;
 +        datasourceExtension.refreshCache();
 +    }
 +
 +    /**
 +     * Gets the style generator that is used for generating styles for cells
 +     * 
 +     * @return the cell style generator, or <code>null</code> if no generator is
 +     *         set
 +     */
 +    public CellStyleGenerator getCellStyleGenerator() {
 +        return cellStyleGenerator;
 +    }
 +
 +    /**
 +     * Sets the style generator that is used for generating styles for rows
 +     * 
 +     * @param rowStyleGenerator
 +     *            the row style generator to set, or <code>null</code> to remove
 +     *            a previously set generator
 +     */
 +    public void setRowStyleGenerator(RowStyleGenerator rowStyleGenerator) {
 +        this.rowStyleGenerator = rowStyleGenerator;
 +        datasourceExtension.refreshCache();
 +    }
 +
 +    /**
 +     * Gets the style generator that is used for generating styles for rows
 +     * 
 +     * @return the row style generator, or <code>null</code> if no generator is
 +     *         set
 +     */
 +    public RowStyleGenerator getRowStyleGenerator() {
 +        return rowStyleGenerator;
 +    }
 +
 +    /**
 +     * Adds a row to the underlying container. The order of the parameters
 +     * should match the current visible column order.
 +     * <p>
 +     * Please note that it's generally only safe to use this method during
 +     * initialization. After Grid has been initialized and the visible column
 +     * order might have been changed, it's better to instead add items directly
 +     * to the underlying container and use {@link Item#getItemProperty(Object)}
 +     * to make sure each value is assigned to the intended property.
 +     * 
 +     * @param values
 +     *            the cell values of the new row, in the same order as the
 +     *            visible column order, not <code>null</code>.
 +     * @return the item id of the new row
 +     * @throws IllegalArgumentException
 +     *             if values is null
 +     * @throws IllegalArgumentException
 +     *             if its length does not match the number of visible columns
 +     * @throws IllegalArgumentException
 +     *             if a parameter value is not an instance of the corresponding
 +     *             property type
 +     * @throws UnsupportedOperationException
 +     *             if the container does not support adding new items
 +     */
 +    public Object addRow(Object... values) {
 +        if (values == null) {
 +            throw new IllegalArgumentException("Values cannot be null");
 +        }
 +
 +        Indexed dataSource = getContainerDataSource();
 +        List<String> columnOrder = getState(false).columnOrder;
 +
 +        if (values.length != columnOrder.size()) {
 +            throw new IllegalArgumentException("There are "
 +                    + columnOrder.size() + " visible columns, but "
 +                    + values.length + " cell values were provided.");
 +        }
 +
 +        // First verify all parameter types
 +        for (int i = 0; i < columnOrder.size(); i++) {
 +            Object propertyId = getPropertyIdByColumnId(columnOrder.get(i));
 +
 +            Class<?> propertyType = dataSource.getType(propertyId);
 +            if (values[i] != null && !propertyType.isInstance(values[i])) {
 +                throw new IllegalArgumentException("Parameter " + i + "("
 +                        + values[i] + ") is not an instance of "
 +                        + propertyType.getCanonicalName());
 +            }
 +        }
 +
 +        Object itemId = dataSource.addItem();
 +        try {
 +            Item item = dataSource.getItem(itemId);
 +            for (int i = 0; i < columnOrder.size(); i++) {
 +                Object propertyId = getPropertyIdByColumnId(columnOrder.get(i));
 +                Property<Object> property = item.getItemProperty(propertyId);
 +                property.setValue(values[i]);
 +            }
 +        } catch (RuntimeException e) {
 +            try {
 +                dataSource.removeItem(itemId);
 +            } catch (Exception e2) {
 +                getLogger().log(Level.SEVERE,
 +                        "Error recovering from exception in addRow", e);
 +            }
 +            throw e;
 +        }
 +
 +        return itemId;
 +    }
 +
 +    private static Logger getLogger() {
 +        return Logger.getLogger(Grid.class.getName());
 +    }
 +
 +    /**
 +     * Sets whether or not the item editor UI is enabled for this grid. When the
 +     * editor is enabled, the user can open it by double-clicking a row or
 +     * hitting enter when a row is focused. The editor can also be opened
 +     * programmatically using the {@link #editItem(Object)} method.
 +     * 
 +     * @param isEnabled
 +     *            <code>true</code> to enable the feature, <code>false</code>
 +     *            otherwise
 +     * @throws IllegalStateException
 +     *             if an item is currently being edited
 +     * 
 +     * @see #getEditedItemId()
 +     */
 +    public void setEditorEnabled(boolean isEnabled)
 +            throws IllegalStateException {
 +        if (isEditorActive()) {
 +            throw new IllegalStateException(
 +                    "Cannot disable the editor while an item ("
 +                            + getEditedItemId() + ") is being edited");
 +        }
 +        if (isEditorEnabled() != isEnabled) {
 +            getState().editorEnabled = isEnabled;
 +        }
 +    }
 +
 +    /**
 +     * Checks whether the item editor UI is enabled for this grid.
 +     * 
 +     * @return <code>true</code> iff the editor is enabled for this grid
 +     * 
 +     * @see #setEditorEnabled(boolean)
 +     * @see #getEditedItemId()
 +     */
 +    public boolean isEditorEnabled() {
 +        return getState(false).editorEnabled;
 +    }
 +
 +    /**
 +     * Gets the id of the item that is currently being edited.
 +     * 
 +     * @return the id of the item that is currently being edited, or
 +     *         <code>null</code> if no item is being edited at the moment
 +     */
 +    public Object getEditedItemId() {
 +        return editedItemId;
 +    }
 +
 +    /**
 +     * Gets the field group that is backing the item editor of this grid.
 +     * 
 +     * @return the backing field group
 +     */
 +    public FieldGroup getEditorFieldGroup() {
 +        return editorFieldGroup;
 +    }
 +
 +    /**
 +     * Sets the field group that is backing the item editor of this grid.
 +     * 
 +     * @param fieldGroup
 +     *            the backing field group
 +     * 
 +     * @throws IllegalStateException
 +     *             if the editor is currently active
 +     */
 +    public void setEditorFieldGroup(FieldGroup fieldGroup) {
 +        if (isEditorActive()) {
 +            throw new IllegalStateException(
 +                    "Cannot change field group while an item ("
 +                            + getEditedItemId() + ") is being edited");
 +        }
 +        editorFieldGroup = fieldGroup;
 +    }
 +
 +    /**
 +     * Returns whether an item is currently being edited in the editor.
 +     * 
 +     * @return true iff the editor is open
 +     */
 +    public boolean isEditorActive() {
 +        return editorActive;
 +    }
 +
 +    private void checkColumnExists(Object propertyId) {
 +        if (getColumn(propertyId) == null) {
 +            throw new IllegalArgumentException(
 +                    "There is no column with the property id " + propertyId);
 +        }
 +    }
 +
 +    private Field<?> getEditorField(Object propertyId) {
 +        checkColumnExists(propertyId);
 +
 +        if (!getColumn(propertyId).isEditable()) {
 +            return null;
 +        }
 +
 +        Field<?> editor = editorFieldGroup.getField(propertyId);
 +
 +        try {
 +            if (editor == null) {
 +                editor = editorFieldGroup.buildAndBind(propertyId);
 +            }
 +        } finally {
 +            if (editor == null) {
 +                editor = editorFieldGroup.getField(propertyId);
 +            }
 +
 +            if (editor != null && editor.getParent() != Grid.this) {
 +                assert editor.getParent() == null;
 +                editor.setParent(this);
 +            }
 +        }
 +        return editor;
 +    }
 +
 +    /**
 +     * Opens the editor interface for the provided item. Scrolls the Grid to
 +     * bring the item to view if it is not already visible.
 +     * 
 +     * Note that any cell content rendered by a WidgetRenderer will not be
 +     * visible in the editor row.
 +     * 
 +     * @param itemId
 +     *            the id of the item to edit
 +     * @throws IllegalStateException
 +     *             if the editor is not enabled or already editing an item in
 +     *             buffered mode
 +     * @throws IllegalArgumentException
 +     *             if the {@code itemId} is not in the backing container
 +     * @see #setEditorEnabled(boolean)
 +     */
 +    public void editItem(Object itemId) throws IllegalStateException,
 +            IllegalArgumentException {
 +        if (!isEditorEnabled()) {
 +            throw new IllegalStateException("Item editor is not enabled");
 +        } else if (isEditorBuffered() && editedItemId != null) {
 +            throw new IllegalStateException("Editing item " + itemId
 +                    + " failed. Item editor is already editing item "
 +                    + editedItemId);
 +        } else if (!getContainerDataSource().containsId(itemId)) {
 +            throw new IllegalArgumentException("Item with id " + itemId
 +                    + " not found in current container");
 +        }
 +        editedItemId = itemId;
 +        getEditorRpc().bind(getContainerDataSource().indexOfId(itemId));
 +    }
 +
 +    protected void doEditItem() {
 +        Item item = getContainerDataSource().getItem(editedItemId);
 +
 +        editorFieldGroup.setItemDataSource(item);
 +
 +        for (Column column : getColumns()) {
 +            column.getState().editorConnector = getEditorField(column
 +                    .getPropertyId());
 +        }
 +
 +        editorActive = true;
 +        // Must ensure that all fields, recursively, are sent to the client
 +        // This is needed because the fields are hidden using isRendered
 +        for (Field<?> f : getEditorFields()) {
 +            f.markAsDirtyRecursive();
 +        }
 +
 +        if (datasource instanceof ItemSetChangeNotifier) {
 +            ((ItemSetChangeNotifier) datasource)
 +                    .addItemSetChangeListener(editorClosingItemSetListener);
 +        }
 +    }
 +
 +    private void setEditorField(Object propertyId, Field<?> field) {
 +        checkColumnExists(propertyId);
 +
 +        Field<?> oldField = editorFieldGroup.getField(propertyId);
 +        if (oldField != null) {
 +            editorFieldGroup.unbind(oldField);
 +            oldField.setParent(null);
 +        }
 +
 +        if (field != null) {
 +            field.setParent(this);
 +            editorFieldGroup.bind(field, propertyId);
 +        }
 +    }
 +
 +    /**
 +     * Saves all changes done to the bound fields.
 +     * <p>
 +     * <em>Note:</em> This is a pass-through call to the backing field group.
 +     * 
 +     * @throws CommitException
 +     *             If the commit was aborted
 +     * 
 +     * @see FieldGroup#commit()
 +     */
 +    public void saveEditor() throws CommitException {
 +        editorFieldGroup.commit();
 +    }
 +
 +    /**
 +     * Cancels the currently active edit if any. Hides the editor and discards
 +     * possible unsaved changes in the editor fields.
 +     */
 +    public void cancelEditor() {
 +        if (isEditorActive()) {
 +            getEditorRpc().cancel(
 +                    getContainerDataSource().indexOfId(editedItemId));
 +            doCancelEditor();
 +        }
 +    }
 +
 +    protected void doCancelEditor() {
 +        editedItemId = null;
 +        editorActive = false;
 +        editorFieldGroup.discard();
 +        editorFieldGroup.setItemDataSource(null);
 +
 +        if (datasource instanceof ItemSetChangeNotifier) {
 +            ((ItemSetChangeNotifier) datasource)
 +                    .removeItemSetChangeListener(editorClosingItemSetListener);
 +        }
 +
 +        // Mark Grid as dirty so the client side gets to know that the editors
 +        // are no longer attached
 +        markAsDirty();
 +    }
 +
 +    void resetEditor() {
 +        if (isEditorActive()) {
 +            /*
 +             * Simply force cancel the editing; throwing here would just make
 +             * Grid.setContainerDataSource semantics more complicated.
 +             */
 +            cancelEditor();
 +        }
 +        for (Field<?> editor : getEditorFields()) {
 +            editor.setParent(null);
 +        }
 +
 +        editedItemId = null;
 +        editorActive = false;
 +        editorFieldGroup = new CustomFieldGroup();
 +    }
 +
 +    /**
 +     * Gets a collection of all fields bound to the item editor of this grid.
 +     * <p>
 +     * When {@link #editItem(Object) editItem} is called, fields are
 +     * automatically created and bound to any unbound properties.
 +     * 
 +     * @return a collection of all the fields bound to the item editor
 +     */
 +    Collection<Field<?>> getEditorFields() {
 +        Collection<Field<?>> fields = editorFieldGroup.getFields();
 +        assert allAttached(fields);
 +        return fields;
 +    }
 +
 +    private boolean allAttached(Collection<? extends Component> components) {
 +        for (Component component : components) {
 +            if (component.getParent() != this) {
 +                return false;
 +            }
 +        }
 +        return true;
 +    }
 +
 +    /**
 +     * Sets the field factory for the {@link FieldGroup}. The field factory is
 +     * only used when {@link FieldGroup} creates a new field.
 +     * <p>
 +     * <em>Note:</em> This is a pass-through call to the backing field group.
 +     * 
 +     * @param fieldFactory
 +     *            The field factory to use
 +     */
 +    public void setEditorFieldFactory(FieldGroupFieldFactory fieldFactory) {
 +        editorFieldGroup.setFieldFactory(fieldFactory);
 +    }
 +
 +    /**
 +     * Sets the error handler for the editor.
 +     * 
 +     * The error handler is called whenever there is an exception in the editor.
 +     * 
 +     * @param editorErrorHandler
 +     *            The editor error handler to use
 +     * @throws IllegalArgumentException
 +     *             if the error handler is null
 +     */
 +    public void setEditorErrorHandler(EditorErrorHandler editorErrorHandler)
 +            throws IllegalArgumentException {
 +        if (editorErrorHandler == null) {
 +            throw new IllegalArgumentException(
 +                    "The error handler cannot be null");
 +        }
 +        this.editorErrorHandler = editorErrorHandler;
 +    }
 +
 +    /**
 +     * Gets the error handler used for the editor
 +     * 
 +     * @see #setErrorHandler(com.vaadin.server.ErrorHandler)
 +     * @return the editor error handler, never null
 +     */
 +    public EditorErrorHandler getEditorErrorHandler() {
 +        return editorErrorHandler;
 +    }
 +
 +    /**
 +     * Gets the field factory for the {@link FieldGroup}. The field factory is
 +     * only used when {@link FieldGroup} creates a new field.
 +     * <p>
 +     * <em>Note:</em> This is a pass-through call to the backing field group.
 +     * 
 +     * @return The field factory in use
 +     */
 +    public FieldGroupFieldFactory getEditorFieldFactory() {
 +        return editorFieldGroup.getFieldFactory();
 +    }
 +
 +    /**
 +     * Sets the caption on the save button in the Grid editor.
 +     * 
 +     * @param saveCaption
 +     *            the caption to set
 +     * @throws IllegalArgumentException
 +     *             if {@code saveCaption} is {@code null}
 +     */
 +    public void setEditorSaveCaption(String saveCaption)
 +            throws IllegalArgumentException {
 +        if (saveCaption == null) {
 +            throw new IllegalArgumentException("Save caption cannot be null");
 +        }
 +        getState().editorSaveCaption = saveCaption;
 +    }
 +
 +    /**
 +     * Gets the current caption of the save button in the Grid editor.
 +     * 
 +     * @return the current caption of the save button
 +     */
 +    public String getEditorSaveCaption() {
 +        return getState(false).editorSaveCaption;
 +    }
 +
 +    /**
 +     * Sets the caption on the cancel button in the Grid editor.
 +     * 
 +     * @param cancelCaption
 +     *            the caption to set
 +     * @throws IllegalArgumentException
 +     *             if {@code cancelCaption} is {@code null}
 +     */
 +    public void setEditorCancelCaption(String cancelCaption)
 +            throws IllegalArgumentException {
 +        if (cancelCaption == null) {
 +            throw new IllegalArgumentException("Cancel caption cannot be null");
 +        }
 +        getState().editorCancelCaption = cancelCaption;
 +    }
 +
 +    /**
 +     * Gets the current caption of the cancel button in the Grid editor.
 +     * 
 +     * @return the current caption of the cancel button
 +     */
 +    public String getEditorCancelCaption() {
 +        return getState(false).editorCancelCaption;
 +    }
 +
 +    /**
 +     * Sets the buffered editor mode. The default mode is buffered (
 +     * <code>true</code>).
 +     * 
 +     * @since 7.6
 +     * @param editorBuffered
 +     *            <code>true</code> to enable buffered editor,
 +     *            <code>false</code> to disable it
 +     * @throws IllegalStateException
 +     *             If editor is active while attempting to change the buffered
 +     *             mode.
 +     */
 +    public void setEditorBuffered(boolean editorBuffered)
 +            throws IllegalStateException {
 +        if (isEditorActive()) {
 +            throw new IllegalStateException(
 +                    "Can't change editor unbuffered mode while editor is active.");
 +        }
 +        getState().editorBuffered = editorBuffered;
 +        editorFieldGroup.setBuffered(editorBuffered);
 +    }
 +
 +    /**
 +     * Gets the buffered editor mode.
 +     * 
 +     * @since 7.6
 +     * @return <code>true</code> if buffered editor is enabled,
 +     *         <code>false</code> otherwise
 +     */
 +    public boolean isEditorBuffered() {
 +        return getState(false).editorBuffered;
 +    }
 +
 +    @Override
 +    public void addItemClickListener(ItemClickListener listener) {
 +        addListener(GridConstants.ITEM_CLICK_EVENT_ID, ItemClickEvent.class,
 +                listener, ItemClickEvent.ITEM_CLICK_METHOD);
 +    }
 +
 +    @Override
 +    @Deprecated
 +    public void addListener(ItemClickListener listener) {
 +        addItemClickListener(listener);
 +    }
 +
 +    @Override
 +    public void removeItemClickListener(ItemClickListener listener) {
 +        removeListener(GridConstants.ITEM_CLICK_EVENT_ID, ItemClickEvent.class,
 +                listener);
 +    }
 +
 +    @Override
 +    @Deprecated
 +    public void removeListener(ItemClickListener listener) {
 +        removeItemClickListener(listener);
 +    }
 +
 +    /**
 +     * Requests that the column widths should be recalculated.
 +     * <p>
 +     * In most cases Grid will know when column widths need to be recalculated
 +     * but this method can be used to force recalculation in situations when
 +     * grid does not recalculate automatically.
 +     * 
 +     * @since 7.4.1
 +     */
 +    public void recalculateColumnWidths() {
 +        getRpcProxy(GridClientRpc.class).recalculateColumnWidths();
 +    }
 +
 +    /**
 +     * Registers a new column visibility change listener
 +     * 
 +     * @since 7.5.0
 +     * @param listener
 +     *            the listener to register
 +     */
 +    public void addColumnVisibilityChangeListener(
 +            ColumnVisibilityChangeListener listener) {
 +        addListener(ColumnVisibilityChangeEvent.class, listener,
 +                COLUMN_VISIBILITY_METHOD);
 +    }
 +
 +    /**
 +     * Removes a previously registered column visibility change listener
 +     * 
 +     * @since 7.5.0
 +     * @param listener
 +     *            the listener to remove
 +     */
 +    public void removeColumnVisibilityChangeListener(
 +            ColumnVisibilityChangeListener listener) {
 +        removeListener(ColumnVisibilityChangeEvent.class, listener,
 +                COLUMN_VISIBILITY_METHOD);
 +    }
 +
 +    private void fireColumnVisibilityChangeEvent(Column column, boolean hidden,
 +            boolean isUserOriginated) {
 +        fireEvent(new ColumnVisibilityChangeEvent(this, column, hidden,
 +                isUserOriginated));
 +    }
 +
 +    /**
 +     * Sets a new details generator for row details.
 +     * <p>
 +     * The currently opened row details will be re-rendered.
 +     * 
 +     * @since 7.5.0
 +     * @param detailsGenerator
 +     *            the details generator to set
 +     * @throws IllegalArgumentException
 +     *             if detailsGenerator is <code>null</code>;
 +     */
 +    public void setDetailsGenerator(DetailsGenerator detailsGenerator)
 +            throws IllegalArgumentException {
 +        detailComponentManager.setDetailsGenerator(detailsGenerator);
 +    }
 +
 +    /**
 +     * Gets the current details generator for row details.
 +     * 
 +     * @since 7.5.0
 +     * @return the detailsGenerator the current details generator
 +     */
 +    public DetailsGenerator getDetailsGenerator() {
 +        return detailComponentManager.getDetailsGenerator();
 +    }
 +
 +    /**
 +     * Shows or hides the details for a specific item.
 +     * 
 +     * @since 7.5.0
 +     * @param itemId
 +     *            the id of the item for which to set details visibility
 +     * @param visible
 +     *            <code>true</code> to show the details, or <code>false</code>
 +     *            to hide them
 +     */
 +    public void setDetailsVisible(Object itemId, boolean visible) {
 +        detailComponentManager.setDetailsVisible(itemId, visible);
 +    }
 +
 +    /**
 +     * Checks whether details are visible for the given item.
 +     * 
 +     * @since 7.5.0
 +     * @param itemId
 +     *            the id of the item for which to check details visibility
 +     * @return <code>true</code> iff the details are visible
 +     */
 +    public boolean isDetailsVisible(Object itemId) {
 +        return detailComponentManager.isDetailsVisible(itemId);
 +    }
 +
 +    private static SelectionMode getDefaultSelectionMode() {
 +        return SelectionMode.SINGLE;
 +    }
 +
 +    @Override
 +    public void readDesign(Element design, DesignContext context) {
 +        super.readDesign(design, context);
 +
 +        Attributes attrs = design.attributes();
 +        if (attrs.hasKey("editable")) {
 +            setEditorEnabled(DesignAttributeHandler.readAttribute("editable",
 +                    attrs, boolean.class));
 +        }
 +        if (attrs.hasKey("rows")) {
 +            setHeightByRows(DesignAttributeHandler.readAttribute("rows", attrs,
 +                    double.class));
 +            setHeightMode(HeightMode.ROW);
 +        }
 +        if (attrs.hasKey("selection-mode")) {
 +            setSelectionMode(DesignAttributeHandler.readAttribute(
 +                    "selection-mode", attrs, SelectionMode.class));
 +        }
 +
 +        if (design.children().size() > 0) {
 +            if (design.children().size() > 1
 +                    || !design.child(0).tagName().equals("table")) {
 +                throw new DesignException(
 +                        "Grid needs to have a table element as its only child");
 +            }
 +            Element table = design.child(0);
 +
 +            Elements colgroups = table.getElementsByTag("colgroup");
 +            if (colgroups.size() != 1) {
 +                throw new DesignException(
 +                        "Table element in declarative Grid needs to have a"
 +                                + " colgroup defining the columns used in Grid");
 +            }
 +
 +            int i = 0;
 +            for (Element col : colgroups.get(0).getElementsByTag("col")) {
 +                String propertyId = DesignAttributeHandler.readAttribute(
 +                        "property-id", col.attributes(), "property-" + i,
 +                        String.class);
 +                addColumn(propertyId, String.class).readDesign(col, context);
 +                ++i;
 +            }
 +
 +            for (Element child : table.children()) {
 +                if (child.tagName().equals("thead")) {
 +                    header.readDesign(child, context);
 +                } else if (child.tagName().equals("tbody")) {
 +                    for (Element row : child.children()) {
 +                        Elements cells = row.children();
 +                        Object[] data = new String[cells.size()];
 +                        for (int c = 0; c < cells.size(); ++c) {
 +                            data[c] = cells.get(c).html();
 +                        }
 +                        addRow(data);
 +                    }
 +
 +                    // Since inline data is used, set HTML renderer for columns
 +                    for (Column c : getColumns()) {
 +                        c.setRenderer(new HtmlRenderer());
 +                    }
 +                } else if (child.tagName().equals("tfoot")) {
 +                    footer.readDesign(child, context);
 +                }
 +            }
 +        }
 +
 +        // Read frozen columns after columns are read.
 +        if (attrs.hasKey("frozen-columns")) {
 +            setFrozenColumnCount(DesignAttributeHandler.readAttribute(
 +                    "frozen-columns", attrs, int.class));
 +        }
 +    }
 +
 +    @Override
 +    public void writeDesign(Element design, DesignContext context) {
 +        super.writeDesign(design, context);
 +
 +        Attributes attrs = design.attributes();
 +        Grid def = context.getDefaultInstance(this);
 +
 +        DesignAttributeHandler.writeAttribute("editable", attrs,
 +                isEditorEnabled(), def.isEditorEnabled(), boolean.class);
 +
 +        DesignAttributeHandler.writeAttribute("frozen-columns", attrs,
 +                getFrozenColumnCount(), def.getFrozenColumnCount(), int.class);
 +
 +        if (getHeightMode() == HeightMode.ROW) {
 +            DesignAttributeHandler.writeAttribute("rows", attrs,
 +                    getHeightByRows(), def.getHeightByRows(), double.class);
 +        }
 +
 +        SelectionMode selectionMode = null;
 +
 +        if (selectionModel.getClass().equals(SingleSelectionModel.class)) {
 +            selectionMode = SelectionMode.SINGLE;
 +        } else if (selectionModel.getClass().equals(MultiSelectionModel.class)) {
 +            selectionMode = SelectionMode.MULTI;
 +        } else if (selectionModel.getClass().equals(NoSelectionModel.class)) {
 +            selectionMode = SelectionMode.NONE;
 +        }
 +
 +        assert selectionMode != null : "Unexpected selection model "
 +                + selectionModel.getClass().getName();
 +
 +        DesignAttributeHandler.writeAttribute("selection-mode", attrs,
 +                selectionMode, getDefaultSelectionMode(), SelectionMode.class);
 +
 +        if (columns.isEmpty()) {
 +            // Empty grid. Structure not needed.
 +            return;
 +        }
 +
 +        // Do structure.
 +        Element tableElement = design.appendElement("table");
 +        Element colGroup = tableElement.appendElement("colgroup");
 +
 +        List<Column> columnOrder = getColumns();
 +        for (int i = 0; i < columnOrder.size(); ++i) {
 +            Column column = columnOrder.get(i);
 +            Element colElement = colGroup.appendElement("col");
 +            column.writeDesign(colElement, context);
 +        }
 +
 +        // Always write thead. Reads correctly when there no header rows
 +        header.writeDesign(tableElement.appendElement("thead"), context);
 +
 +        if (context.shouldWriteData(this)) {
 +            Element bodyElement = tableElement.appendElement("tbody");
 +            for (Object itemId : datasource.getItemIds()) {
 +                Element tableRow = bodyElement.appendElement("tr");
 +                for (Column c : getColumns()) {
 +                    Object value = datasource.getItem(itemId)
 +                            .getItemProperty(c.getPropertyId()).getValue();
 +                    tableRow.appendElement("td").append(
 +                            (value != null ? DesignFormatter
 +                                    .encodeForTextNode(value.toString()) : ""));
 +                }
 +            }
 +        }
 +
 +        if (footer.getRowCount() > 0) {
 +            footer.writeDesign(tableElement.appendElement("tfoot"), context);
 +        }
 +    }
 +
 +    @Override
 +    protected Collection<String> getCustomAttributes() {
 +        Collection<String> result = super.getCustomAttributes();
 +        result.add("editor-enabled");
 +        result.add("editable");
 +        result.add("frozen-column-count");
 +        result.add("frozen-columns");
 +        result.add("height-by-rows");
 +        result.add("rows");
 +        result.add("selection-mode");
 +        result.add("header-visible");
 +        result.add("footer-visible");
 +        result.add("editor-error-handler");
 +        result.add("height-mode");
 +
 +        return result;
 +    }
 +}
index 8517962e9131961d16b07ce19467a040e2030f02,0000000000000000000000000000000000000000..148fd85fffcb07a16e0152460dea5ac2511e8658
mode 100644,000000..100644
--- /dev/null
@@@ -1,1662 -1,0 +1,1658 @@@
 +/*
 + * Copyright 2000-2014 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.util.ArrayList;
 +import java.util.Collection;
 +import java.util.Collections;
 +import java.util.HashMap;
 +import java.util.HashSet;
 +import java.util.Iterator;
 +import java.util.LinkedList;
 +import java.util.List;
 +import java.util.Map;
 +import java.util.Map.Entry;
 +import java.util.Set;
 +
 +import org.jsoup.nodes.Attributes;
 +import org.jsoup.nodes.Element;
 +import org.jsoup.select.Elements;
 +
 +import com.vaadin.event.LayoutEvents.LayoutClickEvent;
 +import com.vaadin.event.LayoutEvents.LayoutClickListener;
 +import com.vaadin.event.LayoutEvents.LayoutClickNotifier;
 +import com.vaadin.server.PaintException;
 +import com.vaadin.server.PaintTarget;
 +import com.vaadin.shared.Connector;
 +import com.vaadin.shared.EventId;
 +import com.vaadin.shared.MouseEventDetails;
 +import com.vaadin.shared.ui.MarginInfo;
 +import com.vaadin.shared.ui.gridlayout.GridLayoutServerRpc;
 +import com.vaadin.shared.ui.gridlayout.GridLayoutState;
 +import com.vaadin.shared.ui.gridlayout.GridLayoutState.ChildComponentData;
 +import com.vaadin.ui.declarative.DesignAttributeHandler;
 +import com.vaadin.ui.declarative.DesignContext;
 +
 +/**
 + * A layout where the components are laid out on a grid using cell coordinates.
 + * 
 + * <p>
 + * The GridLayout also maintains a cursor for adding components in
 + * left-to-right, top-to-bottom order.
 + * </p>
 + * 
 + * <p>
 + * Each component in a <code>GridLayout</code> uses a defined
 + * {@link GridLayout.Area area} (column1,row1,column2,row2) from the grid. The
 + * components may not overlap with the existing components - if you try to do so
 + * you will get an {@link OverlapsException}. Adding a component with cursor
 + * automatically extends the grid by increasing the grid height.
 + * </p>
 + * 
 + * <p>
 + * The grid coordinates, which are specified by a row and column index, always
 + * start from 0 for the topmost row and the leftmost column.
 + * </p>
 + * 
 + * @author Vaadin Ltd.
 + * @since 3.0
 + */
 +@SuppressWarnings("serial")
 +public class GridLayout extends AbstractLayout implements
 +        Layout.AlignmentHandler, Layout.SpacingHandler, Layout.MarginHandler,
 +        LayoutClickNotifier, LegacyComponent {
 +
 +    private GridLayoutServerRpc rpc = new GridLayoutServerRpc() {
 +
 +        @Override
 +        public void layoutClick(MouseEventDetails mouseDetails,
 +                Connector clickedConnector) {
 +            fireEvent(LayoutClickEvent.createEvent(GridLayout.this,
 +                    mouseDetails, clickedConnector));
 +
 +        }
 +    };
 +    /**
 +     * Cursor X position: this is where the next component with unspecified x,y
 +     * is inserted
 +     */
 +    private int cursorX = 0;
 +
 +    /**
 +     * Cursor Y position: this is where the next component with unspecified x,y
 +     * is inserted
 +     */
 +    private int cursorY = 0;
 +
 +    private final LinkedList<Component> components = new LinkedList<Component>();
 +
 +    private Map<Integer, Float> columnExpandRatio = new HashMap<Integer, Float>();
 +    private Map<Integer, Float> rowExpandRatio = new HashMap<Integer, Float>();
 +    private Alignment defaultComponentAlignment = Alignment.TOP_LEFT;
 +
 +    /**
 +     * Constructor for a grid of given size (number of columns and rows).
 +     * 
 +     * The grid may grow or shrink later. Grid grows automatically if you add
 +     * components outside its area.
 +     * 
 +     * @param columns
 +     *            Number of columns in the grid.
 +     * @param rows
 +     *            Number of rows in the grid.
 +     */
 +    public GridLayout(int columns, int rows) {
 +        setColumns(columns);
 +        setRows(rows);
 +        registerRpc(rpc);
 +    }
 +
 +    /**
 +     * Constructs an empty (1x1) grid layout that is extended as needed.
 +     */
 +    public GridLayout() {
 +        this(1, 1);
 +    }
 +
 +    /**
 +     * Constructs a GridLayout of given size (number of columns and rows) and
 +     * adds the given components in order to the grid.
 +     * 
 +     * @see #addComponents(Component...)
 +     * 
 +     * @param columns
 +     *            Number of columns in the grid.
 +     * @param rows
 +     *            Number of rows in the grid.
 +     * @param children
 +     *            Components to add to the grid.
 +     */
 +    public GridLayout(int columns, int rows, Component... children) {
 +        this(columns, rows);
 +        addComponents(children);
 +    }
 +
 +    @Override
 +    protected GridLayoutState getState() {
 +        return (GridLayoutState) super.getState();
 +    }
 +
 +    @Override
 +    protected GridLayoutState getState(boolean markAsDirty) {
 +        return (GridLayoutState) super.getState(markAsDirty);
 +    }
 +
 +    /**
 +     * <p>
 +     * Adds a component to the grid in the specified area. The area is defined
 +     * by specifying the upper left corner (column1, row1) and the lower right
 +     * corner (column2, row2) of the area. The coordinates are zero-based.
 +     * </p>
 +     * 
 +     * <p>
 +     * If the area overlaps with any of the existing components already present
 +     * in the grid, the operation will fail and an {@link OverlapsException} is
 +     * thrown.
 +     * </p>
 +     * 
 +     * @param component
 +     *            the component to be added, not <code>null</code>.
 +     * @param column1
 +     *            the column of the upper left corner of the area <code>c</code>
 +     *            is supposed to occupy. The leftmost column has index 0.
 +     * @param row1
 +     *            the row of the upper left corner of the area <code>c</code> is
 +     *            supposed to occupy. The topmost row has index 0.
 +     * @param column2
 +     *            the column of the lower right corner of the area
 +     *            <code>c</code> is supposed to occupy.
 +     * @param row2
 +     *            the row of the lower right corner of the area <code>c</code>
 +     *            is supposed to occupy.
 +     * @throws OverlapsException
 +     *             if the new component overlaps with any of the components
 +     *             already in the grid.
 +     * @throws OutOfBoundsException
 +     *             if the cells are outside the grid area.
 +     */
 +    public void addComponent(Component component, int column1, int row1,
 +            int column2, int row2) throws OverlapsException,
 +            OutOfBoundsException {
 +
 +        if (component == null) {
 +            throw new NullPointerException("Component must not be null");
 +        }
 +
 +        // Checks that the component does not already exist in the container
 +        if (components.contains(component)) {
 +            throw new IllegalArgumentException(
 +                    "Component is already in the container");
 +        }
 +
 +        // Creates the area
 +        final Area area = new Area(component, column1, row1, column2, row2);
 +
 +        // Checks the validity of the coordinates
 +        if (column2 < column1 || row2 < row1) {
 +            throw new IllegalArgumentException(
 +                    "Illegal coordinates for the component");
 +        }
 +        if (column1 < 0 || row1 < 0 || column2 >= getColumns()
 +                || row2 >= getRows()) {
 +            throw new OutOfBoundsException(area);
 +        }
 +
 +        // Checks that newItem does not overlap with existing items
 +        checkExistingOverlaps(area);
 +
 +        // Inserts the component to right place at the list
 +        // Respect top-down, left-right ordering
 +        // component.setParent(this);
 +        final Iterator<Component> i = components.iterator();
 +        final Map<Connector, ChildComponentData> childDataMap = getState().childData;
 +        int index = 0;
 +        boolean done = false;
 +        while (!done && i.hasNext()) {
 +            final ChildComponentData existingArea = childDataMap.get(i.next());
 +            if ((existingArea.row1 >= row1 && existingArea.column1 > column1)
 +                    || existingArea.row1 > row1) {
 +                components.add(index, component);
 +                done = true;
 +            }
 +            index++;
 +        }
 +        if (!done) {
 +            components.addLast(component);
 +        }
 +
 +        childDataMap.put(component, area.childData);
 +
 +        // Attempt to add to super
 +        try {
 +            super.addComponent(component);
 +        } catch (IllegalArgumentException e) {
 +            childDataMap.remove(component);
 +            components.remove(component);
 +            throw e;
 +        }
 +
 +        // update cursor position, if it's within this area; use first position
 +        // outside this area, even if it's occupied
 +        if (cursorX >= column1 && cursorX <= column2 && cursorY >= row1
 +                && cursorY <= row2) {
 +            // cursor within area
 +            cursorX = column2 + 1; // one right of area
 +            if (cursorX >= getColumns()) {
 +                // overflowed columns
 +                cursorX = 0; // first col
 +                // move one row down, or one row under the area
 +                cursorY = (column1 == 0 ? row2 : row1) + 1;
 +            } else {
 +                cursorY = row1;
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Tests if the given area overlaps with any of the items already on the
 +     * grid.
 +     * 
 +     * @param area
 +     *            the Area to be checked for overlapping.
 +     * @throws OverlapsException
 +     *             if <code>area</code> overlaps with any existing area.
 +     */
 +    private void checkExistingOverlaps(Area area) throws OverlapsException {
 +        for (Entry<Connector, ChildComponentData> entry : getState().childData
 +                .entrySet()) {
 +            if (componentsOverlap(entry.getValue(), area.childData)) {
 +                // Component not added, overlaps with existing component
 +                throw new OverlapsException(new Area(entry.getValue(),
 +                        (Component) entry.getKey()));
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Adds the component to the grid in cells column1,row1 (NortWest corner of
 +     * the area.) End coordinates (SouthEast corner of the area) are the same as
 +     * column1,row1. The coordinates are zero-based. Component width and height
 +     * is 1.
 +     * 
 +     * @param component
 +     *            the component to be added, not <code>null</code>.
 +     * @param column
 +     *            the column index, starting from 0.
 +     * @param row
 +     *            the row index, starting from 0.
 +     * @throws OverlapsException
 +     *             if the new component overlaps with any of the components
 +     *             already in the grid.
 +     * @throws OutOfBoundsException
 +     *             if the cell is outside the grid area.
 +     */
 +    public void addComponent(Component component, int column, int row)
 +            throws OverlapsException, OutOfBoundsException {
 +        this.addComponent(component, column, row, column, row);
 +    }
 +
 +    /**
 +     * Forces the next component to be added at the beginning of the next line.
 +     * 
 +     * <p>
 +     * Sets the cursor column to 0 and increments the cursor row by one.
 +     * </p>
 +     * 
 +     * <p>
 +     * By calling this function you can ensure that no more components are added
 +     * right of the previous component.
 +     * </p>
 +     * 
 +     * @see #space()
 +     */
 +    public void newLine() {
 +        cursorX = 0;
 +        cursorY++;
 +    }
 +
 +    /**
 +     * Moves the cursor forward by one. If the cursor goes out of the right grid
 +     * border, it is moved to the first column of the next row.
 +     * 
 +     * @see #newLine()
 +     */
 +    public void space() {
 +        cursorX++;
 +        if (cursorX >= getColumns()) {
 +            cursorX = 0;
 +            cursorY++;
 +        }
 +    }
 +
 +    /**
 +     * Adds the component into this container to the cursor position. If the
 +     * cursor position is already occupied, the cursor is moved forwards to find
 +     * free position. If the cursor goes out from the bottom of the grid, the
 +     * grid is automatically extended.
 +     * 
 +     * @param component
 +     *            the component to be added, not <code>null</code>.
 +     */
 +    @Override
 +    public void addComponent(Component component) {
 +        if (component == null) {
 +            throw new IllegalArgumentException("Component must not be null");
 +        }
 +
 +        // Finds first available place from the grid
 +        Area area;
 +        boolean done = false;
 +        while (!done) {
 +            try {
 +                area = new Area(component, cursorX, cursorY, cursorX, cursorY);
 +                checkExistingOverlaps(area);
 +                done = true;
 +            } catch (final OverlapsException e) {
 +                space();
 +            }
 +        }
 +
 +        // Extends the grid if needed
 +        if (cursorX >= getColumns()) {
 +            setColumns(cursorX + 1);
 +        }
 +        if (cursorY >= getRows()) {
 +            setRows(cursorY + 1);
 +        }
 +
 +        addComponent(component, cursorX, cursorY);
 +    }
 +
 +    /**
 +     * Removes the specified component from the layout.
 +     * 
 +     * @param component
 +     *            the component to be removed.
 +     */
 +    @Override
 +    public void removeComponent(Component component) {
 +
 +        // Check that the component is contained in the container
 +        if (component == null || !components.contains(component)) {
 +            return;
 +        }
 +
 +        getState().childData.remove(component);
 +        components.remove(component);
 +        super.removeComponent(component);
 +    }
 +
 +    /**
 +     * Removes the component specified by its cell coordinates.
 +     * 
 +     * @param column
 +     *            the component's column, starting from 0.
 +     * @param row
 +     *            the component's row, starting from 0.
 +     */
 +    public void removeComponent(int column, int row) {
 +
 +        // Finds the area
 +        for (final Iterator<Component> i = components.iterator(); i.hasNext();) {
 +            final Component component = i.next();
 +            final ChildComponentData childData = getState().childData
 +                    .get(component);
 +            if (childData.column1 == column && childData.row1 == row) {
 +                removeComponent(component);
 +                return;
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Gets an Iterator for the components contained in the layout. By using the
 +     * Iterator it is possible to step through the contents of the layout.
 +     * 
 +     * @return the Iterator of the components inside the layout.
 +     */
 +    @Override
 +    public Iterator<Component> iterator() {
 +        return Collections.unmodifiableCollection(components).iterator();
 +    }
 +
 +    /**
 +     * Gets the number of components contained in the layout. Consistent with
 +     * the iterator returned by {@link #getComponentIterator()}.
 +     * 
 +     * @return the number of contained components
 +     */
 +    @Override
 +    public int getComponentCount() {
 +        return components.size();
 +    }
 +
 +    @Override
 +    public void changeVariables(Object source, Map<String, Object> variables) {
 +        // TODO Remove once LegacyComponent is no longer implemented
 +    }
 +
 +    /**
 +     * Paints the contents of this component.
 +     * 
 +     * @param target
 +     *            the Paint Event.
 +     * @throws PaintException
 +     *             if the paint operation failed.
 +     */
 +    @Override
 +    public void paintContent(PaintTarget target) throws PaintException {
 +        final Integer[] columnExpandRatioArray = new Integer[getColumns()];
 +        final Integer[] rowExpandRatioArray = new Integer[getRows()];
 +
 +        int realColExpandRatioSum = 0;
 +        float colSum = getExpandRatioSum(columnExpandRatio);
 +        if (colSum == 0) {
 +            // no columns has been expanded, all cols have same expand
 +            // rate
 +            float equalSize = 1 / (float) getColumns();
 +            int myRatio = Math.round(equalSize * 1000);
 +            for (int i = 0; i < getColumns(); i++) {
 +                columnExpandRatioArray[i] = myRatio;
 +            }
 +            realColExpandRatioSum = myRatio * getColumns();
 +        } else {
 +            for (int i = 0; i < getColumns(); i++) {
 +                int myRatio = Math
 +                        .round((getColumnExpandRatio(i) / colSum) * 1000);
 +                columnExpandRatioArray[i] = myRatio;
 +                realColExpandRatioSum += myRatio;
 +            }
 +        }
 +
 +        int realRowExpandRatioSum = 0;
 +        float rowSum = getExpandRatioSum(rowExpandRatio);
 +        if (rowSum == 0) {
 +            // no rows have been expanded
 +            float equalSize = 1 / (float) getRows();
 +            int myRatio = Math.round(equalSize * 1000);
 +            for (int i = 0; i < getRows(); i++) {
 +                rowExpandRatioArray[i] = myRatio;
 +            }
 +            realRowExpandRatioSum = myRatio * getRows();
 +        } else {
 +            for (int cury = 0; cury < getRows(); cury++) {
 +                int myRatio = Math
 +                        .round((getRowExpandRatio(cury) / rowSum) * 1000);
 +                rowExpandRatioArray[cury] = myRatio;
 +                realRowExpandRatioSum += myRatio;
 +            }
 +        }
 +
 +        // correct possible rounding error
 +        if (rowExpandRatioArray.length > 0) {
 +            rowExpandRatioArray[0] -= realRowExpandRatioSum - 1000;
 +        }
 +        if (columnExpandRatioArray.length > 0) {
 +            columnExpandRatioArray[0] -= realColExpandRatioSum - 1000;
 +        }
 +        target.addAttribute("colExpand", columnExpandRatioArray);
 +        target.addAttribute("rowExpand", rowExpandRatioArray);
 +
 +    }
 +
 +    private float getExpandRatioSum(Map<Integer, Float> ratioMap) {
 +        float sum = 0;
 +        for (Iterator<Entry<Integer, Float>> iterator = ratioMap.entrySet()
 +                .iterator(); iterator.hasNext();) {
 +            sum += iterator.next().getValue();
 +        }
 +        return sum;
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see com.vaadin.ui.Layout.AlignmentHandler#getComponentAlignment(com
 +     * .vaadin.ui.Component)
 +     */
 +    @Override
 +    public Alignment getComponentAlignment(Component childComponent) {
 +        ChildComponentData childComponentData = getState(false).childData
 +                .get(childComponent);
 +        if (childComponentData == null) {
 +            throw new IllegalArgumentException(
 +                    "The given component is not a child of this layout");
 +        } else {
 +            return new Alignment(childComponentData.alignment);
 +        }
 +    }
 +
 +    /**
 +     * Defines a rectangular area of cells in a GridLayout.
 +     * 
 +     * <p>
 +     * Also maintains a reference to the component contained in the area.
 +     * </p>
 +     * 
 +     * <p>
 +     * The area is specified by the cell coordinates of its upper left corner
 +     * (column1,row1) and lower right corner (column2,row2). As otherwise with
 +     * GridLayout, the column and row coordinates start from zero.
 +     * </p>
 +     * 
 +     * @author Vaadin Ltd.
 +     * @since 3.0
 +     */
 +    public class Area implements Serializable {
 +        private final ChildComponentData childData;
 +        private final Component component;
 +
 +        /**
 +         * <p>
 +         * Construct a new area on a grid.
 +         * </p>
 +         * 
 +         * @param component
 +         *            the component connected to the area.
 +         * @param column1
 +         *            The column of the upper left corner cell of the area. The
 +         *            leftmost column has index 0.
 +         * @param row1
 +         *            The row of the upper left corner cell of the area. The
 +         *            topmost row has index 0.
 +         * @param column2
 +         *            The column of the lower right corner cell of the area. The
 +         *            leftmost column has index 0.
 +         * @param row2
 +         *            The row of the lower right corner cell of the area. The
 +         *            topmost row has index 0.
 +         */
 +        public Area(Component component, int column1, int row1, int column2,
 +                int row2) {
 +            this.component = component;
 +            childData = new ChildComponentData();
 +            childData.alignment = getDefaultComponentAlignment().getBitMask();
 +            childData.column1 = column1;
 +            childData.row1 = row1;
 +            childData.column2 = column2;
 +            childData.row2 = row2;
 +        }
 +
 +        public Area(ChildComponentData childData, Component component) {
 +            this.childData = childData;
 +            this.component = component;
 +        }
 +
 +        /**
 +         * Tests if this Area overlaps with another Area.
 +         * 
 +         * @param other
 +         *            the other Area that is to be tested for overlap with this
 +         *            area
 +         * @return <code>true</code> if <code>other</code> area overlaps with
 +         *         this on, <code>false</code> if it does not.
 +         */
 +        public boolean overlaps(Area other) {
 +            return componentsOverlap(childData, other.childData);
 +        }
 +
 +        /**
 +         * Gets the component connected to the area.
 +         * 
 +         * @return the Component.
 +         */
 +        public Component getComponent() {
 +            return component;
 +        }
 +
 +        /**
 +         * Gets the column of the top-left corner cell.
 +         * 
 +         * @return the column of the top-left corner cell.
 +         */
 +        public int getColumn1() {
 +            return childData.column1;
 +        }
 +
 +        /**
 +         * Gets the column of the bottom-right corner cell.
 +         * 
 +         * @return the column of the bottom-right corner cell.
 +         */
 +        public int getColumn2() {
 +            return childData.column2;
 +        }
 +
 +        /**
 +         * Gets the row of the top-left corner cell.
 +         * 
 +         * @return the row of the top-left corner cell.
 +         */
 +        public int getRow1() {
 +            return childData.row1;
 +        }
 +
 +        /**
 +         * Gets the row of the bottom-right corner cell.
 +         * 
 +         * @return the row of the bottom-right corner cell.
 +         */
 +        public int getRow2() {
 +            return childData.row2;
 +        }
 +
 +    }
 +
 +    private static boolean componentsOverlap(ChildComponentData a,
 +            ChildComponentData b) {
 +        return a.column1 <= b.column2 && a.row1 <= b.row2
 +                && a.column2 >= b.column1 && a.row2 >= b.row1;
 +    }
 +
 +    /**
 +     * Gridlayout does not support laying components on top of each other. An
 +     * <code>OverlapsException</code> is thrown when a component already exists
 +     * (even partly) at the same space on a grid with the new component.
 +     * 
 +     * @author Vaadin Ltd.
 +     * @since 3.0
 +     */
 +    public class OverlapsException extends java.lang.RuntimeException {
 +
 +        private final Area existingArea;
 +
 +        /**
 +         * Constructs an <code>OverlapsException</code>.
 +         * 
 +         * @param existingArea
 +         */
 +        public OverlapsException(Area existingArea) {
 +            this.existingArea = existingArea;
 +        }
 +
 +        @Override
 +        public String getMessage() {
 +            StringBuilder sb = new StringBuilder();
 +            Component component = existingArea.getComponent();
 +            sb.append(component);
 +            sb.append("( type = ");
 +            sb.append(component.getClass().getName());
 +            if (component.getCaption() != null) {
 +                sb.append(", caption = \"");
 +                sb.append(component.getCaption());
 +                sb.append("\"");
 +            }
 +            sb.append(")");
 +            sb.append(" is already added to ");
 +            sb.append(existingArea.childData.column1);
 +            sb.append(",");
 +            sb.append(existingArea.childData.column1);
 +            sb.append(",");
 +            sb.append(existingArea.childData.row1);
 +            sb.append(",");
 +            sb.append(existingArea.childData.row2);
 +            sb.append("(column1, column2, row1, row2).");
 +
 +            return sb.toString();
 +        }
 +
 +        /**
 +         * Gets the area .
 +         * 
 +         * @return the existing area.
 +         */
 +        public Area getArea() {
 +            return existingArea;
 +        }
 +    }
 +
 +    /**
 +     * An <code>Exception</code> object which is thrown when an area exceeds the
 +     * bounds of the grid.
 +     * 
 +     * @author Vaadin Ltd.
 +     * @since 3.0
 +     */
 +    public class OutOfBoundsException extends java.lang.RuntimeException {
 +
 +        private final Area areaOutOfBounds;
 +
 +        /**
 +         * Constructs an <code>OoutOfBoundsException</code> with the specified
 +         * detail message.
 +         * 
 +         * @param areaOutOfBounds
 +         */
 +        public OutOfBoundsException(Area areaOutOfBounds) {
 +            this.areaOutOfBounds = areaOutOfBounds;
 +        }
 +
 +        /**
 +         * Gets the area that is out of bounds.
 +         * 
 +         * @return the area out of Bound.
 +         */
 +        public Area getArea() {
 +            return areaOutOfBounds;
 +        }
 +    }
 +
 +    /**
 +     * Sets the number of columns in the grid. The column count can not be
 +     * reduced if there are any areas that would be outside of the shrunk grid.
 +     * 
 +     * @param columns
 +     *            the new number of columns in the grid.
 +     */
 +    public void setColumns(int columns) {
 +
 +        // The the param
 +        if (columns < 1) {
 +            throw new IllegalArgumentException(
 +                    "The number of columns and rows in the grid must be at least 1");
 +        }
 +
 +        // In case of no change
 +        if (getColumns() == columns) {
 +            return;
 +        }
 +
 +        // Checks for overlaps
 +        if (getColumns() > columns) {
 +            for (Entry<Connector, ChildComponentData> entry : getState().childData
 +                    .entrySet()) {
 +                if (entry.getValue().column2 >= columns) {
 +                    throw new OutOfBoundsException(new Area(entry.getValue(),
 +                            (Component) entry.getKey()));
 +                }
 +            }
 +        }
 +
 +        // Forget expands for removed columns
 +        if (columns < getColumns()) {
 +            for (int i = columns - 1; i < getColumns(); i++) {
 +                columnExpandRatio.remove(i);
 +                getState().explicitColRatios.remove(i);
 +            }
 +        }
 +
 +        getState().columns = columns;
 +    }
 +
 +    /**
 +     * Get the number of columns in the grid.
 +     * 
 +     * @return the number of columns in the grid.
 +     */
 +    public int getColumns() {
 +        return getState(false).columns;
 +    }
 +
 +    /**
 +     * Sets the number of rows in the grid. The number of rows can not be
 +     * reduced if there are any areas that would be outside of the shrunk grid.
 +     * 
 +     * @param rows
 +     *            the new number of rows in the grid.
 +     */
 +    public void setRows(int rows) {
 +
 +        // The the param
 +        if (rows < 1) {
 +            throw new IllegalArgumentException(
 +                    "The number of columns and rows in the grid must be at least 1");
 +        }
 +
 +        // In case of no change
 +        if (getRows() == rows) {
 +            return;
 +        }
 +
 +        // Checks for overlaps
 +        if (getRows() > rows) {
 +            for (Entry<Connector, ChildComponentData> entry : getState().childData
 +                    .entrySet()) {
 +                if (entry.getValue().row2 >= rows) {
 +                    throw new OutOfBoundsException(new Area(entry.getValue(),
 +                            (Component) entry.getKey()));
 +                }
 +            }
 +        }
 +        // Forget expands for removed rows
 +        if (rows < getRows()) {
 +            for (int i = rows - 1; i < getRows(); i++) {
 +                rowExpandRatio.remove(i);
 +                getState().explicitRowRatios.remove(i);
 +            }
 +        }
 +
 +        getState().rows = rows;
 +    }
 +
 +    /**
 +     * Get the number of rows in the grid.
 +     * 
 +     * @return the number of rows in the grid.
 +     */
 +    public int getRows() {
 +        return getState(false).rows;
 +    }
 +
 +    /**
 +     * Gets the current x-position (column) of the cursor.
 +     * 
 +     * <p>
 +     * The cursor position points the position for the next component that is
 +     * added without specifying its coordinates (grid cell). When the cursor
 +     * position is occupied, the next component will be added to first free
 +     * position after the cursor.
 +     * </p>
 +     * 
 +     * @return the grid column the cursor is on, starting from 0.
 +     */
 +    public int getCursorX() {
 +        return cursorX;
 +    }
 +
 +    /**
 +     * Sets the current cursor x-position. This is usually handled automatically
 +     * by GridLayout.
 +     * 
 +     * @param cursorX
 +     */
 +    public void setCursorX(int cursorX) {
 +        this.cursorX = cursorX;
 +    }
 +
 +    /**
 +     * Gets the current y-position (row) of the cursor.
 +     * 
 +     * <p>
 +     * The cursor position points the position for the next component that is
 +     * added without specifying its coordinates (grid cell). When the cursor
 +     * position is occupied, the next component will be added to the first free
 +     * position after the cursor.
 +     * </p>
 +     * 
 +     * @return the grid row the Cursor is on.
 +     */
 +    public int getCursorY() {
 +        return cursorY;
 +    }
 +
 +    /**
 +     * Sets the current y-coordinate (row) of the cursor. This is usually
 +     * handled automatically by GridLayout.
 +     * 
 +     * @param cursorY
 +     *            the row number, starting from 0 for the topmost row.
 +     */
 +    public void setCursorY(int cursorY) {
 +        this.cursorY = cursorY;
 +    }
 +
 +    /* Documented in superclass */
 +    @Override
 +    public void replaceComponent(Component oldComponent, Component newComponent) {
 +
 +        // Gets the locations
 +        ChildComponentData oldLocation = getState().childData.get(oldComponent);
 +        ChildComponentData newLocation = getState().childData.get(newComponent);
 +
 +        if (oldLocation == null) {
 +            addComponent(newComponent);
 +        } else if (newLocation == null) {
 +            removeComponent(oldComponent);
 +            addComponent(newComponent, oldLocation.column1, oldLocation.row1,
 +                    oldLocation.column2, oldLocation.row2);
 +        } else {
 +            int oldAlignment = oldLocation.alignment;
 +            oldLocation.alignment = newLocation.alignment;
 +            newLocation.alignment = oldAlignment;
 +
 +            getState().childData.put(newComponent, oldLocation);
 +            getState().childData.put(oldComponent, newLocation);
 +        }
 +    }
 +
 +    /*
 +     * Removes all components from this container.
 +     * 
 +     * @see com.vaadin.ui.ComponentContainer#removeAllComponents()
 +     */
 +    @Override
 +    public void removeAllComponents() {
 +        super.removeAllComponents();
 +        cursorX = 0;
 +        cursorY = 0;
 +    }
 +
 +    @Override
 +    public void setComponentAlignment(Component childComponent,
 +            Alignment alignment) {
 +        ChildComponentData childComponentData = getState().childData
 +                .get(childComponent);
 +        if (childComponentData == null) {
 +            throw new IllegalArgumentException(
 +                    "Component must be added to layout before using setComponentAlignment()");
 +        } else {
 +            if (alignment == null) {
 +                childComponentData.alignment = GridLayoutState.ALIGNMENT_DEFAULT
 +                        .getBitMask();
 +            } else {
 +                childComponentData.alignment = alignment.getBitMask();
 +            }
 +        }
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see com.vaadin.ui.Layout.SpacingHandler#setSpacing(boolean)
 +     */
 +    @Override
 +    public void setSpacing(boolean spacing) {
 +        getState().spacing = spacing;
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see com.vaadin.ui.Layout.SpacingHandler#isSpacing()
 +     */
 +    @Override
 +    public boolean isSpacing() {
 +        return getState(false).spacing;
 +    }
 +
 +    /**
 +     * Inserts an empty row at the specified position in the grid.
 +     * 
 +     * @param row
 +     *            Index of the row before which the new row will be inserted.
 +     *            The leftmost row has index 0.
 +     */
 +    public void insertRow(int row) {
 +        if (row > getRows()) {
 +            throw new IllegalArgumentException("Cannot insert row at " + row
 +                    + " in a gridlayout with height " + getRows());
 +        }
 +
 +        for (ChildComponentData existingArea : getState().childData.values()) {
 +            // Areas ending below the row needs to be moved down or stretched
 +            if (existingArea.row2 >= row) {
 +                existingArea.row2++;
 +
 +                // Stretch areas that span over the selected row
 +                if (existingArea.row1 >= row) {
 +                    existingArea.row1++;
 +                }
 +
 +            }
 +        }
 +
 +        if (cursorY >= row) {
 +            cursorY++;
 +        }
 +
 +        setRows(getRows() + 1);
 +        markAsDirty();
 +    }
 +
 +    /**
 +     * Removes a row and all the components in the row.
 +     * 
 +     * <p>
 +     * Components which span over several rows are removed if the selected row
 +     * is on the first row of such a component.
 +     * </p>
 +     * 
 +     * <p>
 +     * If the last row is removed then all remaining components will be removed
 +     * and the grid will be reduced to one row. The cursor will be moved to the
 +     * upper left cell of the grid.
 +     * </p>
 +     * 
 +     * @param row
 +     *            Index of the row to remove. The leftmost row has index 0.
 +     */
 +    public void removeRow(int row) {
 +        if (row >= getRows()) {
 +            throw new IllegalArgumentException("Cannot delete row " + row
 +                    + " from a gridlayout with height " + getRows());
 +        }
 +
 +        // Remove all components in row
 +        for (int col = 0; col < getColumns(); col++) {
 +            removeComponent(col, row);
 +        }
 +
 +        // Shrink or remove areas in the selected row
 +        for (ChildComponentData existingArea : getState().childData.values()) {
 +            if (existingArea.row2 >= row) {
 +                existingArea.row2--;
 +
 +                if (existingArea.row1 > row) {
 +                    existingArea.row1--;
 +                }
 +            }
 +        }
 +
 +        if (getRows() == 1) {
 +            /*
 +             * Removing the last row means that the dimensions of the Grid
 +             * layout will be truncated to 1 empty row and the cursor is moved
 +             * to the first cell
 +             */
 +            cursorX = 0;
 +            cursorY = 0;
 +        } else {
 +            setRows(getRows() - 1);
 +            if (cursorY > row) {
 +                cursorY--;
 +            }
 +        }
 +
 +        markAsDirty();
 +
 +    }
 +
 +    /**
 +     * Sets the expand ratio of given column.
 +     * 
 +     * <p>
 +     * The expand ratio defines how excess space is distributed among columns.
 +     * Excess space means space that is left over from components that are not
 +     * sized relatively. By default, the excess space is distributed evenly.
 +     * </p>
 +     * 
 +     * <p>
 +     * Note that the component width of the GridLayout must be defined (fixed or
 +     * relative, as opposed to undefined) for this method to have any effect.
 +     * </p>
 +     * 
 +     * @see #setWidth(float, int)
 +     * 
 +     * @param columnIndex
 +     * @param ratio
 +     */
 +    public void setColumnExpandRatio(int columnIndex, float ratio) {
 +        columnExpandRatio.put(columnIndex, ratio);
 +        getState().explicitColRatios.add(columnIndex);
 +        markAsDirty();
 +    }
 +
 +    /**
 +     * Returns the expand ratio of given column
 +     * 
 +     * @see #setColumnExpandRatio(int, float)
 +     * 
 +     * @param columnIndex
 +     * @return the expand ratio, 0.0f by default
 +     */
 +    public float getColumnExpandRatio(int columnIndex) {
 +        Float r = columnExpandRatio.get(columnIndex);
 +        return r == null ? 0 : r.floatValue();
 +    }
 +
 +    /**
 +     * Sets the expand ratio of given row.
 +     * 
 +     * <p>
 +     * Expand ratio defines how excess space is distributed among rows. Excess
 +     * space means the space left over from components that are not sized
 +     * relatively. By default, the excess space is distributed evenly.
 +     * </p>
 +     * 
 +     * <p>
 +     * Note, that height needs to be defined (fixed or relative, as opposed to
 +     * undefined height) for this method to have any effect.
 +     * </p>
 +     * 
 +     * @see #setHeight(float, int)
 +     * 
 +     * @param rowIndex
 +     *            The row index, starting from 0 for the topmost row.
 +     * @param ratio
 +     */
 +    public void setRowExpandRatio(int rowIndex, float ratio) {
 +        rowExpandRatio.put(rowIndex, ratio);
 +        getState().explicitRowRatios.add(rowIndex);
 +        markAsDirty();
 +    }
 +
 +    /**
 +     * Returns the expand ratio of given row.
 +     * 
 +     * @see #setRowExpandRatio(int, float)
 +     * 
 +     * @param rowIndex
 +     *            The row index, starting from 0 for the topmost row.
 +     * @return the expand ratio, 0.0f by default
 +     */
 +    public float getRowExpandRatio(int rowIndex) {
 +        Float r = rowExpandRatio.get(rowIndex);
 +        return r == null ? 0 : r.floatValue();
 +    }
 +
 +    /**
 +     * Gets the Component at given index.
 +     * 
 +     * @param x
 +     *            The column index, starting from 0 for the leftmost column.
 +     * @param y
 +     *            The row index, starting from 0 for the topmost row.
 +     * @return Component in given cell or null if empty
 +     */
 +    public Component getComponent(int x, int y) {
 +        for (Entry<Connector, ChildComponentData> entry : getState(false).childData
 +                .entrySet()) {
 +            ChildComponentData childData = entry.getValue();
 +            if (childData.column1 <= x && x <= childData.column2
 +                    && childData.row1 <= y && y <= childData.row2) {
 +                return (Component) entry.getKey();
 +            }
 +        }
 +        return null;
 +    }
 +
 +    /**
 +     * Returns information about the area where given component is laid in the
 +     * GridLayout.
 +     * 
 +     * @param component
 +     *            the component whose area information is requested.
 +     * @return an Area object that contains information how component is laid in
 +     *         the grid
 +     */
 +    public Area getComponentArea(Component component) {
 +        ChildComponentData childComponentData = getState(false).childData
 +                .get(component);
 +        if (childComponentData == null) {
 +            return null;
 +        } else {
 +            return new Area(childComponentData, component);
 +        }
 +    }
 +
 +    @Override
 +    public void addLayoutClickListener(LayoutClickListener listener) {
 +        addListener(EventId.LAYOUT_CLICK_EVENT_IDENTIFIER,
 +                LayoutClickEvent.class, listener,
 +                LayoutClickListener.clickMethod);
 +    }
 +
 +    /**
 +     * @deprecated As of 7.0, replaced by
 +     *             {@link #addLayoutClickListener(LayoutClickListener)}
 +     **/
 +    @Override
 +    @Deprecated
 +    public void addListener(LayoutClickListener listener) {
 +        addLayoutClickListener(listener);
 +    }
 +
 +    @Override
 +    public void removeLayoutClickListener(LayoutClickListener listener) {
 +        removeListener(EventId.LAYOUT_CLICK_EVENT_IDENTIFIER,
 +                LayoutClickEvent.class, listener);
 +    }
 +
 +    /**
 +     * @deprecated As of 7.0, replaced by
 +     *             {@link #removeLayoutClickListener(LayoutClickListener)}
 +     **/
 +    @Override
 +    @Deprecated
 +    public void removeListener(LayoutClickListener listener) {
 +        removeLayoutClickListener(listener);
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see com.vaadin.ui.Layout.MarginHandler#setMargin(boolean)
 +     */
 +    @Override
 +    public void setMargin(boolean enabled) {
 +        setMargin(new MarginInfo(enabled));
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see
 +     * com.vaadin.ui.Layout.MarginHandler#setMargin(com.vaadin.shared.ui.MarginInfo
 +     * )
 +     */
 +    @Override
 +    public void setMargin(MarginInfo marginInfo) {
 +        getState().marginsBitmask = marginInfo.getBitMask();
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see com.vaadin.ui.Layout.MarginHandler#getMargin()
 +     */
 +    @Override
 +    public MarginInfo getMargin() {
 +        return new MarginInfo(getState(false).marginsBitmask);
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see com.vaadin.ui.Layout.AlignmentHandler#getDefaultComponentAlignment()
 +     */
 +    @Override
 +    public Alignment getDefaultComponentAlignment() {
 +        return defaultComponentAlignment;
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see
 +     * com.vaadin.ui.Layout.AlignmentHandler#setDefaultComponentAlignment(com
 +     * .vaadin.ui.Alignment)
 +     */
 +    @Override
 +    public void setDefaultComponentAlignment(Alignment defaultAlignment) {
 +        defaultComponentAlignment = defaultAlignment;
 +    }
 +
 +    /**
 +     * Sets whether empty rows and columns should be considered as non-existent
 +     * when rendering or not. If this is set to true then the spacing between
 +     * multiple empty columns (or rows) will be collapsed.
 +     * 
 +     * The default behavior is to consider all rows and columns as visible
 +     * 
 +     * NOTE that this must be set before the initial rendering takes place.
 +     * Updating this on the fly is not supported.
 +     * 
 +     * @since 7.3
 +     * @param hideEmptyRowsAndColumns
 +     *            true to hide empty rows and columns, false to leave them as-is
 +     */
 +    public void setHideEmptyRowsAndColumns(boolean hideEmptyRowsAndColumns) {
 +        getState().hideEmptyRowsAndColumns = hideEmptyRowsAndColumns;
 +    }
 +
 +    /**
 +     * Checks whether whether empty rows and columns should be considered as
 +     * non-existent when rendering or not.
 +     * 
 +     * @see #setHideEmptyRowsAndColumns(boolean)
 +     * @since 7.3
 +     * @return true if empty rows and columns are hidden, false otherwise
 +     */
 +    public boolean isHideEmptyRowsAndColumns() {
 +        return getState(false).hideEmptyRowsAndColumns;
 +    }
 +
 +    /**
 +     * {@inheritDoc}
 +     * <p>
 +     * After reading the design, cursorY is set to point to a row outside of the
 +     * GridLayout area. CursorX is reset to 0.
 +     */
 +    @Override
 +    public void readDesign(Element design, DesignContext designContext) {
 +        super.readDesign(design, designContext);
 +
 +        setMargin(readMargin(design, getMargin(), designContext));
 +
 +        List<Element> rowElements = new ArrayList<Element>();
 +        List<Map<Integer, Component>> rows = new ArrayList<Map<Integer, Component>>();
 +        // Prepare a 2D map for reading column contents
 +        for (Element e : design.children()) {
 +            if (e.tagName().equalsIgnoreCase("row")) {
 +                rowElements.add(e);
 +                rows.add(new HashMap<Integer, Component>());
 +
 +            }
 +        }
 +        setRows(Math.max(rows.size(), 1));
-                     child = designContext.readDesign(col.child(0));
++        Map<Component, Alignment> alignments = new HashMap<Component, Alignment>();
 +        List<Integer> columnExpandRatios = new ArrayList<Integer>();
 +        for (int row = 0; row < rowElements.size(); ++row) {
 +            Element rowElement = rowElements.get(row);
 +
 +            // Row Expand
 +            if (rowElement.hasAttr("expand")) {
 +                int expand = DesignAttributeHandler.readAttribute("expand",
 +                        rowElement.attributes(), int.class);
 +                setRowExpandRatio(row, expand);
 +            }
 +
 +            Elements cols = rowElement.children();
 +
 +            // Amount of skipped columns due to spanned components
 +            int skippedColumns = 0;
 +
 +            for (int column = 0; column < cols.size(); ++column) {
 +                while (rows.get(row).containsKey(column + skippedColumns)) {
 +                    // Skip any spanned components
 +                    skippedColumns++;
 +                }
 +
 +                Element col = cols.get(column);
 +                Component child = null;
 +
 +                if (col.children().size() > 0) {
-                     if (alignment.isMiddle()) {
-                         childElement.attr(":middle", true);
-                     } else if (alignment.isBottom()) {
-                         childElement.attr(":bottom", true);
-                     }
-                     if (alignment.isCenter()) {
-                         childElement.attr(":center", true);
-                     } else if (alignment.isRight()) {
-                         childElement.attr(":right", true);
-                     }
++                    Element childElement = col.child(0);
++                    child = designContext.readDesign(childElement);
++                    alignments.put(child, DesignAttributeHandler
++                            .readAlignment(childElement.attributes()));
 +                    // TODO: Currently ignoring any extra children.
 +                    // Needs Error handling?
 +                } // Else: Empty placeholder. No child component.
 +
 +                // Handle rowspan and colspan for this child component
 +                Attributes attr = col.attributes();
 +                int colspan = DesignAttributeHandler.readAttribute("colspan",
 +                        attr, 1, int.class);
 +                int rowspan = DesignAttributeHandler.readAttribute("rowspan",
 +                        attr, 1, int.class);
 +
 +                for (int rowIndex = row; rowIndex < row + rowspan; ++rowIndex) {
 +                    for (int colIndex = column; colIndex < column + colspan; ++colIndex) {
 +                        if (rowIndex == rows.size()) {
 +                            // Rowspan with not enough rows. Fix by adding rows.
 +                            rows.add(new HashMap<Integer, Component>());
 +                        }
 +                        rows.get(rowIndex)
 +                                .put(colIndex + skippedColumns, child);
 +                    }
 +                }
 +
 +                // Read column expand ratios if handling the first row.
 +                if (row == 0) {
 +                    if (col.hasAttr("expand")) {
 +                        for (String expand : col.attr("expand").split(",")) {
 +                            columnExpandRatios.add(Integer.parseInt(expand));
 +                        }
 +                    } else {
 +                        for (int c = 0; c < colspan; ++c) {
 +                            columnExpandRatios.add(0);
 +                        }
 +                    }
 +                }
 +
 +                skippedColumns += (colspan - 1);
 +            }
 +        }
 +
 +        // Calculate highest column count and set columns
 +        int colMax = 0;
 +        for (Map<Integer, Component> cols : rows) {
 +            if (colMax < cols.size()) {
 +                colMax = cols.size();
 +            }
 +        }
 +        setColumns(Math.max(colMax, 1));
 +
 +        for (int i = 0; i < columnExpandRatios.size(); ++i) {
 +            setColumnExpandRatio(i, columnExpandRatios.get(i));
 +        }
 +
 +        // Reiterate through the 2D map and add components to GridLayout
 +        Set<Component> visited = new HashSet<Component>();
 +
 +        // Ignore any missing components
 +        visited.add(null);
 +
 +        for (int i = 0; i < rows.size(); ++i) {
 +            Map<Integer, Component> row = rows.get(i);
 +            for (int j = 0; j < colMax; ++j) {
 +                Component child = row.get(j);
 +                if (visited.contains(child)) {
 +                    // Empty location or already handled child
 +                    continue;
 +                }
 +                visited.add(child);
 +
 +                // Figure out col and rowspan from 2D map
 +                int colspan = 0;
 +                while (j + colspan + 1 < row.size()
 +                        && row.get(j + colspan + 1) == child) {
 +                    ++colspan;
 +                }
 +
 +                int rowspan = 0;
 +                while (i + rowspan + 1 < rows.size()
 +                        && rows.get(i + rowspan + 1).get(j) == child) {
 +                    ++rowspan;
 +                }
 +
 +                // Add component with area
 +                addComponent(child, j, i, j + colspan, i + rowspan);
++                setComponentAlignment(child, alignments.get(child));
 +            }
 +        }
 +        // Set cursor position explicitly
 +        setCursorY(getRows());
 +        setCursorX(0);
 +    }
 +
 +    @Override
 +    public void writeDesign(Element design, DesignContext designContext) {
 +        super.writeDesign(design, designContext);
 +
 +        GridLayout def = designContext.getDefaultInstance(this);
 +
 +        writeMargin(design, getMargin(), def.getMargin(), designContext);
 +
 +        if (!designContext.shouldWriteChildren(this, def)) {
 +            return;
 +        }
 +
 +        if (components.isEmpty()) {
 +            writeEmptyColsAndRows(design, designContext);
 +            return;
 +        }
 +
 +        final Map<Connector, ChildComponentData> childData = getState().childData;
 +
 +        // Make a 2D map of component areas.
 +        Component[][] componentMap = new Component[getState().rows][getState().columns];
 +        final Component dummyComponent = new Label("");
 +
 +        for (Component component : components) {
 +            ChildComponentData coords = childData.get(component);
 +            for (int row = coords.row1; row <= coords.row2; ++row) {
 +                for (int col = coords.column1; col <= coords.column2; ++col) {
 +                    componentMap[row][col] = component;
 +                }
 +            }
 +        }
 +
 +        // Go through the map and write only needed column tags
 +        Set<Connector> visited = new HashSet<Connector>();
 +
 +        // Skip the dummy placeholder
 +        visited.add(dummyComponent);
 +
 +        for (int i = 0; i < componentMap.length; ++i) {
 +            Element row = design.appendElement("row");
 +
 +            // Row Expand
 +            DesignAttributeHandler.writeAttribute("expand", row.attributes(),
 +                    (int) getRowExpandRatio(i), 0, int.class);
 +
 +            int colspan = 1;
 +            Element col;
 +            for (int j = 0; j < componentMap[i].length; ++j) {
 +                Component child = componentMap[i][j];
 +                if (child != null) {
 +                    if (visited.contains(child)) {
 +                        // Child has already been written in the design
 +                        continue;
 +                    }
 +                    visited.add(child);
 +
 +                    Element childElement = designContext.createElement(child);
 +                    col = row.appendElement("column");
 +
 +                    // Write child data into design
 +                    ChildComponentData coords = childData.get(child);
 +
 +                    Alignment alignment = getComponentAlignment(child);
++                    DesignAttributeHandler.writeAlignment(childElement,
++                            alignment);
 +
 +                    col.appendChild(childElement);
 +                    if (coords.row1 != coords.row2) {
 +                        col.attr("rowspan", ""
 +                                + (1 + coords.row2 - coords.row1));
 +                    }
 +
 +                    colspan = 1 + coords.column2 - coords.column1;
 +                    if (colspan > 1) {
 +                        col.attr("colspan", "" + colspan);
 +                    }
 +
 +                } else {
 +                    boolean hasExpands = false;
 +                    if (i == 0
 +                            && lastComponentOnRow(componentMap[i], j, visited)) {
 +                        // A column with expand and no content in the end of
 +                        // first row needs to be present.
 +                        for (int c = j; c < componentMap[i].length; ++c) {
 +                            if ((int) getColumnExpandRatio(c) > 0) {
 +                                hasExpands = true;
 +                            }
 +                        }
 +                    }
 +
 +                    if (lastComponentOnRow(componentMap[i], j, visited)
 +                            && !hasExpands) {
 +                        continue;
 +                    }
 +
 +                    // Empty placeholder tag.
 +                    col = row.appendElement("column");
 +
 +                    // Use colspan to make placeholders more pleasant
 +                    while (j + colspan < componentMap[i].length
 +                            && componentMap[i][j + colspan] == child) {
 +                        ++colspan;
 +                    }
 +
 +                    int rowspan = getRowSpan(componentMap, i, j, colspan, child);
 +                    if (colspan > 1) {
 +                        col.attr("colspan", "" + colspan);
 +                    }
 +                    if (rowspan > 1) {
 +                        col.attr("rowspan", "" + rowspan);
 +                    }
 +                    for (int x = 0; x < rowspan; ++x) {
 +                        for (int y = 0; y < colspan; ++y) {
 +                            // Mark handled columns
 +                            componentMap[i + x][j + y] = dummyComponent;
 +                        }
 +                    }
 +                }
 +
 +                // Column expands
 +                if (i == 0) {
 +                    // Only do expands on first row
 +                    String expands = "";
 +                    boolean expandRatios = false;
 +                    for (int c = 0; c < colspan; ++c) {
 +                        int colExpand = (int) getColumnExpandRatio(j + c);
 +                        if (colExpand > 0) {
 +                            expandRatios = true;
 +                        }
 +                        expands += (c > 0 ? "," : "") + colExpand;
 +                    }
 +                    if (expandRatios) {
 +                        col.attr("expand", expands);
 +                    }
 +                }
 +
 +                j += colspan - 1;
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Fills in the design with rows and empty columns. This needs to be done
 +     * for empty {@link GridLayout}, because there's no other way to serialize
 +     * info about number of columns and rows if there are absolutely no
 +     * components in the {@link GridLayout}
 +     * 
 +     * @param design
 +     * @param designContext
 +     */
 +    private void writeEmptyColsAndRows(Element design,
 +            DesignContext designContext) {
 +        int rowCount = getState(false).rows;
 +        int colCount = getState(false).columns;
 +
 +        // only write cols and rows tags if size is not 1x1
 +        if (rowCount == 1 && colCount == 1) {
 +            return;
 +        }
 +
 +        for (int i = 0; i < rowCount; i++) {
 +            Element row = design.appendElement("row");
 +            for (int j = 0; j < colCount; j++) {
 +                row.appendElement("column");
 +            }
 +        }
 +
 +    }
 +
 +    private int getRowSpan(Component[][] compMap, int i, int j, int colspan,
 +            Component child) {
 +        int rowspan = 1;
 +        while (i + rowspan < compMap.length && compMap[i + rowspan][j] == child) {
 +            for (int k = 0; k < colspan; ++k) {
 +                if (compMap[i + rowspan][j + k] != child) {
 +                    return rowspan;
 +                }
 +            }
 +            rowspan++;
 +        }
 +        return rowspan;
 +    }
 +
 +    private boolean lastComponentOnRow(Component[] componentArray, int j,
 +            Set<Connector> visited) {
 +        while ((++j) < componentArray.length) {
 +            Component child = componentArray[j];
 +            if (child != null && !visited.contains(child)) {
 +                return false;
 +            }
 +        }
 +        return true;
 +    }
 +
 +    @Override
 +    protected Collection<String> getCustomAttributes() {
 +        Collection<String> result = super.getCustomAttributes();
 +        result.add("cursor-x");
 +        result.add("cursor-y");
 +        result.add("rows");
 +        result.add("columns");
 +        return result;
 +    }
 +}
index b5cd384f530266841a07f2f26d51362e9cacc0f2,0000000000000000000000000000000000000000..70399ae566b45e739befcc429ac9f631e006cfe9
mode 100644,000000..100644
--- /dev/null
@@@ -1,1503 -1,0 +1,1505 @@@
-             window.close();
 +/*
 + * Copyright 2000-2014 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.Arrays;
 +import java.util.Collection;
 +import java.util.Collections;
 +import java.util.List;
 +import java.util.Map;
 +
 +import org.jsoup.nodes.Element;
 +import org.jsoup.select.Elements;
 +
 +import com.vaadin.event.FieldEvents.BlurEvent;
 +import com.vaadin.event.FieldEvents.BlurListener;
 +import com.vaadin.event.FieldEvents.BlurNotifier;
 +import com.vaadin.event.FieldEvents.FocusEvent;
 +import com.vaadin.event.FieldEvents.FocusListener;
 +import com.vaadin.event.FieldEvents.FocusNotifier;
 +import com.vaadin.event.MouseEvents.ClickEvent;
 +import com.vaadin.event.ShortcutAction;
 +import com.vaadin.event.ShortcutAction.KeyCode;
 +import com.vaadin.event.ShortcutAction.ModifierKey;
 +import com.vaadin.event.ShortcutListener;
 +import com.vaadin.server.PaintException;
 +import com.vaadin.server.PaintTarget;
 +import com.vaadin.shared.Connector;
 +import com.vaadin.shared.MouseEventDetails;
 +import com.vaadin.shared.ui.window.WindowMode;
 +import com.vaadin.shared.ui.window.WindowRole;
 +import com.vaadin.shared.ui.window.WindowServerRpc;
 +import com.vaadin.shared.ui.window.WindowState;
 +import com.vaadin.ui.declarative.DesignAttributeHandler;
 +import com.vaadin.ui.declarative.DesignContext;
 +import com.vaadin.ui.declarative.DesignException;
 +import com.vaadin.util.ReflectTools;
 +
 +/**
 + * A component that represents a floating popup window that can be added to a
 + * {@link UI}. A window is added to a {@code UI} using
 + * {@link UI#addWindow(Window)}. </p>
 + * <p>
 + * The contents of a window is set using {@link #setContent(Component)} or by
 + * using the {@link #Window(String, Component)} constructor.
 + * </p>
 + * <p>
 + * A window can be positioned on the screen using absolute coordinates (pixels)
 + * or set to be centered using {@link #center()}
 + * </p>
 + * <p>
 + * The caption is displayed in the window header.
 + * </p>
 + * <p>
 + * In Vaadin versions prior to 7.0.0, Window was also used as application level
 + * windows. This function is now covered by the {@link UI} class.
 + * </p>
 + * 
 + * @author Vaadin Ltd.
 + * @since 3.0
 + */
 +@SuppressWarnings({ "serial", "deprecation" })
 +public class Window extends Panel implements FocusNotifier, BlurNotifier,
 +        LegacyComponent {
 +
 +    private WindowServerRpc rpc = new WindowServerRpc() {
 +
 +        @Override
 +        public void click(MouseEventDetails mouseDetails) {
 +            fireEvent(new ClickEvent(Window.this, mouseDetails));
 +        }
 +
 +        @Override
 +        public void windowModeChanged(WindowMode newState) {
 +            setWindowMode(newState);
 +        }
 +
 +        @Override
 +        public void windowMoved(int x, int y) {
 +            if (x != getState(false).positionX) {
 +                setPositionX(x);
 +            }
 +            if (y != getState(false).positionY) {
 +                setPositionY(y);
 +            }
 +        }
 +    };
 +
 +    /**
 +     * Holds registered CloseShortcut instances for query and later removal
 +     */
 +    private List<CloseShortcut> closeShortcuts = new ArrayList<CloseShortcut>(4);
 +
 +    /**
 +     * Creates a new, empty window
 +     */
 +    public Window() {
 +        this("", null);
 +    }
 +
 +    /**
 +     * Creates a new, empty window with a given title.
 +     * 
 +     * @param caption
 +     *            the title of the window.
 +     */
 +    public Window(String caption) {
 +        this(caption, null);
 +    }
 +
 +    /**
 +     * Creates a new, empty window with the given content and title.
 +     * 
 +     * @param caption
 +     *            the title of the window.
 +     * @param content
 +     *            the contents of the window
 +     */
 +    public Window(String caption, Component content) {
 +        super(caption, content);
 +        registerRpc(rpc);
 +        setSizeUndefined();
 +        setCloseShortcut(KeyCode.ESCAPE);
 +    }
 +
 +    /* ********************************************************************* */
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see com.vaadin.ui.Panel#paintContent(com.vaadin.server.PaintTarget)
 +     */
 +
 +    @Override
 +    public synchronized void paintContent(PaintTarget target)
 +            throws PaintException {
 +        if (bringToFront != null) {
 +            target.addAttribute("bringToFront", bringToFront.intValue());
 +            bringToFront = null;
 +        }
 +
 +        // Contents of the window panel is painted
 +        super.paintContent(target);
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see
 +     * com.vaadin.ui.AbstractComponent#setParent(com.vaadin.server.ClientConnector
 +     * )
 +     */
 +    @Override
 +    public void setParent(HasComponents parent) {
 +        if (parent == null || parent instanceof UI) {
 +            super.setParent(parent);
 +        } else {
 +            throw new IllegalArgumentException(
 +                    "A Window can only be added to a UI using UI.addWindow(Window window)");
 +        }
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see com.vaadin.ui.Panel#changeVariables(java.lang.Object, java.util.Map)
 +     */
 +
 +    @Override
 +    public void changeVariables(Object source, Map<String, Object> variables) {
 +
 +        // TODO Are these for top level windows or sub windows?
 +        boolean sizeHasChanged = false;
 +        // size is handled in super class, but resize events only in windows ->
 +        // so detect if size change occurs before super.changeVariables()
 +        if (variables.containsKey("height")
 +                && (getHeightUnits() != Unit.PIXELS || (Integer) variables
 +                        .get("height") != getHeight())) {
 +            sizeHasChanged = true;
 +        }
 +        if (variables.containsKey("width")
 +                && (getWidthUnits() != Unit.PIXELS || (Integer) variables
 +                        .get("width") != getWidth())) {
 +            sizeHasChanged = true;
 +        }
 +
 +        super.changeVariables(source, variables);
 +
 +        // Positioning
 +        final Integer positionx = (Integer) variables.get("positionx");
 +        if (positionx != null) {
 +            final int x = positionx.intValue();
 +            // This is information from the client so it is already using the
 +            // position. No need to repaint.
 +            setPositionX(x < 0 ? -1 : x);
 +        }
 +        final Integer positiony = (Integer) variables.get("positiony");
 +        if (positiony != null) {
 +            final int y = positiony.intValue();
 +            // This is information from the client so it is already using the
 +            // position. No need to repaint.
 +            setPositionY(y < 0 ? -1 : y);
 +        }
 +
 +        if (isClosable()) {
 +            // Closing
 +            final Boolean close = (Boolean) variables.get("close");
 +            if (close != null && close.booleanValue()) {
 +                close();
 +            }
 +        }
 +
 +        // fire event if size has really changed
 +        if (sizeHasChanged) {
 +            fireResize();
 +        }
 +
 +        if (variables.containsKey(FocusEvent.EVENT_ID)) {
 +            fireEvent(new FocusEvent(this));
 +        } else if (variables.containsKey(BlurEvent.EVENT_ID)) {
 +            fireEvent(new BlurEvent(this));
 +        }
 +
 +    }
 +
 +    /**
 +     * Method that handles window closing (from UI).
 +     * 
 +     * <p>
 +     * By default, windows are removed from their respective UIs and thus
 +     * visually closed on browser-side.
 +     * </p>
 +     * 
 +     * <p>
 +     * To react to a window being closed (after it is closed), register a
 +     * {@link CloseListener}.
 +     * </p>
 +     */
 +    public void close() {
 +        UI uI = getUI();
 +
 +        // Don't do anything if not attached to a UI
 +        if (uI != null) {
 +            // window is removed from the UI
 +            uI.removeWindow(this);
 +        }
 +    }
 +
 +    /**
 +     * Gets the distance of Window left border in pixels from left border of the
 +     * containing (main window) when the window is in {@link WindowMode#NORMAL}.
 +     * 
 +     * @return the Distance of Window left border in pixels from left border of
 +     *         the containing (main window).or -1 if unspecified
 +     * @since 4.0.0
 +     */
 +    public int getPositionX() {
 +        return getState(false).positionX;
 +    }
 +
 +    /**
 +     * Sets the position of the window on the screen using
 +     * {@link #setPositionX(int)} and {@link #setPositionY(int)}
 +     * 
 +     * @since 7.5
 +     * @param x
 +     *            The new x coordinate for the window
 +     * @param y
 +     *            The new y coordinate for the window
 +     */
 +    public void setPosition(int x, int y) {
 +        setPositionX(x);
 +        setPositionY(y);
 +    }
 +
 +    /**
 +     * Sets the distance of Window left border in pixels from left border of the
 +     * containing (main window). Has effect only if in {@link WindowMode#NORMAL}
 +     * mode.
 +     * 
 +     * @param positionX
 +     *            the Distance of Window left border in pixels from left border
 +     *            of the containing (main window). or -1 if unspecified.
 +     * @since 4.0.0
 +     */
 +    public void setPositionX(int positionX) {
 +        getState().positionX = positionX;
 +        getState().centered = false;
 +    }
 +
 +    /**
 +     * Gets the distance of Window top border in pixels from top border of the
 +     * containing (main window) when the window is in {@link WindowMode#NORMAL}
 +     * state, or when next set to that state.
 +     * 
 +     * @return Distance of Window top border in pixels from top border of the
 +     *         containing (main window). or -1 if unspecified
 +     * 
 +     * @since 4.0.0
 +     */
 +    public int getPositionY() {
 +        return getState(false).positionY;
 +    }
 +
 +    /**
 +     * Sets the distance of Window top border in pixels from top border of the
 +     * containing (main window). Has effect only if in {@link WindowMode#NORMAL}
 +     * mode.
 +     * 
 +     * @param positionY
 +     *            the Distance of Window top border in pixels from top border of
 +     *            the containing (main window). or -1 if unspecified
 +     * 
 +     * @since 4.0.0
 +     */
 +    public void setPositionY(int positionY) {
 +        getState().positionY = positionY;
 +        getState().centered = false;
 +    }
 +
 +    private static final Method WINDOW_CLOSE_METHOD;
 +    static {
 +        try {
 +            WINDOW_CLOSE_METHOD = CloseListener.class.getDeclaredMethod(
 +                    "windowClose", new Class[] { CloseEvent.class });
 +        } catch (final java.lang.NoSuchMethodException e) {
 +            // This should never happen
 +            throw new java.lang.RuntimeException(
 +                    "Internal error, window close method not found");
 +        }
 +    }
 +
 +    public static class CloseEvent extends Component.Event {
 +
 +        /**
 +         * 
 +         * @param source
 +         */
 +        public CloseEvent(Component source) {
 +            super(source);
 +        }
 +
 +        /**
 +         * Gets the Window.
 +         * 
 +         * @return the window.
 +         */
 +        public Window getWindow() {
 +            return (Window) getSource();
 +        }
 +    }
 +
 +    /**
 +     * An interface used for listening to Window close events. Add the
 +     * CloseListener to a window and
 +     * {@link CloseListener#windowClose(CloseEvent)} will be called whenever the
 +     * user closes the window.
 +     * 
 +     * <p>
 +     * Since Vaadin 6.5, removing a window using {@link #removeWindow(Window)}
 +     * fires the CloseListener.
 +     * </p>
 +     */
 +    public interface CloseListener extends Serializable {
 +        /**
 +         * Called when the user closes a window. Use
 +         * {@link CloseEvent#getWindow()} to get a reference to the
 +         * {@link Window} that was closed.
 +         * 
 +         * @param e
 +         *            Event containing
 +         */
 +        public void windowClose(CloseEvent e);
 +    }
 +
 +    /**
 +     * Adds a CloseListener to the window.
 +     * 
 +     * For a window the CloseListener is fired when the user closes it (clicks
 +     * on the close button).
 +     * 
 +     * For a browser level window the CloseListener is fired when the browser
 +     * level window is closed. Note that closing a browser level window does not
 +     * mean it will be destroyed. Also note that Opera does not send events like
 +     * all other browsers and therefore the close listener might not be called
 +     * if Opera is used.
 +     * 
 +     * <p>
 +     * Since Vaadin 6.5, removing windows using {@link #removeWindow(Window)}
 +     * does fire the CloseListener.
 +     * </p>
 +     * 
 +     * @param listener
 +     *            the CloseListener to add.
 +     */
 +    public void addCloseListener(CloseListener listener) {
 +        addListener(CloseEvent.class, listener, WINDOW_CLOSE_METHOD);
 +    }
 +
 +    /**
 +     * @deprecated As of 7.0, replaced by
 +     *             {@link #addCloseListener(CloseListener)}
 +     **/
 +    @Deprecated
 +    public void addListener(CloseListener listener) {
 +        addCloseListener(listener);
 +    }
 +
 +    /**
 +     * Removes the CloseListener from the window.
 +     * 
 +     * <p>
 +     * For more information on CloseListeners see {@link CloseListener}.
 +     * </p>
 +     * 
 +     * @param listener
 +     *            the CloseListener to remove.
 +     */
 +    public void removeCloseListener(CloseListener listener) {
 +        removeListener(CloseEvent.class, listener, WINDOW_CLOSE_METHOD);
 +    }
 +
 +    /**
 +     * @deprecated As of 7.0, replaced by
 +     *             {@link #removeCloseListener(CloseListener)}
 +     **/
 +    @Deprecated
 +    public void removeListener(CloseListener listener) {
 +        removeCloseListener(listener);
 +    }
 +
 +    protected void fireClose() {
 +        fireEvent(new Window.CloseEvent(this));
 +    }
 +
 +    /**
 +     * Event which is fired when the mode of the Window changes.
 +     * 
 +     * @author Vaadin Ltd
 +     * @since 7.1
 +     * 
 +     */
 +    public static class WindowModeChangeEvent extends Component.Event {
 +
 +        private final WindowMode windowMode;
 +
 +        /**
 +         * 
 +         * @param source
 +         */
 +        public WindowModeChangeEvent(Component source, WindowMode windowMode) {
 +            super(source);
 +            this.windowMode = windowMode;
 +        }
 +
 +        /**
 +         * Gets the Window.
 +         * 
 +         * @return the window
 +         */
 +        public Window getWindow() {
 +            return (Window) getSource();
 +        }
 +
 +        /**
 +         * Gets the new window mode.
 +         * 
 +         * @return the new mode
 +         */
 +        public WindowMode getWindowMode() {
 +            return windowMode;
 +        }
 +    }
 +
 +    /**
 +     * An interface used for listening to Window maximize / restore events. Add
 +     * the WindowModeChangeListener to a window and
 +     * {@link WindowModeChangeListener#windowModeChanged(WindowModeChangeEvent)}
 +     * will be called whenever the window is maximized (
 +     * {@link WindowMode#MAXIMIZED}) or restored ({@link WindowMode#NORMAL} ).
 +     */
 +    public interface WindowModeChangeListener extends Serializable {
 +
 +        public static final Method windowModeChangeMethod = ReflectTools
 +                .findMethod(WindowModeChangeListener.class,
 +                        "windowModeChanged", WindowModeChangeEvent.class);
 +
 +        /**
 +         * Called when the user maximizes / restores a window. Use
 +         * {@link WindowModeChangeEvent#getWindow()} to get a reference to the
 +         * {@link Window} that was maximized / restored. Use
 +         * {@link WindowModeChangeEvent#getWindowMode()} to get a reference to
 +         * the new state.
 +         * 
 +         * @param event
 +         */
 +        public void windowModeChanged(WindowModeChangeEvent event);
 +    }
 +
 +    /**
 +     * Adds a WindowModeChangeListener to the window.
 +     * 
 +     * The WindowModeChangeEvent is fired when the user changed the display
 +     * state by clicking the maximize/restore button or by double clicking on
 +     * the window header. The event is also fired if the state is changed using
 +     * {@link #setWindowMode(WindowMode)}.
 +     * 
 +     * @param listener
 +     *            the WindowModeChangeListener to add.
 +     */
 +    public void addWindowModeChangeListener(WindowModeChangeListener listener) {
 +        addListener(WindowModeChangeEvent.class, listener,
 +                WindowModeChangeListener.windowModeChangeMethod);
 +    }
 +
 +    /**
 +     * Removes the WindowModeChangeListener from the window.
 +     * 
 +     * @param listener
 +     *            the WindowModeChangeListener to remove.
 +     */
 +    public void removeWindowModeChangeListener(WindowModeChangeListener listener) {
 +        removeListener(WindowModeChangeEvent.class, listener,
 +                WindowModeChangeListener.windowModeChangeMethod);
 +    }
 +
 +    protected void fireWindowWindowModeChange() {
 +        fireEvent(new Window.WindowModeChangeEvent(this, getState().windowMode));
 +    }
 +
 +    /**
 +     * Method for the resize event.
 +     */
 +    private static final Method WINDOW_RESIZE_METHOD;
 +    static {
 +        try {
 +            WINDOW_RESIZE_METHOD = ResizeListener.class.getDeclaredMethod(
 +                    "windowResized", new Class[] { ResizeEvent.class });
 +        } catch (final java.lang.NoSuchMethodException e) {
 +            // This should never happen
 +            throw new java.lang.RuntimeException(
 +                    "Internal error, window resized method not found");
 +        }
 +    }
 +
 +    /**
 +     * Resize events are fired whenever the client-side fires a resize-event
 +     * (e.g. the browser window is resized). The frequency may vary across
 +     * browsers.
 +     */
 +    public static class ResizeEvent extends Component.Event {
 +
 +        /**
 +         * 
 +         * @param source
 +         */
 +        public ResizeEvent(Component source) {
 +            super(source);
 +        }
 +
 +        /**
 +         * Get the window form which this event originated
 +         * 
 +         * @return the window
 +         */
 +        public Window getWindow() {
 +            return (Window) getSource();
 +        }
 +    }
 +
 +    /**
 +     * Listener for window resize events.
 +     * 
 +     * @see com.vaadin.ui.Window.ResizeEvent
 +     */
 +    public interface ResizeListener extends Serializable {
 +        public void windowResized(ResizeEvent e);
 +    }
 +
 +    /**
 +     * Add a resize listener.
 +     * 
 +     * @param listener
 +     */
 +    public void addResizeListener(ResizeListener listener) {
 +        addListener(ResizeEvent.class, listener, WINDOW_RESIZE_METHOD);
 +    }
 +
 +    /**
 +     * @deprecated As of 7.0, replaced by
 +     *             {@link #addResizeListener(ResizeListener)}
 +     **/
 +    @Deprecated
 +    public void addListener(ResizeListener listener) {
 +        addResizeListener(listener);
 +    }
 +
 +    /**
 +     * Remove a resize listener.
 +     * 
 +     * @param listener
 +     */
 +    public void removeResizeListener(ResizeListener listener) {
 +        removeListener(ResizeEvent.class, listener);
 +    }
 +
 +    /**
 +     * @deprecated As of 7.0, replaced by
 +     *             {@link #removeResizeListener(ResizeListener)}
 +     **/
 +    @Deprecated
 +    public void removeListener(ResizeListener listener) {
 +        removeResizeListener(listener);
 +    }
 +
 +    /**
 +     * Fire the resize event.
 +     */
 +    protected void fireResize() {
 +        fireEvent(new ResizeEvent(this));
 +    }
 +
 +    /**
 +     * Used to keep the right order of windows if multiple windows are brought
 +     * to front in a single changeset. If this is not used, the order is quite
 +     * random (depends on the order getting to dirty list. e.g. which window got
 +     * variable changes).
 +     */
 +    private Integer bringToFront = null;
 +
 +    /**
 +     * If there are currently several windows visible, calling this method makes
 +     * this window topmost.
 +     * <p>
 +     * This method can only be called if this window connected a UI. Else an
 +     * illegal state exception is thrown. Also if there are modal windows and
 +     * this window is not modal, and illegal state exception is thrown.
 +     * <p>
 +     */
 +    public void bringToFront() {
 +        UI uI = getUI();
 +        if (uI == null) {
 +            throw new IllegalStateException(
 +                    "Window must be attached to parent before calling bringToFront method.");
 +        }
 +        int maxBringToFront = -1;
 +        for (Window w : uI.getWindows()) {
 +            if (!isModal() && w.isModal()) {
 +                throw new IllegalStateException(
 +                        "The UI contains modal windows, non-modal window cannot be brought to front.");
 +            }
 +            if (w.bringToFront != null) {
 +                maxBringToFront = Math.max(maxBringToFront,
 +                        w.bringToFront.intValue());
 +            }
 +        }
 +        bringToFront = Integer.valueOf(maxBringToFront + 1);
 +        markAsDirty();
 +    }
 +
 +    /**
 +     * Sets window modality. When a modal window is open, components outside
 +     * that window cannot be accessed.
 +     * <p>
 +     * Keyboard navigation is restricted by blocking the tab key at the top and
 +     * bottom of the window by activating the tab stop function internally.
 +     * 
 +     * @param modal
 +     *            true if modality is to be turned on
 +     */
 +    public void setModal(boolean modal) {
 +        getState().modal = modal;
 +        center();
 +    }
 +
 +    /**
 +     * @return true if this window is modal.
 +     */
 +    public boolean isModal() {
 +        return getState(false).modal;
 +    }
 +
 +    /**
 +     * Sets window resizable.
 +     * 
 +     * @param resizable
 +     *            true if resizability is to be turned on
 +     */
 +    public void setResizable(boolean resizable) {
 +        getState().resizable = resizable;
 +    }
 +
 +    /**
 +     * 
 +     * @return true if window is resizable by the end-user, otherwise false.
 +     */
 +    public boolean isResizable() {
 +        return getState(false).resizable;
 +    }
 +
 +    /**
 +     * 
 +     * @return true if a delay is used before recalculating sizes, false if
 +     *         sizes are recalculated immediately.
 +     */
 +    public boolean isResizeLazy() {
 +        return getState(false).resizeLazy;
 +    }
 +
 +    /**
 +     * Should resize operations be lazy, i.e. should there be a delay before
 +     * layout sizes are recalculated. Speeds up resize operations in slow UIs
 +     * with the penalty of slightly decreased usability.
 +     * 
 +     * Note, some browser send false resize events for the browser window and
 +     * are therefore always lazy.
 +     * 
 +     * @param resizeLazy
 +     *            true to use a delay before recalculating sizes, false to
 +     *            calculate immediately.
 +     */
 +    public void setResizeLazy(boolean resizeLazy) {
 +        getState().resizeLazy = resizeLazy;
 +    }
 +
 +    /**
 +     * Sets this window to be centered relative to its parent window. Affects
 +     * windows only. If the window is resized as a result of the size of its
 +     * content changing, it will keep itself centered as long as its position is
 +     * not explicitly changed programmatically or by the user.
 +     * <p>
 +     * <b>NOTE:</b> This method has several issues as currently implemented.
 +     * Please refer to http://dev.vaadin.com/ticket/8971 for details.
 +     */
 +    public void center() {
 +        getState().centered = true;
 +    }
 +
 +    /**
 +     * Returns the closable status of the window. If a window is closable, it
 +     * typically shows an X in the upper right corner. Clicking on the X sends a
 +     * close event to the server. Setting closable to false will remove the X
 +     * from the window and prevent the user from closing the window.
 +     * 
 +     * Note! For historical reasons readonly controls the closability of the
 +     * window and therefore readonly and closable affect each other. Setting
 +     * readonly to true will set closable to false and vice versa.
 +     * <p/>
 +     * 
 +     * @return true if the window can be closed by the user.
 +     */
 +    public boolean isClosable() {
 +        return !isReadOnly();
 +    }
 +
 +    /**
 +     * Sets the closable status for the window. If a window is closable it
 +     * typically shows an X in the upper right corner. Clicking on the X sends a
 +     * close event to the server. Setting closable to false will remove the X
 +     * from the window and prevent the user from closing the window.
 +     * 
 +     * Note! For historical reasons readonly controls the closability of the
 +     * window and therefore readonly and closable affect each other. Setting
 +     * readonly to true will set closable to false and vice versa.
 +     * <p/>
 +     * 
 +     * @param closable
 +     *            determines if the window can be closed by the user.
 +     */
 +    public void setClosable(boolean closable) {
 +        setReadOnly(!closable);
 +    }
 +
 +    /**
 +     * Indicates whether a window can be dragged or not. By default a window is
 +     * draggable.
 +     * <p/>
 +     * 
 +     * @param draggable
 +     *            true if the window can be dragged by the user
 +     */
 +    public boolean isDraggable() {
 +        return getState(false).draggable;
 +    }
 +
 +    /**
 +     * Enables or disables that a window can be dragged (moved) by the user. By
 +     * default a window is draggable.
 +     * <p/>
 +     * 
 +     * @param draggable
 +     *            true if the window can be dragged by the user
 +     */
 +    public void setDraggable(boolean draggable) {
 +        getState().draggable = draggable;
 +    }
 +
 +    /**
 +     * Gets the current mode of the window.
 +     * 
 +     * @see WindowMode
 +     * @return the mode of the window.
 +     */
 +    public WindowMode getWindowMode() {
 +        return getState(false).windowMode;
 +    }
 +
 +    /**
 +     * Sets the mode for the window
 +     * 
 +     * @see WindowMode
 +     * @param windowMode
 +     *            The new mode
 +     */
 +    public void setWindowMode(WindowMode windowMode) {
 +        if (windowMode != getWindowMode()) {
 +            getState().windowMode = windowMode;
 +            fireWindowWindowModeChange();
 +        }
 +    }
 +
 +    /**
 +     * This is the old way of adding a keyboard shortcut to close a
 +     * {@link Window} - to preserve compatibility with existing code under the
 +     * new functionality, this method now first removes all registered close
 +     * shortcuts, then adds the default ESCAPE shortcut key, and then attempts
 +     * to add the shortcut provided as parameters to this method. This method,
 +     * and its companion {@link #removeCloseShortcut()}, are now considered
 +     * deprecated, as their main function is to preserve exact backwards
 +     * compatibility with old code. For all new code, use the new keyboard
 +     * shortcuts API: {@link #addCloseShortcut(int,int...)},
 +     * {@link #removeCloseShortcut(int,int...)},
 +     * {@link #removeAllCloseShortcuts()}, {@link #hasCloseShortcut(int,int...)}
 +     * and {@link #getCloseShortcuts()}.
 +     * <p>
 +     * Original description: Makes it possible to close the window by pressing
 +     * the given {@link KeyCode} and (optional) {@link ModifierKey}s.<br/>
 +     * Note that this shortcut only reacts while the window has focus, closing
 +     * itself - if you want to close a window from a UI, use
 +     * {@link UI#addAction(com.vaadin.event.Action)} of the UI instead.
 +     * 
 +     * @param keyCode
 +     *            the keycode for invoking the shortcut
 +     * @param modifiers
 +     *            the (optional) modifiers for invoking the shortcut. Can be set
 +     *            to null to be explicit about not having modifiers.
 +     * 
 +     * @deprecated Use {@link #addCloseShortcut(int, int...)} instead.
 +     */
 +    @Deprecated
 +    public void setCloseShortcut(int keyCode, int... modifiers) {
 +        removeCloseShortcut();
 +        addCloseShortcut(keyCode, modifiers);
 +    }
 +
 +    /**
 +     * Removes all keyboard shortcuts previously set with
 +     * {@link #setCloseShortcut(int, int...)} and
 +     * {@link #addCloseShortcut(int, int...)}, then adds the default
 +     * {@link KeyCode#ESCAPE} shortcut.
 +     * <p>
 +     * This is the old way of removing the (single) keyboard close shortcut, and
 +     * is retained only for exact backwards compatibility. For all new code, use
 +     * the new keyboard shortcuts API: {@link #addCloseShortcut(int,int...)},
 +     * {@link #removeCloseShortcut(int,int...)},
 +     * {@link #removeAllCloseShortcuts()}, {@link #hasCloseShortcut(int,int...)}
 +     * and {@link #getCloseShortcuts()}.
 +     * 
 +     * @deprecated Use {@link #removeCloseShortcut(int, int...)} instead.
 +     */
 +    @Deprecated
 +    public void removeCloseShortcut() {
 +        for (int i = 0; i < closeShortcuts.size(); ++i) {
 +            CloseShortcut sc = closeShortcuts.get(i);
 +            removeAction(sc);
 +        }
 +        closeShortcuts.clear();
 +        addCloseShortcut(KeyCode.ESCAPE);
 +    }
 +
 +    /**
 +     * Adds a close shortcut - pressing this key while holding down all (if any)
 +     * modifiers specified while this Window is in focus will close the Window.
 +     * 
 +     * @since 7.6
 +     * @param keyCode
 +     *            the keycode for invoking the shortcut
 +     * @param modifiers
 +     *            the (optional) modifiers for invoking the shortcut. Can be set
 +     *            to null to be explicit about not having modifiers.
 +     */
 +    public void addCloseShortcut(int keyCode, int... modifiers) {
 +
 +        // Ignore attempts to re-add existing shortcuts
 +        if (hasCloseShortcut(keyCode, modifiers)) {
 +            return;
 +        }
 +
 +        // Actually add the shortcut
 +        CloseShortcut shortcut = new CloseShortcut(this, keyCode, modifiers);
 +        addAction(shortcut);
 +        closeShortcuts.add(shortcut);
 +    }
 +
 +    /**
 +     * Removes a close shortcut previously added with
 +     * {@link #addCloseShortcut(int, int...)}.
 +     * 
 +     * @since 7.6
 +     * @param keyCode
 +     *            the keycode for invoking the shortcut
 +     * @param modifiers
 +     *            the (optional) modifiers for invoking the shortcut. Can be set
 +     *            to null to be explicit about not having modifiers.
 +     */
 +    public void removeCloseShortcut(int keyCode, int... modifiers) {
 +        for (CloseShortcut shortcut : closeShortcuts) {
 +            if (shortcut.equals(keyCode, modifiers)) {
 +                removeAction(shortcut);
 +                closeShortcuts.remove(shortcut);
 +                return;
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Removes all close shortcuts. This includes the default ESCAPE shortcut.
 +     * It is up to the user to add back any and all keyboard close shortcuts
 +     * they may require. For more fine-grained control over shortcuts, use
 +     * {@link #removeCloseShortcut(int, int...)}.
 +     * 
 +     * @since 7.6
 +     */
 +    public void removeAllCloseShortcuts() {
 +        for (CloseShortcut shortcut : closeShortcuts) {
 +            removeAction(shortcut);
 +        }
 +        closeShortcuts.clear();
 +    }
 +
 +    /**
 +     * Checks if a close window shortcut key has already been registered.
 +     * 
 +     * @since 7.6
 +     * @param keyCode
 +     *            the keycode for invoking the shortcut
 +     * @param modifiers
 +     *            the (optional) modifiers for invoking the shortcut. Can be set
 +     *            to null to be explicit about not having modifiers.
 +     * @return true, if an exactly matching shortcut has been registered.
 +     */
 +    public boolean hasCloseShortcut(int keyCode, int... modifiers) {
 +        for (CloseShortcut shortcut : closeShortcuts) {
 +            if (shortcut.equals(keyCode, modifiers)) {
 +                return true;
 +            }
 +        }
 +        return false;
 +    }
 +
 +    /**
 +     * Returns an unmodifiable collection of {@link CloseShortcut} objects
 +     * currently registered with this {@link Window}. This method is provided
 +     * mainly so that users can implement their own serialization routines. To
 +     * check if a certain combination of keys has been registered as a close
 +     * shortcut, use the {@link #hasCloseShortcut(int, int...)} method instead.
 +     * 
 +     * @since 7.6
 +     * @return an unmodifiable Collection of CloseShortcut objects.
 +     */
 +    public Collection<CloseShortcut> getCloseShortcuts() {
 +        return Collections.unmodifiableCollection(closeShortcuts);
 +    }
 +
 +    /**
 +     * A {@link ShortcutListener} specifically made to define a keyboard
 +     * shortcut that closes the window.
 +     * 
 +     * <pre>
 +     * <code>
 +     *  // within the window using helper
 +     *  window.setCloseShortcut(KeyCode.ESCAPE, null);
 +     * 
 +     *  // or globally
 +     *  getUI().addAction(new Window.CloseShortcut(window, KeyCode.ESCAPE));
 +     * </code>
 +     * </pre>
 +     * 
 +     */
 +    public static class CloseShortcut extends ShortcutListener {
 +        protected Window window;
 +
 +        /**
 +         * Creates a keyboard shortcut for closing the given window using the
 +         * shorthand notation defined in {@link ShortcutAction}.
 +         * 
 +         * @param window
 +         *            to be closed when the shortcut is invoked
 +         * @param shorthandCaption
 +         *            the caption with shortcut keycode and modifiers indicated
 +         */
 +        public CloseShortcut(Window window, String shorthandCaption) {
 +            super(shorthandCaption);
 +            this.window = window;
 +        }
 +
 +        /**
 +         * Creates a keyboard shortcut for closing the given window using the
 +         * given {@link KeyCode} and {@link ModifierKey}s.
 +         * 
 +         * @param window
 +         *            to be closed when the shortcut is invoked
 +         * @param keyCode
 +         *            KeyCode to react to
 +         * @param modifiers
 +         *            optional modifiers for shortcut
 +         */
 +        public CloseShortcut(Window window, int keyCode, int... modifiers) {
 +            super(null, keyCode, modifiers);
 +            this.window = window;
 +        }
 +
 +        /**
 +         * Creates a keyboard shortcut for closing the given window using the
 +         * given {@link KeyCode}.
 +         * 
 +         * @param window
 +         *            to be closed when the shortcut is invoked
 +         * @param keyCode
 +         *            KeyCode to react to
 +         */
 +        public CloseShortcut(Window window, int keyCode) {
 +            this(window, keyCode, null);
 +        }
 +
 +        @Override
 +        public void handleAction(Object sender, Object target) {
++            if (window.isClosable()) {
++                window.close();
++            }
 +        }
 +
 +        public boolean equals(int keyCode, int... modifiers) {
 +            if (keyCode != getKeyCode()) {
 +                return false;
 +            }
 +
 +            if (getModifiers() != null) {
 +                int[] mods = null;
 +                if (modifiers != null) {
 +                    // Modifiers provided by the parent ShortcutAction class
 +                    // are guaranteed to be sorted. We still need to sort
 +                    // the modifiers passed in as argument.
 +                    mods = Arrays.copyOf(modifiers, modifiers.length);
 +                    Arrays.sort(mods);
 +                }
 +                return Arrays.equals(mods, getModifiers());
 +            }
 +            return true;
 +        }
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see
 +     * com.vaadin.event.FieldEvents.FocusNotifier#addFocusListener(com.vaadin
 +     * .event.FieldEvents.FocusListener)
 +     */
 +    @Override
 +    public void addFocusListener(FocusListener listener) {
 +        addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
 +                FocusListener.focusMethod);
 +    }
 +
 +    /**
 +     * @deprecated As of 7.0, replaced by
 +     *             {@link #addFocusListener(FocusListener)}
 +     **/
 +    @Override
 +    @Deprecated
 +    public void addListener(FocusListener listener) {
 +        addFocusListener(listener);
 +    }
 +
 +    @Override
 +    public void removeFocusListener(FocusListener listener) {
 +        removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener);
 +    }
 +
 +    /**
 +     * @deprecated As of 7.0, replaced by
 +     *             {@link #removeFocusListener(FocusListener)}
 +     **/
 +    @Override
 +    @Deprecated
 +    public void removeListener(FocusListener listener) {
 +        removeFocusListener(listener);
 +    }
 +
 +    /*
 +     * (non-Javadoc)
 +     * 
 +     * @see
 +     * com.vaadin.event.FieldEvents.BlurNotifier#addBlurListener(com.vaadin.
 +     * event.FieldEvents.BlurListener)
 +     */
 +    @Override
 +    public void addBlurListener(BlurListener listener) {
 +        addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
 +                BlurListener.blurMethod);
 +    }
 +
 +    /**
 +     * @deprecated As of 7.0, replaced by {@link #addBlurListener(BlurListener)}
 +     **/
 +    @Override
 +    @Deprecated
 +    public void addListener(BlurListener listener) {
 +        addBlurListener(listener);
 +    }
 +
 +    @Override
 +    public void removeBlurListener(BlurListener listener) {
 +        removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener);
 +    }
 +
 +    /**
 +     * @deprecated As of 7.0, replaced by
 +     *             {@link #removeBlurListener(BlurListener)}
 +     **/
 +    @Override
 +    @Deprecated
 +    public void removeListener(BlurListener listener) {
 +        removeBlurListener(listener);
 +    }
 +
 +    /**
 +     * {@inheritDoc}
 +     * 
 +     * Cause the window to be brought on top of other windows and gain keyboard
 +     * focus.
 +     */
 +    @Override
 +    public void focus() {
 +        /*
 +         * When focusing a window it basically means it should be brought to the
 +         * front. Instead of just moving the keyboard focus we focus the window
 +         * and bring it top-most.
 +         */
 +        super.focus();
 +        bringToFront();
 +    }
 +
 +    @Override
 +    protected WindowState getState() {
 +        return (WindowState) super.getState();
 +    }
 +
 +    @Override
 +    protected WindowState getState(boolean markAsDirty) {
 +        return (WindowState) super.getState(markAsDirty);
 +    }
 +
 +    /**
 +     * Allows to specify which components contain the description for the
 +     * window. Text contained in these components will be read by assistive
 +     * devices when it is opened.
 +     * 
 +     * @param components
 +     *            the components to use as description
 +     */
 +    public void setAssistiveDescription(Component... components) {
 +        if (components == null) {
 +            throw new IllegalArgumentException(
 +                    "Parameter connectors must be non-null");
 +        } else {
 +            getState().contentDescription = components;
 +        }
 +    }
 +
 +    /**
 +     * Gets the components that are used as assistive description. Text
 +     * contained in these components will be read by assistive devices when the
 +     * window is opened.
 +     * 
 +     * @return array of previously set components
 +     */
 +    public Component[] getAssistiveDescription() {
 +        Connector[] contentDescription = getState(false).contentDescription;
 +        if (contentDescription == null) {
 +            return null;
 +        }
 +
 +        Component[] target = new Component[contentDescription.length];
 +        System.arraycopy(contentDescription, 0, target, 0,
 +                contentDescription.length);
 +
 +        return target;
 +    }
 +
 +    /**
 +     * Sets the accessibility prefix for the window caption.
 +     * 
 +     * This prefix is read to assistive device users before the window caption,
 +     * but not visible on the page.
 +     * 
 +     * @param prefix
 +     *            String that is placed before the window caption
 +     */
 +    public void setAssistivePrefix(String prefix) {
 +        getState().assistivePrefix = prefix;
 +    }
 +
 +    /**
 +     * Gets the accessibility prefix for the window caption.
 +     * 
 +     * This prefix is read to assistive device users before the window caption,
 +     * but not visible on the page.
 +     * 
 +     * @return The accessibility prefix
 +     */
 +    public String getAssistivePrefix() {
 +        return getState(false).assistivePrefix;
 +    }
 +
 +    /**
 +     * Sets the accessibility postfix for the window caption.
 +     * 
 +     * This postfix is read to assistive device users after the window caption,
 +     * but not visible on the page.
 +     * 
 +     * @param prefix
 +     *            String that is placed after the window caption
 +     */
 +    public void setAssistivePostfix(String assistivePostfix) {
 +        getState().assistivePostfix = assistivePostfix;
 +    }
 +
 +    /**
 +     * Gets the accessibility postfix for the window caption.
 +     * 
 +     * This postfix is read to assistive device users after the window caption,
 +     * but not visible on the page.
 +     * 
 +     * @return The accessibility postfix
 +     */
 +    public String getAssistivePostfix() {
 +        return getState(false).assistivePostfix;
 +    }
 +
 +    /**
 +     * Sets the WAI-ARIA role the window.
 +     * 
 +     * This role defines how an assistive device handles a window. Available
 +     * roles are alertdialog and dialog (@see <a
 +     * href="http://www.w3.org/TR/2011/CR-wai-aria-20110118/roles">Roles
 +     * Model</a>).
 +     * 
 +     * The default role is dialog.
 +     * 
 +     * @param role
 +     *            WAI-ARIA role to set for the window
 +     */
 +    public void setAssistiveRole(WindowRole role) {
 +        getState().role = role;
 +    }
 +
 +    /**
 +     * Gets the WAI-ARIA role the window.
 +     * 
 +     * This role defines how an assistive device handles a window. Available
 +     * roles are alertdialog and dialog (@see <a
 +     * href="http://www.w3.org/TR/2011/CR-wai-aria-20110118/roles">Roles
 +     * Model</a>).
 +     * 
 +     * @return WAI-ARIA role set for the window
 +     */
 +    public WindowRole getAssistiveRole() {
 +        return getState(false).role;
 +    }
 +
 +    /**
 +     * Set if it should be prevented to set the focus to a component outside a
 +     * non-modal window with the tab key.
 +     * <p>
 +     * This is meant to help users of assistive devices to not leaving the
 +     * window unintentionally.
 +     * <p>
 +     * For modal windows, this function is activated automatically, while
 +     * preserving the stored value of tabStop.
 +     * 
 +     * @param tabStop
 +     *            true to keep the focus inside the window when reaching the top
 +     *            or bottom, false (default) to allow leaving the window
 +     */
 +    public void setTabStopEnabled(boolean tabStop) {
 +        getState().assistiveTabStop = tabStop;
 +    }
 +
 +    /**
 +     * Get if it is prevented to leave a window with the tab key.
 +     * 
 +     * @return true when the focus is limited to inside the window, false when
 +     *         focus can leave the window
 +     */
 +    public boolean isTabStopEnabled() {
 +        return getState(false).assistiveTabStop;
 +    }
 +
 +    /**
 +     * Sets the message that is provided to users of assistive devices when the
 +     * user reaches the top of the window when leaving a window with the tab key
 +     * is prevented.
 +     * <p>
 +     * This message is not visible on the screen.
 +     * 
 +     * @param topMessage
 +     *            String provided when the user navigates with Shift-Tab keys to
 +     *            the top of the window
 +     */
 +    public void setTabStopTopAssistiveText(String topMessage) {
 +        getState().assistiveTabStopTopText = topMessage;
 +    }
 +
 +    /**
 +     * Sets the message that is provided to users of assistive devices when the
 +     * user reaches the bottom of the window when leaving a window with the tab
 +     * key is prevented.
 +     * <p>
 +     * This message is not visible on the screen.
 +     * 
 +     * @param bottomMessage
 +     *            String provided when the user navigates with the Tab key to
 +     *            the bottom of the window
 +     */
 +    public void setTabStopBottomAssistiveText(String bottomMessage) {
 +        getState().assistiveTabStopBottomText = bottomMessage;
 +    }
 +
 +    /**
 +     * Gets the message that is provided to users of assistive devices when the
 +     * user reaches the top of the window when leaving a window with the tab key
 +     * is prevented.
 +     * 
 +     * @return the top message
 +     */
 +    public String getTabStopTopAssistiveText() {
 +        return getState(false).assistiveTabStopTopText;
 +    }
 +
 +    /**
 +     * Gets the message that is provided to users of assistive devices when the
 +     * user reaches the bottom of the window when leaving a window with the tab
 +     * key is prevented.
 +     * 
 +     * @return the bottom message
 +     */
 +    public String getTabStopBottomAssistiveText() {
 +        return getState(false).assistiveTabStopBottomText;
 +    }
 +
 +    @Override
 +    public void readDesign(Element design, DesignContext context) {
 +        super.readDesign(design, context);
 +
 +        if (design.hasAttr("center")) {
 +            center();
 +        }
 +        if (design.hasAttr("position")) {
 +            String[] position = design.attr("position").split(",");
 +            setPositionX(Integer.parseInt(position[0]));
 +            setPositionY(Integer.parseInt(position[1]));
 +        }
 +
 +        // Parse shortcuts if defined, otherwise rely on default behavior
 +        if (design.hasAttr("close-shortcut")) {
 +
 +            // Parse shortcuts
 +            String[] shortcutStrings = DesignAttributeHandler.readAttribute(
 +                    "close-shortcut", design.attributes(), String.class).split(
 +                    "\\s+");
 +
 +            removeAllCloseShortcuts();
 +
 +            for (String part : shortcutStrings) {
 +                if (!part.isEmpty()) {
 +                    ShortcutAction shortcut = DesignAttributeHandler
 +                            .getFormatter().parse(part.trim(),
 +                                    ShortcutAction.class);
 +                    addCloseShortcut(shortcut.getKeyCode(),
 +                            shortcut.getModifiers());
 +                }
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Reads the content and possible assistive descriptions from the list of
 +     * child elements of a design. If an element has an
 +     * {@code :assistive-description} attribute, adds the parsed component to
 +     * the list of components used as the assistive description of this Window.
 +     * Otherwise, sets the component as the content of this Window. If there are
 +     * multiple non-description elements, throws a DesignException.
 +     * 
 +     * @param children
 +     *            child elements in a design
 +     * @param context
 +     *            the DesignContext instance used to parse the design
 +     * 
 +     * @throws DesignException
 +     *             if there are multiple non-description child elements
 +     * @throws DesignException
 +     *             if a child element could not be parsed as a Component
 +     * 
 +     * @see #setContent(Component)
 +     * @see #setAssistiveDescription(Component...)
 +     */
 +    @Override
 +    protected void readDesignChildren(Elements children, DesignContext context) {
 +        List<Component> descriptions = new ArrayList<Component>();
 +        Elements content = new Elements();
 +
 +        for (Element child : children) {
 +            if (child.hasAttr(":assistive-description")) {
 +                descriptions.add(context.readDesign(child));
 +            } else {
 +                content.add(child);
 +            }
 +        }
 +        super.readDesignChildren(content, context);
 +        setAssistiveDescription(descriptions.toArray(new Component[0]));
 +    }
 +
 +    @Override
 +    public void writeDesign(Element design, DesignContext context) {
 +        super.writeDesign(design, context);
 +
 +        Window def = context.getDefaultInstance(this);
 +
 +        if (getState().centered) {
 +            design.attr("center", true);
 +        }
 +
 +        DesignAttributeHandler.writeAttribute("position", design.attributes(),
 +                getPosition(), def.getPosition(), String.class);
 +
 +        // Process keyboard shortcuts
 +        if (closeShortcuts.size() == 1 && hasCloseShortcut(KeyCode.ESCAPE)) {
 +            // By default, we won't write anything if we're relying on default
 +            // shortcut behavior
 +        } else {
 +            // Dump all close shortcuts to a string...
 +            String attrString = "";
 +
 +            // TODO: add canonical support for array data in XML attributes
 +            for (CloseShortcut shortcut : closeShortcuts) {
 +                String shortcutString = DesignAttributeHandler.getFormatter()
 +                        .format(shortcut, CloseShortcut.class);
 +                attrString += shortcutString + " ";
 +            }
 +
 +            // Write everything except the last "," to the design
 +            DesignAttributeHandler.writeAttribute("close-shortcut",
 +                    design.attributes(), attrString.trim(), null, String.class);
 +        }
 +
 +        for (Component c : getAssistiveDescription()) {
 +            Element child = context.createElement(c).attr(
 +                    ":assistive-description", true);
 +            design.appendChild(child);
 +        }
 +    }
 +
 +    private String getPosition() {
 +        return getPositionX() + "," + getPositionY();
 +    }
 +
 +    @Override
 +    protected Collection<String> getCustomAttributes() {
 +        Collection<String> result = super.getCustomAttributes();
 +        result.add("center");
 +        result.add("position");
 +        result.add("position-y");
 +        result.add("position-x");
 +        result.add("close-shortcut");
 +        return result;
 +    }
 +}
index cee2ebe381342a38588de99c78536dc41d0d56d7,0000000000000000000000000000000000000000..bbfd90b2ccb98ed70952ac92e09bf7e51b5b9791
mode 100644,000000..100644
--- /dev/null
@@@ -1,455 -1,0 +1,508 @@@
 +/*
 + * Copyright 2000-2014 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.declarative;
 +
 +import java.beans.BeanInfo;
 +import java.beans.IntrospectionException;
 +import java.beans.Introspector;
 +import java.beans.PropertyDescriptor;
 +import java.io.Serializable;
 +import java.lang.reflect.Method;
 +import java.util.ArrayList;
 +import java.util.Collection;
 +import java.util.List;
 +import java.util.Map;
 +import java.util.concurrent.ConcurrentHashMap;
 +import java.util.logging.Level;
 +import java.util.logging.Logger;
 +import java.util.regex.Matcher;
 +import java.util.regex.Pattern;
 +
 +import org.jsoup.nodes.Attribute;
 +import org.jsoup.nodes.Attributes;
 +import org.jsoup.nodes.Element;
 +import org.jsoup.nodes.Node;
 +
 +import com.vaadin.data.util.converter.Converter;
++import com.vaadin.shared.ui.AlignmentInfo;
 +import com.vaadin.shared.util.SharedUtil;
++import com.vaadin.ui.Alignment;
 +
 +/**
 + * Default attribute handler implementation used when parsing designs to
 + * component trees. Handles all the component attributes that do not require
 + * custom handling.
 + * 
 + * @since 7.4
 + * @author Vaadin Ltd
 + */
 +public class DesignAttributeHandler implements Serializable {
 +
 +    private static Logger getLogger() {
 +        return Logger.getLogger(DesignAttributeHandler.class.getName());
 +    }
 +
 +    private static Map<Class<?>, AttributeCacheEntry> cache = new ConcurrentHashMap<Class<?>, AttributeCacheEntry>();
 +
 +    // translates string <-> object
 +    private static DesignFormatter FORMATTER = new DesignFormatter();
 +
 +    /**
 +     * Returns the currently used formatter. All primitive types and all types
 +     * needed by Vaadin components are handled by that formatter.
 +     * 
 +     * @return An instance of the formatter.
 +     */
 +    public static DesignFormatter getFormatter() {
 +        return FORMATTER;
 +    }
 +
 +    /**
 +     * Clears the children and attributes of the given element
 +     * 
 +     * @param design
 +     *            the element to be cleared
 +     */
 +    public static void clearElement(Element design) {
 +        Attributes attr = design.attributes();
 +        for (Attribute a : attr.asList()) {
 +            attr.remove(a.getKey());
 +        }
 +        List<Node> children = new ArrayList<Node>();
 +        children.addAll(design.childNodes());
 +        for (Node node : children) {
 +            node.remove();
 +        }
 +    }
 +
 +    /**
 +     * Assigns the specified design attribute to the given component.
 +     * 
 +     * @param target
 +     *            the target to which the attribute should be set
 +     * @param attribute
 +     *            the name of the attribute to be set
 +     * @param value
 +     *            the string value of the attribute
 +     * @return true on success
 +     */
 +    public static boolean assignValue(Object target, String attribute,
 +            String value) {
 +        if (target == null || attribute == null || value == null) {
 +            throw new IllegalArgumentException(
 +                    "Parameters with null value not allowed");
 +        }
 +        boolean success = false;
 +        try {
 +            Method setter = findSetterForAttribute(target.getClass(), attribute);
 +            if (setter == null) {
 +                // if we don't have the setter, there is no point in continuing
 +                success = false;
 +            } else {
 +                // we have a value from design attributes, let's use that
 +                Object param = getFormatter().parse(value,
 +                        setter.getParameterTypes()[0]);
 +                setter.invoke(target, param);
 +                success = true;
 +            }
 +        } catch (Exception e) {
 +            getLogger().log(
 +                    Level.WARNING,
 +                    "Failed to set value \"" + value + "\" to attribute "
 +                            + attribute, e);
 +        }
 +        if (!success) {
 +            getLogger().info(
 +                    "property " + attribute
 +                            + " ignored by default attribute handler");
 +        }
 +        return success;
 +    }
 +
 +    /**
 +     * Searches for supported setter and getter types from the specified class
 +     * and returns the list of corresponding design attributes
 +     * 
 +     * @param clazz
 +     *            the class scanned for setters
 +     * @return the list of supported design attributes
 +     */
 +    public static Collection<String> getSupportedAttributes(Class<?> clazz) {
 +        resolveSupportedAttributes(clazz);
 +        return cache.get(clazz).getAttributes();
 +    }
 +
 +    /**
 +     * Resolves the supported attributes and corresponding getters and setters
 +     * for the class using introspection. After resolving, the information is
 +     * cached internally by this class
 +     * 
 +     * @param clazz
 +     *            the class to resolve the supported attributes for
 +     */
 +    private static void resolveSupportedAttributes(Class<?> clazz) {
 +        if (clazz == null) {
 +            throw new IllegalArgumentException("The clazz can not be null");
 +        }
 +        if (cache.containsKey(clazz)) {
 +            // NO-OP
 +            return;
 +        }
 +        BeanInfo beanInfo;
 +        try {
 +            beanInfo = Introspector.getBeanInfo(clazz);
 +        } catch (IntrospectionException e) {
 +            throw new RuntimeException(
 +                    "Could not get supported attributes for class "
 +                            + clazz.getName());
 +        }
 +        AttributeCacheEntry entry = new AttributeCacheEntry();
 +        for (PropertyDescriptor descriptor : beanInfo.getPropertyDescriptors()) {
 +            Method getter = descriptor.getReadMethod();
 +            Method setter = descriptor.getWriteMethod();
 +            if (getter != null && setter != null
 +                    && getFormatter().canConvert(descriptor.getPropertyType())) {
 +                String attribute = toAttributeName(descriptor.getName());
 +                entry.addAttribute(attribute, getter, setter);
 +            }
 +        }
 +        cache.put(clazz, entry);
 +    }
 +
 +    /**
 +     * Writes the specified attribute to the design if it differs from the
 +     * default value got from the <code> defaultInstance <code>
 +     * 
 +     * @param component
 +     *            the component used to get the attribute value
 +     * @param attribute
 +     *            the key for the attribute
 +     * @param attr
 +     *            the attribute list where the attribute will be written
 +     * @param defaultInstance
 +     *            the default instance for comparing default values
 +     */
 +    @SuppressWarnings({ "unchecked", "rawtypes" })
 +    public static void writeAttribute(Object component, String attribute,
 +            Attributes attr, Object defaultInstance) {
 +        Method getter = findGetterForAttribute(component.getClass(), attribute);
 +        if (getter == null) {
 +            getLogger().warning(
 +                    "Could not find getter for attribute " + attribute);
 +        } else {
 +            try {
 +                // compare the value with default value
 +                Object value = getter.invoke(component);
 +                Object defaultValue = getter.invoke(defaultInstance);
 +                writeAttribute(attribute, attr, value, defaultValue,
 +                        (Class) getter.getReturnType());
 +            } catch (Exception e) {
 +                getLogger()
 +                        .log(Level.SEVERE,
 +                                "Failed to invoke getter for attribute "
 +                                        + attribute, e);
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Writes the given attribute value to a set of attributes if it differs
 +     * from the default attribute value.
 +     * 
 +     * @param attribute
 +     *            the attribute key
 +     * @param attributes
 +     *            the set of attributes where the new attribute is written
 +     * @param value
 +     *            the attribute value
 +     * @param defaultValue
 +     *            the default attribute value
 +     * @param inputType
 +     *            the type of the input value
 +     */
 +    public static <T> void writeAttribute(String attribute,
 +            Attributes attributes, T value, T defaultValue, Class<T> inputType) {
 +        if (!getFormatter().canConvert(inputType)) {
 +            throw new IllegalArgumentException("input type: "
 +                    + inputType.getName() + " not supported");
 +        }
 +        if (!SharedUtil.equals(value, defaultValue)) {
 +            String attributeValue = toAttributeValue(inputType, value);
 +            if ("".equals(attributeValue)
 +                    && (inputType == boolean.class || inputType == Boolean.class)) {
 +                attributes.put(attribute, true);
 +            } else {
 +                attributes.put(attribute, attributeValue);
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Reads the given attribute from a set of attributes. If attribute does not
 +     * exist return a given default value.
 +     * 
 +     * @param attribute
 +     *            the attribute key
 +     * @param attributes
 +     *            the set of attributes to read from
 +     * @param defaultValue
 +     *            the default value to return if attribute does not exist
 +     * @param outputType
 +     *            the output type for the attribute
 +     * @return the attribute value or the default value if the attribute is not
 +     *         found
 +     */
 +    public static <T> T readAttribute(String attribute, Attributes attributes,
 +            T defaultValue, Class<T> outputType) {
 +        T value = readAttribute(attribute, attributes, outputType);
 +        if (value != null) {
 +            return value;
 +        }
 +        return defaultValue;
 +    }
 +
 +    /**
 +     * Reads the given attribute from a set of attributes.
 +     * 
 +     * @param attribute
 +     *            the attribute key
 +     * @param attributes
 +     *            the set of attributes to read from
 +     * @param outputType
 +     *            the output type for the attribute
 +     * @return the attribute value or null
 +     */
 +    public static <T> T readAttribute(String attribute, Attributes attributes,
 +            Class<T> outputType) {
 +        if (!getFormatter().canConvert(outputType)) {
 +            throw new IllegalArgumentException("output type: "
 +                    + outputType.getName() + " not supported");
 +        }
 +        if (!attributes.hasKey(attribute)) {
 +            return null;
 +        } else {
 +            try {
 +                String value = attributes.get(attribute);
 +                return getFormatter().parse(value, outputType);
 +            } catch (Exception e) {
 +                throw new DesignException("Failed to read attribute "
 +                        + attribute, e);
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Returns the design attribute name corresponding the given method name.
 +     * For example given a method name <code>setPrimaryStyleName</code> the
 +     * return value would be <code>primary-style-name</code>
 +     * 
 +     * @param propertyName
 +     *            the property name returned by {@link IntroSpector}
 +     * @return the design attribute name corresponding the given method name
 +     */
 +    private static String toAttributeName(String propertyName) {
 +        propertyName = removeSubsequentUppercase(propertyName);
 +        String[] words = propertyName.split("(?<!^)(?=[A-Z])");
 +        StringBuilder builder = new StringBuilder();
 +        for (int i = 0; i < words.length; i++) {
 +            if (builder.length() > 0) {
 +                builder.append("-");
 +            }
 +            builder.append(words[i].toLowerCase());
 +        }
 +        return builder.toString();
 +    }
 +
 +    /**
 +     * Replaces subsequent UPPERCASE strings of length 2 or more followed either
 +     * by another uppercase letter or an end of string. This is to generalise
 +     * handling of method names like <tt>showISOWeekNumbers</tt>.
 +     * 
 +     * @param param
 +     *            Input string.
 +     * @return Input string with sequences of UPPERCASE turned into Normalcase.
 +     */
 +    private static String removeSubsequentUppercase(String param) {
 +        StringBuffer result = new StringBuffer();
 +        // match all two-or-more caps letters lead by a non-uppercase letter
 +        // followed by either a capital letter or string end
 +        Pattern pattern = Pattern.compile("(^|[^A-Z])([A-Z]{2,})([A-Z]|$)");
 +        Matcher matcher = pattern.matcher(param);
 +        while (matcher.find()) {
 +            String matched = matcher.group(2);
 +            // if this is a beginning of the string, the whole matched group is
 +            // written in lower case
 +            if (matcher.group(1).isEmpty()) {
 +                matcher.appendReplacement(result, matched.toLowerCase()
 +                        + matcher.group(3));
 +                // otherwise the first character of the group stays uppercase,
 +                // while the others are lower case
 +            } else {
 +                matcher.appendReplacement(
 +                        result,
 +                        matcher.group(1) + matched.substring(0, 1)
 +                                + matched.substring(1).toLowerCase()
 +                                + matcher.group(3));
 +            }
 +            // in both cases the uppercase letter of the next word (or string's
 +            // end) is added
 +            // this implies there is at least one extra lowercase letter after
 +            // it to be caught by the next call to find()
 +        }
 +        matcher.appendTail(result);
 +        return result.toString();
 +    }
 +
 +    /**
 +     * Serializes the given value to valid design attribute representation
 +     * 
 +     * @param sourceType
 +     *            the type of the value
 +     * @param value
 +     *            the value to be serialized
 +     * @return the given value as design attribute representation
 +     */
 +    private static String toAttributeValue(Class<?> sourceType, Object value) {
 +        if (value == null) {
 +            // TODO: Handle corner case where sourceType is String and default
 +            // value is not null. How to represent null value in attributes?
 +            return "";
 +        }
 +        Converter<String, Object> converter = getFormatter().findConverterFor(
 +                sourceType);
 +        if (converter != null) {
 +            return converter.convertToPresentation(value, String.class, null);
 +        } else {
 +            return value.toString();
 +        }
 +    }
 +
 +    /**
 +     * Returns a setter that can be used for assigning the given design
 +     * attribute to the class
 +     * 
 +     * @param clazz
 +     *            the class that is scanned for setters
 +     * @param attribute
 +     *            the design attribute to find setter for
 +     * @return the setter method or null if not found
 +     */
 +    private static Method findSetterForAttribute(Class<?> clazz,
 +            String attribute) {
 +        resolveSupportedAttributes(clazz);
 +        return cache.get(clazz).getSetter(attribute);
 +    }
 +
 +    /**
 +     * Returns a getter that can be used for reading the given design attribute
 +     * value from the class
 +     * 
 +     * @param clazz
 +     *            the class that is scanned for getters
 +     * @param attribute
 +     *            the design attribute to find getter for
 +     * @return the getter method or null if not found
 +     */
 +    private static Method findGetterForAttribute(Class<?> clazz,
 +            String attribute) {
 +        resolveSupportedAttributes(clazz);
 +        return cache.get(clazz).getGetter(attribute);
 +    }
 +
 +    /**
 +     * Cache object for caching supported attributes and their getters and
 +     * setters
 +     * 
 +     * @author Vaadin Ltd
 +     */
 +    private static class AttributeCacheEntry implements Serializable {
 +        private Map<String, Method[]> accessMethods = new ConcurrentHashMap<String, Method[]>();
 +
 +        private void addAttribute(String attribute, Method getter, Method setter) {
 +            Method[] methods = new Method[2];
 +            methods[0] = getter;
 +            methods[1] = setter;
 +            accessMethods.put(attribute, methods);
 +        }
 +
 +        private Collection<String> getAttributes() {
 +            ArrayList<String> attributes = new ArrayList<String>();
 +            attributes.addAll(accessMethods.keySet());
 +            return attributes;
 +        }
 +
 +        private Method getGetter(String attribute) {
 +            Method[] methods = accessMethods.get(attribute);
 +            return (methods != null && methods.length > 0) ? methods[0] : null;
 +        }
 +
 +        private Method getSetter(String attribute) {
 +            Method[] methods = accessMethods.get(attribute);
 +            return (methods != null && methods.length > 1) ? methods[1] : null;
 +        }
 +    }
++
++    /**
++     * Read the alignment from the given child component attributes.
++     * 
++     * @since 7.6.4
++     * @param attr
++     *            the child component attributes
++     * @return the component alignment
++     */
++    public static Alignment readAlignment(Attributes attr) {
++        int bitMask = 0;
++        if (attr.hasKey(":middle")) {
++            bitMask += AlignmentInfo.Bits.ALIGNMENT_VERTICAL_CENTER;
++        } else if (attr.hasKey(":bottom")) {
++            bitMask += AlignmentInfo.Bits.ALIGNMENT_BOTTOM;
++        } else {
++            bitMask += AlignmentInfo.Bits.ALIGNMENT_TOP;
++        }
++        if (attr.hasKey(":center")) {
++            bitMask += AlignmentInfo.Bits.ALIGNMENT_HORIZONTAL_CENTER;
++        } else if (attr.hasKey(":right")) {
++            bitMask += AlignmentInfo.Bits.ALIGNMENT_RIGHT;
++        } else {
++            bitMask += AlignmentInfo.Bits.ALIGNMENT_LEFT;
++        }
++
++        return new Alignment(bitMask);
++    }
++
++    /**
++     * Writes the alignment to the given child element attributes.
++     * 
++     * @since 7.6.4
++     * @param childElement
++     *            the child element
++     * @param alignment
++     *            the component alignment
++     */
++    public static void writeAlignment(Element childElement, Alignment alignment) {
++        if (alignment.isMiddle()) {
++            childElement.attr(":middle", true);
++        } else if (alignment.isBottom()) {
++            childElement.attr(":bottom", true);
++        }
++        if (alignment.isCenter()) {
++            childElement.attr(":center", true);
++        } else if (alignment.isRight()) {
++            childElement.attr(":right", true);
++        }
++    }
++
 +}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..579a3f18589919e1a03a4c60dc39e65326baf480
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,113 @@@
++/*
++ * Copyright 2000-2014 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.server;
++
++import java.lang.annotation.ElementType;
++import java.lang.annotation.Retention;
++import java.lang.annotation.RetentionPolicy;
++import java.lang.annotation.Target;
++
++import org.junit.Assert;
++import org.junit.Test;
++
++import com.vaadin.annotations.Theme;
++import com.vaadin.annotations.Widgetset;
++
++/**
++ * Tests for {@link UIProvider} class.
++ * 
++ * @author Vaadin Ltd
++ */
++public class UIProviderTest {
++
++    @Test
++    public void getAnnotationFor_widgetsetAnnotationForSubclass_annotationFound() {
++        Assert.assertNotNull("Widgetset annotation is not found for subclass",
++                UIProvider.getAnnotationFor(TestClass.class, Widgetset.class));
++    }
++
++    @Test
++    public void getAnnotationFor_themeAnnotationForSubclass_annotationFound() {
++        Assert.assertNotNull("Theme annotation is not found for subclass",
++                UIProvider.getAnnotationFor(TestClass.class, Theme.class));
++    }
++
++    @Test
++    public void getAnnotationFor_themeAnnotationForSubclass_annotationOverridden() {
++        Assert.assertEquals(
++                "Theme annotation is not overridden correctly in subclass",
++                "c", UIProvider.getAnnotationFor(TestClass.class, Theme.class)
++                        .value());
++    }
++
++    @Test
++    public void getAnnotationFor_notInheritedAnnotationForSubclass_annotationFound() {
++        Assert.assertNotNull(
++                "TestAnnotation annotation is not found for subclass",
++                UIProvider.getAnnotationFor(TestClass.class,
++                        TestAnnotation.class));
++    }
++
++    @Test
++    public void getAnnotationFor_directAnnotationForSubclass_annotationFound() {
++        Assert.assertNotNull(
++                "TestAnnotation1 annotation is not found for subclass",
++                UIProvider.getAnnotationFor(TestClass.class,
++                        TestAnnotation1.class));
++    }
++
++    @Test
++    public void getAnnotationFor_annotationInheritedFromInterface_annotationFound() {
++        Assert.assertNotNull(
++                "Theme annotation is not inherited from interface", UIProvider
++                        .getAnnotationFor(ClassImplementingInterface.class,
++                                Theme.class));
++    }
++
++    @Retention(RetentionPolicy.RUNTIME)
++    @Target(ElementType.TYPE)
++    public @interface TestAnnotation {
++
++    }
++
++    @Retention(RetentionPolicy.RUNTIME)
++    @Target(ElementType.TYPE)
++    public @interface TestAnnotation1 {
++
++    }
++
++    @Widgetset("a")
++    @Theme("b")
++    @TestAnnotation
++    public static class TestSuperClass {
++
++    }
++
++    @TestAnnotation1
++    @Theme("c")
++    public static class TestClass extends TestSuperClass {
++
++    }
++
++    @Theme("d")
++    public interface InterfaceWithAnnotation {
++    }
++
++    public static class ClassImplementingInterface implements
++            InterfaceWithAnnotation {
++    }
++
++}
index 0e4293481efaeaf1aa1778873df662804a3bcd76,0000000000000000000000000000000000000000..e3975e41a8dc0a265323c38f77d0fc4245172238
mode 100644,000000..100644
--- /dev/null
@@@ -1,288 -1,0 +1,304 @@@
-         Button b1 = new Button("Button 0,0 -> 1,1");
 +/*
 + * Copyright 2000-2014 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.tests.server.component.gridlayout;
 +
 +import java.io.ByteArrayInputStream;
 +import java.io.ByteArrayOutputStream;
 +import java.io.IOException;
 +
 +import org.junit.Assert;
 +import org.junit.Test;
 +
 +import com.vaadin.shared.ui.label.ContentMode;
 +import com.vaadin.tests.server.component.DeclarativeMarginTestBase;
 +import com.vaadin.ui.Alignment;
 +import com.vaadin.ui.Button;
 +import com.vaadin.ui.Component;
 +import com.vaadin.ui.GridLayout;
 +import com.vaadin.ui.Label;
 +import com.vaadin.ui.declarative.Design;
 +import com.vaadin.ui.declarative.DesignContext;
 +
 +public class GridLayoutDeclarativeTest extends
 +        DeclarativeMarginTestBase<GridLayout> {
 +
 +    @Test
 +    public void testMargins() {
 +        testMargins("vaadin-grid-layout");
 +    }
 +
 +    @Test
 +    public void testSimpleGridLayout() {
 +        Button b1 = new Button("Button 0,0");
 +        Button b2 = new Button("Button 0,1");
 +        Button b3 = new Button("Button 1,0");
 +        Button b4 = new Button("Button 1,1");
 +        b1.setCaptionAsHtml(true);
 +        b2.setCaptionAsHtml(true);
 +        b3.setCaptionAsHtml(true);
 +        b4.setCaptionAsHtml(true);
 +        String design = "<vaadin-grid-layout><row>" //
 +                + "<column expand=1>" + writeChild(b1) + "</column>" //
 +                + "<column expand=3>" + writeChild(b2) + "</column>" //
 +                + "</row><row>" //
 +                + "<column>" + writeChild(b3) + "</column>" //
 +                + "<column>" + writeChild(b4) + "</column>" //
 +                + "</row></vaadin-grid-layout>";
 +        GridLayout gl = new GridLayout(2, 2);
 +        gl.addComponent(b1);
 +        gl.addComponent(b2);
 +        gl.addComponent(b3);
 +        gl.addComponent(b4);
 +        gl.setColumnExpandRatio(0, 1.0f);
 +        gl.setColumnExpandRatio(1, 3.0f);
 +        testWrite(design, gl);
 +        testRead(design, gl);
 +    }
 +
 +    @Test
 +    public void testOneBigComponentGridLayout() {
-         Button b1 = new Button("Button 0,0 -> 0,2");
++        Button b1 = new Button("Button 0,0 -&gt; 1,1");
 +        b1.setCaptionAsHtml(true);
 +        String design = "<vaadin-grid-layout><row>" //
 +                + "<column colspan=2 rowspan=2>" + writeChild(b1) + "</column>" //
 +                + "</row><row expand=2>" //
 +                + "</row></vaadin-grid-layout>";
 +        GridLayout gl = new GridLayout(2, 2);
 +        gl.addComponent(b1, 0, 0, 1, 1);
 +        gl.setRowExpandRatio(1, 2);
 +        testWrite(design, gl);
 +        testRead(design, gl);
 +    }
 +
 +    @Test
 +    public void testMultipleSpannedComponentsGridLayout() {
 +        GridLayout gl = new GridLayout(5, 5);
-         Button b2 = new Button("Button 0,3 -> 3,3");
++        Button b1 = new Button("Button 0,0 -&gt; 0,2");
 +        b1.setCaptionAsHtml(true);
 +        gl.addComponent(b1, 0, 0, 2, 0);
 +
-         Button b3 = new Button("Button 0,4 -> 1,4");
++        Button b2 = new Button("Button 0,3 -&gt; 3,3");
 +        b2.setCaptionAsHtml(true);
 +        gl.addComponent(b2, 3, 0, 3, 3);
 +
-         Button b4 = new Button("Button 1,0 -> 3,1");
++        Button b3 = new Button("Button 0,4 -&gt; 1,4");
 +        b3.setCaptionAsHtml(true);
 +        gl.addComponent(b3, 4, 0, 4, 1);
 +
-         Button b6 = new Button("Button 3,4 -> 4,4");
++        Button b4 = new Button("Button 1,0 -&gt; 3,1");
 +        b4.setCaptionAsHtml(true);
 +        gl.addComponent(b4, 0, 1, 1, 3);
 +
 +        Button b5 = new Button("Button 2,2");
 +        b5.setCaptionAsHtml(true);
 +        gl.addComponent(b5, 2, 2);
 +
-         Button b7 = new Button("Button 4,1 -> 4,2");
++        Button b6 = new Button("Button 3,4 -&gt; 4,4");
 +        b6.setCaptionAsHtml(true);
 +        gl.addComponent(b6, 4, 3, 4, 4);
 +
-         Button b1 = new Button("Button 0,4 -> 4,4");
++        Button b7 = new Button("Button 4,1 -&gt; 4,2");
 +        b7.setCaptionAsHtml(true);
 +        gl.addComponent(b7, 2, 4, 3, 4);
 +
 +        /*
 +         * Buttons in the GridLayout
 +         */
 +
 +        // 1 1 1 2 3
 +        // 4 4 - 2 3
 +        // 4 4 5 2 -
 +        // 4 4 - 2 6
 +        // - - 7 7 6
 +
 +        String design = "<vaadin-grid-layout><row>" //
 +                + "<column colspan=3>" + writeChild(b1) + "</column>" //
 +                + "<column rowspan=4>" + writeChild(b2) + "</column>" //
 +                + "<column rowspan=2>" + writeChild(b3) + "</column>" //
 +                + "</row><row>" //
 +                + "<column rowspan=3 colspan=2>" + writeChild(b4) + "</column>" //
 +                + "</row><row>" //
 +                + "<column>" + writeChild(b5) + "</column>" //
 +                + "</row><row>" //
 +                + "<column />" // Empty placeholder
 +                + "<column rowspan=2>" + writeChild(b6) + "</column>" //
 +                + "</row><row>" //
 +                + "<column colspan=2 />" // Empty placeholder
 +                + "<column colspan=2>" + writeChild(b7) + "</column>" //
 +                + "</row></vaadin-grid-layout>";
 +        testWrite(design, gl);
 +        testRead(design, gl);
 +    }
 +
 +    @Test
 +    public void testManyExtraGridLayoutSlots() {
 +        GridLayout gl = new GridLayout(5, 5);
-         Button b1 = new Button("Button 0,4 -> 4,4");
++        Button b1 = new Button("Button 0,4 -&gt; 4,4");
 +        b1.setCaptionAsHtml(true);
 +        gl.addComponent(b1, 4, 0, 4, 4);
 +        gl.setColumnExpandRatio(2, 2.0f);
 +
 +        String design = "<vaadin-grid-layout><row>" //
 +                + "<column colspan=4 rowspan=5 expand='0,0,2,0' />" //
 +                + "<column rowspan=5>" + writeChild(b1) + "</column>" //
 +                + "</row><row>" //
 +                + "</row><row>" //
 +                + "</row><row>" //
 +                + "</row><row>" //
 +                + "</row></vaadin-grid-layout>";
 +        testWrite(design, gl);
 +        testRead(design, gl);
 +    }
 +
 +    @Test
 +    public void testManyEmptyColumnsWithOneExpand() {
 +        GridLayout gl = new GridLayout(5, 5);
-             Assert.assertTrue(Math.abs(expected.getRowExpandRatio(row)
-                     - result.getRowExpandRatio(row)) < 0.00001);
++        Button b1 = new Button("Button 0,4 -&gt; 4,4");
 +        b1.setCaptionAsHtml(true);
 +        gl.addComponent(b1, 0, 0, 0, 4);
 +        gl.setColumnExpandRatio(4, 2.0f);
 +
 +        String design = "<vaadin-grid-layout><row>" //
 +                + "<column rowspan=5>" + writeChild(b1) + "</column>" //
 +                + "<column colspan=4 rowspan=5 expand='0,0,0,2' />" //
 +                + "</row><row>" //
 +                + "</row><row>" //
 +                + "</row><row>" //
 +                + "</row><row>" //
 +                + "</row></vaadin-grid-layout>";
 +        testWrite(design, gl);
 +        testRead(design, gl);
 +    }
 +
 +    @Test
 +    public void testEmptyGridLayout() {
 +        GridLayout gl = new GridLayout();
 +        String design = "<vaadin-grid-layout />";
 +        testWrite(design, gl);
 +        testRead(design, gl);
 +    }
 +
 +    private String writeChild(Component childComponent) {
 +        return new DesignContext().createElement(childComponent).toString();
 +    }
 +
 +    @Override
 +    public GridLayout testRead(String design, GridLayout expected) {
 +        expected.setCursorX(0);
 +        expected.setCursorY(expected.getRows());
 +
 +        GridLayout result = super.testRead(design, expected);
 +        for (int row = 0; row < expected.getRows(); ++row) {
-             Assert.assertTrue(Math.abs(expected.getColumnExpandRatio(col)
-                     - result.getColumnExpandRatio(col)) < 0.00001);
++            Assert.assertEquals(expected.getRowExpandRatio(row),
++                    result.getRowExpandRatio(row), 0.00001);
 +        }
 +        for (int col = 0; col < expected.getColumns(); ++col) {
++            Assert.assertEquals(expected.getColumnExpandRatio(col),
++                    result.getColumnExpandRatio(col), 0.00001);
 +        }
++        for (int row = 0; row < expected.getRows(); ++row) {
++            for (int col = 0; col < expected.getColumns(); ++col) {
++                Component eC = expected.getComponent(col, row);
++                Component rC = result.getComponent(col, row);
++
++                assertEquals(eC, rC);
++                if (eC == null) {
++                    continue;
++                }
++
++                Assert.assertEquals(expected.getComponentAlignment(eC),
++                        result.getComponentAlignment(rC));
++
++            }
++        }
++
 +        return result;
 +    }
 +
 +    @Test
 +    public void testNestedGridLayouts() {
 +        String design = "<!DOCTYPE html>" + //
 +                "<html>" + //
 +                " <body> " + //
 +                "  <vaadin-grid-layout> " + //
 +                "   <row> " + //
 +                "    <column> " + //
 +                "     <vaadin-grid-layout> " + //
 +                "      <row> " + //
 +                "       <column> " + //
 +                "        <vaadin-button>" + //
 +                "          Button " + //
 +                "        </vaadin-button> " + //
 +                "       </column> " + //
 +                "      </row> " + //
 +                "     </vaadin-grid-layout> " + //
 +                "    </column> " + //
 +                "   </row> " + //
 +                "  </vaadin-grid-layout>  " + //
 +                " </body>" + //
 +                "</html>";
 +        GridLayout outer = new GridLayout();
 +        GridLayout inner = new GridLayout();
 +        Button b = new Button("Button");
 +        b.setCaptionAsHtml(true);
 +        inner.addComponent(b);
 +        outer.addComponent(inner);
 +        testRead(design, outer);
 +        testWrite(design, outer);
 +
 +    }
 +
 +    @Test
 +    public void testEmptyGridLayoutWithColsAndRowsSet() throws IOException {
 +        GridLayout layout = new GridLayout();
 +        layout.setRows(2);
 +        layout.setColumns(2);
 +
 +        ByteArrayOutputStream out = new ByteArrayOutputStream();
 +        DesignContext context = new DesignContext();
 +        context.setRootComponent(layout);
 +        Design.write(context, out);
 +
 +        ByteArrayInputStream input = new ByteArrayInputStream(out.toByteArray());
 +        Component component = Design.read(input);
 +        GridLayout readLayout = (GridLayout) component;
 +
 +        Assert.assertEquals(layout.getRows(), readLayout.getRows());
 +    }
 +
 +    @Test
 +    public void testGridLayoutAlignments() {
 +        String design = "<vaadin-grid-layout><row>" //
 +                + "<column><vaadin-label :middle>0</label></column>"//
 +                + "<column><vaadin-label :right>1</label>"//
 +                + "</row><row>" //
 +                + "<column><vaadin-label :bottom :center>2</label></column>"//
 +                + "<column><vaadin-label :middle :center>3</label>" //
 +                + "</row></vaadin-grid-layout>";
 +        GridLayout gl = new GridLayout(2, 2);
 +
 +        Alignment[] alignments = { Alignment.MIDDLE_LEFT, Alignment.TOP_RIGHT,
 +                Alignment.BOTTOM_CENTER, Alignment.MIDDLE_CENTER };
 +        for (int i = 0; i < 4; i++) {
 +            Label child = new Label("" + i, ContentMode.HTML);
 +            gl.addComponent(child);
 +            gl.setComponentAlignment(child, alignments[i]);
 +        }
 +
 +        testWrite(design, gl);
 +        testRead(design, gl);
 +    }
 +}
index a8979b36cf9cec1bc87c7dce6081bdb11989b4dd,0000000000000000000000000000000000000000..92f79560155159cd673bdb33618c6c1bc95becfa
mode 100644,000000..100644
--- /dev/null
@@@ -1,191 -1,0 +1,201 @@@
-         setMargins(top, right, bottom, left);
 +/*
 + * Copyright 2000-2014 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.shared.ui;
 +
 +import java.io.Serializable;
 +
 +/**
 + * Describes the margin settings for each edge of a Component.
 + * 
 + * @author Vaadin Ltd
 + */
 +public class MarginInfo implements Serializable {
 +
 +    private static final int TOP = 1;
 +    private static final int RIGHT = 2;
 +    private static final int BOTTOM = 4;
 +    private static final int LEFT = 8;
 +    private static final int ALL = TOP | RIGHT | BOTTOM | LEFT;
 +
 +    private int bitMask;
 +
 +    /**
 +     * Creates a MarginInfo object with all edges set to either enabled or
 +     * disabled.
 +     * 
 +     * @param enabled
 +     *            the value to set for all edges
 +     */
 +    public MarginInfo(boolean enabled) {
 +        setMargins(enabled);
 +    }
 +
 +    /**
 +     * Creates a MarginInfo object from a bit mask.
 +     * 
 +     * @param bitMask
 +     *            bits to set
 +     */
 +    public MarginInfo(int bitMask) {
 +        this.bitMask = bitMask;
 +    }
 +
 +    /**
 +     * Creates a MarginInfo object by having each edge specified in clockwise
 +     * order (analogous to CSS).
 +     * 
 +     * @param top
 +     *            enable or disable top margin
 +     * @param right
 +     *            enable or disable right margin
 +     * @param bottom
 +     *            enable or disable bottom margin
 +     * @param left
 +     *            enable or disable left margin
 +     */
 +    public MarginInfo(boolean top, boolean right, boolean bottom, boolean left) {
-         bitMask = top ? TOP : 0;
-         bitMask += right ? RIGHT : 0;
-         bitMask += bottom ? BOTTOM : 0;
-         bitMask += left ? LEFT : 0;
++        doSetMargins(top, right, bottom, left);
++    }
++
++    public MarginInfo(boolean vertical, boolean horizontal) {
++        this(vertical, horizontal, vertical, horizontal);
 +    }
 +
 +    /**
 +     * Enables or disables margins on all edges simultaneously.
 +     * 
 +     * @param enabled
 +     *            if true, enables margins on all edges. If false, disables
 +     *            margins on all edges.
 +     */
 +    public void setMargins(boolean enabled) {
 +        bitMask = enabled ? ALL : 0;
 +    }
 +
 +    /**
 +     * Sets margins on all edges individually.
 +     * 
 +     * @param top
 +     *            enable or disable top margin
 +     * @param right
 +     *            enable or disable right margin
 +     * @param bottom
 +     *            enable or disable bottom margin
 +     * @param left
 +     *            enable or disable left margin
 +     */
 +    public void setMargins(boolean top, boolean right, boolean bottom,
 +            boolean left) {
++        doSetMargins(top, right, bottom, left);
 +    }
 +
 +    /**
 +     * Copies margin values from another MarginInfo object.
 +     * 
 +     * @param marginInfo
 +     *            another marginInfo object
 +     */
 +    public void setMargins(MarginInfo marginInfo) {
 +        bitMask = marginInfo.bitMask;
 +    }
 +
 +    /**
 +     * Checks if this MarginInfo object has margins on all edges enabled.
 +     * 
 +     * @since 7.5.0
 +     * 
 +     * @return true if all edges have margins enabled
 +     */
 +    public boolean hasAll() {
 +        return (bitMask & ALL) == ALL;
 +    }
 +
 +    /**
 +     * Checks if this MarginInfo object has the left edge margin enabled.
 +     * 
 +     * @return true if left edge margin is enabled
 +     */
 +    public boolean hasLeft() {
 +        return (bitMask & LEFT) == LEFT;
 +    }
 +
 +    /**
 +     * Checks if this MarginInfo object has the right edge margin enabled.
 +     * 
 +     * @return true if right edge margin is enabled
 +     */
 +    public boolean hasRight() {
 +        return (bitMask & RIGHT) == RIGHT;
 +    }
 +
 +    /**
 +     * Checks if this MarginInfo object has the top edge margin enabled.
 +     * 
 +     * @return true if top edge margin is enabled
 +     */
 +    public boolean hasTop() {
 +        return (bitMask & TOP) == TOP;
 +    }
 +
 +    /**
 +     * Checks if this MarginInfo object has the bottom edge margin enabled.
 +     * 
 +     * @return true if bottom edge margin is enabled
 +     */
 +    public boolean hasBottom() {
 +        return (bitMask & BOTTOM) == BOTTOM;
 +    }
 +
 +    /**
 +     * Returns the current bit mask that make up the margin settings.
 +     * 
 +     * @return an integer bit mask
 +     */
 +    public int getBitMask() {
 +        return bitMask;
 +    }
 +
 +    @Override
 +    public boolean equals(Object obj) {
 +        if (!(obj instanceof MarginInfo)) {
 +            return false;
 +        }
 +
 +        return ((MarginInfo) obj).bitMask == bitMask;
 +    }
 +
 +    @Override
 +    public int hashCode() {
 +        return bitMask;
 +    }
 +
 +    @Override
 +    public String toString() {
 +        return "MarginInfo(" + hasTop() + ", " + hasRight() + ", "
 +                + hasBottom() + ", " + hasLeft() + ")";
 +
 +    }
++
++    private void doSetMargins(boolean top, boolean right, boolean bottom,
++            boolean left) {
++        bitMask = top ? TOP : 0;
++        bitMask += right ? RIGHT : 0;
++        bitMask += bottom ? BOTTOM : 0;
++        bitMask += left ? LEFT : 0;
++    }
++
 +}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..241518c3e3c107e9a696c4c7f5c3923a634b567c
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,34 @@@
++/*
++ * Copyright 2000-2014 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.shared.ui.draganddropwrapper;
++
++import com.vaadin.shared.communication.ServerRpc;
++
++/**
++ * RPC interface for calls from client to server.
++ * 
++ * @since 7.6.4
++ * @author Vaadin Ltd
++ */
++public interface DragAndDropWrapperServerRpc extends ServerRpc {
++
++    /**
++     * Called to poll the server to see if any changes have been made e.g. when
++     * the upload is complete.
++     */
++    public void poll();
++
++}
index 6f28986d220217868a65d28140c2ebb16b102c5c,99c0302abc31e0b7a199e1b64965e6b450cfe49f..281d3707e9993db8ecde6bbdb739a67a053c3e3e
          </parallel>
      </target>
  
-     <target name="test-tb2" depends="clean-testbench-errors">
-         <property name="war.file"
-             location="${vaadin.basedir}/result/artifacts/${vaadin.version}/vaadin-uitest/vaadin-uitest-${vaadin.version}.war" />
-         <parallel>
-             <daemons>
-                 <ant antfile="${uitest.dir}/vaadin-server.xml"
-                     inheritall="true" inheritrefs="true" target="deploy-and-start" />
-             </daemons>
-             <sequential>
-                 <ant antfile="${uitest.dir}/vaadin-server.xml"
-                     target="wait-for-startup" />
-                 <ant antfile="${uitest.dir}/test.xml" target="tb2-tests" />
-             </sequential>
-         </parallel>
-     </target>
      <target name="test-tb3" depends="clean-testbench-errors">
          <property name="war.file"
 -            location="${vaadin.basedir}/result/artifacts/${vaadin.version}/vaadin-uitest/vaadin-uitest-${vaadin.version}.war" />
 +            location="${vaadin.basedir}/uitest/result/lib/vaadin-uitest-${vaadin.version}.war" />
 +        <mkdir dir="${vaadin.basedir}/result" />
          <parallel>
              <daemons>
                  <ant antfile="${uitest.dir}/vaadin-server.xml"