]> source.dussan.org Git - vaadin-framework.git/commitdiff
Move widget classes from c.v.c.ui.<widget> to c.v.c.ui (#9392) 50/250/2
authorJohannes Dahlström <johannesd@vaadin.com>
Mon, 12 Nov 2012 11:22:47 +0000 (13:22 +0200)
committerJohannes Dahlström <johannesd@vaadin.com>
Tue, 13 Nov 2012 15:51:45 +0000 (17:51 +0200)
Change-Id: I5bc64ed7446ca6f87311bf63f49fb883cfd8b538

170 files changed:
client/src/com/vaadin/client/ApplicationConnection.java
client/src/com/vaadin/client/ComponentLocator.java
client/src/com/vaadin/client/LayoutManager.java
client/src/com/vaadin/client/SuperDevMode.java
client/src/com/vaadin/client/VDebugConsole.java
client/src/com/vaadin/client/VUIDLBrowser.java
client/src/com/vaadin/client/ui/ShortcutActionHandler.java
client/src/com/vaadin/client/ui/VAbsoluteLayout.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VAbstractSplitPanel.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VAccordion.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VAudio.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VBrowserFrame.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VButton.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VCalendarPanel.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VCheckBox.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VCssLayout.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VCustomComponent.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VCustomLayout.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VDateField.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VDateFieldCalendar.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VDragAndDropWrapper.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VDragAndDropWrapperIE.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VFilterSelect.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VGridLayout.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VHorizontalLayout.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VImage.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VLabel.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VLink.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VListSelect.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VMenuBar.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VNativeButton.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VNativeSelect.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VNotification.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VOptionGroup.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VOptionGroupBase.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VOrderedLayout.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VPanel.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VPasswordField.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VPopupCalendar.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VPopupView.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VProgressIndicator.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VRichTextArea.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VRichTextToolbar.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VScrollTable.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VSlider.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VSplitPanelHorizontal.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VSplitPanelVertical.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VTabsheet.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VTabsheetBase.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VTabsheetPanel.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VTextArea.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VTextField.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VTextualDate.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VTree.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VTreeTable.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VTwinColSelect.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VUI.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VUpload.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VVerticalLayout.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VVideo.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/VWindow.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/absolutelayout/AbsoluteLayoutConnector.java
client/src/com/vaadin/client/ui/absolutelayout/VAbsoluteLayout.java [deleted file]
client/src/com/vaadin/client/ui/accordion/AccordionConnector.java
client/src/com/vaadin/client/ui/accordion/VAccordion.java [deleted file]
client/src/com/vaadin/client/ui/audio/AudioConnector.java
client/src/com/vaadin/client/ui/audio/VAudio.java [deleted file]
client/src/com/vaadin/client/ui/browserframe/BrowserFrameConnector.java
client/src/com/vaadin/client/ui/browserframe/VBrowserFrame.java [deleted file]
client/src/com/vaadin/client/ui/button/ButtonConnector.java
client/src/com/vaadin/client/ui/button/VButton.java [deleted file]
client/src/com/vaadin/client/ui/checkbox/CheckBoxConnector.java
client/src/com/vaadin/client/ui/checkbox/VCheckBox.java [deleted file]
client/src/com/vaadin/client/ui/combobox/ComboBoxConnector.java
client/src/com/vaadin/client/ui/combobox/VFilterSelect.java [deleted file]
client/src/com/vaadin/client/ui/csslayout/CssLayoutConnector.java
client/src/com/vaadin/client/ui/csslayout/VCssLayout.java [deleted file]
client/src/com/vaadin/client/ui/customcomponent/CustomComponentConnector.java
client/src/com/vaadin/client/ui/customcomponent/VCustomComponent.java [deleted file]
client/src/com/vaadin/client/ui/customlayout/CustomLayoutConnector.java
client/src/com/vaadin/client/ui/customlayout/VCustomLayout.java [deleted file]
client/src/com/vaadin/client/ui/datefield/AbstractDateFieldConnector.java
client/src/com/vaadin/client/ui/datefield/InlineDateFieldConnector.java
client/src/com/vaadin/client/ui/datefield/PopupDateFieldConnector.java
client/src/com/vaadin/client/ui/datefield/TextualDateConnector.java
client/src/com/vaadin/client/ui/datefield/VCalendarPanel.java [deleted file]
client/src/com/vaadin/client/ui/datefield/VDateField.java [deleted file]
client/src/com/vaadin/client/ui/datefield/VDateFieldCalendar.java [deleted file]
client/src/com/vaadin/client/ui/datefield/VPopupCalendar.java [deleted file]
client/src/com/vaadin/client/ui/datefield/VTextualDate.java [deleted file]
client/src/com/vaadin/client/ui/dd/VTargetInSubtree.java
client/src/com/vaadin/client/ui/draganddropwrapper/DragAndDropWrapperConnector.java
client/src/com/vaadin/client/ui/draganddropwrapper/VDragAndDropWrapper.java [deleted file]
client/src/com/vaadin/client/ui/draganddropwrapper/VDragAndDropWrapperIE.java [deleted file]
client/src/com/vaadin/client/ui/gridlayout/GridLayoutConnector.java
client/src/com/vaadin/client/ui/gridlayout/VGridLayout.java [deleted file]
client/src/com/vaadin/client/ui/image/ImageConnector.java
client/src/com/vaadin/client/ui/image/VImage.java [deleted file]
client/src/com/vaadin/client/ui/label/LabelConnector.java
client/src/com/vaadin/client/ui/label/VLabel.java [deleted file]
client/src/com/vaadin/client/ui/link/LinkConnector.java
client/src/com/vaadin/client/ui/link/VLink.java [deleted file]
client/src/com/vaadin/client/ui/listselect/ListSelectConnector.java
client/src/com/vaadin/client/ui/listselect/VListSelect.java [deleted file]
client/src/com/vaadin/client/ui/menubar/MenuBarConnector.java
client/src/com/vaadin/client/ui/menubar/VMenuBar.java [deleted file]
client/src/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java
client/src/com/vaadin/client/ui/nativebutton/VNativeButton.java [deleted file]
client/src/com/vaadin/client/ui/nativeselect/NativeSelectConnector.java
client/src/com/vaadin/client/ui/nativeselect/VNativeSelect.java [deleted file]
client/src/com/vaadin/client/ui/notification/VNotification.java [deleted file]
client/src/com/vaadin/client/ui/optiongroup/OptionGroupBaseConnector.java
client/src/com/vaadin/client/ui/optiongroup/OptionGroupConnector.java
client/src/com/vaadin/client/ui/optiongroup/VOptionGroup.java [deleted file]
client/src/com/vaadin/client/ui/optiongroup/VOptionGroupBase.java [deleted file]
client/src/com/vaadin/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java
client/src/com/vaadin/client/ui/orderedlayout/HorizontalLayoutConnector.java
client/src/com/vaadin/client/ui/orderedlayout/VHorizontalLayout.java [deleted file]
client/src/com/vaadin/client/ui/orderedlayout/VOrderedLayout.java [deleted file]
client/src/com/vaadin/client/ui/orderedlayout/VVerticalLayout.java [deleted file]
client/src/com/vaadin/client/ui/orderedlayout/VerticalLayoutConnector.java
client/src/com/vaadin/client/ui/panel/PanelConnector.java
client/src/com/vaadin/client/ui/panel/VPanel.java [deleted file]
client/src/com/vaadin/client/ui/passwordfield/PasswordFieldConnector.java
client/src/com/vaadin/client/ui/passwordfield/VPasswordField.java [deleted file]
client/src/com/vaadin/client/ui/popupview/PopupViewConnector.java
client/src/com/vaadin/client/ui/popupview/VPopupView.java [deleted file]
client/src/com/vaadin/client/ui/progressindicator/ProgressIndicatorConnector.java
client/src/com/vaadin/client/ui/progressindicator/VProgressIndicator.java [deleted file]
client/src/com/vaadin/client/ui/richtextarea/RichTextAreaConnector.java
client/src/com/vaadin/client/ui/richtextarea/VRichTextArea.java [deleted file]
client/src/com/vaadin/client/ui/richtextarea/VRichTextToolbar.java [deleted file]
client/src/com/vaadin/client/ui/slider/SliderConnector.java
client/src/com/vaadin/client/ui/slider/VSlider.java [deleted file]
client/src/com/vaadin/client/ui/splitpanel/AbstractSplitPanelConnector.java
client/src/com/vaadin/client/ui/splitpanel/HorizontalSplitPanelConnector.java
client/src/com/vaadin/client/ui/splitpanel/VAbstractSplitPanel.java [deleted file]
client/src/com/vaadin/client/ui/splitpanel/VSplitPanelHorizontal.java [deleted file]
client/src/com/vaadin/client/ui/splitpanel/VSplitPanelVertical.java [deleted file]
client/src/com/vaadin/client/ui/splitpanel/VerticalSplitPanelConnector.java
client/src/com/vaadin/client/ui/table/TableConnector.java
client/src/com/vaadin/client/ui/table/VScrollTable.java [deleted file]
client/src/com/vaadin/client/ui/tabsheet/TabsheetBaseConnector.java
client/src/com/vaadin/client/ui/tabsheet/TabsheetConnector.java
client/src/com/vaadin/client/ui/tabsheet/VTabsheet.java [deleted file]
client/src/com/vaadin/client/ui/tabsheet/VTabsheetBase.java [deleted file]
client/src/com/vaadin/client/ui/tabsheet/VTabsheetPanel.java [deleted file]
client/src/com/vaadin/client/ui/textarea/TextAreaConnector.java
client/src/com/vaadin/client/ui/textarea/VTextArea.java [deleted file]
client/src/com/vaadin/client/ui/textfield/TextFieldConnector.java
client/src/com/vaadin/client/ui/textfield/VTextField.java [deleted file]
client/src/com/vaadin/client/ui/tree/TreeConnector.java
client/src/com/vaadin/client/ui/tree/VTree.java [deleted file]
client/src/com/vaadin/client/ui/treetable/TreeTableConnector.java
client/src/com/vaadin/client/ui/treetable/VTreeTable.java [deleted file]
client/src/com/vaadin/client/ui/twincolselect/TwinColSelectConnector.java
client/src/com/vaadin/client/ui/twincolselect/VTwinColSelect.java [deleted file]
client/src/com/vaadin/client/ui/ui/UIConnector.java
client/src/com/vaadin/client/ui/ui/VUI.java [deleted file]
client/src/com/vaadin/client/ui/upload/UploadConnector.java
client/src/com/vaadin/client/ui/upload/UploadIFrameOnloadStrategy.java
client/src/com/vaadin/client/ui/upload/UploadIFrameOnloadStrategyIE.java
client/src/com/vaadin/client/ui/upload/VUpload.java [deleted file]
client/src/com/vaadin/client/ui/video/VVideo.java [deleted file]
client/src/com/vaadin/client/ui/video/VideoConnector.java
client/src/com/vaadin/client/ui/window/VWindow.java [deleted file]
client/src/com/vaadin/client/ui/window/WindowConnector.java
uitest/src/com/vaadin/tests/widgetset/client/DummyLabelConnector.java
uitest/src/com/vaadin/tests/widgetset/client/MissingFromDefaultWidgetsetConnector.java
uitest/src/com/vaadin/tests/widgetset/client/VExtendedTextArea.java

index 5475be487709f8cc6e283503ced2d5739d9aa1b3..e35fd82424cf19156beae2ce1771a0262221456e 100644 (file)
@@ -75,9 +75,9 @@ import com.vaadin.client.metadata.TypeData;
 import com.vaadin.client.ui.AbstractComponentConnector;
 import com.vaadin.client.ui.AbstractConnector;
 import com.vaadin.client.ui.VContextMenu;
+import com.vaadin.client.ui.VNotification;
+import com.vaadin.client.ui.VNotification.HideEvent;
 import com.vaadin.client.ui.dd.VDragAndDropManager;
-import com.vaadin.client.ui.notification.VNotification;
-import com.vaadin.client.ui.notification.VNotification.HideEvent;
 import com.vaadin.client.ui.ui.UIConnector;
 import com.vaadin.client.ui.window.WindowConnector;
 import com.vaadin.shared.ApplicationConstants;
index 15962fefa97b863d9f099661401dceb802377bd0..99f973c467f60a3df3a8a0fb497e5438bf2727b5 100644 (file)
@@ -27,12 +27,12 @@ import com.google.gwt.user.client.ui.RootPanel;
 import com.google.gwt.user.client.ui.SimplePanel;
 import com.google.gwt.user.client.ui.Widget;
 import com.vaadin.client.ui.SubPartAware;
-import com.vaadin.client.ui.csslayout.VCssLayout;
-import com.vaadin.client.ui.gridlayout.VGridLayout;
-import com.vaadin.client.ui.orderedlayout.VOrderedLayout;
-import com.vaadin.client.ui.tabsheet.VTabsheetPanel;
-import com.vaadin.client.ui.ui.VUI;
-import com.vaadin.client.ui.window.VWindow;
+import com.vaadin.client.ui.VCssLayout;
+import com.vaadin.client.ui.VGridLayout;
+import com.vaadin.client.ui.VOrderedLayout;
+import com.vaadin.client.ui.VTabsheetPanel;
+import com.vaadin.client.ui.VUI;
+import com.vaadin.client.ui.VWindow;
 import com.vaadin.client.ui.window.WindowConnector;
 import com.vaadin.shared.ComponentState;
 import com.vaadin.shared.Connector;
index 99f207edce7eba9b29c9bfeb6847c3d3644ef61d..a3533bdd017c1bdc292599d1175ca7c3f53a198e 100644 (file)
@@ -31,10 +31,10 @@ import com.vaadin.client.MeasuredSize.MeasureResult;
 import com.vaadin.client.ui.ManagedLayout;
 import com.vaadin.client.ui.PostLayoutListener;
 import com.vaadin.client.ui.SimpleManagedLayout;
+import com.vaadin.client.ui.VNotification;
 import com.vaadin.client.ui.layout.ElementResizeEvent;
 import com.vaadin.client.ui.layout.ElementResizeListener;
 import com.vaadin.client.ui.layout.LayoutDependencyTree;
-import com.vaadin.client.ui.notification.VNotification;
 
 public class LayoutManager {
     private static final String LOOP_ABORT_MESSAGE = "Aborting layout after 100 passes. This would probably be an infinite loop.";
index 35f9e71df2ae05bb14709a6e841a746484abbf4c..b8bf0f75c3258a4b17a2881daff1e663e0bddb36 100644 (file)
@@ -22,9 +22,9 @@ import com.google.gwt.jsonp.client.JsonpRequestBuilder;
 import com.google.gwt.storage.client.Storage;
 import com.google.gwt.user.client.Window.Location;
 import com.google.gwt.user.client.rpc.AsyncCallback;
-import com.vaadin.client.ui.notification.VNotification;
-import com.vaadin.client.ui.notification.VNotification.EventListener;
-import com.vaadin.client.ui.notification.VNotification.HideEvent;
+import com.vaadin.client.ui.VNotification;
+import com.vaadin.client.ui.VNotification.EventListener;
+import com.vaadin.client.ui.VNotification.HideEvent;
 
 /**
  * Class that enables SuperDevMode using a ?superdevmode parameter in the url.
index a436f6e6480d40c215ef169df41beeb7feafc0dc..67980cc5a393423dabacd23e59d1e665df8b5723 100644 (file)
@@ -68,8 +68,8 @@ import com.google.gwt.user.client.ui.RootPanel;
 import com.google.gwt.user.client.ui.VerticalPanel;
 import com.google.gwt.user.client.ui.Widget;
 import com.vaadin.client.ui.VLazyExecutor;
+import com.vaadin.client.ui.VNotification;
 import com.vaadin.client.ui.VOverlay;
-import com.vaadin.client.ui.notification.VNotification;
 import com.vaadin.client.ui.ui.UIConnector;
 import com.vaadin.client.ui.window.WindowConnector;
 import com.vaadin.shared.Version;
index 6f393b0bb0d2de4fbc78c4be394e276e57cc76b4..26c41eddf63b73f91ced952524a63ce5a916be5e 100644 (file)
@@ -39,7 +39,7 @@ import com.google.gwt.json.client.JSONValue;
 import com.google.gwt.user.client.ui.RootPanel;
 import com.google.gwt.user.client.ui.Widget;
 import com.vaadin.client.ui.UnknownComponentConnector;
-import com.vaadin.client.ui.window.VWindow;
+import com.vaadin.client.ui.VWindow;
 
 /**
  * TODO Rename to something more Vaadin7-ish?
index cd14f145855eb715b63e96a44c9fec013f3fa4af..7165fd74903d2f6515caf971301a0f6fb5ba7a56 100644 (file)
@@ -33,7 +33,6 @@ import com.vaadin.client.ComponentConnector;
 import com.vaadin.client.ConnectorMap;
 import com.vaadin.client.UIDL;
 import com.vaadin.client.Util;
-import com.vaadin.client.ui.richtextarea.VRichTextArea;
 import com.vaadin.shared.Connector;
 import com.vaadin.shared.ui.ShortCutConstants;
 
diff --git a/client/src/com/vaadin/client/ui/VAbsoluteLayout.java b/client/src/com/vaadin/client/ui/VAbsoluteLayout.java
new file mode 100644 (file)
index 0000000..a177ca7
--- /dev/null
@@ -0,0 +1,570 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui;
+
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.StyleConstants;
+import com.vaadin.client.VCaption;
+
+public class VAbsoluteLayout extends ComplexPanel {
+
+    /** Tag name for widget creation */
+    public static final String TAGNAME = "absolutelayout";
+
+    /** Class name, prefix in styling */
+    public static final String CLASSNAME = "v-absolutelayout";
+
+    private DivElement marginElement;
+
+    protected final Element canvas = DOM.createDiv();
+
+    /**
+     * Default constructor
+     */
+    public VAbsoluteLayout() {
+        setElement(Document.get().createDivElement());
+        marginElement = Document.get().createDivElement();
+        canvas.getStyle().setProperty("position", "relative");
+        canvas.getStyle().setProperty("overflow", "hidden");
+        marginElement.appendChild(canvas);
+        getElement().appendChild(marginElement);
+        setStyleName(CLASSNAME);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.user.client.ui.Panel#add(com.google.gwt.user.client.ui
+     * .Widget)
+     */
+    @Override
+    public void add(Widget child) {
+        AbsoluteWrapper wrapper = new AbsoluteWrapper(child);
+        wrapper.updateStyleNames();
+        super.add(wrapper, canvas);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.user.client.ui.ComplexPanel#remove(com.google.gwt.user
+     * .client.ui.Widget)
+     */
+    @Override
+    public boolean remove(Widget w) {
+        AbsoluteWrapper wrapper = getChildWrapper(w);
+        if (wrapper != null) {
+            wrapper.destroy();
+            return super.remove(wrapper);
+        }
+        return super.remove(w);
+    }
+
+    /**
+     * Does this layout contain a widget
+     * 
+     * @param widget
+     *            The widget to check
+     * @return Returns true if the widget is in this layout, false if not
+     */
+    public boolean contains(Widget widget) {
+        return getChildWrapper(widget) != null;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see com.google.gwt.user.client.ui.ComplexPanel#getWidget(int)
+     */
+    @Override
+    public Widget getWidget(int index) {
+        for (int i = 0, j = 0; i < super.getWidgetCount(); i++) {
+            Widget w = getWidget(i);
+            if (w instanceof AbsoluteWrapper) {
+                if (j == index) {
+                    return w;
+                } else {
+                    j++;
+                }
+            }
+        }
+        return null;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see com.google.gwt.user.client.ui.ComplexPanel#getWidgetCount()
+     */
+    @Override
+    public int getWidgetCount() {
+        int counter = 0;
+        for (int i = 0; i < super.getWidgetCount(); i++) {
+            if (getWidget(i) instanceof AbsoluteWrapper) {
+                counter++;
+            }
+        }
+        return counter;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.user.client.ui.ComplexPanel#getWidgetIndex(com.google.
+     * gwt.user.client.ui.Widget)
+     */
+    @Override
+    public int getWidgetIndex(Widget child) {
+        for (int i = 0, j = 0; i < super.getWidgetCount(); i++) {
+            Widget w = getWidget(i);
+            if (w instanceof AbsoluteWrapper) {
+                if (child == w) {
+                    return j;
+                } else {
+                    j++;
+                }
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Sets a caption for a contained widget
+     * 
+     * @param child
+     *            The child widget to set the caption for
+     * @param caption
+     *            The caption of the widget
+     */
+    public void setWidgetCaption(Widget child, VCaption caption) {
+        AbsoluteWrapper wrapper = getChildWrapper(child);
+        if (wrapper != null) {
+            if (caption != null) {
+                if (!getChildren().contains(caption)) {
+                    super.add(caption, canvas);
+                }
+                wrapper.setCaption(caption);
+                caption.updateCaption();
+                wrapper.updateCaptionPosition();
+            } else if (wrapper.getCaption() != null) {
+                wrapper.setCaption(null);
+            }
+        }
+    }
+
+    /**
+     * Set the position of the widget in the layout. The position is a CSS
+     * property string using properties such as top,left,right,top
+     * 
+     * @param child
+     *            The child widget to set the position for
+     * @param position
+     *            The position string
+     */
+    public void setWidgetPosition(Widget child, String position) {
+        AbsoluteWrapper wrapper = getChildWrapper(child);
+        if (wrapper != null) {
+            wrapper.setPosition(position);
+        }
+    }
+
+    /**
+     * Get the caption for a widget
+     * 
+     * @param child
+     *            The child widget to get the caption of
+     */
+    public VCaption getWidgetCaption(Widget child) {
+        AbsoluteWrapper wrapper = getChildWrapper(child);
+        if (wrapper != null) {
+            return wrapper.getCaption();
+        }
+        return null;
+    }
+
+    /**
+     * Get the pixel width of an slot in the layout
+     * 
+     * @param child
+     *            The widget in the layout.
+     * @return Returns the size in pixels, or 0 if child is not in the layout
+     */
+    public int getWidgetSlotWidth(Widget child) {
+        AbsoluteWrapper wrapper = getChildWrapper(child);
+        if (wrapper != null) {
+            return wrapper.getOffsetWidth();
+        }
+        return 0;
+    }
+
+    /**
+     * Get the pixel height of an slot in the layout
+     * 
+     * @param child
+     *            The widget in the layout
+     * @return Returns the size in pixels, or 0 if the child is not in the
+     *         layout
+     */
+    public int getWidgetSlotHeight(Widget child) {
+        AbsoluteWrapper wrapper = getChildWrapper(child);
+        if (wrapper != null) {
+            return wrapper.getOffsetHeight();
+        }
+        return 0;
+    }
+
+    /**
+     * Get the wrapper for a widget
+     * 
+     * @param child
+     *            The child to get the wrapper for
+     * @return
+     */
+    protected AbsoluteWrapper getChildWrapper(Widget child) {
+        for (Widget w : getChildren()) {
+            if (w instanceof AbsoluteWrapper) {
+                AbsoluteWrapper wrapper = (AbsoluteWrapper) w;
+                if (wrapper.getWidget() == child) {
+                    return wrapper;
+                }
+            }
+        }
+        return null;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.user.client.ui.UIObject#setStylePrimaryName(java.lang.
+     * String)
+     */
+    @Override
+    public void setStylePrimaryName(String style) {
+        updateStylenames(style);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.user.client.ui.UIObject#setStyleName(java.lang.String)
+     */
+    @Override
+    public void setStyleName(String style) {
+        super.setStyleName(style);
+        updateStylenames(style);
+        addStyleName(StyleConstants.UI_LAYOUT);
+    }
+
+    /**
+     * Updates all style names contained in the layout
+     * 
+     * @param primaryStyleName
+     *            The style name to use as primary
+     */
+    protected void updateStylenames(String primaryStyleName) {
+        super.setStylePrimaryName(primaryStyleName);
+        canvas.setClassName(getStylePrimaryName() + "-canvas");
+        canvas.setClassName(getStylePrimaryName() + "-margin");
+        for (Widget w : getChildren()) {
+            if (w instanceof AbsoluteWrapper) {
+                AbsoluteWrapper wrapper = (AbsoluteWrapper) w;
+                wrapper.updateStyleNames();
+            }
+        }
+    }
+
+    /**
+     * Performs a vertical layout of the layout. Should be called when a widget
+     * is added or removed
+     */
+    public void layoutVertically() {
+        for (Widget widget : getChildren()) {
+            if (widget instanceof AbsoluteWrapper) {
+                AbsoluteWrapper wrapper = (AbsoluteWrapper) widget;
+
+                /*
+                 * Cleanup old wrappers which have been left empty by other
+                 * inner layouts moving the widget from the wrapper into their
+                 * own hierarchy. This usually happens when a call to
+                 * setWidget(widget) is done in an inner layout which
+                 * automatically detaches the widget from the parent, in this
+                 * case the wrapper, and re-attaches it somewhere else. This has
+                 * to be done in the layout phase since the order of the
+                 * hierarchy events are not defined.
+                 */
+                if (wrapper.getWidget() == null) {
+                    wrapper.destroy();
+                    super.remove(wrapper);
+                    continue;
+                }
+
+                Style wrapperStyle = wrapper.getElement().getStyle();
+                Style widgetStyle = wrapper.getWidget().getElement().getStyle();
+                if (widgetStyle.getHeight() != null
+                        && widgetStyle.getHeight().endsWith("%")) {
+                    int h;
+                    if (wrapper.top != null && wrapper.bottom != null) {
+                        h = wrapper.getOffsetHeight();
+                    } else if (wrapper.bottom != null) {
+                        // top not defined, available space 0... bottom of
+                        // wrapper
+                        h = wrapper.getElement().getOffsetTop()
+                                + wrapper.getOffsetHeight();
+                    } else {
+                        // top defined or both undefined, available space ==
+                        // canvas - top
+                        h = canvas.getOffsetHeight()
+                                - wrapper.getElement().getOffsetTop();
+                    }
+                    wrapperStyle.setHeight(h, Unit.PX);
+                } else {
+                    wrapperStyle.clearHeight();
+                }
+
+                wrapper.updateCaptionPosition();
+            }
+        }
+    }
+
+    /**
+     * Performs an horizontal layout. Should be called when a widget is add or
+     * removed
+     */
+    public void layoutHorizontally() {
+        for (Widget widget : getChildren()) {
+            if (widget instanceof AbsoluteWrapper) {
+                AbsoluteWrapper wrapper = (AbsoluteWrapper) widget;
+
+                /*
+                 * Cleanup old wrappers which have been left empty by other
+                 * inner layouts moving the widget from the wrapper into their
+                 * own hierarchy. This usually happens when a call to
+                 * setWidget(widget) is done in an inner layout which
+                 * automatically detaches the widget from the parent, in this
+                 * case the wrapper, and re-attaches it somewhere else. This has
+                 * to be done in the layout phase since the order of the
+                 * hierarchy events are not defined.
+                 */
+                if (wrapper.getWidget() == null) {
+                    wrapper.destroy();
+                    super.remove(wrapper);
+                    continue;
+                }
+
+                Style wrapperStyle = wrapper.getElement().getStyle();
+                Style widgetStyle = wrapper.getWidget().getElement().getStyle();
+
+                if (widgetStyle.getWidth() != null
+                        && widgetStyle.getWidth().endsWith("%")) {
+                    int w;
+                    if (wrapper.left != null && wrapper.right != null) {
+                        w = wrapper.getOffsetWidth();
+                    } else if (wrapper.right != null) {
+                        // left == null
+                        // available width == right edge == offsetleft + width
+                        w = wrapper.getOffsetWidth()
+                                + wrapper.getElement().getOffsetLeft();
+                    } else {
+                        // left != null && right == null || left == null &&
+                        // right == null
+                        // available width == canvas width - offset left
+                        w = canvas.getOffsetWidth()
+                                - wrapper.getElement().getOffsetLeft();
+                    }
+                    wrapperStyle.setWidth(w, Unit.PX);
+                } else {
+                    wrapperStyle.clearWidth();
+                }
+
+                wrapper.updateCaptionPosition();
+            }
+        }
+    }
+
+    /**
+     * Sets style names for the wrapper wrapping the widget in the layout. The
+     * style names will be prefixed with v-absolutelayout-wrapper.
+     * 
+     * @param widget
+     *            The widget which wrapper we want to add the stylenames to
+     * @param stylenames
+     *            The style names that should be added to the wrapper
+     */
+    public void setWidgetWrapperStyleNames(Widget widget, String... stylenames) {
+        AbsoluteWrapper wrapper = getChildWrapper(widget);
+        if (wrapper == null) {
+            throw new IllegalArgumentException(
+                    "No wrapper for widget found, has the widget been added to the layout?");
+        }
+        wrapper.setWrapperStyleNames(stylenames);
+    }
+
+    /**
+     * Internal wrapper for wrapping widgets in the Absolute layout
+     */
+    protected class AbsoluteWrapper extends SimplePanel {
+        private String css;
+        private String left;
+        private String top;
+        private String right;
+        private String bottom;
+        private String zIndex;
+
+        private VCaption caption;
+        private String[] extraStyleNames;
+
+        /**
+         * Constructor
+         * 
+         * @param child
+         *            The child to wrap
+         */
+        public AbsoluteWrapper(Widget child) {
+            setWidget(child);
+        }
+
+        /**
+         * Get the caption of the wrapper
+         */
+        public VCaption getCaption() {
+            return caption;
+        }
+
+        /**
+         * Set the caption for the wrapper
+         * 
+         * @param caption
+         *            The caption for the wrapper
+         */
+        public void setCaption(VCaption caption) {
+            if (caption != null) {
+                this.caption = caption;
+            } else if (this.caption != null) {
+                this.caption.removeFromParent();
+                this.caption = caption;
+            }
+        }
+
+        /**
+         * Removes the wrapper caption and itself from the layout
+         */
+        public void destroy() {
+            if (caption != null) {
+                caption.removeFromParent();
+            }
+            removeFromParent();
+        }
+
+        /**
+         * Set the position for the wrapper in the layout
+         * 
+         * @param position
+         *            The position string
+         */
+        public void setPosition(String position) {
+            if (css == null || !css.equals(position)) {
+                css = position;
+                top = right = bottom = left = zIndex = null;
+                if (!css.equals("")) {
+                    String[] properties = css.split(";");
+                    for (int i = 0; i < properties.length; i++) {
+                        String[] keyValue = properties[i].split(":");
+                        if (keyValue[0].equals("left")) {
+                            left = keyValue[1];
+                        } else if (keyValue[0].equals("top")) {
+                            top = keyValue[1];
+                        } else if (keyValue[0].equals("right")) {
+                            right = keyValue[1];
+                        } else if (keyValue[0].equals("bottom")) {
+                            bottom = keyValue[1];
+                        } else if (keyValue[0].equals("z-index")) {
+                            zIndex = keyValue[1];
+                        }
+                    }
+                }
+                // ensure ne values
+                Style style = getElement().getStyle();
+                /*
+                 * IE8 dies when nulling zIndex, even in IE7 mode. All other css
+                 * properties (and even in older IE's) accept null values just
+                 * fine. Assign empty string instead of null.
+                 */
+                if (zIndex != null) {
+                    style.setProperty("zIndex", zIndex);
+                } else {
+                    style.setProperty("zIndex", "");
+                }
+                style.setProperty("top", top);
+                style.setProperty("left", left);
+                style.setProperty("right", right);
+                style.setProperty("bottom", bottom);
+
+            }
+            updateCaptionPosition();
+        }
+
+        /**
+         * Updates the caption position by using element offset left and top
+         */
+        private void updateCaptionPosition() {
+            if (caption != null) {
+                Style style = caption.getElement().getStyle();
+                style.setProperty("position", "absolute");
+                style.setPropertyPx("left", getElement().getOffsetLeft());
+                style.setPropertyPx("top", getElement().getOffsetTop()
+                        - caption.getHeight());
+            }
+        }
+
+        /**
+         * Sets the style names of the wrapper. Will be prefixed with the
+         * v-absolutelayout-wrapper prefix
+         * 
+         * @param stylenames
+         *            The wrapper style names
+         */
+        public void setWrapperStyleNames(String... stylenames) {
+            extraStyleNames = stylenames;
+            updateStyleNames();
+        }
+
+        /**
+         * Updates the style names using the primary style name as prefix
+         */
+        protected void updateStyleNames() {
+            setStyleName(VAbsoluteLayout.this.getStylePrimaryName()
+                    + "-wrapper");
+            if (extraStyleNames != null) {
+                for (String stylename : extraStyleNames) {
+                    addStyleDependentName(stylename);
+                }
+            }
+        }
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VAbstractSplitPanel.java b/client/src/com/vaadin/client/ui/VAbstractSplitPanel.java
new file mode 100644 (file)
index 0000000..9b1143e
--- /dev/null
@@ -0,0 +1,800 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.Collections;
+import java.util.List;
+
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.event.dom.client.TouchCancelEvent;
+import com.google.gwt.event.dom.client.TouchCancelHandler;
+import com.google.gwt.event.dom.client.TouchEndEvent;
+import com.google.gwt.event.dom.client.TouchEndHandler;
+import com.google.gwt.event.dom.client.TouchMoveEvent;
+import com.google.gwt.event.dom.client.TouchMoveHandler;
+import com.google.gwt.event.dom.client.TouchStartEvent;
+import com.google.gwt.event.dom.client.TouchStartHandler;
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.ComplexPanel;
+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.ConnectorMap;
+import com.vaadin.client.LayoutManager;
+import com.vaadin.client.StyleConstants;
+import com.vaadin.client.Util;
+import com.vaadin.client.VConsole;
+import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
+import com.vaadin.client.ui.VAbstractSplitPanel.SplitterMoveHandler.SplitterMoveEvent;
+import com.vaadin.shared.ui.Orientation;
+
+public class VAbstractSplitPanel extends ComplexPanel {
+
+    private boolean enabled = false;
+
+    public static final String CLASSNAME = "v-splitpanel";
+
+    private static final int MIN_SIZE = 30;
+
+    private Orientation orientation = Orientation.HORIZONTAL;
+
+    Widget firstChild;
+
+    Widget secondChild;
+
+    private final Element wrapper = DOM.createDiv();
+
+    private final Element firstContainer = DOM.createDiv();
+
+    private final Element secondContainer = DOM.createDiv();
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public final Element splitter = DOM.createDiv();
+
+    private boolean resizing;
+
+    private boolean resized = false;
+
+    private int origX;
+
+    private int origY;
+
+    private int origMouseX;
+
+    private int origMouseY;
+
+    private boolean locked = false;
+
+    private boolean positionReversed = false;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public List<String> componentStyleNames = Collections.emptyList();
+
+    private Element draggingCurtain;
+
+    /** 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 boolean immediate;
+
+    /**
+     * The current position of the split handle in either percentages or pixels
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public String position;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public String maximumPosition;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public String minimumPosition;
+
+    private TouchScrollHandler touchScrollHandler;
+
+    protected Element scrolledContainer;
+
+    protected int origScrollTop;
+
+    public VAbstractSplitPanel() {
+        this(Orientation.HORIZONTAL);
+    }
+
+    public VAbstractSplitPanel(Orientation orientation) {
+        setElement(DOM.createDiv());
+        setStyleName(StyleConstants.UI_LAYOUT);
+        switch (orientation) {
+        case HORIZONTAL:
+            addStyleName(CLASSNAME + "-horizontal");
+            break;
+        case VERTICAL:
+        default:
+            addStyleName(CLASSNAME + "-vertical");
+            break;
+        }
+        // size below will be overridden in update from uidl, initial size
+        // needed to keep IE alive
+        setWidth(MIN_SIZE + "px");
+        setHeight(MIN_SIZE + "px");
+        constructDom();
+        setOrientation(orientation);
+        sinkEvents(Event.MOUSEEVENTS);
+
+        makeScrollable();
+
+        addDomHandler(new TouchCancelHandler() {
+            @Override
+            public void onTouchCancel(TouchCancelEvent event) {
+                // TODO When does this actually happen??
+                VConsole.log("TOUCH CANCEL");
+            }
+        }, TouchCancelEvent.getType());
+        addDomHandler(new TouchStartHandler() {
+            @Override
+            public void onTouchStart(TouchStartEvent event) {
+                Node target = event.getTouches().get(0).getTarget().cast();
+                if (splitter.isOrHasChild(target)) {
+                    onMouseDown(Event.as(event.getNativeEvent()));
+                }
+            }
+        }, TouchStartEvent.getType());
+        addDomHandler(new TouchMoveHandler() {
+            @Override
+            public void onTouchMove(TouchMoveEvent event) {
+                if (resizing) {
+                    onMouseMove(Event.as(event.getNativeEvent()));
+                }
+            }
+        }, TouchMoveEvent.getType());
+        addDomHandler(new TouchEndHandler() {
+            @Override
+            public void onTouchEnd(TouchEndEvent event) {
+                if (resizing) {
+                    onMouseUp(Event.as(event.getNativeEvent()));
+                }
+            }
+        }, TouchEndEvent.getType());
+
+    }
+
+    protected void constructDom() {
+        DOM.appendChild(splitter, DOM.createDiv()); // for styling
+        DOM.appendChild(getElement(), wrapper);
+        DOM.setStyleAttribute(wrapper, "position", "relative");
+        DOM.setStyleAttribute(wrapper, "width", "100%");
+        DOM.setStyleAttribute(wrapper, "height", "100%");
+
+        DOM.appendChild(wrapper, secondContainer);
+        DOM.appendChild(wrapper, firstContainer);
+        DOM.appendChild(wrapper, splitter);
+
+        DOM.setStyleAttribute(splitter, "position", "absolute");
+        DOM.setStyleAttribute(secondContainer, "position", "absolute");
+
+        setStylenames();
+    }
+
+    private void setOrientation(Orientation orientation) {
+        this.orientation = orientation;
+        if (orientation == Orientation.HORIZONTAL) {
+            DOM.setStyleAttribute(splitter, "height", "100%");
+            DOM.setStyleAttribute(splitter, "top", "0");
+            DOM.setStyleAttribute(firstContainer, "height", "100%");
+            DOM.setStyleAttribute(secondContainer, "height", "100%");
+        } else {
+            DOM.setStyleAttribute(splitter, "width", "100%");
+            DOM.setStyleAttribute(splitter, "left", "0");
+            DOM.setStyleAttribute(firstContainer, "width", "100%");
+            DOM.setStyleAttribute(secondContainer, "width", "100%");
+        }
+    }
+
+    @Override
+    public boolean remove(Widget w) {
+        boolean removed = super.remove(w);
+        if (removed) {
+            if (firstChild == w) {
+                firstChild = null;
+            } else {
+                secondChild = null;
+            }
+        }
+        return removed;
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void setLocked(boolean newValue) {
+        if (locked != newValue) {
+            locked = newValue;
+            splitterSize = -1;
+            setStylenames();
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void setPositionReversed(boolean reversed) {
+        if (positionReversed != reversed) {
+            if (orientation == Orientation.HORIZONTAL) {
+                DOM.setStyleAttribute(splitter, "right", "");
+                DOM.setStyleAttribute(splitter, "left", "");
+            } else if (orientation == Orientation.VERTICAL) {
+                DOM.setStyleAttribute(splitter, "top", "");
+                DOM.setStyleAttribute(splitter, "bottom", "");
+            }
+
+            positionReversed = reversed;
+        }
+    }
+
+    /**
+     * Converts given split position string (in pixels or percentage) to a
+     * floating point pixel value.
+     * 
+     * @param pos
+     * @return
+     */
+    private float convertToPixels(String pos) {
+        float posAsFloat;
+        if (pos.indexOf("%") > 0) {
+            posAsFloat = Math.round(Float.parseFloat(pos.substring(0,
+                    pos.length() - 1))
+                    / 100
+                    * (orientation == Orientation.HORIZONTAL ? getOffsetWidth()
+                            : getOffsetHeight()));
+        } else {
+            posAsFloat = Float.parseFloat(pos.substring(0, pos.length() - 2));
+        }
+        return posAsFloat;
+    }
+
+    /**
+     * Converts given split position string (in pixels or percentage) to a float
+     * percentage value.
+     * 
+     * @param pos
+     * @return
+     */
+    private float convertToPercentage(String pos) {
+        if (pos.endsWith("px")) {
+            float pixelPosition = Float.parseFloat(pos.substring(0,
+                    pos.length() - 2));
+            int offsetLength = orientation == Orientation.HORIZONTAL ? getOffsetWidth()
+                    : getOffsetHeight();
+
+            // Take splitter size into account at the edge
+            if (pixelPosition + getSplitterSize() >= offsetLength) {
+                return 100;
+            }
+
+            return pixelPosition / offsetLength * 100;
+        } else {
+            assert pos.endsWith("%");
+            return Float.parseFloat(pos.substring(0, pos.length() - 1));
+        }
+    }
+
+    /**
+     * Returns the given position clamped to the range between current minimum
+     * and maximum positions.
+     * 
+     * TODO Should this be in the connector?
+     * 
+     * @param pos
+     *            Position of the splitter as a CSS string, either pixels or a
+     *            percentage.
+     * @return minimumPosition if pos is less than minimumPosition;
+     *         maximumPosition if pos is greater than maximumPosition; pos
+     *         otherwise.
+     */
+    private String checkSplitPositionLimits(String pos) {
+        float positionAsFloat = convertToPixels(pos);
+
+        if (maximumPosition != null
+                && convertToPixels(maximumPosition) < positionAsFloat) {
+            pos = maximumPosition;
+        } else if (minimumPosition != null
+                && convertToPixels(minimumPosition) > positionAsFloat) {
+            pos = minimumPosition;
+        }
+        return pos;
+    }
+
+    /**
+     * Converts given string to the same units as the split position is.
+     * 
+     * @param pos
+     *            position to be converted
+     * @return converted position string
+     */
+    private String convertToPositionUnits(String pos) {
+        if (position.indexOf("%") != -1 && pos.indexOf("%") == -1) {
+            // position is in percentage, pos in pixels
+            pos = convertToPercentage(pos) + "%";
+        } else if (position.indexOf("px") > 0 && pos.indexOf("px") == -1) {
+            // position is in pixels and pos in percentage
+            pos = convertToPixels(pos) + "px";
+        }
+
+        return pos;
+    }
+
+    public void setSplitPosition(String pos) {
+        if (pos == null) {
+            return;
+        }
+
+        pos = checkSplitPositionLimits(pos);
+        if (!pos.equals(position)) {
+            position = convertToPositionUnits(pos);
+        }
+
+        // Convert percentage values to pixels
+        if (pos.indexOf("%") > 0) {
+            int size = orientation == Orientation.HORIZONTAL ? getOffsetWidth()
+                    : getOffsetHeight();
+            float percentage = Float.parseFloat(pos.substring(0,
+                    pos.length() - 1));
+            pos = percentage / 100 * size + "px";
+        }
+
+        String attributeName;
+        if (orientation == Orientation.HORIZONTAL) {
+            if (positionReversed) {
+                attributeName = "right";
+            } else {
+                attributeName = "left";
+            }
+        } else {
+            if (positionReversed) {
+                attributeName = "bottom";
+            } else {
+                attributeName = "top";
+            }
+        }
+
+        Style style = splitter.getStyle();
+        if (!pos.equals(style.getProperty(attributeName))) {
+            style.setProperty(attributeName, pos);
+            updateSizes();
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void updateSizes() {
+        if (!isAttached()) {
+            return;
+        }
+
+        int wholeSize;
+        int pixelPosition;
+
+        switch (orientation) {
+        case HORIZONTAL:
+            wholeSize = DOM.getElementPropertyInt(wrapper, "clientWidth");
+            pixelPosition = DOM.getElementPropertyInt(splitter, "offsetLeft");
+
+            // reposition splitter in case it is out of box
+            if ((pixelPosition > 0 && pixelPosition + getSplitterSize() > wholeSize)
+                    || (positionReversed && pixelPosition < 0)) {
+                pixelPosition = wholeSize - getSplitterSize();
+                if (pixelPosition < 0) {
+                    pixelPosition = 0;
+                }
+                setSplitPosition(pixelPosition + "px");
+                return;
+            }
+
+            DOM.setStyleAttribute(firstContainer, "width", pixelPosition + "px");
+            int secondContainerWidth = (wholeSize - pixelPosition - getSplitterSize());
+            if (secondContainerWidth < 0) {
+                secondContainerWidth = 0;
+            }
+            DOM.setStyleAttribute(secondContainer, "width",
+                    secondContainerWidth + "px");
+            DOM.setStyleAttribute(secondContainer, "left",
+                    (pixelPosition + getSplitterSize()) + "px");
+
+            LayoutManager layoutManager = LayoutManager.get(client);
+            ConnectorMap connectorMap = ConnectorMap.get(client);
+            if (firstChild != null) {
+                ComponentConnector connector = connectorMap
+                        .getConnector(firstChild);
+                if (connector.isRelativeWidth()) {
+                    layoutManager.reportWidthAssignedToRelative(connector,
+                            pixelPosition);
+                } else {
+                    layoutManager.setNeedsMeasure(connector);
+                }
+            }
+            if (secondChild != null) {
+                ComponentConnector connector = connectorMap
+                        .getConnector(secondChild);
+                if (connector.isRelativeWidth()) {
+                    layoutManager.reportWidthAssignedToRelative(connector,
+                            secondContainerWidth);
+                } else {
+                    layoutManager.setNeedsMeasure(connector);
+                }
+            }
+            break;
+        case VERTICAL:
+            wholeSize = DOM.getElementPropertyInt(wrapper, "clientHeight");
+            pixelPosition = DOM.getElementPropertyInt(splitter, "offsetTop");
+
+            // reposition splitter in case it is out of box
+            if ((pixelPosition > 0 && pixelPosition + getSplitterSize() > wholeSize)
+                    || (positionReversed && pixelPosition < 0)) {
+                pixelPosition = wholeSize - getSplitterSize();
+                if (pixelPosition < 0) {
+                    pixelPosition = 0;
+                }
+                setSplitPosition(pixelPosition + "px");
+                return;
+            }
+
+            DOM.setStyleAttribute(firstContainer, "height", pixelPosition
+                    + "px");
+            int secondContainerHeight = (wholeSize - pixelPosition - getSplitterSize());
+            if (secondContainerHeight < 0) {
+                secondContainerHeight = 0;
+            }
+            DOM.setStyleAttribute(secondContainer, "height",
+                    secondContainerHeight + "px");
+            DOM.setStyleAttribute(secondContainer, "top",
+                    (pixelPosition + getSplitterSize()) + "px");
+
+            layoutManager = LayoutManager.get(client);
+            connectorMap = ConnectorMap.get(client);
+            if (firstChild != null) {
+                ComponentConnector connector = connectorMap
+                        .getConnector(firstChild);
+                if (connector.isRelativeHeight()) {
+                    layoutManager.reportHeightAssignedToRelative(connector,
+                            pixelPosition);
+                } else {
+                    layoutManager.setNeedsMeasure(connector);
+                }
+            }
+            if (secondChild != null) {
+                ComponentConnector connector = connectorMap
+                        .getConnector(secondChild);
+                if (connector.isRelativeHeight()) {
+                    layoutManager.reportHeightAssignedToRelative(connector,
+                            secondContainerHeight);
+                } else {
+                    layoutManager.setNeedsMeasure(connector);
+                }
+            }
+            break;
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void setFirstWidget(Widget w) {
+        if (firstChild != null) {
+            firstChild.removeFromParent();
+        }
+        if (w != null) {
+            super.add(w, firstContainer);
+        }
+        firstChild = w;
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void setSecondWidget(Widget w) {
+        if (secondChild != null) {
+            secondChild.removeFromParent();
+        }
+        if (w != null) {
+            super.add(w, secondContainer);
+        }
+        secondChild = w;
+    }
+
+    @Override
+    public void onBrowserEvent(Event event) {
+        switch (DOM.eventGetType(event)) {
+        case Event.ONMOUSEMOVE:
+            // case Event.ONTOUCHMOVE:
+            if (resizing) {
+                onMouseMove(event);
+            }
+            break;
+        case Event.ONMOUSEDOWN:
+            // case Event.ONTOUCHSTART:
+            onMouseDown(event);
+            break;
+        case Event.ONMOUSEOUT:
+            // Dragging curtain interferes with click events if added in
+            // mousedown so we add it only when needed i.e., if the mouse moves
+            // outside the splitter.
+            if (resizing) {
+                showDraggingCurtain();
+            }
+            break;
+        case Event.ONMOUSEUP:
+            // case Event.ONTOUCHEND:
+            if (resizing) {
+                onMouseUp(event);
+            }
+            break;
+        case Event.ONCLICK:
+            resizing = false;
+            break;
+        }
+        // Only fire click event listeners if the splitter isn't moved
+        if (Util.isTouchEvent(event) || !resized) {
+            super.onBrowserEvent(event);
+        } else if (DOM.eventGetType(event) == Event.ONMOUSEUP) {
+            // Reset the resized flag after a mouseup has occured so the next
+            // mousedown/mouseup can be interpreted as a click.
+            resized = false;
+        }
+    }
+
+    public void onMouseDown(Event event) {
+        if (locked || !isEnabled()) {
+            return;
+        }
+        final Element trg = event.getEventTarget().cast();
+        if (trg == splitter || trg == DOM.getChild(splitter, 0)) {
+            resizing = true;
+            DOM.setCapture(getElement());
+            origX = DOM.getElementPropertyInt(splitter, "offsetLeft");
+            origY = DOM.getElementPropertyInt(splitter, "offsetTop");
+            origMouseX = Util.getTouchOrMouseClientX(event);
+            origMouseY = Util.getTouchOrMouseClientY(event);
+            event.stopPropagation();
+            event.preventDefault();
+        }
+    }
+
+    public void onMouseMove(Event event) {
+        switch (orientation) {
+        case HORIZONTAL:
+            final int x = Util.getTouchOrMouseClientX(event);
+            onHorizontalMouseMove(x);
+            break;
+        case VERTICAL:
+        default:
+            final int y = Util.getTouchOrMouseClientY(event);
+            onVerticalMouseMove(y);
+            break;
+        }
+
+    }
+
+    private void onHorizontalMouseMove(int x) {
+        int newX = origX + x - origMouseX;
+        if (newX < 0) {
+            newX = 0;
+        }
+        if (newX + getSplitterSize() > getOffsetWidth()) {
+            newX = getOffsetWidth() - getSplitterSize();
+        }
+
+        if (position.indexOf("%") > 0) {
+            position = convertToPositionUnits(newX + "px");
+        } else {
+            // Reversed position
+            if (positionReversed) {
+                position = (getOffsetWidth() - newX - getSplitterSize()) + "px";
+            } else {
+                position = newX + "px";
+            }
+        }
+
+        if (origX != newX) {
+            resized = true;
+        }
+
+        // Reversed position
+        if (positionReversed) {
+            newX = getOffsetWidth() - newX - getSplitterSize();
+        }
+
+        setSplitPosition(newX + "px");
+    }
+
+    private void onVerticalMouseMove(int y) {
+        int newY = origY + y - origMouseY;
+        if (newY < 0) {
+            newY = 0;
+        }
+
+        if (newY + getSplitterSize() > getOffsetHeight()) {
+            newY = getOffsetHeight() - getSplitterSize();
+        }
+
+        if (position.indexOf("%") > 0) {
+            position = convertToPositionUnits(newY + "px");
+        } else {
+            // Reversed position
+            if (positionReversed) {
+                position = (getOffsetHeight() - newY - getSplitterSize())
+                        + "px";
+            } else {
+                position = newY + "px";
+            }
+        }
+
+        if (origY != newY) {
+            resized = true;
+        }
+
+        // Reversed position
+        if (positionReversed) {
+            newY = getOffsetHeight() - newY - getSplitterSize();
+        }
+
+        setSplitPosition(newY + "px");
+    }
+
+    public void onMouseUp(Event event) {
+        DOM.releaseCapture(getElement());
+        hideDraggingCurtain();
+        resizing = false;
+        if (!Util.isTouchEvent(event)) {
+            onMouseMove(event);
+        }
+        fireEvent(new SplitterMoveEvent(this));
+    }
+
+    public interface SplitterMoveHandler extends EventHandler {
+        public void splitterMoved(SplitterMoveEvent event);
+
+        public static class SplitterMoveEvent extends
+                GwtEvent<SplitterMoveHandler> {
+
+            public static final Type<SplitterMoveHandler> TYPE = new Type<SplitterMoveHandler>();
+
+            private Widget splitPanel;
+
+            public SplitterMoveEvent(Widget splitPanel) {
+                this.splitPanel = splitPanel;
+            }
+
+            @Override
+            public com.google.gwt.event.shared.GwtEvent.Type<SplitterMoveHandler> getAssociatedType() {
+                return TYPE;
+            }
+
+            @Override
+            protected void dispatch(SplitterMoveHandler handler) {
+                handler.splitterMoved(this);
+            }
+
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public String getSplitterPosition() {
+        return position;
+    }
+
+    /**
+     * Used in FF to avoid losing mouse capture when pointer is moved on an
+     * iframe.
+     */
+    private void showDraggingCurtain() {
+        if (!isDraggingCurtainRequired()) {
+            return;
+        }
+        if (draggingCurtain == null) {
+            draggingCurtain = DOM.createDiv();
+            DOM.setStyleAttribute(draggingCurtain, "position", "absolute");
+            DOM.setStyleAttribute(draggingCurtain, "top", "0px");
+            DOM.setStyleAttribute(draggingCurtain, "left", "0px");
+            DOM.setStyleAttribute(draggingCurtain, "width", "100%");
+            DOM.setStyleAttribute(draggingCurtain, "height", "100%");
+            DOM.setStyleAttribute(draggingCurtain, "zIndex", ""
+                    + VOverlay.Z_INDEX);
+
+            DOM.appendChild(wrapper, draggingCurtain);
+        }
+    }
+
+    /**
+     * A dragging curtain is required in Gecko and Webkit.
+     * 
+     * @return true if the browser requires a dragging curtain
+     */
+    private boolean isDraggingCurtainRequired() {
+        return (BrowserInfo.get().isGecko() || BrowserInfo.get().isWebkit());
+    }
+
+    /**
+     * Hides dragging curtain
+     */
+    private void hideDraggingCurtain() {
+        if (draggingCurtain != null) {
+            DOM.removeChild(wrapper, draggingCurtain);
+            draggingCurtain = null;
+        }
+    }
+
+    private int splitterSize = -1;
+
+    private int getSplitterSize() {
+        if (splitterSize < 0) {
+            if (isAttached()) {
+                switch (orientation) {
+                case HORIZONTAL:
+                    splitterSize = DOM.getElementPropertyInt(splitter,
+                            "offsetWidth");
+                    break;
+
+                default:
+                    splitterSize = DOM.getElementPropertyInt(splitter,
+                            "offsetHeight");
+                    break;
+                }
+            }
+        }
+        return splitterSize;
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void setStylenames() {
+        final String splitterClass = CLASSNAME
+                + (orientation == Orientation.HORIZONTAL ? "-hsplitter"
+                        : "-vsplitter");
+        final String firstContainerClass = CLASSNAME + "-first-container";
+        final String secondContainerClass = CLASSNAME + "-second-container";
+        final String lockedSuffix = locked ? "-locked" : "";
+
+        splitter.setClassName(splitterClass + lockedSuffix);
+        firstContainer.setClassName(firstContainerClass);
+        secondContainer.setClassName(secondContainerClass);
+
+        for (String styleName : componentStyleNames) {
+            splitter.addClassName(splitterClass + "-" + styleName
+                    + lockedSuffix);
+            firstContainer.addClassName(firstContainerClass + "-" + styleName);
+            secondContainer
+                    .addClassName(secondContainerClass + "-" + styleName);
+        }
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    /**
+     * Ensures the panels are scrollable eg. after style name changes
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public void makeScrollable() {
+        if (touchScrollHandler == null) {
+            touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this);
+        }
+        touchScrollHandler.addElement(firstContainer);
+        touchScrollHandler.addElement(secondContainer);
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VAccordion.java b/client/src/com/vaadin/client/ui/VAccordion.java
new file mode 100644 (file)
index 0000000..6132fb0
--- /dev/null
@@ -0,0 +1,568 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ConnectorMap;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.Util;
+import com.vaadin.client.VCaption;
+import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
+import com.vaadin.shared.ui.tabsheet.TabsheetBaseConstants;
+
+public class VAccordion extends VTabsheetBase {
+
+    public static final String CLASSNAME = "v-accordion";
+
+    private Set<Widget> widgets = new HashSet<Widget>();
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public HashMap<StackItem, UIDL> lazyUpdateMap = new HashMap<StackItem, UIDL>();
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public StackItem openTab = null;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public int selectedUIDLItemIndex = -1;
+
+    private final TouchScrollHandler touchScrollHandler;
+
+    public VAccordion() {
+        super(CLASSNAME);
+        touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this);
+    }
+
+    @Override
+    public void renderTab(UIDL tabUidl, int index, boolean selected,
+            boolean hidden) {
+        StackItem item;
+        int itemIndex;
+        if (getWidgetCount() <= index) {
+            // Create stackItem and render caption
+            item = new StackItem(tabUidl);
+            if (getWidgetCount() == 0) {
+                item.addStyleDependentName("first");
+            }
+            itemIndex = getWidgetCount();
+            add(item, getElement());
+        } else {
+            item = getStackItem(index);
+            item = moveStackItemIfNeeded(item, index, tabUidl);
+            itemIndex = index;
+        }
+        item.updateCaption(tabUidl);
+
+        item.setVisible(!hidden);
+
+        if (selected) {
+            selectedUIDLItemIndex = itemIndex;
+        }
+
+        if (tabUidl.getChildCount() > 0) {
+            lazyUpdateMap.put(item, tabUidl.getChildUIDL(0));
+        }
+    }
+
+    @Override
+    public void setStylePrimaryName(String style) {
+        super.setStylePrimaryName(style);
+        updateStyleNames(style);
+    }
+
+    @Override
+    public void setStyleName(String style) {
+        super.setStyleName(style);
+        updateStyleNames(style);
+    }
+
+    protected void updateStyleNames(String primaryStyleName) {
+        for (Widget w : getChildren()) {
+            if (w instanceof StackItem) {
+                StackItem item = (StackItem) w;
+                item.updateStyleNames(primaryStyleName);
+            }
+        }
+    }
+
+    /**
+     * This method tries to find out if a tab has been rendered with a different
+     * index previously. If this is the case it re-orders the children so the
+     * same StackItem is used for rendering this time. E.g. if the first tab has
+     * been removed all tabs which contain cached content must be moved 1 step
+     * up to preserve the cached content.
+     * 
+     * @param item
+     * @param newIndex
+     * @param tabUidl
+     * @return
+     */
+    private StackItem moveStackItemIfNeeded(StackItem item, int newIndex,
+            UIDL tabUidl) {
+        UIDL tabContentUIDL = null;
+        ComponentConnector tabContent = null;
+        if (tabUidl.getChildCount() > 0) {
+            tabContentUIDL = tabUidl.getChildUIDL(0);
+            tabContent = client.getPaintable(tabContentUIDL);
+        }
+
+        Widget itemWidget = item.getComponent();
+        if (tabContent != null) {
+            if (tabContent.getWidget() != itemWidget) {
+                /*
+                 * This is not the same widget as before, find out if it has
+                 * been moved
+                 */
+                int oldIndex = -1;
+                StackItem oldItem = null;
+                for (int i = 0; i < getWidgetCount(); i++) {
+                    Widget w = getWidget(i);
+                    oldItem = (StackItem) w;
+                    if (tabContent == oldItem.getComponent()) {
+                        oldIndex = i;
+                        break;
+                    }
+                }
+
+                if (oldIndex != -1 && oldIndex > newIndex) {
+                    /*
+                     * The tab has previously been rendered in another position
+                     * so we must move the cached content to correct position.
+                     * We move only items with oldIndex > newIndex to prevent
+                     * moving items already rendered in this update. If for
+                     * instance tabs 1,2,3 are removed and added as 3,2,1 we
+                     * cannot re-use "1" when we get to the third tab.
+                     */
+                    insert(oldItem, getElement(), newIndex, true);
+                    return oldItem;
+                }
+            }
+        } else {
+            // Tab which has never been loaded. Must assure we use an empty
+            // StackItem
+            Widget oldWidget = item.getComponent();
+            if (oldWidget != null) {
+                oldWidget.removeFromParent();
+            }
+        }
+        return item;
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void open(int itemIndex) {
+        StackItem item = (StackItem) getWidget(itemIndex);
+        boolean alreadyOpen = false;
+        if (openTab != null) {
+            if (openTab.isOpen()) {
+                if (openTab == item) {
+                    alreadyOpen = true;
+                } else {
+                    openTab.close();
+                }
+            }
+        }
+        if (!alreadyOpen) {
+            item.open();
+            activeTabIndex = itemIndex;
+            openTab = item;
+        }
+
+        // Update the size for the open tab
+        updateOpenTabSize();
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void close(StackItem item) {
+        if (!item.isOpen()) {
+            return;
+        }
+
+        item.close();
+        activeTabIndex = -1;
+        openTab = null;
+
+    }
+
+    @Override
+    protected void selectTab(final int index, final UIDL contentUidl) {
+        StackItem item = getStackItem(index);
+        if (index != activeTabIndex) {
+            open(index);
+            iLayout();
+            // TODO Check if this is needed
+            client.runDescendentsLayout(this);
+
+        }
+        item.setContent(contentUidl);
+    }
+
+    public void onSelectTab(StackItem item) {
+        final int index = getWidgetIndex(item);
+        if (index != activeTabIndex && !disabled && !readonly
+                && !disabledTabKeys.contains(tabKeys.get(index))) {
+            addStyleDependentName("loading");
+            client.updateVariable(id, "selected", "" + tabKeys.get(index), true);
+        }
+    }
+
+    /**
+     * Sets the size of the open tab.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public void updateOpenTabSize() {
+        if (openTab == null) {
+            return;
+        }
+
+        // WIDTH
+        if (!isDynamicWidth()) {
+            openTab.setWidth("100%");
+        } else {
+            openTab.setWidth(null);
+        }
+
+        // HEIGHT
+        if (!isDynamicHeight()) {
+            int usedPixels = 0;
+            for (Widget w : getChildren()) {
+                StackItem item = (StackItem) w;
+                if (item == openTab) {
+                    usedPixels += item.getCaptionHeight();
+                } else {
+                    // This includes the captionNode borders
+                    usedPixels += item.getHeight();
+                }
+            }
+
+            int offsetHeight = getOffsetHeight();
+
+            int spaceForOpenItem = offsetHeight - usedPixels;
+
+            if (spaceForOpenItem < 0) {
+                spaceForOpenItem = 0;
+            }
+
+            openTab.setHeight(spaceForOpenItem);
+        } else {
+            openTab.setHeightFromWidget();
+
+        }
+
+    }
+
+    public void iLayout() {
+        if (openTab == null) {
+            return;
+        }
+
+        if (isDynamicWidth()) {
+            int maxWidth = 40;
+            for (Widget w : getChildren()) {
+                StackItem si = (StackItem) w;
+                int captionWidth = si.getCaptionWidth();
+                if (captionWidth > maxWidth) {
+                    maxWidth = captionWidth;
+                }
+            }
+            int widgetWidth = openTab.getWidgetWidth();
+            if (widgetWidth > maxWidth) {
+                maxWidth = widgetWidth;
+            }
+            super.setWidth(maxWidth + "px");
+            openTab.setWidth(maxWidth);
+        }
+    }
+
+    /**
+     * A StackItem has always two children, Child 0 is a VCaption, Child 1 is
+     * the actual child widget.
+     */
+    public class StackItem extends ComplexPanel implements ClickHandler {
+
+        public void setHeight(int height) {
+            if (height == -1) {
+                super.setHeight("");
+                DOM.setStyleAttribute(content, "height", "0px");
+            } else {
+                super.setHeight((height + getCaptionHeight()) + "px");
+                DOM.setStyleAttribute(content, "height", height + "px");
+                DOM.setStyleAttribute(content, "top", getCaptionHeight() + "px");
+
+            }
+        }
+
+        public Widget getComponent() {
+            if (getWidgetCount() < 2) {
+                return null;
+            }
+            return getWidget(1);
+        }
+
+        @Override
+        public void setVisible(boolean visible) {
+            super.setVisible(visible);
+        }
+
+        public void setHeightFromWidget() {
+            Widget widget = getChildWidget();
+            if (widget == null) {
+                return;
+            }
+
+            int paintableHeight = widget.getElement().getOffsetHeight();
+            setHeight(paintableHeight);
+
+        }
+
+        /**
+         * Returns caption width including padding
+         * 
+         * @return
+         */
+        public int getCaptionWidth() {
+            if (caption == null) {
+                return 0;
+            }
+
+            int captionWidth = caption.getRequiredWidth();
+            int padding = Util.measureHorizontalPaddingAndBorder(
+                    caption.getElement(), 18);
+            return captionWidth + padding;
+        }
+
+        public void setWidth(int width) {
+            if (width == -1) {
+                super.setWidth("");
+            } else {
+                super.setWidth(width + "px");
+            }
+        }
+
+        public int getHeight() {
+            return getOffsetHeight();
+        }
+
+        public int getCaptionHeight() {
+            return captionNode.getOffsetHeight();
+        }
+
+        private VCaption caption;
+        private boolean open = false;
+        private Element content = DOM.createDiv();
+        private Element captionNode = DOM.createDiv();
+
+        public StackItem(UIDL tabUidl) {
+            setElement(DOM.createDiv());
+            caption = new VCaption(client);
+            caption.addClickHandler(this);
+            super.add(caption, captionNode);
+            DOM.appendChild(captionNode, caption.getElement());
+            DOM.appendChild(getElement(), captionNode);
+            DOM.appendChild(getElement(), content);
+
+            updateStyleNames(VAccordion.this.getStylePrimaryName());
+
+            touchScrollHandler.addElement(getContainerElement());
+
+            close();
+        }
+
+        private void updateStyleNames(String primaryStyleName) {
+            content.removeClassName(getStylePrimaryName() + "-content");
+            captionNode.removeClassName(getStylePrimaryName() + "-caption");
+
+            setStylePrimaryName(primaryStyleName + "-item");
+
+            captionNode.addClassName(getStylePrimaryName() + "-caption");
+            content.addClassName(getStylePrimaryName() + "-content");
+        }
+
+        @Override
+        public void onBrowserEvent(Event event) {
+            onSelectTab(this);
+        }
+
+        public Element getContainerElement() {
+            return content;
+        }
+
+        public Widget getChildWidget() {
+            if (getWidgetCount() > 1) {
+                return getWidget(1);
+            } else {
+                return null;
+            }
+        }
+
+        public void replaceWidget(Widget newWidget) {
+            if (getWidgetCount() > 1) {
+                Widget oldWidget = getWidget(1);
+                ComponentConnector oldPaintable = ConnectorMap.get(client)
+                        .getConnector(oldWidget);
+                ConnectorMap.get(client).unregisterConnector(oldPaintable);
+                widgets.remove(oldWidget);
+                remove(1);
+            }
+            add(newWidget, content);
+            widgets.add(newWidget);
+        }
+
+        public void open() {
+            open = true;
+            DOM.setStyleAttribute(content, "top", getCaptionHeight() + "px");
+            DOM.setStyleAttribute(content, "left", "0px");
+            DOM.setStyleAttribute(content, "visibility", "");
+            addStyleDependentName("open");
+        }
+
+        public void hide() {
+            DOM.setStyleAttribute(content, "visibility", "hidden");
+        }
+
+        public void close() {
+            DOM.setStyleAttribute(content, "visibility", "hidden");
+            DOM.setStyleAttribute(content, "top", "-100000px");
+            DOM.setStyleAttribute(content, "left", "-100000px");
+            removeStyleDependentName("open");
+            setHeight(-1);
+            setWidth("");
+            open = false;
+        }
+
+        public boolean isOpen() {
+            return open;
+        }
+
+        public void setContent(UIDL contentUidl) {
+            final ComponentConnector newPntbl = client
+                    .getPaintable(contentUidl);
+            Widget newWidget = newPntbl.getWidget();
+            if (getChildWidget() == null) {
+                add(newWidget, content);
+                widgets.add(newWidget);
+            } else if (getChildWidget() != newWidget) {
+                replaceWidget(newWidget);
+            }
+            if (contentUidl.getBooleanAttribute("cached")) {
+                /*
+                 * The size of a cached, relative sized component must be
+                 * updated to report correct size.
+                 */
+                client.handleComponentRelativeSize(newPntbl.getWidget());
+            }
+            if (isOpen() && isDynamicHeight()) {
+                setHeightFromWidget();
+            }
+        }
+
+        @Override
+        public void onClick(ClickEvent event) {
+            onSelectTab(this);
+        }
+
+        public void updateCaption(UIDL uidl) {
+            // TODO need to call this because the caption does not have an owner
+            caption.updateCaptionWithoutOwner(
+                    uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_CAPTION),
+                    uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DISABLED),
+                    uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION),
+                    uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ERROR_MESSAGE),
+                    uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ICON));
+        }
+
+        public int getWidgetWidth() {
+            return DOM.getFirstChild(content).getOffsetWidth();
+        }
+
+        public boolean contains(ComponentConnector p) {
+            return (getChildWidget() == p.getWidget());
+        }
+
+        public boolean isCaptionVisible() {
+            return caption.isVisible();
+        }
+
+    }
+
+    @Override
+    protected void clearPaintables() {
+        clear();
+    }
+
+    boolean isDynamicWidth() {
+        ComponentConnector paintable = ConnectorMap.get(client).getConnector(
+                this);
+        return paintable.isUndefinedWidth();
+    }
+
+    boolean isDynamicHeight() {
+        ComponentConnector paintable = ConnectorMap.get(client).getConnector(
+                this);
+        return paintable.isUndefinedHeight();
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public Iterator<Widget> getWidgetIterator() {
+        return widgets.iterator();
+    }
+
+    @Override
+    public int getTabCount() {
+        return getWidgetCount();
+    }
+
+    @Override
+    public void removeTab(int index) {
+        StackItem item = getStackItem(index);
+        remove(item);
+        touchScrollHandler.removeElement(item.getContainerElement());
+    }
+
+    @Override
+    public ComponentConnector getTab(int index) {
+        if (index < getWidgetCount()) {
+            StackItem stackItem = getStackItem(index);
+            if (stackItem == null) {
+                return null;
+            }
+            Widget w = stackItem.getChildWidget();
+            if (w != null) {
+                return ConnectorMap.get(client).getConnector(w);
+            }
+        }
+
+        return null;
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public StackItem getStackItem(int index) {
+        return (StackItem) getWidget(index);
+    }
+
+}
diff --git a/client/src/com/vaadin/client/ui/VAudio.java b/client/src/com/vaadin/client/ui/VAudio.java
new file mode 100644 (file)
index 0000000..da3910e
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.dom.client.AudioElement;
+import com.google.gwt.dom.client.Document;
+
+public class VAudio extends VMediaBase {
+    private static String CLASSNAME = "v-audio";
+
+    private AudioElement audio;
+
+    public VAudio() {
+        audio = Document.get().createAudioElement();
+        setMediaElement(audio);
+        setStyleName(CLASSNAME);
+    }
+
+}
diff --git a/client/src/com/vaadin/client/ui/VBrowserFrame.java b/client/src/com/vaadin/client/ui/VBrowserFrame.java
new file mode 100644 (file)
index 0000000..78bf55a
--- /dev/null
@@ -0,0 +1,122 @@
+package com.vaadin.client.ui;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.IFrameElement;
+import com.google.gwt.user.client.ui.Widget;
+
+public class VBrowserFrame extends Widget {
+
+    protected IFrameElement iframe;
+    protected Element altElement;
+    protected String altText;
+
+    public static final String CLASSNAME = "v-browserframe";
+
+    public VBrowserFrame() {
+        Element root = Document.get().createDivElement();
+        setElement(root);
+
+        setStyleName(CLASSNAME);
+
+        createAltTextElement();
+    }
+
+    /**
+     * Always creates new iframe inside widget. Will replace previous iframe.
+     * 
+     * @return
+     */
+    protected IFrameElement createIFrameElement(String src) {
+        String name = null;
+
+        // Remove alt text
+        if (altElement != null) {
+            getElement().removeChild(altElement);
+            altElement = null;
+        }
+
+        // Remove old iframe
+        if (iframe != null) {
+            name = iframe.getAttribute("name");
+            getElement().removeChild(iframe);
+            iframe = null;
+        }
+
+        iframe = Document.get().createIFrameElement();
+        iframe.setSrc(src);
+        iframe.setFrameBorder(0);
+        iframe.setAttribute("width", "100%");
+        iframe.setAttribute("height", "100%");
+        iframe.setAttribute("allowTransparency", "true");
+
+        getElement().appendChild(iframe);
+
+        // Reset old attributes (except src)
+        if (name != null) {
+            iframe.setName(name);
+        }
+
+        return iframe;
+    }
+
+    protected void createAltTextElement() {
+        if (iframe != null) {
+            return;
+        }
+
+        if (altElement == null) {
+            altElement = Document.get().createSpanElement();
+            getElement().appendChild(altElement);
+        }
+
+        if (altText != null) {
+            altElement.setInnerText(altText);
+        } else {
+            altElement.setInnerText("");
+        }
+    }
+
+    public void setAlternateText(String altText) {
+        if (this.altText != altText) {
+            this.altText = altText;
+            if (altElement != null) {
+                if (altText != null) {
+                    altElement.setInnerText(altText);
+                } else {
+                    altElement.setInnerText("");
+                }
+            }
+        }
+    }
+
+    /**
+     * Set the source (the "src" attribute) of iframe. Will replace old iframe
+     * with new.
+     * 
+     * @param source
+     *            Source of iframe.
+     */
+    public void setSource(String source) {
+
+        if (source == null) {
+            if (iframe != null) {
+                getElement().removeChild(iframe);
+                iframe = null;
+            }
+            createAltTextElement();
+            setAlternateText(altText);
+            return;
+        }
+
+        if (iframe == null || iframe.getSrc() != source) {
+            createIFrameElement(source);
+        }
+    }
+
+    public void setName(String name) {
+        if (iframe != null) {
+            iframe.setName(name);
+        }
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VButton.java b/client/src/com/vaadin/client/ui/VButton.java
new file mode 100644 (file)
index 0000000..f896f16
--- /dev/null
@@ -0,0 +1,445 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+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.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.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.Accessibility;
+import com.google.gwt.user.client.ui.FocusWidget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.Util;
+
+public class VButton extends FocusWidget implements ClickHandler {
+
+    public static final String CLASSNAME = "v-button";
+    private static final String CLASSNAME_PRESSED = "v-pressed";
+
+    // mouse movement is checked before synthesizing click event on mouseout
+    protected static int MOVE_THRESHOLD = 3;
+    protected int mousedownX = 0;
+    protected int mousedownY = 0;
+
+    /** 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 final Element wrapper = DOM.createSpan();
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public Element errorIndicatorElement;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public final Element captionElement = DOM.createSpan();
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public Icon icon;
+
+    /**
+     * Helper flag to handle special-case where the button is moved from under
+     * mouse while clicking it. In this case mouse leaves the button without
+     * moving.
+     */
+    protected boolean clickPending;
+
+    private boolean enabled = true;
+
+    private int tabIndex = 0;
+
+    /*
+     * BELOW PRIVATE MEMBERS COPY-PASTED FROM GWT CustomButton
+     */
+
+    /**
+     * If <code>true</code>, this widget is capturing with the mouse held down.
+     */
+    private boolean isCapturing;
+
+    /**
+     * If <code>true</code>, this widget has focus with the space bar down. This
+     * means that we will get events when the button is released, but we should
+     * trigger the button only if the button is still focused at that point.
+     */
+    private boolean isFocusing;
+
+    /**
+     * Used to decide whether to allow clicks to propagate up to the superclass
+     * or container elements.
+     */
+    private boolean disallowNextClick = false;
+    private boolean isHovering;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public int clickShortcut = 0;
+
+    private HandlerRegistration focusHandlerRegistration;
+    private HandlerRegistration blurHandlerRegistration;
+
+    public VButton() {
+        super(DOM.createDiv());
+        setTabIndex(0);
+        sinkEvents(Event.ONCLICK | Event.MOUSEEVENTS | Event.FOCUSEVENTS
+                | Event.KEYEVENTS);
+
+        // Add a11y role "button"
+        Accessibility.setRole(getElement(), Accessibility.ROLE_BUTTON);
+
+        getElement().appendChild(wrapper);
+        wrapper.appendChild(captionElement);
+
+        setStyleName(CLASSNAME);
+
+        addClickHandler(this);
+    }
+
+    @Override
+    public void setStyleName(String style) {
+        super.setStyleName(style);
+        wrapper.setClassName(getStylePrimaryName() + "-wrap");
+        captionElement.setClassName(getStylePrimaryName() + "-caption");
+    }
+
+    @Override
+    public void setStylePrimaryName(String style) {
+        super.setStylePrimaryName(style);
+        wrapper.setClassName(getStylePrimaryName() + "-wrap");
+        captionElement.setClassName(getStylePrimaryName() + "-caption");
+    }
+
+    public void setText(String text) {
+        captionElement.setInnerText(text);
+    }
+
+    public void setHtml(String html) {
+        captionElement.setInnerHTML(html);
+    }
+
+    @SuppressWarnings("deprecation")
+    @Override
+    /*
+     * Copy-pasted from GWT CustomButton, some minor modifications done:
+     * 
+     * -for IE/Opera added CLASSNAME_PRESSED
+     * 
+     * -event.preventDefault() commented from ONMOUSEDOWN (Firefox won't apply
+     * :active styles if it is present)
+     * 
+     * -Tooltip event handling added
+     * 
+     * -onload event handler added (for icon handling)
+     */
+    public void onBrowserEvent(Event event) {
+        if (DOM.eventGetType(event) == Event.ONLOAD) {
+            Util.notifyParentOfSizeChange(this, true);
+        }
+        // Should not act on button if disabled.
+        if (!isEnabled()) {
+            // This can happen when events are bubbled up from non-disabled
+            // children
+            return;
+        }
+
+        int type = DOM.eventGetType(event);
+        switch (type) {
+        case Event.ONCLICK:
+            // If clicks are currently disallowed, keep it from bubbling or
+            // being passed to the superclass.
+            if (disallowNextClick) {
+                event.stopPropagation();
+                disallowNextClick = false;
+                return;
+            }
+            break;
+        case Event.ONMOUSEDOWN:
+            if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) {
+                // This was moved from mouseover, which iOS sometimes skips.
+                // We're certainly hovering at this point, and we don't actually
+                // need that information before this point.
+                setHovering(true);
+            }
+            if (event.getButton() == Event.BUTTON_LEFT) {
+                // save mouse position to detect movement before synthesizing
+                // event later
+                mousedownX = event.getClientX();
+                mousedownY = event.getClientY();
+
+                disallowNextClick = true;
+                clickPending = true;
+                setFocus(true);
+                DOM.setCapture(getElement());
+                isCapturing = true;
+                // Prevent dragging (on some browsers);
+                // DOM.eventPreventDefault(event);
+                if (BrowserInfo.get().isIE() || BrowserInfo.get().isOpera()) {
+                    addStyleName(CLASSNAME_PRESSED);
+                }
+            }
+            break;
+        case Event.ONMOUSEUP:
+            if (isCapturing) {
+                isCapturing = false;
+                DOM.releaseCapture(getElement());
+                if (isHovering() && event.getButton() == Event.BUTTON_LEFT) {
+                    // Click ok
+                    disallowNextClick = false;
+                }
+                if (BrowserInfo.get().isIE() || BrowserInfo.get().isOpera()) {
+                    removeStyleName(CLASSNAME_PRESSED);
+                }
+                // Explicitly prevent IE 8 from propagating mouseup events
+                // upward (fixes #6753)
+                if (BrowserInfo.get().isIE8()) {
+                    event.stopPropagation();
+                }
+            }
+            break;
+        case Event.ONMOUSEMOVE:
+            clickPending = false;
+            if (isCapturing) {
+                // Prevent dragging (on other browsers);
+                DOM.eventPreventDefault(event);
+            }
+            break;
+        case Event.ONMOUSEOUT:
+            Element to = event.getRelatedTarget();
+            if (getElement().isOrHasChild(DOM.eventGetTarget(event))
+                    && (to == null || !getElement().isOrHasChild(to))) {
+                if (clickPending
+                        && Math.abs(mousedownX - event.getClientX()) < MOVE_THRESHOLD
+                        && Math.abs(mousedownY - event.getClientY()) < MOVE_THRESHOLD) {
+                    onClick();
+                    break;
+                }
+                clickPending = false;
+                if (isCapturing) {
+                }
+                setHovering(false);
+                if (BrowserInfo.get().isIE() || BrowserInfo.get().isOpera()) {
+                    removeStyleName(CLASSNAME_PRESSED);
+                }
+            }
+            break;
+        case Event.ONBLUR:
+            if (isFocusing) {
+                isFocusing = false;
+            }
+            break;
+        case Event.ONLOSECAPTURE:
+            if (isCapturing) {
+                isCapturing = false;
+            }
+            break;
+        }
+
+        super.onBrowserEvent(event);
+
+        // Synthesize clicks based on keyboard events AFTER the normal key
+        // handling.
+        if ((event.getTypeInt() & Event.KEYEVENTS) != 0) {
+            switch (type) {
+            case Event.ONKEYDOWN:
+                // Stop propagation when the user starts pressing a button that
+                // we are handling to prevent actions from getting triggered
+                if (event.getKeyCode() == 32 /* space */) {
+                    isFocusing = true;
+                    event.preventDefault();
+                    event.stopPropagation();
+                } else if (event.getKeyCode() == KeyCodes.KEY_ENTER) {
+                    event.stopPropagation();
+                }
+                break;
+            case Event.ONKEYUP:
+                if (isFocusing && event.getKeyCode() == 32 /* space */) {
+                    isFocusing = false;
+                    onClick();
+                    event.stopPropagation();
+                    event.preventDefault();
+                }
+                break;
+            case Event.ONKEYPRESS:
+                if (event.getKeyCode() == KeyCodes.KEY_ENTER) {
+                    onClick();
+                    event.stopPropagation();
+                    event.preventDefault();
+                }
+                break;
+            }
+        }
+    }
+
+    final void setHovering(boolean hovering) {
+        if (hovering != isHovering()) {
+            isHovering = hovering;
+        }
+    }
+
+    final boolean isHovering() {
+        return isHovering;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event
+     * .dom.client.ClickEvent)
+     */
+    @Override
+    public void onClick(ClickEvent event) {
+        if (BrowserInfo.get().isSafari()) {
+            VButton.this.setFocus(true);
+        }
+
+        clickPending = false;
+    }
+
+    /*
+     * ALL BELOW COPY-PASTED FROM GWT CustomButton
+     */
+
+    /**
+     * Called internally when the user finishes clicking on this button. The
+     * default behavior is to fire the click event to listeners. Subclasses that
+     * override {@link #onClickStart()} should override this method to restore
+     * the normal widget display.
+     * <p>
+     * To add custom code for a click event, override
+     * {@link #onClick(ClickEvent)} instead of this.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public void onClick() {
+        // Allow the click we're about to synthesize to pass through to the
+        // superclass and containing elements. Element.dispatchEvent() is
+        // synchronous, so we simply set and clear the flag within this method.
+
+        disallowNextClick = false;
+
+        // Mouse coordinates are not always available (e.g., when the click is
+        // caused by a keyboard event).
+        NativeEvent evt = Document.get().createClickEvent(1, 0, 0, 0, 0, false,
+                false, false, false);
+        getElement().dispatchEvent(evt);
+    }
+
+    /**
+     * Sets whether this button is enabled.
+     * 
+     * @param enabled
+     *            <code>true</code> to enable the button, <code>false</code> to
+     *            disable it
+     */
+
+    @Override
+    public final void setEnabled(boolean enabled) {
+        if (isEnabled() != enabled) {
+            this.enabled = enabled;
+            if (!enabled) {
+                cleanupCaptureState();
+                Accessibility.removeState(getElement(),
+                        Accessibility.STATE_PRESSED);
+                super.setTabIndex(-1);
+            } else {
+                Accessibility.setState(getElement(),
+                        Accessibility.STATE_PRESSED, "false");
+                super.setTabIndex(tabIndex);
+            }
+        }
+    }
+
+    @Override
+    public final boolean isEnabled() {
+        return enabled;
+    }
+
+    @Override
+    public final void setTabIndex(int index) {
+        super.setTabIndex(index);
+        tabIndex = index;
+    }
+
+    /**
+     * Resets internal state if this button can no longer service events. This
+     * can occur when the widget becomes detached or disabled.
+     */
+    private void cleanupCaptureState() {
+        if (isCapturing || isFocusing) {
+            DOM.releaseCapture(getElement());
+            isCapturing = false;
+            isFocusing = false;
+        }
+    }
+
+    private static native int getHorizontalBorderAndPaddingWidth(Element elem)
+    /*-{
+        // THIS METHOD IS ONLY USED FOR INTERNET EXPLORER, IT DOESN'T WORK WITH OTHERS
+       
+       var convertToPixel = function(elem, value) {
+           // From the awesome hack by Dean Edwards
+            // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+            // Remember the original values
+            var left = elem.style.left, rsLeft = elem.runtimeStyle.left;
+
+            // Put in the new values to get a computed value out
+            elem.runtimeStyle.left = elem.currentStyle.left;
+            elem.style.left = value || 0;
+            var ret = elem.style.pixelLeft;
+
+            // Revert the changed values
+            elem.style.left = left;
+            elem.runtimeStyle.left = rsLeft;
+            
+            return ret;
+       }
+       
+       var ret = 0;
+
+        var sides = ["Right","Left"];
+        for(var i=0; i<2; i++) {
+            var side = sides[i];
+            var value;
+            // Border -------------------------------------------------------
+            if(elem.currentStyle["border"+side+"Style"] != "none") {
+                value = elem.currentStyle["border"+side+"Width"];
+                if ( !/^\d+(px)?$/i.test( value ) && /^\d/.test( value ) ) {
+                    ret += convertToPixel(elem, value);
+                } else if(value.length > 2) {
+                    ret += parseInt(value.substr(0, value.length-2));
+                }
+            }
+                    
+            // Padding -------------------------------------------------------
+            value = elem.currentStyle["padding"+side];
+            if ( !/^\d+(px)?$/i.test( value ) && /^\d/.test( value ) ) {
+                ret += convertToPixel(elem, value);
+            } else if(value.length > 2) {
+                ret += parseInt(value.substr(0, value.length-2));
+            }
+        }
+
+       return ret;
+    }-*/;
+
+}
diff --git a/client/src/com/vaadin/client/ui/VCalendarPanel.java b/client/src/com/vaadin/client/ui/VCalendarPanel.java
new file mode 100644 (file)
index 0000000..4e147b5
--- /dev/null
@@ -0,0 +1,1824 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.Date;
+import java.util.Iterator;
+
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.DomEvent;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.dom.client.MouseDownEvent;
+import com.google.gwt.event.dom.client.MouseDownHandler;
+import com.google.gwt.event.dom.client.MouseOutEvent;
+import com.google.gwt.event.dom.client.MouseOutHandler;
+import com.google.gwt.event.dom.client.MouseUpEvent;
+import com.google.gwt.event.dom.client.MouseUpHandler;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.FlexTable;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.InlineHTML;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.DateTimeService;
+import com.vaadin.client.Util;
+import com.vaadin.client.VConsole;
+import com.vaadin.shared.ui.datefield.Resolution;
+
+@SuppressWarnings("deprecation")
+public class VCalendarPanel extends FocusableFlexTable implements
+        KeyDownHandler, KeyPressHandler, MouseOutHandler, MouseDownHandler,
+        MouseUpHandler, BlurHandler, FocusHandler, SubPartAware {
+
+    public interface SubmitListener {
+
+        /**
+         * Called when calendar user triggers a submitting operation in calendar
+         * panel. Eg. clicking on day or hitting enter.
+         */
+        void onSubmit();
+
+        /**
+         * On eg. ESC key.
+         */
+        void onCancel();
+    }
+
+    /**
+     * Blur listener that listens to blur event from the panel
+     */
+    public interface FocusOutListener {
+        /**
+         * @return true if the calendar panel is not used after focus moves out
+         */
+        boolean onFocusOut(DomEvent<?> event);
+    }
+
+    /**
+     * FocusChangeListener is notified when the panel changes its _focused_
+     * value.
+     */
+    public interface FocusChangeListener {
+        void focusChanged(Date focusedDate);
+    }
+
+    /**
+     * Dispatches an event when the panel when time is changed
+     */
+    public interface TimeChangeListener {
+
+        void changed(int hour, int min, int sec, int msec);
+    }
+
+    /**
+     * Represents a Date button in the calendar
+     */
+    private class VEventButton extends Button {
+        public VEventButton() {
+            addMouseDownHandler(VCalendarPanel.this);
+            addMouseOutHandler(VCalendarPanel.this);
+            addMouseUpHandler(VCalendarPanel.this);
+        }
+    }
+
+    private static final String CN_FOCUSED = "focused";
+
+    private static final String CN_TODAY = "today";
+
+    private static final String CN_SELECTED = "selected";
+
+    private static final String CN_OFFMONTH = "offmonth";
+
+    /**
+     * Represents a click handler for when a user selects a value by using the
+     * mouse
+     */
+    private ClickHandler dayClickHandler = new ClickHandler() {
+        /*
+         * (non-Javadoc)
+         * 
+         * @see
+         * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt
+         * .event.dom.client.ClickEvent)
+         */
+        @Override
+        public void onClick(ClickEvent event) {
+            Date newDate = ((Day) event.getSource()).getDate();
+            if (newDate.getMonth() != displayedMonth.getMonth()
+                    || newDate.getYear() != displayedMonth.getYear()) {
+                // If an off-month date was clicked, we must change the
+                // displayed month and re-render the calendar (#8931)
+                displayedMonth.setMonth(newDate.getMonth());
+                displayedMonth.setYear(newDate.getYear());
+                renderCalendar();
+            }
+            focusDay(newDate);
+            selectFocused();
+            onSubmit();
+        }
+    };
+
+    private VEventButton prevYear;
+
+    private VEventButton nextYear;
+
+    private VEventButton prevMonth;
+
+    private VEventButton nextMonth;
+
+    private VTime time;
+
+    private FlexTable days = new FlexTable();
+
+    private Resolution resolution = Resolution.YEAR;
+
+    private int focusedRow;
+
+    private Timer mouseTimer;
+
+    private Date value;
+
+    private boolean enabled = true;
+
+    private boolean readonly = false;
+
+    private DateTimeService dateTimeService;
+
+    private boolean showISOWeekNumbers;
+
+    private Date displayedMonth;
+
+    private Date focusedDate;
+
+    private Day selectedDay;
+
+    private Day focusedDay;
+
+    private FocusOutListener focusOutListener;
+
+    private SubmitListener submitListener;
+
+    private FocusChangeListener focusChangeListener;
+
+    private TimeChangeListener timeChangeListener;
+
+    private boolean hasFocus = false;
+
+    private VDateField parent;
+
+    private boolean initialRenderDone = false;
+
+    public VCalendarPanel() {
+
+        setStyleName(VDateField.CLASSNAME + "-calendarpanel");
+
+        /*
+         * Firefox auto-repeat works correctly only if we use a key press
+         * handler, other browsers handle it correctly when using a key down
+         * handler
+         */
+        if (BrowserInfo.get().isGecko()) {
+            addKeyPressHandler(this);
+        } else {
+            addKeyDownHandler(this);
+        }
+        addFocusHandler(this);
+        addBlurHandler(this);
+    }
+
+    public void setParentField(VDateField parent) {
+        this.parent = parent;
+    }
+
+    /**
+     * Sets the focus to given date in the current view. Used when moving in the
+     * calendar with the keyboard.
+     * 
+     * @param date
+     *            A Date representing the day of month to be focused. Must be
+     *            one of the days currently visible.
+     */
+    private void focusDay(Date date) {
+        // Only used when calender body is present
+        if (resolution.getCalendarField() > Resolution.MONTH.getCalendarField()) {
+            if (focusedDay != null) {
+                focusedDay.removeStyleDependentName(CN_FOCUSED);
+            }
+
+            if (date != null && focusedDate != null) {
+                focusedDate.setTime(date.getTime());
+                int rowCount = days.getRowCount();
+                for (int i = 0; i < rowCount; i++) {
+                    int cellCount = days.getCellCount(i);
+                    for (int j = 0; j < cellCount; j++) {
+                        Widget widget = days.getWidget(i, j);
+                        if (widget != null && widget instanceof Day) {
+                            Day curday = (Day) widget;
+                            if (curday.getDate().equals(date)) {
+                                curday.addStyleDependentName(CN_FOCUSED);
+                                focusedDay = curday;
+                                focusedRow = i;
+                                return;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Sets the selection highlight to a given day in the current view
+     * 
+     * @param date
+     *            A Date representing the day of month to be selected. Must be
+     *            one of the days currently visible.
+     * 
+     */
+    private void selectDate(Date date) {
+        if (selectedDay != null) {
+            selectedDay.removeStyleDependentName(CN_SELECTED);
+        }
+
+        int rowCount = days.getRowCount();
+        for (int i = 0; i < rowCount; i++) {
+            int cellCount = days.getCellCount(i);
+            for (int j = 0; j < cellCount; j++) {
+                Widget widget = days.getWidget(i, j);
+                if (widget != null && widget instanceof Day) {
+                    Day curday = (Day) widget;
+                    if (curday.getDate().equals(date)) {
+                        curday.addStyleDependentName(CN_SELECTED);
+                        selectedDay = curday;
+                        return;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Updates year, month, day from focusedDate to value
+     */
+    private void selectFocused() {
+        if (focusedDate != null) {
+            if (value == null) {
+                // No previously selected value (set to null on server side).
+                // Create a new date using current date and time
+                value = new Date();
+            }
+            /*
+             * #5594 set Date (day) to 1 in order to prevent any kind of
+             * wrapping of months when later setting the month. (e.g. 31 ->
+             * month with 30 days -> wraps to the 1st of the following month,
+             * e.g. 31st of May -> 31st of April = 1st of May)
+             */
+            value.setDate(1);
+            if (value.getYear() != focusedDate.getYear()) {
+                value.setYear(focusedDate.getYear());
+            }
+            if (value.getMonth() != focusedDate.getMonth()) {
+                value.setMonth(focusedDate.getMonth());
+            }
+            if (value.getDate() != focusedDate.getDate()) {
+            }
+            // We always need to set the date, even if it hasn't changed, since
+            // it was forced to 1 above.
+            value.setDate(focusedDate.getDate());
+
+            selectDate(focusedDate);
+        } else {
+            VConsole.log("Trying to select a the focused date which is NULL!");
+        }
+    }
+
+    protected boolean onValueChange() {
+        return false;
+    }
+
+    public Resolution getResolution() {
+        return resolution;
+    }
+
+    public void setResolution(Resolution resolution) {
+        this.resolution = resolution;
+        if (time != null) {
+            time.removeFromParent();
+            time = null;
+        }
+    }
+
+    private boolean isReadonly() {
+        return readonly;
+    }
+
+    private boolean isEnabled() {
+        return enabled;
+    }
+
+    @Override
+    public void setStyleName(String style) {
+        super.setStyleName(style);
+        if (initialRenderDone) {
+            // Dynamic updates to the stylename needs to render the calendar to
+            // update the inner element stylenames
+            renderCalendar();
+        }
+    }
+
+    @Override
+    public void setStylePrimaryName(String style) {
+        super.setStylePrimaryName(style);
+        if (initialRenderDone) {
+            // Dynamic updates to the stylename needs to render the calendar to
+            // update the inner element stylenames
+            renderCalendar();
+        }
+    }
+
+    private void clearCalendarBody(boolean remove) {
+        if (!remove) {
+            // Leave the cells in place but clear their contents
+
+            // This has the side effect of ensuring that the calendar always
+            // contain 7 rows.
+            for (int row = 1; row < 7; row++) {
+                for (int col = 0; col < 8; col++) {
+                    days.setHTML(row, col, "&nbsp;");
+                }
+            }
+        } else if (getRowCount() > 1) {
+            removeRow(1);
+            days.clear();
+        }
+    }
+
+    /**
+     * Builds the top buttons and current month and year header.
+     * 
+     * @param needsMonth
+     *            Should the month buttons be visible?
+     */
+    private void buildCalendarHeader(boolean needsMonth) {
+
+        getRowFormatter().addStyleName(0,
+                parent.getStylePrimaryName() + "-calendarpanel-header");
+
+        if (prevMonth == null && needsMonth) {
+            prevMonth = new VEventButton();
+            prevMonth.setHTML("&lsaquo;");
+            prevMonth.setStyleName("v-button-prevmonth");
+            prevMonth.setTabIndex(-1);
+            nextMonth = new VEventButton();
+            nextMonth.setHTML("&rsaquo;");
+            nextMonth.setStyleName("v-button-nextmonth");
+            nextMonth.setTabIndex(-1);
+
+            setWidget(0, 3, nextMonth);
+            setWidget(0, 1, prevMonth);
+        } else if (prevMonth != null && !needsMonth) {
+            // Remove month traverse buttons
+            remove(prevMonth);
+            remove(nextMonth);
+            prevMonth = null;
+            nextMonth = null;
+        }
+
+        if (prevYear == null) {
+            prevYear = new VEventButton();
+            prevYear.setHTML("&laquo;");
+            prevYear.setStyleName("v-button-prevyear");
+            prevYear.setTabIndex(-1);
+            nextYear = new VEventButton();
+            nextYear.setHTML("&raquo;");
+            nextYear.setStyleName("v-button-nextyear");
+            nextYear.setTabIndex(-1);
+            setWidget(0, 0, prevYear);
+            setWidget(0, 4, nextYear);
+        }
+
+        final String monthName = needsMonth ? getDateTimeService().getMonth(
+                displayedMonth.getMonth()) : "";
+        final int year = displayedMonth.getYear() + 1900;
+
+        getFlexCellFormatter().setStyleName(0, 2,
+                parent.getStylePrimaryName() + "-calendarpanel-month");
+        getFlexCellFormatter().setStyleName(0, 0,
+                parent.getStylePrimaryName() + "-calendarpanel-prevyear");
+        getFlexCellFormatter().setStyleName(0, 4,
+                parent.getStylePrimaryName() + "-calendarpanel-nextyear");
+        getFlexCellFormatter().setStyleName(0, 3,
+                parent.getStylePrimaryName() + "-calendarpanel-nextmonth");
+        getFlexCellFormatter().setStyleName(0, 1,
+                parent.getStylePrimaryName() + "-calendarpanel-prevmonth");
+
+        setHTML(0, 2, "<span class=\"" + parent.getStylePrimaryName()
+                + "-calendarpanel-month\">" + monthName + " " + year
+                + "</span>");
+    }
+
+    private DateTimeService getDateTimeService() {
+        return dateTimeService;
+    }
+
+    public void setDateTimeService(DateTimeService dateTimeService) {
+        this.dateTimeService = dateTimeService;
+    }
+
+    /**
+     * Returns whether ISO 8601 week numbers should be shown in the value
+     * selector or not. ISO 8601 defines that a week always starts with a Monday
+     * so the week numbers are only shown if this is the case.
+     * 
+     * @return true if week number should be shown, false otherwise
+     */
+    public boolean isShowISOWeekNumbers() {
+        return showISOWeekNumbers;
+    }
+
+    public void setShowISOWeekNumbers(boolean showISOWeekNumbers) {
+        this.showISOWeekNumbers = showISOWeekNumbers;
+    }
+
+    /**
+     * Builds the day and time selectors of the calendar.
+     */
+    private void buildCalendarBody() {
+
+        final int weekColumn = 0;
+        final int firstWeekdayColumn = 1;
+        final int headerRow = 0;
+
+        setWidget(1, 0, days);
+        setCellPadding(0);
+        setCellSpacing(0);
+        getFlexCellFormatter().setColSpan(1, 0, 5);
+        getFlexCellFormatter().setStyleName(1, 0,
+                parent.getStylePrimaryName() + "-calendarpanel-body");
+
+        days.getFlexCellFormatter().setStyleName(headerRow, weekColumn,
+                "v-week");
+        days.setHTML(headerRow, weekColumn, "<strong></strong>");
+        // Hide the week column if week numbers are not to be displayed.
+        days.getFlexCellFormatter().setVisible(headerRow, weekColumn,
+                isShowISOWeekNumbers());
+
+        days.getRowFormatter().setStyleName(headerRow,
+                parent.getStylePrimaryName() + "-calendarpanel-weekdays");
+
+        if (isShowISOWeekNumbers()) {
+            days.getFlexCellFormatter().setStyleName(headerRow, weekColumn,
+                    "v-first");
+            days.getFlexCellFormatter().setStyleName(headerRow,
+                    firstWeekdayColumn, "");
+            days.getRowFormatter()
+                    .addStyleName(
+                            headerRow,
+                            parent.getStylePrimaryName()
+                                    + "-calendarpanel-weeknumbers");
+        } else {
+            days.getFlexCellFormatter().setStyleName(headerRow, weekColumn, "");
+            days.getFlexCellFormatter().setStyleName(headerRow,
+                    firstWeekdayColumn, "v-first");
+        }
+
+        days.getFlexCellFormatter().setStyleName(headerRow,
+                firstWeekdayColumn + 6, "v-last");
+
+        // Print weekday names
+        final int firstDay = getDateTimeService().getFirstDayOfWeek();
+        for (int i = 0; i < 7; i++) {
+            int day = i + firstDay;
+            if (day > 6) {
+                day = 0;
+            }
+            if (getResolution().getCalendarField() > Resolution.MONTH
+                    .getCalendarField()) {
+                days.setHTML(headerRow, firstWeekdayColumn + i, "<strong>"
+                        + getDateTimeService().getShortDay(day) + "</strong>");
+            } else {
+                days.setHTML(headerRow, firstWeekdayColumn + i, "");
+            }
+        }
+
+        // Zero out hours, minutes, seconds, and milliseconds to compare dates
+        // without time part
+        final Date tmp = new Date();
+        final Date today = new Date(tmp.getYear(), tmp.getMonth(),
+                tmp.getDate());
+
+        final Date selectedDate = value == null ? null : new Date(
+                value.getYear(), value.getMonth(), value.getDate());
+
+        final int startWeekDay = getDateTimeService().getStartWeekDay(
+                displayedMonth);
+        final Date curr = (Date) displayedMonth.clone();
+        // Start from the first day of the week that at least partially belongs
+        // to the current month
+        curr.setDate(1 - startWeekDay);
+
+        // No month has more than 6 weeks so 6 is a safe maximum for rows.
+        for (int weekOfMonth = 1; weekOfMonth < 7; weekOfMonth++) {
+            for (int dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++) {
+
+                // Actually write the day of month
+                Day day = new Day((Date) curr.clone());
+                day.setStyleName(parent.getStylePrimaryName()
+                        + "-calendarpanel-day");
+
+                if (curr.equals(selectedDate)) {
+                    day.addStyleDependentName(CN_SELECTED);
+                    selectedDay = day;
+                }
+                if (curr.equals(today)) {
+                    day.addStyleDependentName(CN_TODAY);
+                }
+                if (curr.equals(focusedDate)) {
+                    focusedDay = day;
+                    focusedRow = weekOfMonth;
+                    if (hasFocus) {
+                        day.addStyleDependentName(CN_FOCUSED);
+                    }
+                }
+                if (curr.getMonth() != displayedMonth.getMonth()) {
+                    day.addStyleDependentName(CN_OFFMONTH);
+                }
+
+                days.setWidget(weekOfMonth, firstWeekdayColumn + dayOfWeek, day);
+
+                // ISO week numbers if requested
+                days.getCellFormatter().setVisible(weekOfMonth, weekColumn,
+                        isShowISOWeekNumbers());
+                if (isShowISOWeekNumbers()) {
+                    final String baseCssClass = parent.getStylePrimaryName()
+                            + "-calendarpanel-weeknumber";
+                    String weekCssClass = baseCssClass;
+
+                    int weekNumber = DateTimeService.getISOWeekNumber(curr);
+
+                    days.setHTML(weekOfMonth, 0, "<span class=\""
+                            + weekCssClass + "\"" + ">" + weekNumber
+                            + "</span>");
+                }
+                curr.setDate(curr.getDate() + 1);
+            }
+        }
+    }
+
+    /**
+     * Do we need the time selector
+     * 
+     * @return True if it is required
+     */
+    private boolean isTimeSelectorNeeded() {
+        return getResolution().getCalendarField() > Resolution.DAY
+                .getCalendarField();
+    }
+
+    /**
+     * Updates the calendar and text field with the selected dates.
+     */
+    public void renderCalendar() {
+
+        super.setStylePrimaryName(parent.getStylePrimaryName()
+                + "-calendarpanel");
+
+        if (focusedDate == null) {
+            Date now = new Date();
+            // focusedDate must have zero hours, mins, secs, millisecs
+            focusedDate = new Date(now.getYear(), now.getMonth(), now.getDate());
+            displayedMonth = new Date(now.getYear(), now.getMonth(), 1);
+        }
+
+        if (getResolution().getCalendarField() <= Resolution.MONTH
+                .getCalendarField() && focusChangeListener != null) {
+            focusChangeListener.focusChanged(new Date(focusedDate.getTime()));
+        }
+
+        final boolean needsMonth = getResolution().getCalendarField() > Resolution.YEAR
+                .getCalendarField();
+        boolean needsBody = getResolution().getCalendarField() >= Resolution.DAY
+                .getCalendarField();
+        buildCalendarHeader(needsMonth);
+        clearCalendarBody(!needsBody);
+        if (needsBody) {
+            buildCalendarBody();
+        }
+
+        if (isTimeSelectorNeeded() && time == null) {
+            time = new VTime();
+            setWidget(2, 0, time);
+            getFlexCellFormatter().setColSpan(2, 0, 5);
+            getFlexCellFormatter().setStyleName(2, 0,
+                    parent.getStylePrimaryName() + "-calendarpanel-time");
+        } else if (isTimeSelectorNeeded()) {
+            time.updateTimes();
+        } else if (time != null) {
+            remove(time);
+        }
+
+        initialRenderDone = true;
+    }
+
+    /**
+     * Moves the focus forward the given number of days.
+     */
+    private void focusNextDay(int days) {
+        int oldMonth = focusedDate.getMonth();
+        int oldYear = focusedDate.getYear();
+        focusedDate.setDate(focusedDate.getDate() + days);
+
+        if (focusedDate.getMonth() == oldMonth
+                && focusedDate.getYear() == oldYear) {
+            // Month did not change, only move the selection
+            focusDay(focusedDate);
+        } else {
+            // If the month changed we need to re-render the calendar
+            displayedMonth.setMonth(focusedDate.getMonth());
+            displayedMonth.setYear(focusedDate.getYear());
+            renderCalendar();
+        }
+    }
+
+    /**
+     * Moves the focus backward the given number of days.
+     */
+    private void focusPreviousDay(int days) {
+        focusNextDay(-days);
+    }
+
+    /**
+     * Selects the next month
+     */
+    private void focusNextMonth() {
+
+        int currentMonth = focusedDate.getMonth();
+        focusedDate.setMonth(currentMonth + 1);
+        int requestedMonth = (currentMonth + 1) % 12;
+
+        /*
+         * If the selected value was e.g. 31.3 the new value would be 31.4 but
+         * this value is invalid so the new value will be 1.5. This is taken
+         * care of by decreasing the value until we have the correct month.
+         */
+        while (focusedDate.getMonth() != requestedMonth) {
+            focusedDate.setDate(focusedDate.getDate() - 1);
+        }
+        displayedMonth.setMonth(displayedMonth.getMonth() + 1);
+
+        renderCalendar();
+    }
+
+    /**
+     * Selects the previous month
+     */
+    private void focusPreviousMonth() {
+        int currentMonth = focusedDate.getMonth();
+        focusedDate.setMonth(currentMonth - 1);
+
+        /*
+         * If the selected value was e.g. 31.12 the new value would be 31.11 but
+         * this value is invalid so the new value will be 1.12. This is taken
+         * care of by decreasing the value until we have the correct month.
+         */
+        while (focusedDate.getMonth() == currentMonth) {
+            focusedDate.setDate(focusedDate.getDate() - 1);
+        }
+        displayedMonth.setMonth(displayedMonth.getMonth() - 1);
+
+        renderCalendar();
+    }
+
+    /**
+     * Selects the previous year
+     */
+    private void focusPreviousYear(int years) {
+        int currentMonth = focusedDate.getMonth();
+        focusedDate.setYear(focusedDate.getYear() - years);
+        displayedMonth.setYear(displayedMonth.getYear() - years);
+        /*
+         * If the focused date was a leap day (Feb 29), the new date becomes Mar
+         * 1 if the new year is not also a leap year. Set it to Feb 28 instead.
+         */
+        if (focusedDate.getMonth() != currentMonth) {
+            focusedDate.setDate(0);
+        }
+        renderCalendar();
+    }
+
+    /**
+     * Selects the next year
+     */
+    private void focusNextYear(int years) {
+        int currentMonth = focusedDate.getMonth();
+        focusedDate.setYear(focusedDate.getYear() + years);
+        displayedMonth.setYear(displayedMonth.getYear() + years);
+        /*
+         * If the focused date was a leap day (Feb 29), the new date becomes Mar
+         * 1 if the new year is not also a leap year. Set it to Feb 28 instead.
+         */
+        if (focusedDate.getMonth() != currentMonth) {
+            focusedDate.setDate(0);
+        }
+        renderCalendar();
+    }
+
+    /**
+     * Handles a user click on the component
+     * 
+     * @param sender
+     *            The component that was clicked
+     * @param updateVariable
+     *            Should the value field be updated
+     * 
+     */
+    private void processClickEvent(Widget sender) {
+        if (!isEnabled() || isReadonly()) {
+            return;
+        }
+        if (sender == prevYear) {
+            focusPreviousYear(1);
+        } else if (sender == nextYear) {
+            focusNextYear(1);
+        } else if (sender == prevMonth) {
+            focusPreviousMonth();
+        } else if (sender == nextMonth) {
+            focusNextMonth();
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
+     * .event.dom.client.KeyDownEvent)
+     */
+    @Override
+    public void onKeyDown(KeyDownEvent event) {
+        handleKeyPress(event);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google
+     * .gwt.event.dom.client.KeyPressEvent)
+     */
+    @Override
+    public void onKeyPress(KeyPressEvent event) {
+        handleKeyPress(event);
+    }
+
+    /**
+     * Handles the keypress from both the onKeyPress event and the onKeyDown
+     * event
+     * 
+     * @param event
+     *            The keydown/keypress event
+     */
+    private void handleKeyPress(DomEvent<?> event) {
+        if (time != null
+                && time.getElement().isOrHasChild(
+                        (Node) event.getNativeEvent().getEventTarget().cast())) {
+            int nativeKeyCode = event.getNativeEvent().getKeyCode();
+            if (nativeKeyCode == getSelectKey()) {
+                onSubmit(); // submit happens if enter key hit down on listboxes
+                event.preventDefault();
+                event.stopPropagation();
+            }
+            return;
+        }
+
+        // Check tabs
+        int keycode = event.getNativeEvent().getKeyCode();
+        if (keycode == KeyCodes.KEY_TAB && event.getNativeEvent().getShiftKey()) {
+            if (onTabOut(event)) {
+                return;
+            }
+        }
+
+        // Handle the navigation
+        if (handleNavigation(keycode, event.getNativeEvent().getCtrlKey()
+                || event.getNativeEvent().getMetaKey(), event.getNativeEvent()
+                .getShiftKey())) {
+            event.preventDefault();
+        }
+
+    }
+
+    /**
+     * Notifies submit-listeners of a submit event
+     */
+    private void onSubmit() {
+        if (getSubmitListener() != null) {
+            getSubmitListener().onSubmit();
+        }
+    }
+
+    /**
+     * Notifies submit-listeners of a cancel event
+     */
+    private void onCancel() {
+        if (getSubmitListener() != null) {
+            getSubmitListener().onCancel();
+        }
+    }
+
+    /**
+     * Handles the keyboard navigation when the resolution is set to years.
+     * 
+     * @param keycode
+     *            The keycode to process
+     * @param ctrl
+     *            Is ctrl pressed?
+     * @param shift
+     *            is shift pressed
+     * @return Returns true if the keycode was processed, else false
+     */
+    protected boolean handleNavigationYearMode(int keycode, boolean ctrl,
+            boolean shift) {
+
+        // Ctrl and Shift selection not supported
+        if (ctrl || shift) {
+            return false;
+        }
+
+        else if (keycode == getPreviousKey()) {
+            focusNextYear(10); // Add 10 years
+            return true;
+        }
+
+        else if (keycode == getForwardKey()) {
+            focusNextYear(1); // Add 1 year
+            return true;
+        }
+
+        else if (keycode == getNextKey()) {
+            focusPreviousYear(10); // Subtract 10 years
+            return true;
+        }
+
+        else if (keycode == getBackwardKey()) {
+            focusPreviousYear(1); // Subtract 1 year
+            return true;
+
+        } else if (keycode == getSelectKey()) {
+            value = (Date) focusedDate.clone();
+            onSubmit();
+            return true;
+
+        } else if (keycode == getResetKey()) {
+            // Restore showing value the selected value
+            focusedDate.setTime(value.getTime());
+            renderCalendar();
+            return true;
+
+        } else if (keycode == getCloseKey()) {
+            // TODO fire listener, on users responsibility??
+
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Handle the keyboard navigation when the resolution is set to MONTH
+     * 
+     * @param keycode
+     *            The keycode to handle
+     * @param ctrl
+     *            Was the ctrl key pressed?
+     * @param shift
+     *            Was the shift key pressed?
+     * @return
+     */
+    protected boolean handleNavigationMonthMode(int keycode, boolean ctrl,
+            boolean shift) {
+
+        // Ctrl selection not supported
+        if (ctrl) {
+            return false;
+
+        } else if (keycode == getPreviousKey()) {
+            focusNextYear(1); // Add 1 year
+            return true;
+
+        } else if (keycode == getForwardKey()) {
+            focusNextMonth(); // Add 1 month
+            return true;
+
+        } else if (keycode == getNextKey()) {
+            focusPreviousYear(1); // Subtract 1 year
+            return true;
+
+        } else if (keycode == getBackwardKey()) {
+            focusPreviousMonth(); // Subtract 1 month
+            return true;
+
+        } else if (keycode == getSelectKey()) {
+            value = (Date) focusedDate.clone();
+            onSubmit();
+            return true;
+
+        } else if (keycode == getResetKey()) {
+            // Restore showing value the selected value
+            focusedDate.setTime(value.getTime());
+            renderCalendar();
+            return true;
+
+        } else if (keycode == getCloseKey() || keycode == KeyCodes.KEY_TAB) {
+
+            // TODO fire close event
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Handle keyboard navigation what the resolution is set to DAY
+     * 
+     * @param keycode
+     *            The keycode to handle
+     * @param ctrl
+     *            Was the ctrl key pressed?
+     * @param shift
+     *            Was the shift key pressed?
+     * @return Return true if the key press was handled by the method, else
+     *         return false.
+     */
+    protected boolean handleNavigationDayMode(int keycode, boolean ctrl,
+            boolean shift) {
+
+        // Ctrl key is not in use
+        if (ctrl) {
+            return false;
+        }
+
+        /*
+         * Jumps to the next day.
+         */
+        if (keycode == getForwardKey() && !shift) {
+            focusNextDay(1);
+            return true;
+
+            /*
+             * Jumps to the previous day
+             */
+        } else if (keycode == getBackwardKey() && !shift) {
+            focusPreviousDay(1);
+            return true;
+
+            /*
+             * Jumps one week forward in the calendar
+             */
+        } else if (keycode == getNextKey() && !shift) {
+            focusNextDay(7);
+            return true;
+
+            /*
+             * Jumps one week back in the calendar
+             */
+        } else if (keycode == getPreviousKey() && !shift) {
+            focusPreviousDay(7);
+            return true;
+
+            /*
+             * Selects the value that is chosen
+             */
+        } else if (keycode == getSelectKey() && !shift) {
+            selectFocused();
+            onSubmit(); // submit
+            return true;
+
+        } else if (keycode == getCloseKey()) {
+            onCancel();
+            // TODO close event
+
+            return true;
+
+            /*
+             * Jumps to the next month
+             */
+        } else if (shift && keycode == getForwardKey()) {
+            focusNextMonth();
+            return true;
+
+            /*
+             * Jumps to the previous month
+             */
+        } else if (shift && keycode == getBackwardKey()) {
+            focusPreviousMonth();
+            return true;
+
+            /*
+             * Jumps to the next year
+             */
+        } else if (shift && keycode == getPreviousKey()) {
+            focusNextYear(1);
+            return true;
+
+            /*
+             * Jumps to the previous year
+             */
+        } else if (shift && keycode == getNextKey()) {
+            focusPreviousYear(1);
+            return true;
+
+            /*
+             * Resets the selection
+             */
+        } else if (keycode == getResetKey() && !shift) {
+            // Restore showing value the selected value
+            focusedDate = new Date(value.getYear(), value.getMonth(),
+                    value.getDate());
+            displayedMonth = new Date(value.getYear(), value.getMonth(), 1);
+            renderCalendar();
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Handles the keyboard navigation
+     * 
+     * @param keycode
+     *            The key code that was pressed
+     * @param ctrl
+     *            Was the ctrl key pressed
+     * @param shift
+     *            Was the shift key pressed
+     * @return Return true if key press was handled by the component, else
+     *         return false
+     */
+    protected boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
+        if (!isEnabled() || isReadonly()) {
+            return false;
+        }
+
+        else if (resolution == Resolution.YEAR) {
+            return handleNavigationYearMode(keycode, ctrl, shift);
+        }
+
+        else if (resolution == Resolution.MONTH) {
+            return handleNavigationMonthMode(keycode, ctrl, shift);
+        }
+
+        else if (resolution == Resolution.DAY) {
+            return handleNavigationDayMode(keycode, ctrl, shift);
+        }
+
+        else {
+            return handleNavigationDayMode(keycode, ctrl, shift);
+        }
+
+    }
+
+    /**
+     * Returns the reset key which will reset the calendar to the previous
+     * selection. By default this is backspace but it can be overriden to change
+     * the key to whatever you want.
+     * 
+     * @return
+     */
+    protected int getResetKey() {
+        return KeyCodes.KEY_BACKSPACE;
+    }
+
+    /**
+     * Returns the select key which selects the value. By default this is the
+     * enter key but it can be changed to whatever you like by overriding this
+     * method.
+     * 
+     * @return
+     */
+    protected int getSelectKey() {
+        return KeyCodes.KEY_ENTER;
+    }
+
+    /**
+     * Returns the key that closes the popup window if this is a VPopopCalendar.
+     * Else this does nothing. By default this is the Escape key but you can
+     * change the key to whatever you want by overriding this method.
+     * 
+     * @return
+     */
+    protected int getCloseKey() {
+        return KeyCodes.KEY_ESCAPE;
+    }
+
+    /**
+     * The key that selects the next day in the calendar. By default this is the
+     * right arrow key but by overriding this method it can be changed to
+     * whatever you like.
+     * 
+     * @return
+     */
+    protected int getForwardKey() {
+        return KeyCodes.KEY_RIGHT;
+    }
+
+    /**
+     * The key that selects the previous day in the calendar. By default this is
+     * the left arrow key but by overriding this method it can be changed to
+     * whatever you like.
+     * 
+     * @return
+     */
+    protected int getBackwardKey() {
+        return KeyCodes.KEY_LEFT;
+    }
+
+    /**
+     * The key that selects the next week in the calendar. By default this is
+     * the down arrow key but by overriding this method it can be changed to
+     * whatever you like.
+     * 
+     * @return
+     */
+    protected int getNextKey() {
+        return KeyCodes.KEY_DOWN;
+    }
+
+    /**
+     * The key that selects the previous week in the calendar. By default this
+     * is the up arrow key but by overriding this method it can be changed to
+     * whatever you like.
+     * 
+     * @return
+     */
+    protected int getPreviousKey() {
+        return KeyCodes.KEY_UP;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.MouseOutHandler#onMouseOut(com.google
+     * .gwt.event.dom.client.MouseOutEvent)
+     */
+    @Override
+    public void onMouseOut(MouseOutEvent event) {
+        if (mouseTimer != null) {
+            mouseTimer.cancel();
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.MouseDownHandler#onMouseDown(com.google
+     * .gwt.event.dom.client.MouseDownEvent)
+     */
+    @Override
+    public void onMouseDown(MouseDownEvent event) {
+        // Allow user to click-n-hold for fast-forward or fast-rewind.
+        // Timer is first used for a 500ms delay after mousedown. After that has
+        // elapsed, another timer is triggered to go off every 150ms. Both
+        // timers are cancelled on mouseup or mouseout.
+        if (event.getSource() instanceof VEventButton) {
+            final VEventButton sender = (VEventButton) event.getSource();
+            processClickEvent(sender);
+            mouseTimer = new Timer() {
+                @Override
+                public void run() {
+                    mouseTimer = new Timer() {
+                        @Override
+                        public void run() {
+                            processClickEvent(sender);
+                        }
+                    };
+                    mouseTimer.scheduleRepeating(150);
+                }
+            };
+            mouseTimer.schedule(500);
+        }
+
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.MouseUpHandler#onMouseUp(com.google.gwt
+     * .event.dom.client.MouseUpEvent)
+     */
+    @Override
+    public void onMouseUp(MouseUpEvent event) {
+        if (mouseTimer != null) {
+            mouseTimer.cancel();
+        }
+    }
+
+    /**
+     * Sets the data of the Panel.
+     * 
+     * @param currentDate
+     *            The date to set
+     */
+    public void setDate(Date currentDate) {
+
+        // Check that we are not re-rendering an already active date
+        if (currentDate == value && currentDate != null) {
+            return;
+        }
+
+        Date oldDisplayedMonth = displayedMonth;
+        value = currentDate;
+
+        if (value == null) {
+            focusedDate = displayedMonth = null;
+        } else {
+            focusedDate = new Date(value.getYear(), value.getMonth(),
+                    value.getDate());
+            displayedMonth = new Date(value.getYear(), value.getMonth(), 1);
+        }
+
+        // Re-render calendar if the displayed month is changed,
+        // or if a time selector is needed but does not exist.
+        if ((isTimeSelectorNeeded() && time == null)
+                || oldDisplayedMonth == null || value == null
+                || oldDisplayedMonth.getYear() != value.getYear()
+                || oldDisplayedMonth.getMonth() != value.getMonth()) {
+            renderCalendar();
+        } else {
+            focusDay(focusedDate);
+            selectFocused();
+            if (isTimeSelectorNeeded()) {
+                time.updateTimes();
+            }
+        }
+
+        if (!hasFocus) {
+            focusDay(null);
+        }
+    }
+
+    /**
+     * TimeSelector is a widget consisting of list boxes that modifie the Date
+     * object that is given for.
+     * 
+     */
+    public class VTime extends FlowPanel implements ChangeHandler {
+
+        private ListBox hours;
+
+        private ListBox mins;
+
+        private ListBox sec;
+
+        private ListBox ampm;
+
+        /**
+         * Constructor
+         */
+        public VTime() {
+            super();
+            setStyleName(VDateField.CLASSNAME + "-time");
+            buildTime();
+        }
+
+        private ListBox createListBox() {
+            ListBox lb = new ListBox();
+            lb.setStyleName(VNativeSelect.CLASSNAME);
+            lb.addChangeHandler(this);
+            lb.addBlurHandler(VCalendarPanel.this);
+            lb.addFocusHandler(VCalendarPanel.this);
+            return lb;
+        }
+
+        /**
+         * Constructs the ListBoxes and updates their value
+         * 
+         * @param redraw
+         *            Should new instances of the listboxes be created
+         */
+        private void buildTime() {
+            clear();
+
+            hours = createListBox();
+            if (getDateTimeService().isTwelveHourClock()) {
+                hours.addItem("12");
+                for (int i = 1; i < 12; i++) {
+                    hours.addItem((i < 10) ? "0" + i : "" + i);
+                }
+            } else {
+                for (int i = 0; i < 24; i++) {
+                    hours.addItem((i < 10) ? "0" + i : "" + i);
+                }
+            }
+
+            hours.addChangeHandler(this);
+            if (getDateTimeService().isTwelveHourClock()) {
+                ampm = createListBox();
+                final String[] ampmText = getDateTimeService().getAmPmStrings();
+                ampm.addItem(ampmText[0]);
+                ampm.addItem(ampmText[1]);
+                ampm.addChangeHandler(this);
+            }
+
+            if (getResolution().getCalendarField() >= Resolution.MINUTE
+                    .getCalendarField()) {
+                mins = createListBox();
+                for (int i = 0; i < 60; i++) {
+                    mins.addItem((i < 10) ? "0" + i : "" + i);
+                }
+                mins.addChangeHandler(this);
+            }
+            if (getResolution().getCalendarField() >= Resolution.SECOND
+                    .getCalendarField()) {
+                sec = createListBox();
+                for (int i = 0; i < 60; i++) {
+                    sec.addItem((i < 10) ? "0" + i : "" + i);
+                }
+                sec.addChangeHandler(this);
+            }
+
+            final String delimiter = getDateTimeService().getClockDelimeter();
+            if (isReadonly()) {
+                int h = 0;
+                if (value != null) {
+                    h = value.getHours();
+                }
+                if (getDateTimeService().isTwelveHourClock()) {
+                    h -= h < 12 ? 0 : 12;
+                }
+                add(new VLabel(h < 10 ? "0" + h : "" + h));
+            } else {
+                add(hours);
+            }
+
+            if (getResolution().getCalendarField() >= Resolution.MINUTE
+                    .getCalendarField()) {
+                add(new VLabel(delimiter));
+                if (isReadonly()) {
+                    final int m = mins.getSelectedIndex();
+                    add(new VLabel(m < 10 ? "0" + m : "" + m));
+                } else {
+                    add(mins);
+                }
+            }
+            if (getResolution().getCalendarField() >= Resolution.SECOND
+                    .getCalendarField()) {
+                add(new VLabel(delimiter));
+                if (isReadonly()) {
+                    final int s = sec.getSelectedIndex();
+                    add(new VLabel(s < 10 ? "0" + s : "" + s));
+                } else {
+                    add(sec);
+                }
+            }
+            if (getResolution() == Resolution.HOUR) {
+                add(new VLabel(delimiter + "00")); // o'clock
+            }
+            if (getDateTimeService().isTwelveHourClock()) {
+                add(new VLabel("&nbsp;"));
+                if (isReadonly()) {
+                    int i = 0;
+                    if (value != null) {
+                        i = (value.getHours() < 12) ? 0 : 1;
+                    }
+                    add(new VLabel(ampm.getItemText(i)));
+                } else {
+                    add(ampm);
+                }
+            }
+
+            if (isReadonly()) {
+                return;
+            }
+
+            // Update times
+            updateTimes();
+
+            ListBox lastDropDown = getLastDropDown();
+            lastDropDown.addKeyDownHandler(new KeyDownHandler() {
+                @Override
+                public void onKeyDown(KeyDownEvent event) {
+                    boolean shiftKey = event.getNativeEvent().getShiftKey();
+                    if (shiftKey) {
+                        return;
+                    } else {
+                        int nativeKeyCode = event.getNativeKeyCode();
+                        if (nativeKeyCode == KeyCodes.KEY_TAB) {
+                            onTabOut(event);
+                        }
+                    }
+                }
+            });
+
+        }
+
+        private ListBox getLastDropDown() {
+            int i = getWidgetCount() - 1;
+            while (i >= 0) {
+                Widget widget = getWidget(i);
+                if (widget instanceof ListBox) {
+                    return (ListBox) widget;
+                }
+                i--;
+            }
+            return null;
+        }
+
+        /**
+         * Updates the valus to correspond to the values in value
+         */
+        public void updateTimes() {
+            boolean selected = true;
+            if (value == null) {
+                value = new Date();
+                selected = false;
+            }
+            if (getDateTimeService().isTwelveHourClock()) {
+                int h = value.getHours();
+                ampm.setSelectedIndex(h < 12 ? 0 : 1);
+                h -= ampm.getSelectedIndex() * 12;
+                hours.setSelectedIndex(h);
+            } else {
+                hours.setSelectedIndex(value.getHours());
+            }
+            if (getResolution().getCalendarField() >= Resolution.MINUTE
+                    .getCalendarField()) {
+                mins.setSelectedIndex(value.getMinutes());
+            }
+            if (getResolution().getCalendarField() >= Resolution.SECOND
+                    .getCalendarField()) {
+                sec.setSelectedIndex(value.getSeconds());
+            }
+            if (getDateTimeService().isTwelveHourClock()) {
+                ampm.setSelectedIndex(value.getHours() < 12 ? 0 : 1);
+            }
+
+            hours.setEnabled(isEnabled());
+            if (mins != null) {
+                mins.setEnabled(isEnabled());
+            }
+            if (sec != null) {
+                sec.setEnabled(isEnabled());
+            }
+            if (ampm != null) {
+                ampm.setEnabled(isEnabled());
+            }
+
+        }
+
+        private int getMilliseconds() {
+            return DateTimeService.getMilliseconds(value);
+        }
+
+        private DateTimeService getDateTimeService() {
+            if (dateTimeService == null) {
+                dateTimeService = new DateTimeService();
+            }
+            return dateTimeService;
+        }
+
+        /*
+         * (non-Javadoc) VT
+         * 
+         * @see
+         * com.google.gwt.event.dom.client.ChangeHandler#onChange(com.google.gwt
+         * .event.dom.client.ChangeEvent)
+         */
+        @Override
+        public void onChange(ChangeEvent event) {
+            /*
+             * Value from dropdowns gets always set for the value. Like year and
+             * month when resolution is month or year.
+             */
+            if (event.getSource() == hours) {
+                int h = hours.getSelectedIndex();
+                if (getDateTimeService().isTwelveHourClock()) {
+                    h = h + ampm.getSelectedIndex() * 12;
+                }
+                value.setHours(h);
+                if (timeChangeListener != null) {
+                    timeChangeListener.changed(h, value.getMinutes(),
+                            value.getSeconds(),
+                            DateTimeService.getMilliseconds(value));
+                }
+                event.preventDefault();
+                event.stopPropagation();
+            } else if (event.getSource() == mins) {
+                final int m = mins.getSelectedIndex();
+                value.setMinutes(m);
+                if (timeChangeListener != null) {
+                    timeChangeListener.changed(value.getHours(), m,
+                            value.getSeconds(),
+                            DateTimeService.getMilliseconds(value));
+                }
+                event.preventDefault();
+                event.stopPropagation();
+            } else if (event.getSource() == sec) {
+                final int s = sec.getSelectedIndex();
+                value.setSeconds(s);
+                if (timeChangeListener != null) {
+                    timeChangeListener.changed(value.getHours(),
+                            value.getMinutes(), s,
+                            DateTimeService.getMilliseconds(value));
+                }
+                event.preventDefault();
+                event.stopPropagation();
+            } else if (event.getSource() == ampm) {
+                final int h = hours.getSelectedIndex()
+                        + (ampm.getSelectedIndex() * 12);
+                value.setHours(h);
+                if (timeChangeListener != null) {
+                    timeChangeListener.changed(h, value.getMinutes(),
+                            value.getSeconds(),
+                            DateTimeService.getMilliseconds(value));
+                }
+                event.preventDefault();
+                event.stopPropagation();
+            }
+        }
+
+    }
+
+    /**
+     * A widget representing a single day in the calendar panel.
+     */
+    private class Day extends InlineHTML {
+        private final Date date;
+
+        Day(Date date) {
+            super("" + date.getDate());
+            this.date = date;
+            addClickHandler(dayClickHandler);
+        }
+
+        public Date getDate() {
+            return date;
+        }
+    }
+
+    public Date getDate() {
+        return value;
+    }
+
+    /**
+     * If true should be returned if the panel will not be used after this
+     * event.
+     * 
+     * @param event
+     * @return
+     */
+    protected boolean onTabOut(DomEvent<?> event) {
+        if (focusOutListener != null) {
+            return focusOutListener.onFocusOut(event);
+        }
+        return false;
+    }
+
+    /**
+     * A focus out listener is triggered when the panel loosed focus. This can
+     * happen either after a user clicks outside the panel or tabs out.
+     * 
+     * @param listener
+     *            The listener to trigger
+     */
+    public void setFocusOutListener(FocusOutListener listener) {
+        focusOutListener = listener;
+    }
+
+    /**
+     * The submit listener is called when the user selects a value from the
+     * calender either by clicking the day or selects it by keyboard.
+     * 
+     * @param submitListener
+     *            The listener to trigger
+     */
+    public void setSubmitListener(SubmitListener submitListener) {
+        this.submitListener = submitListener;
+    }
+
+    /**
+     * The given FocusChangeListener is notified when the focused date changes
+     * by user either clicking on a new date or by using the keyboard.
+     * 
+     * @param listener
+     *            The FocusChangeListener to be notified
+     */
+    public void setFocusChangeListener(FocusChangeListener listener) {
+        focusChangeListener = listener;
+    }
+
+    /**
+     * The time change listener is triggered when the user changes the time.
+     * 
+     * @param listener
+     */
+    public void setTimeChangeListener(TimeChangeListener listener) {
+        timeChangeListener = listener;
+    }
+
+    /**
+     * Returns the submit listener that listens to selection made from the panel
+     * 
+     * @return The listener or NULL if no listener has been set
+     */
+    public SubmitListener getSubmitListener() {
+        return submitListener;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event
+     * .dom.client.BlurEvent)
+     */
+    @Override
+    public void onBlur(final BlurEvent event) {
+        if (event.getSource() instanceof VCalendarPanel) {
+            hasFocus = false;
+            focusDay(null);
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
+     * .dom.client.FocusEvent)
+     */
+    @Override
+    public void onFocus(FocusEvent event) {
+        if (event.getSource() instanceof VCalendarPanel) {
+            hasFocus = true;
+
+            // Focuses the current day if the calendar shows the days
+            if (focusedDay != null) {
+                focusDay(focusedDate);
+            }
+        }
+    }
+
+    private static final String SUBPART_NEXT_MONTH = "nextmon";
+    private static final String SUBPART_PREV_MONTH = "prevmon";
+
+    private static final String SUBPART_NEXT_YEAR = "nexty";
+    private static final String SUBPART_PREV_YEAR = "prevy";
+    private static final String SUBPART_HOUR_SELECT = "h";
+    private static final String SUBPART_MINUTE_SELECT = "m";
+    private static final String SUBPART_SECS_SELECT = "s";
+    private static final String SUBPART_MSECS_SELECT = "ms";
+    private static final String SUBPART_AMPM_SELECT = "ampm";
+    private static final String SUBPART_DAY = "day";
+    private static final String SUBPART_MONTH_YEAR_HEADER = "header";
+
+    @Override
+    public String getSubPartName(Element subElement) {
+        if (contains(nextMonth, subElement)) {
+            return SUBPART_NEXT_MONTH;
+        } else if (contains(prevMonth, subElement)) {
+            return SUBPART_PREV_MONTH;
+        } else if (contains(nextYear, subElement)) {
+            return SUBPART_NEXT_YEAR;
+        } else if (contains(prevYear, subElement)) {
+            return SUBPART_PREV_YEAR;
+        } else if (contains(days, subElement)) {
+            // Day, find out which dayOfMonth and use that as the identifier
+            Day day = Util.findWidget(subElement, Day.class);
+            if (day != null) {
+                Date date = day.getDate();
+                int id = date.getDate();
+                // Zero or negative ids map to days of the preceding month,
+                // past-the-end-of-month ids to days of the following month
+                if (date.getMonth() < displayedMonth.getMonth()) {
+                    id -= DateTimeService.getNumberOfDaysInMonth(date);
+                } else if (date.getMonth() > displayedMonth.getMonth()) {
+                    id += DateTimeService
+                            .getNumberOfDaysInMonth(displayedMonth);
+                }
+                return SUBPART_DAY + id;
+            }
+        } else if (time != null) {
+            if (contains(time.hours, subElement)) {
+                return SUBPART_HOUR_SELECT;
+            } else if (contains(time.mins, subElement)) {
+                return SUBPART_MINUTE_SELECT;
+            } else if (contains(time.sec, subElement)) {
+                return SUBPART_SECS_SELECT;
+            } else if (contains(time.ampm, subElement)) {
+                return SUBPART_AMPM_SELECT;
+
+            }
+        } else if (getCellFormatter().getElement(0, 2).isOrHasChild(subElement)) {
+            return SUBPART_MONTH_YEAR_HEADER;
+        }
+
+        return null;
+    }
+
+    /**
+     * Checks if subElement is inside the widget DOM hierarchy.
+     * 
+     * @param w
+     * @param subElement
+     * @return true if {@code w} is a parent of subElement, false otherwise.
+     */
+    private boolean contains(Widget w, Element subElement) {
+        if (w == null || w.getElement() == null) {
+            return false;
+        }
+
+        return w.getElement().isOrHasChild(subElement);
+    }
+
+    @Override
+    public Element getSubPartElement(String subPart) {
+        if (SUBPART_NEXT_MONTH.equals(subPart)) {
+            return nextMonth.getElement();
+        }
+        if (SUBPART_PREV_MONTH.equals(subPart)) {
+            return prevMonth.getElement();
+        }
+        if (SUBPART_NEXT_YEAR.equals(subPart)) {
+            return nextYear.getElement();
+        }
+        if (SUBPART_PREV_YEAR.equals(subPart)) {
+            return prevYear.getElement();
+        }
+        if (SUBPART_HOUR_SELECT.equals(subPart)) {
+            return time.hours.getElement();
+        }
+        if (SUBPART_MINUTE_SELECT.equals(subPart)) {
+            return time.mins.getElement();
+        }
+        if (SUBPART_SECS_SELECT.equals(subPart)) {
+            return time.sec.getElement();
+        }
+        if (SUBPART_AMPM_SELECT.equals(subPart)) {
+            return time.ampm.getElement();
+        }
+        if (subPart.startsWith(SUBPART_DAY)) {
+            // Zero or negative ids map to days in the preceding month,
+            // past-the-end-of-month ids to days in the following month
+            int dayOfMonth = Integer.parseInt(subPart.substring(SUBPART_DAY
+                    .length()));
+            Date date = new Date(displayedMonth.getYear(),
+                    displayedMonth.getMonth(), dayOfMonth);
+            Iterator<Widget> iter = days.iterator();
+            while (iter.hasNext()) {
+                Widget w = iter.next();
+                if (w instanceof Day) {
+                    Day day = (Day) w;
+                    if (day.getDate().equals(date)) {
+                        return day.getElement();
+                    }
+                }
+            }
+        }
+
+        if (SUBPART_MONTH_YEAR_HEADER.equals(subPart)) {
+            return (Element) getCellFormatter().getElement(0, 2).getChild(0);
+        }
+        return null;
+    }
+
+    @Override
+    protected void onDetach() {
+        super.onDetach();
+        if (mouseTimer != null) {
+            mouseTimer.cancel();
+        }
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VCheckBox.java b/client/src/com/vaadin/client/ui/VCheckBox.java
new file mode 100644 (file)
index 0000000..4688ee5
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.Util;
+import com.vaadin.client.VTooltip;
+
+public class VCheckBox extends com.google.gwt.user.client.ui.CheckBox implements
+        Field {
+
+    public static final String CLASSNAME = "v-checkbox";
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public String id;
+
+    /** 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 ApplicationConnection client;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public Element errorIndicatorElement;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public Icon icon;
+
+    public VCheckBox() {
+        setStyleName(CLASSNAME);
+
+        Element el = DOM.getFirstChild(getElement());
+        while (el != null) {
+            DOM.sinkEvents(el,
+                    (DOM.getEventsSunk(el) | VTooltip.TOOLTIP_EVENTS));
+            el = DOM.getNextSibling(el);
+        }
+    }
+
+    @Override
+    public void onBrowserEvent(Event event) {
+        if (icon != null && (event.getTypeInt() == Event.ONCLICK)
+                && (DOM.eventGetTarget(event) == icon.getElement())) {
+            // Click on icon should do nothing if widget is disabled
+            if (isEnabled()) {
+                setValue(!getValue());
+            }
+        }
+        super.onBrowserEvent(event);
+        if (event.getTypeInt() == Event.ONLOAD) {
+            Util.notifyParentOfSizeChange(this, true);
+        }
+    }
+
+}
diff --git a/client/src/com/vaadin/client/ui/VCssLayout.java b/client/src/com/vaadin/client/ui/VCssLayout.java
new file mode 100644 (file)
index 0000000..a11b2ff
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.StyleConstants;
+
+/**
+ * VCCSlayout is a layout which supports configuring it's children with CSS
+ * selectors
+ */
+public class VCssLayout extends FlowPanel {
+
+    public static final String CLASSNAME = "v-csslayout";
+
+    /**
+     * Default constructor
+     */
+    public VCssLayout() {
+        super();
+        setStyleName(CLASSNAME);
+        addStyleName(StyleConstants.UI_LAYOUT);
+    }
+
+    /**
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public void addOrMove(Widget child, int index) {
+        if (child.getParent() == this) {
+            int currentIndex = getWidgetIndex(child);
+            if (index == currentIndex) {
+                return;
+            }
+        }
+        insert(child, index);
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VCustomComponent.java b/client/src/com/vaadin/client/ui/VCustomComponent.java
new file mode 100644 (file)
index 0000000..cb9793c
--- /dev/null
@@ -0,0 +1,30 @@
+/* 
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.user.client.ui.SimplePanel;
+
+public class VCustomComponent extends SimplePanel {
+
+    private static final String CLASSNAME = "v-customcomponent";
+
+    public VCustomComponent() {
+        super();
+        setStyleName(CLASSNAME);
+    }
+
+}
diff --git a/client/src/com/vaadin/client/ui/VCustomLayout.java b/client/src/com/vaadin/client/ui/VCustomLayout.java
new file mode 100644 (file)
index 0000000..aae8255
--- /dev/null
@@ -0,0 +1,445 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.HashMap;
+import java.util.Iterator;
+
+import com.google.gwt.dom.client.ImageElement;
+import com.google.gwt.dom.client.NodeList;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.BorderStyle;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.ComplexPanel;
+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.StyleConstants;
+import com.vaadin.client.Util;
+import com.vaadin.client.VCaption;
+import com.vaadin.client.VCaptionWrapper;
+
+/**
+ * Custom Layout implements complex layout defined with HTML template.
+ * 
+ * @author Vaadin Ltd
+ * 
+ */
+public class VCustomLayout extends ComplexPanel {
+
+    public static final String CLASSNAME = "v-customlayout";
+
+    /** Location-name to containing element in DOM map */
+    private final HashMap<String, Element> locationToElement = new HashMap<String, Element>();
+
+    /** Location-name to contained widget map */
+    final HashMap<String, Widget> locationToWidget = new HashMap<String, Widget>();
+
+    /** Widget to captionwrapper map */
+    private final HashMap<Widget, VCaptionWrapper> childWidgetToCaptionWrapper = new HashMap<Widget, VCaptionWrapper>();
+
+    /**
+     * Unexecuted scripts loaded from the template.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public String scripts = "";
+
+    /**
+     * Paintable ID of this paintable.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public String pid;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public ApplicationConnection client;
+
+    private boolean htmlInitialized = false;
+
+    private Element elementWithNativeResizeFunction;
+
+    private String height = "";
+
+    private String width = "";
+
+    public VCustomLayout() {
+        setElement(DOM.createDiv());
+        // Clear any unwanted styling
+        Style style = getElement().getStyle();
+        style.setBorderStyle(BorderStyle.NONE);
+        style.setMargin(0, Unit.PX);
+        style.setPadding(0, Unit.PX);
+
+        if (BrowserInfo.get().isIE()) {
+            style.setPosition(Position.RELATIVE);
+        }
+
+        setStyleName(CLASSNAME);
+    }
+
+    @Override
+    public void setStyleName(String style) {
+        super.setStyleName(style);
+        addStyleName(StyleConstants.UI_LAYOUT);
+    }
+
+    /**
+     * Sets widget to given location.
+     * 
+     * If location already contains a widget it will be removed.
+     * 
+     * @param widget
+     *            Widget to be set into location.
+     * @param location
+     *            location name where widget will be added
+     * 
+     * @throws IllegalArgumentException
+     *             if no such location is found in the layout.
+     */
+    public void setWidget(Widget widget, String location) {
+
+        if (widget == null) {
+            return;
+        }
+
+        // If no given location is found in the layout, and exception is throws
+        Element elem = locationToElement.get(location);
+        if (elem == null && hasTemplate()) {
+            throw new IllegalArgumentException("No location " + location
+                    + " found");
+        }
+
+        // Get previous widget
+        final Widget previous = locationToWidget.get(location);
+        // NOP if given widget already exists in this location
+        if (previous == widget) {
+            return;
+        }
+
+        if (previous != null) {
+            remove(previous);
+        }
+
+        // if template is missing add element in order
+        if (!hasTemplate()) {
+            elem = getElement();
+        }
+
+        // Add widget to location
+        super.add(widget, elem);
+        locationToWidget.put(location, widget);
+    }
+
+    /** Initialize HTML-layout. */
+    public void initializeHTML(String template, String themeUri) {
+
+        // Connect body of the template to DOM
+        template = extractBodyAndScriptsFromTemplate(template);
+
+        // TODO prefix img src:s here with a regeps, cannot work further with IE
+
+        String relImgPrefix = themeUri + "/layouts/";
+
+        // prefix all relative image elements to point to theme dir with a
+        // regexp search
+        template = template.replaceAll(
+                "<((?:img)|(?:IMG))\\s([^>]*)src=\"((?![a-z]+:)[^/][^\"]+)\"",
+                "<$1 $2src=\"" + relImgPrefix + "$3\"");
+        // also support src attributes without quotes
+        template = template
+                .replaceAll(
+                        "<((?:img)|(?:IMG))\\s([^>]*)src=[^\"]((?![a-z]+:)[^/][^ />]+)[ />]",
+                        "<$1 $2src=\"" + relImgPrefix + "$3\"");
+        // also prefix relative style="...url(...)..."
+        template = template
+                .replaceAll(
+                        "(<[^>]+style=\"[^\"]*url\\()((?![a-z]+:)[^/][^\"]+)(\\)[^>]*>)",
+                        "$1 " + relImgPrefix + "$2 $3");
+
+        getElement().setInnerHTML(template);
+
+        // Remap locations to elements
+        locationToElement.clear();
+        scanForLocations(getElement());
+
+        initImgElements();
+
+        elementWithNativeResizeFunction = DOM.getFirstChild(getElement());
+        if (elementWithNativeResizeFunction == null) {
+            elementWithNativeResizeFunction = getElement();
+        }
+        publishResizedFunction(elementWithNativeResizeFunction);
+
+        htmlInitialized = true;
+    }
+
+    private native boolean uriEndsWithSlash()
+    /*-{
+        var path =  $wnd.location.pathname;
+        if(path.charAt(path.length - 1) == "/")
+            return true;
+        return false;
+    }-*/;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean hasTemplate() {
+        return htmlInitialized;
+    }
+
+    /** Collect locations from template */
+    private void scanForLocations(Element elem) {
+
+        final String location = elem.getAttribute("location");
+        if (!"".equals(location)) {
+            locationToElement.put(location, elem);
+            elem.setInnerHTML("");
+
+        } else {
+            final int len = DOM.getChildCount(elem);
+            for (int i = 0; i < len; i++) {
+                scanForLocations(DOM.getChild(elem, i));
+            }
+        }
+    }
+
+    /**
+     * Evaluate given script in browser document.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public static native void eval(String script)
+    /*-{
+      try {
+        if (script != null)
+      eval("{ var document = $doc; var window = $wnd; "+ script + "}");
+      } catch (e) {
+      }
+    }-*/;
+
+    /**
+     * Img elements needs some special handling in custom layout. Img elements
+     * will get their onload events sunk. This way custom layout can notify
+     * parent about possible size change.
+     */
+    private void initImgElements() {
+        NodeList<com.google.gwt.dom.client.Element> nodeList = getElement()
+                .getElementsByTagName("IMG");
+        for (int i = 0; i < nodeList.getLength(); i++) {
+            com.google.gwt.dom.client.ImageElement img = (ImageElement) nodeList
+                    .getItem(i);
+            DOM.sinkEvents((Element) img.cast(), Event.ONLOAD);
+        }
+    }
+
+    /**
+     * Extract body part and script tags from raw html-template.
+     * 
+     * Saves contents of all script-tags to private property: scripts. Returns
+     * contents of the body part for the html without script-tags. Also replaces
+     * all _UID_ tags with an unique id-string.
+     * 
+     * @param html
+     *            Original HTML-template received from server
+     * @return html that is used to create the HTMLPanel.
+     */
+    private String extractBodyAndScriptsFromTemplate(String html) {
+
+        // Replace UID:s
+        html = html.replaceAll("_UID_", pid + "__");
+
+        // Exctract script-tags
+        scripts = "";
+        int endOfPrevScript = 0;
+        int nextPosToCheck = 0;
+        String lc = html.toLowerCase();
+        String res = "";
+        int scriptStart = lc.indexOf("<script", nextPosToCheck);
+        while (scriptStart > 0) {
+            res += html.substring(endOfPrevScript, scriptStart);
+            scriptStart = lc.indexOf(">", scriptStart);
+            final int j = lc.indexOf("</script>", scriptStart);
+            scripts += html.substring(scriptStart + 1, j) + ";";
+            nextPosToCheck = endOfPrevScript = j + "</script>".length();
+            scriptStart = lc.indexOf("<script", nextPosToCheck);
+        }
+        res += html.substring(endOfPrevScript);
+
+        // Extract body
+        html = res;
+        lc = html.toLowerCase();
+        int startOfBody = lc.indexOf("<body");
+        if (startOfBody < 0) {
+            res = html;
+        } else {
+            res = "";
+            startOfBody = lc.indexOf(">", startOfBody) + 1;
+            final int endOfBody = lc.indexOf("</body>", startOfBody);
+            if (endOfBody > startOfBody) {
+                res = html.substring(startOfBody, endOfBody);
+            } else {
+                res = html.substring(startOfBody);
+            }
+        }
+
+        return res;
+    }
+
+    /** Update caption for given widget */
+    public void updateCaption(ComponentConnector paintable) {
+        Widget widget = paintable.getWidget();
+        VCaptionWrapper wrapper = childWidgetToCaptionWrapper.get(widget);
+        if (VCaption.isNeeded(paintable.getState())) {
+            if (wrapper == null) {
+                // Add a wrapper between the layout and the child widget
+                final String loc = getLocation(widget);
+                super.remove(widget);
+                wrapper = new VCaptionWrapper(paintable, client);
+                super.add(wrapper, locationToElement.get(loc));
+                childWidgetToCaptionWrapper.put(widget, wrapper);
+            }
+            wrapper.updateCaption();
+        } else {
+            if (wrapper != null) {
+                // Remove the wrapper and add the widget directly to the layout
+                final String loc = getLocation(widget);
+                super.remove(wrapper);
+                super.add(widget, locationToElement.get(loc));
+                childWidgetToCaptionWrapper.remove(widget);
+            }
+        }
+    }
+
+    /** Get the location of an widget */
+    public String getLocation(Widget w) {
+        for (final Iterator<String> i = locationToWidget.keySet().iterator(); i
+                .hasNext();) {
+            final String location = i.next();
+            if (locationToWidget.get(location) == w) {
+                return location;
+            }
+        }
+        return null;
+    }
+
+    /** Removes given widget from the layout */
+    @Override
+    public boolean remove(Widget w) {
+        final String location = getLocation(w);
+        if (location != null) {
+            locationToWidget.remove(location);
+        }
+        final VCaptionWrapper cw = childWidgetToCaptionWrapper.get(w);
+        if (cw != null) {
+            childWidgetToCaptionWrapper.remove(w);
+            return super.remove(cw);
+        } else if (w != null) {
+            return super.remove(w);
+        }
+        return false;
+    }
+
+    /** Adding widget without specifying location is not supported */
+    @Override
+    public void add(Widget w) {
+        throw new UnsupportedOperationException();
+    }
+
+    /** Clear all widgets from the layout */
+    @Override
+    public void clear() {
+        super.clear();
+        locationToWidget.clear();
+        childWidgetToCaptionWrapper.clear();
+    }
+
+    /**
+     * This method is published to JS side with the same name into first DOM
+     * node of custom layout. This way if one implements some resizeable
+     * containers in custom layout he/she can notify children after resize.
+     */
+    public void notifyChildrenOfSizeChange() {
+        client.runDescendentsLayout(this);
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        if (elementWithNativeResizeFunction != null) {
+            detachResizedFunction(elementWithNativeResizeFunction);
+        }
+    }
+
+    private native void detachResizedFunction(Element element)
+    /*-{
+       element.notifyChildrenOfSizeChange = null;
+    }-*/;
+
+    private native void publishResizedFunction(Element element)
+    /*-{
+       var self = this;
+       element.notifyChildrenOfSizeChange = $entry(function() {
+               self.@com.vaadin.client.ui.customlayout.VCustomLayout::notifyChildrenOfSizeChange()();
+       });
+    }-*/;
+
+    /**
+     * In custom layout one may want to run layout functions made with
+     * JavaScript. This function tests if one exists (with name "iLayoutJS" in
+     * layouts first DOM node) and runs et. Return value is used to determine if
+     * children needs to be notified of size changes.
+     * <p>
+     * Note! When implementing a JS layout function you most likely want to call
+     * notifyChildrenOfSizeChange() function on your custom layouts main
+     * element. That method is used to control whether child components layout
+     * functions are to be run.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     * 
+     * @param el
+     * @return true if layout function exists and was run successfully, else
+     *         false.
+     */
+    public native boolean iLayoutJS(Element el)
+    /*-{
+       if(el && el.iLayoutJS) {
+               try {
+                       el.iLayoutJS();
+                       return true;
+               } catch (e) {
+                       return false;
+               }
+       } else {
+               return false;
+       }
+    }-*/;
+
+    @Override
+    public void onBrowserEvent(Event event) {
+        super.onBrowserEvent(event);
+        if (event.getTypeInt() == Event.ONLOAD) {
+            Util.notifyParentOfSizeChange(this, true);
+            event.cancelBubble(true);
+        }
+    }
+
+}
diff --git a/client/src/com/vaadin/client/ui/VDateField.java b/client/src/com/vaadin/client/ui/VDateField.java
new file mode 100644 (file)
index 0000000..5c16f10
--- /dev/null
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.Date;
+
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.DateTimeService;
+import com.vaadin.shared.ui.datefield.Resolution;
+
+public class VDateField extends FlowPanel implements Field {
+
+    public static final String CLASSNAME = "v-datefield";
+
+    /** 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 ApplicationConnection client;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean immediate;
+
+    @Deprecated
+    public static final Resolution RESOLUTION_YEAR = Resolution.YEAR;
+    @Deprecated
+    public static final Resolution RESOLUTION_MONTH = Resolution.MONTH;
+    @Deprecated
+    public static final Resolution RESOLUTION_DAY = Resolution.DAY;
+    @Deprecated
+    public static final Resolution RESOLUTION_HOUR = Resolution.HOUR;
+    @Deprecated
+    public static final Resolution RESOLUTION_MIN = Resolution.MINUTE;
+    @Deprecated
+    public static final Resolution RESOLUTION_SEC = Resolution.SECOND;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public static String resolutionToString(Resolution res) {
+        if (res.getCalendarField() > Resolution.DAY.getCalendarField()) {
+            return "full";
+        }
+        if (res == Resolution.DAY) {
+            return "day";
+        }
+        if (res == Resolution.MONTH) {
+            return "month";
+        }
+        return "year";
+    }
+
+    protected Resolution currentResolution = Resolution.YEAR;
+
+    protected String currentLocale;
+
+    protected boolean readonly;
+
+    protected boolean enabled;
+
+    /**
+     * The date that is selected in the date field. Null if an invalid date is
+     * specified.
+     */
+    private Date date = null;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public DateTimeService dts;
+
+    protected boolean showISOWeekNumbers = false;
+
+    public VDateField() {
+        setStyleName(CLASSNAME);
+        dts = new DateTimeService();
+    }
+
+    /**
+     * We need this redundant native function because Java's Date object doesn't
+     * have a setMilliseconds method.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public static native double getTime(int y, int m, int d, int h, int mi,
+            int s, int ms)
+    /*-{
+       try {
+               var date = new Date(2000,1,1,1); // don't use current date here
+               if(y && y >= 0) date.setFullYear(y);
+               if(m && m >= 1) date.setMonth(m-1);
+               if(d && d >= 0) date.setDate(d);
+               if(h >= 0) date.setHours(h);
+               if(mi >= 0) date.setMinutes(mi);
+               if(s >= 0) date.setSeconds(s);
+               if(ms >= 0) date.setMilliseconds(ms);
+               return date.getTime();
+       } catch (e) {
+               // TODO print some error message on the console
+               //console.log(e);
+               return (new Date()).getTime();
+       }
+    }-*/;
+
+    public int getMilliseconds() {
+        return DateTimeService.getMilliseconds(date);
+    }
+
+    public void setMilliseconds(int ms) {
+        DateTimeService.setMilliseconds(date, ms);
+    }
+
+    public Resolution getCurrentResolution() {
+        return currentResolution;
+    }
+
+    public void setCurrentResolution(Resolution currentResolution) {
+        this.currentResolution = currentResolution;
+    }
+
+    public String getCurrentLocale() {
+        return currentLocale;
+    }
+
+    public void setCurrentLocale(String currentLocale) {
+        this.currentLocale = currentLocale;
+    }
+
+    public Date getCurrentDate() {
+        return date;
+    }
+
+    public void setCurrentDate(Date date) {
+        this.date = date;
+    }
+
+    public boolean isImmediate() {
+        return immediate;
+    }
+
+    public void setImmediate(boolean immediate) {
+        this.immediate = immediate;
+    }
+
+    public boolean isReadonly() {
+        return readonly;
+    }
+
+    public void setReadonly(boolean readonly) {
+        this.readonly = readonly;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public DateTimeService getDateTimeService() {
+        return dts;
+    }
+
+    public String getId() {
+        return paintableId;
+    }
+
+    public ApplicationConnection getClient() {
+        return client;
+    }
+
+    /**
+     * Returns whether ISO 8601 week numbers should be shown in the date
+     * selector or not. ISO 8601 defines that a week always starts with a Monday
+     * so the week numbers are only shown if this is the case.
+     * 
+     * @return true if week number should be shown, false otherwise
+     */
+    public boolean isShowISOWeekNumbers() {
+        return showISOWeekNumbers;
+    }
+
+    public void setShowISOWeekNumbers(boolean showISOWeekNumbers) {
+        this.showISOWeekNumbers = showISOWeekNumbers;
+    }
+
+    /**
+     * Returns a copy of the current date. Modifying the returned date will not
+     * modify the value of this VDateField. Use {@link #setDate(Date)} to change
+     * the current date.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     * 
+     * @return A copy of the current date
+     */
+    public Date getDate() {
+        Date current = getCurrentDate();
+        if (current == null) {
+            return null;
+        } else {
+            return (Date) getCurrentDate().clone();
+        }
+    }
+
+    /**
+     * Sets the current date for this VDateField.
+     * 
+     * @param date
+     *            The new date to use
+     */
+    protected void setDate(Date date) {
+        this.date = date;
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VDateFieldCalendar.java b/client/src/com/vaadin/client/ui/VDateFieldCalendar.java
new file mode 100644 (file)
index 0000000..b7621f0
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.Date;
+
+import com.google.gwt.event.dom.client.DomEvent;
+import com.vaadin.client.DateTimeService;
+import com.vaadin.client.ui.VCalendarPanel.FocusOutListener;
+import com.vaadin.client.ui.VCalendarPanel.SubmitListener;
+import com.vaadin.shared.ui.datefield.Resolution;
+
+/**
+ * A client side implementation for InlineDateField
+ */
+public class VDateFieldCalendar extends VDateField {
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public final VCalendarPanel calendarPanel;
+
+    public VDateFieldCalendar() {
+        super();
+        calendarPanel = new VCalendarPanel();
+        calendarPanel.setParentField(this);
+        add(calendarPanel);
+        calendarPanel.setSubmitListener(new SubmitListener() {
+            @Override
+            public void onSubmit() {
+                updateValueFromPanel();
+            }
+
+            @Override
+            public void onCancel() {
+                // TODO Auto-generated method stub
+
+            }
+        });
+        calendarPanel.setFocusOutListener(new FocusOutListener() {
+            @Override
+            public boolean onFocusOut(DomEvent<?> event) {
+                updateValueFromPanel();
+                return false;
+            }
+        });
+    }
+
+    /**
+     * TODO refactor: almost same method as in VPopupCalendar.updateValue
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+
+    @SuppressWarnings("deprecation")
+    public void updateValueFromPanel() {
+
+        // If field is invisible at the beginning, client can still be null when
+        // this function is called.
+        if (getClient() == null) {
+            return;
+        }
+
+        Date date2 = calendarPanel.getDate();
+        Date currentDate = getCurrentDate();
+        if (currentDate == null || date2.getTime() != currentDate.getTime()) {
+            setCurrentDate((Date) date2.clone());
+            getClient().updateVariable(getId(), "year", date2.getYear() + 1900,
+                    false);
+            if (getCurrentResolution().getCalendarField() > Resolution.YEAR
+                    .getCalendarField()) {
+                getClient().updateVariable(getId(), "month",
+                        date2.getMonth() + 1, false);
+                if (getCurrentResolution().getCalendarField() > Resolution.MONTH
+                        .getCalendarField()) {
+                    getClient().updateVariable(getId(), "day", date2.getDate(),
+                            false);
+                    if (getCurrentResolution().getCalendarField() > Resolution.DAY
+                            .getCalendarField()) {
+                        getClient().updateVariable(getId(), "hour",
+                                date2.getHours(), false);
+                        if (getCurrentResolution().getCalendarField() > Resolution.HOUR
+                                .getCalendarField()) {
+                            getClient().updateVariable(getId(), "min",
+                                    date2.getMinutes(), false);
+                            if (getCurrentResolution().getCalendarField() > Resolution.MINUTE
+                                    .getCalendarField()) {
+                                getClient().updateVariable(getId(), "sec",
+                                        date2.getSeconds(), false);
+                                if (getCurrentResolution().getCalendarField() > Resolution.SECOND
+                                        .getCalendarField()) {
+                                    getClient().updateVariable(
+                                            getId(),
+                                            "msec",
+                                            DateTimeService
+                                                    .getMilliseconds(date2),
+                                            false);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            if (isImmediate()) {
+                getClient().sendPendingVariableChanges();
+            }
+        }
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VDragAndDropWrapper.java b/client/src/com/vaadin/client/ui/VDragAndDropWrapper.java
new file mode 100644 (file)
index 0000000..af728b3
--- /dev/null
@@ -0,0 +1,614 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui;
+
+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.NativeEvent;
+import com.google.gwt.event.dom.client.MouseDownEvent;
+import com.google.gwt.event.dom.client.MouseDownHandler;
+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.Element;
+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.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 {
+
+    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;
+
+    public VDragAndDropWrapper() {
+        super();
+
+        hookHtml5Events(getElement());
+        setStyleName(CLASSNAME);
+        addDomHandler(new MouseDownHandler() {
+
+            @Override
+            public void onMouseDown(MouseDownEvent event) {
+                if (startDrag(event.getNativeEvent())) {
+                    event.preventDefault(); // prevent text selection
+                }
+            }
+        }, MouseDownEvent.getType());
+
+        addDomHandler(new TouchStartHandler() {
+
+            @Override
+            public void onTouchStart(TouchStartEvent event) {
+                if (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) {
+            VTransferable transferable = new VTransferable();
+            transferable.setDragSource(ConnectorMap.get(client).getConnector(
+                    VDragAndDropWrapper.this));
+
+            ComponentConnector paintable = Util.findPaintable(client,
+                    (Element) event.getEventTarget().cast());
+            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 {
+                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;
+
+    /** 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;
+
+    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 Element getDragStartElement() {
+        return getElement();
+    }
+
+    private boolean uploading;
+
+    private ReadyStateChangeHandler readyStateChangeHandler = new ReadyStateChangeHandler() {
+
+        @Override
+        public void onReadyStateChange(XMLHttpRequest xhr) {
+            if (xhr.getReadyState() == XMLHttpRequest.DONE) {
+                // visit server for possible
+                // variable changes
+                client.sendPendingVariableChanges();
+                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(ConnectorMap.get(client)
+                        .getConnector(this));
+
+                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);
+
+        String s = event.getEffectAllowed();
+        if ("all".equals(s) || s.contains("opy")) {
+            event.setDropEffect("copy");
+        } else {
+            event.setDropEffect(s);
+        }
+
+        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);
+                    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');
+            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 static final String OVER_STYLE = "v-ddwrapper-over";
+
+    public class CustomDropHandler extends VAbstractDropHandler {
+
+        @Override
+        public void dragEnter(VDragEvent drag) {
+            updateDropDetails(drag);
+            currentlyValid = false;
+            super.dragEnter(drag);
+        }
+
+        @Override
+        public void dragLeave(VDragEvent drag) {
+            deEmphasis(true);
+            dragleavetimer = null;
+        }
+
+        @Override
+        public void dragOver(final VDragEvent drag) {
+            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) {
+            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) {
+            currentlyValid = true;
+            emphasis(drag);
+        }
+
+        @Override
+        public ComponentConnector getConnector() {
+            return ConnectorMap.get(client).getConnector(
+                    VDragAndDropWrapper.this);
+        }
+
+        @Override
+        public ApplicationConnection getApplicationConnection() {
+            return client;
+        }
+
+    }
+
+    protected native void hookHtml5DragStart(Element el)
+    /*-{
+        var me = this;
+        el.addEventListener("dragstart",  $entry(function(ev) {
+            return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragStart(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
+        }), false);
+    }-*/;
+
+    /**
+     * Prototype code, memory leak risk.
+     * 
+     * @param el
+     */
+    protected native void hookHtml5Events(Element el)
+    /*-{
+            var me = this;
+
+            el.addEventListener("dragenter",  $entry(function(ev) {
+                return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragEnter(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
+            }), false);
+
+            el.addEventListener("dragleave",  $entry(function(ev) {
+                return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragLeave(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
+            }), false);
+
+            el.addEventListener("dragover",  $entry(function(ev) {
+                return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragOver(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
+            }), false);
+
+            el.addEventListener("drop",  $entry(function(ev) {
+                return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragDrop(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
+            }), false);
+    }-*/;
+
+    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(
+                ConnectorMap.get(client).getConnector(getElement()));
+    }
+
+    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();
+    }
+
+}
diff --git a/client/src/com/vaadin/client/ui/VDragAndDropWrapperIE.java b/client/src/com/vaadin/client/ui/VDragAndDropWrapperIE.java
new file mode 100644 (file)
index 0000000..8dcd64a
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.dom.client.AnchorElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.user.client.Element;
+import com.vaadin.client.VConsole;
+
+public class VDragAndDropWrapperIE extends VDragAndDropWrapper {
+    private AnchorElement anchor = null;
+
+    @Override
+    protected Element getDragStartElement() {
+        VConsole.log("IE get drag start element...");
+        Element div = getElement();
+        if (dragStartMode == HTML5) {
+            if (anchor == null) {
+                anchor = Document.get().createAnchorElement();
+                anchor.setHref("#");
+                anchor.setClassName("drag-start");
+                div.appendChild(anchor);
+            }
+            VConsole.log("IE get drag start element...");
+            return (Element) anchor.cast();
+        } else {
+            if (anchor != null) {
+                div.removeChild(anchor);
+                anchor = null;
+            }
+            return div;
+        }
+    }
+
+    @Override
+    protected native void hookHtml5DragStart(Element el)
+    /*-{
+        var me = this;
+
+        el.attachEvent("ondragstart",  $entry(function(ev) {
+            return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragStart(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
+        }));
+    }-*/;
+
+    @Override
+    protected native void hookHtml5Events(Element el)
+    /*-{
+        var me = this;
+
+        el.attachEvent("ondragenter",  $entry(function(ev) {
+            return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragEnter(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
+        }));
+
+        el.attachEvent("ondragleave",  $entry(function(ev) {
+            return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragLeave(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
+        }));
+
+        el.attachEvent("ondragover",  $entry(function(ev) {
+            return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragOver(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
+        }));
+
+        el.attachEvent("ondrop",  $entry(function(ev) {
+            return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragDrop(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
+        }));
+    }-*/;
+
+}
diff --git a/client/src/com/vaadin/client/ui/VFilterSelect.java b/client/src/com/vaadin/client/ui/VFilterSelect.java
new file mode 100644 (file)
index 0000000..54498c4
--- /dev/null
@@ -0,0 +1,1818 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+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.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+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.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+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.Image;
+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.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ConnectorMap;
+import com.vaadin.client.Focusable;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.Util;
+import com.vaadin.client.VConsole;
+import com.vaadin.client.ui.menubar.MenuBar;
+import com.vaadin.client.ui.menubar.MenuItem;
+import com.vaadin.shared.ComponentState;
+import com.vaadin.shared.EventId;
+import com.vaadin.shared.ui.ComponentStateUtil;
+import com.vaadin.shared.ui.combobox.FilteringMode;
+
+/**
+ * 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 {
+
+    /**
+     * Represents a suggestion in the suggestion popup box
+     */
+    public class FilterSelectSuggestion implements Suggestion, Command {
+
+        private final String key;
+        private final String caption;
+        private String iconUri;
+
+        /**
+         * Constructor
+         * 
+         * @param uidl
+         *            The UIDL recieved from the server
+         */
+        public FilterSelectSuggestion(UIDL uidl) {
+            key = uidl.getStringAttribute("key");
+            caption = uidl.getStringAttribute("caption");
+            if (uidl.hasAttribute("icon")) {
+                iconUri = client.translateVaadinUri(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();
+            if (iconUri != null) {
+                sb.append("<img src=\"");
+                sb.append(Util.escapeAttribute(iconUri));
+                sb.append("\" alt=\"\" class=\"v-icon\" />");
+            }
+            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 = Util.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 int getOptionKey() {
+            return Integer.parseInt(key);
+        }
+
+        /**
+         * Get the URI of the icon. Used when constructing the displayed option.
+         * 
+         * @return
+         */
+        public String getIconUri() {
+            return iconUri;
+        }
+
+        /**
+         * Executes a selection of this item.
+         */
+
+        @Override
+        public void execute() {
+            onSuggestionSelected(this);
+        }
+    }
+
+    /**
+     * 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;
+
+        /**
+         * Default constructor
+         */
+        SuggestionPopup() {
+            super(true, false, true);
+            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);
+        }
+
+        /**
+         * 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) {
+
+            /*
+             * 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
+             */
+            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);
+                }
+            });
+        }
+
+        /**
+         * Should the next page button be visible to the user?
+         * 
+         * @param active
+         */
+        private void setNextButtonActive(boolean 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 (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() {
+            final MenuItem cur = menu.getSelectedItem();
+            final int index = 1 + menu.getItems().indexOf(cur);
+            if (menu.getItems().size() > index) {
+                final MenuItem newSelectedItem = menu.getItems().get(index);
+                menu.selectItem(newSelectedItem);
+                tb.setText(newSelectedItem.getText());
+                tb.setSelectionRange(lastFilter.length(), newSelectedItem
+                        .getText().length() - lastFilter.length());
+
+            } else if (hasNextPage()) {
+                selectPopupItemWhenResponseIsReceived = Select.FIRST;
+                filterOptions(currentPage + 1, lastFilter);
+            }
+        }
+
+        /**
+         * Selects the previous item in the filtered selections
+         */
+        public void selectPrevItem() {
+            final MenuItem cur = menu.getSelectedItem();
+            final int index = -1 + menu.getItems().indexOf(cur);
+            if (index > -1) {
+                final MenuItem newSelectedItem = menu.getItems().get(index);
+                menu.selectItem(newSelectedItem);
+                tb.setText(newSelectedItem.getText());
+                tb.setSelectionRange(lastFilter.length(), newSelectedItem
+                        .getText().length() - lastFilter.length());
+            } else if (index == -1) {
+                if (currentPage > 0) {
+                    selectPopupItemWhenResponseIsReceived = Select.LAST;
+                    filterOptions(currentPage - 1, lastFilter);
+                }
+            } else {
+                final MenuItem newSelectedItem = menu.getItems().get(
+                        menu.getItems().size() - 1);
+                menu.selectItem(newSelectedItem);
+                tb.setText(newSelectedItem.getText());
+                tb.setSelectionRange(lastFilter.length(), newSelectedItem
+                        .getText().length() - lastFilter.length());
+            }
+        }
+
+        /*
+         * 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() {
+                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() {
+                if (currentPage + pagesToScroll > 0) {
+                    pagesToScroll--;
+                    cancel();
+                    schedule(200);
+                }
+            }
+
+            public void scrollDown() {
+                if (totalMatches > (currentPage + pagesToScroll + 1)
+                        * pageLength) {
+                    pagesToScroll++;
+                    cancel();
+                    schedule(200);
+                }
+            }
+        }
+
+        /*
+         * (non-Javadoc)
+         * 
+         * @see
+         * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt
+         * .user.client.Event)
+         */
+
+        @Override
+        public void onBrowserEvent(Event event) {
+            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();
+                }
+            } else if (event.getTypeInt() == Event.ONMOUSEWHEEL) {
+                int velocity = event.getMouseWheelVelocityY();
+                if (velocity > 0) {
+                    lazyPageScroller.scrollDown();
+                } else {
+                    lazyPageScroller.scrollUp();
+                }
+            }
+
+            /*
+             * 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) {
+            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;
+        }
+
+        /*
+         * (non-Javadoc)
+         * 
+         * @see
+         * com.google.gwt.user.client.ui.PopupPanel$PositionCallback#setPosition
+         * (int, int)
+         */
+
+        @Override
+        public void setPosition(int offsetWidth, int offsetHeight) {
+
+            int top = -1;
+            int left = -1;
+
+            // reset menu size and retrieve its "natural" size
+            menu.setHeight("");
+            if (currentPage > 0) {
+                // fix height to avoid height change when getting to last page
+                menu.fixHeightTo(pageLength);
+            }
+            offsetHeight = getOffsetHeight();
+
+            final int desiredWidth = getMainWidth();
+            Element menuFirstChild = menu.getElement().getFirstChildElement()
+                    .cast();
+            int naturalMenuWidth = menuFirstChild.getOffsetWidth();
+
+            if (popupOuterPadding == -1) {
+                popupOuterPadding = Util.measureHorizontalPaddingAndBorder(
+                        getElement(), 2);
+            }
+
+            if (naturalMenuWidth < desiredWidth) {
+                menu.setWidth((desiredWidth - popupOuterPadding) + "px");
+                menuFirstChild.getStyle().setWidth(100, Unit.PCT);
+                naturalMenuWidth = desiredWidth;
+            }
+
+            if (BrowserInfo.get().isIE()) {
+                /*
+                 * IE requires us to specify the width for the container
+                 * element. Otherwise it will be 100% wide
+                 */
+                int rootWidth = naturalMenuWidth - popupOuterPadding;
+                getContainerElement().getStyle().setWidth(rootWidth, Unit.PX);
+            }
+
+            if (offsetHeight + getPopupTop() > Window.getClientHeight()
+                    + Window.getScrollTop()) {
+                // popup on top of input instead
+                top = getPopupTop() - offsetHeight
+                        - VFilterSelect.this.getOffsetHeight();
+                if (top < 0) {
+                    top = 0;
+                }
+            } else {
+                top = getPopupTop();
+                /*
+                 * Take popup top margin into account. getPopupTop() returns the
+                 * top value including the margin but the value we give must not
+                 * include the margin.
+                 */
+                int topMargin = (top - topPosition);
+                top -= topMargin;
+            }
+
+            // fetch real width (mac FF bugs here due GWT popups overflow:auto )
+            offsetWidth = menuFirstChild.getOffsetWidth();
+            if (offsetWidth + getPopupLeft() > Window.getClientWidth()
+                    + Window.getScrollLeft()) {
+                left = VFilterSelect.this.getAbsoluteLeft()
+                        + VFilterSelect.this.getOffsetWidth()
+                        + Window.getScrollLeft() - offsetWidth;
+                if (left < 0) {
+                    left = 0;
+                }
+            } else {
+                left = getPopupLeft();
+            }
+            setPopupPosition(left, top);
+        }
+
+        /**
+         * Was the popup just closed?
+         * 
+         * @return true if popup was just closed
+         */
+        public boolean isJustClosed() {
+            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 (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, ComponentState componentState) {
+            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 {
+
+        /**
+         * Tracks the item that is currently selected using the keyboard. This
+         * is need only because mouseover changes the selection and we do not
+         * want to use that selection when pressing enter to select the item.
+         */
+        private MenuItem keyboardSelectedItem;
+
+        private VLazyExecutor delayedImageLoadExecutioner = new VLazyExecutor(
+                100, new ScheduledCommand() {
+
+                    @Override
+                    public void execute() {
+                        if (suggestionPopup.isVisible()
+                                && suggestionPopup.isAttached()) {
+                            setWidth("");
+                            getElement().getFirstChildElement().getStyle()
+                                    .clearWidth();
+                            suggestionPopup
+                                    .setPopupPositionAndShow(suggestionPopup);
+                        }
+
+                    }
+                });
+
+        /**
+         * Default constructor
+         */
+        SuggestionMenu() {
+            super(true);
+            addDomHandler(this, LoadEvent.getType());
+        }
+
+        /**
+         * 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 pagelenth) {
+            if (currentSuggestions.size() > 0) {
+                final int pixels = pagelenth * (getOffsetHeight() - 2)
+                        / currentSuggestions.size();
+                setHeight((pixels + 2) + "px");
+            }
+        }
+
+        /**
+         * Sets the suggestions rendered in the menu
+         * 
+         * @param suggestions
+         *            The suggestions to be rendered in the menu
+         */
+        public void setSuggestions(
+                Collection<FilterSelectSuggestion> suggestions) {
+            // Reset keyboard selection when contents is updated to avoid
+            // reusing old, invalid data
+            setKeyboardSelectedItem(null);
+
+            clearItems();
+            final Iterator<FilterSelectSuggestion> it = suggestions.iterator();
+            while (it.hasNext()) {
+                final FilterSelectSuggestion s = it.next();
+                final MenuItem mi = new MenuItem(s.getDisplayString(), true, s);
+
+                Util.sinkOnloadForImages(mi.getElement());
+
+                this.addItem(mi);
+                if (s == currentSuggestion) {
+                    selectItem(mi);
+                }
+            }
+        }
+
+        /**
+         * Send the current selection to the server. Triggered when a selection
+         * is made or on a blur event.
+         */
+        public void 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);
+                suggestionPopup.hide();
+                return;
+            }
+
+            updateSelectionWhenReponseIsReceived = waitingForFilteringResponse;
+            if (!waitingForFilteringResponse) {
+                doPostFilterSelectedItemAction();
+            }
+        }
+
+        /**
+         * Triggered after a selection has been made
+         */
+        public void 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);
+                }
+            } 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();
+                    tb.setText(text);
+                    selectedOptionKey = currentSuggestion.key;
+                } else {
+                    // Null selected
+                    tb.setText("");
+                    selectedOptionKey = null;
+                }
+            }
+            suggestionPopup.hide();
+        }
+
+        private static final String SUBPART_PREFIX = "item";
+
+        @Override
+        public 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(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) {
+            // Handle icon onload events to ensure shadow is resized
+            // correctly
+            delayedImageLoadExecutioner.trigger();
+
+        }
+
+        public void selectFirstItem() {
+            MenuItem firstItem = getItems().get(0);
+            selectItem(firstItem);
+        }
+
+        private MenuItem getKeyboardSelectedItem() {
+            return keyboardSelectedItem;
+        }
+
+        public void setKeyboardSelectedItem(MenuItem firstItem) {
+            keyboardSelectedItem = firstItem;
+        }
+
+        public void selectLastItem() {
+            List<MenuItem> items = getItems();
+            MenuItem lastItem = items.get(items.size() - 1);
+            selectItem(lastItem);
+        }
+    }
+
+    @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 = new TextBox() {
+
+        // Overridden to avoid selecting text when text input is disabled
+        @Override
+        public void setSelectionRange(int pos, int length) {
+            if (textInputEnabled) {
+                super.setSelectionRange(pos, length);
+            } else {
+                super.setSelectionRange(getValue().length(), 0);
+            }
+        };
+    };
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public final SuggestionPopup suggestionPopup = new 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 final Image selectedItemIcon = new Image();
+
+    /** 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;
+
+    private boolean tabPressedWhenPopupOpen = 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() {
+        selectedItemIcon.setStyleName("v-icon");
+        selectedItemIcon.addLoadHandler(new LoadHandler() {
+
+            @Override
+            public void onLoad(LoadEvent event) {
+                if (BrowserInfo.get().isIE8()) {
+                    // IE8 needs some help to discover it should reposition the
+                    // text field
+                    forceReflow();
+                }
+                updateRootWidth();
+                updateSelectedIconPosition();
+            }
+        });
+
+        popupOpener.sinkEvents(Event.ONMOUSEDOWN);
+        panel.add(tb);
+        panel.add(popupOpener);
+        initWidget(panel);
+        tb.addKeyDownHandler(this);
+        tb.addKeyUpHandler(this);
+
+        tb.addFocusHandler(this);
+        tb.addBlurHandler(this);
+        tb.addClickHandler(this);
+
+        popupOpener.addClickHandler(this);
+
+        setStyleName(CLASSNAME);
+    }
+
+    @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 (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) {
+        if (filter.equals(lastFilter) && currentPage == page) {
+            if (!suggestionPopup.isAttached()) {
+                suggestionPopup.showSuggestions(currentSuggestions,
+                        currentPage, totalMatches);
+            }
+            return;
+        }
+        if (!filter.equals(lastFilter)) {
+            // we are on subsequent page and text has changed -> reset page
+            if ("".equals(filter)) {
+                // let server decide
+                page = -1;
+            } else {
+                page = 0;
+            }
+        }
+
+        waitingForFilteringResponse = true;
+        client.updateVariable(paintableId, "filter", filter, false);
+        client.updateVariable(paintableId, "page", page, immediate);
+        lastFilter = filter;
+        currentPage = page;
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void updateReadOnly() {
+        tb.setReadOnly(readonly || !textInputEnabled);
+    }
+
+    public void setTextInputEnabled(boolean textInputEnabled) {
+        // Always update styles as they might have been overwritten
+        if (textInputEnabled) {
+            removeStyleDependentName(STYLE_NO_INPUT);
+        } else {
+            addStyleDependentName(STYLE_NO_INPUT);
+        }
+
+        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) {
+        tb.setText(text);
+    }
+
+    /**
+     * 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() {
+        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) {
+        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) {
+        updateSelectionWhenReponseIsReceived = false;
+
+        currentSuggestion = suggestion;
+        String newKey;
+        if (suggestion.key.equals("")) {
+            // "nullselection"
+            newKey = "";
+        } else {
+            // normal selection
+            newKey = String.valueOf(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);
+            // 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.isAttached()) {
+                panel.remove(selectedItemIcon);
+                if (BrowserInfo.get().isIE8()) {
+                    // IE8 needs some help to discover it should reposition the
+                    // text field
+                    forceReflow();
+                }
+                updateRootWidth();
+            }
+        } else {
+            panel.insert(selectedItemIcon, 0);
+            selectedItemIcon.setUrl(iconUri);
+            updateRootWidth();
+            updateSelectedIconPosition();
+        }
+    }
+
+    private void forceReflow() {
+        Util.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 = Util.getRequiredHeight(selectedItemIcon);
+        int marginTop = (availableHeight - iconHeight) / 2;
+        DOM.setStyleAttribute(selectedItemIcon.getElement(), "marginTop",
+                marginTop + "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();
+
+            debug("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.
+                 */
+                debug("Ignoring " + keyCode
+                        + " because we are waiting for a filtering response");
+                DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
+                event.stopPropagation();
+                return;
+            }
+
+            if (suggestionPopup.isAttached()) {
+                debug("Keycode " + keyCode + " target is popup");
+                popupKeyDown(event);
+            } else {
+                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) {
+        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) {
+        // 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();
+            suggestionPopup.menu.setKeyboardSelectedItem(suggestionPopup.menu
+                    .getSelectedItem());
+            DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
+            event.stopPropagation();
+            break;
+        case KeyCodes.KEY_UP:
+            suggestionPopup.selectPrevItem();
+            suggestionPopup.menu.setKeyboardSelectedItem(suggestionPopup.menu
+                    .getSelectedItem());
+            DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
+            event.stopPropagation();
+            break;
+        case KeyCodes.KEY_PAGEDOWN:
+            if (hasNextPage()) {
+                filterOptions(currentPage + 1, lastFilter);
+            }
+            event.stopPropagation();
+            break;
+        case KeyCodes.KEY_PAGEUP:
+            if (currentPage > 0) {
+                filterOptions(currentPage - 1, lastFilter);
+            }
+            event.stopPropagation();
+            break;
+        case KeyCodes.KEY_TAB:
+            tabPressedWhenPopupOpen = true;
+            filterOptions(currentPage);
+            // onBlur() takes care of the rest
+            break;
+        case KeyCodes.KEY_ESCAPE:
+            reset();
+            event.stopPropagation();
+            break;
+        case KeyCodes.KEY_ENTER:
+            if (suggestionPopup.menu.getKeyboardSelectedItem() == null) {
+                /*
+                 * Nothing selected using up/down. Happens e.g. when entering a
+                 * text (causes popup to open) and then pressing enter.
+                 */
+                if (!allowNewItem) {
+                    /*
+                     * New items are not allowed: If there is only one
+                     * suggestion, select that. Otherwise do nothing.
+                     */
+                    if (currentSuggestions.size() == 1) {
+                        onSuggestionSelected(currentSuggestions.get(0));
+                    }
+                } else {
+                    // Handle addition of new items.
+                    suggestionPopup.menu.doSelectedItemAction();
+                }
+            } else {
+                /*
+                 * Get the suggestion that was navigated to using up/down.
+                 */
+                currentSuggestion = ((FilterSelectSuggestion) suggestionPopup.menu
+                        .getKeyboardSelectedItem().getCommand());
+                onSuggestionSelected(currentSuggestion);
+            }
+
+            event.stopPropagation();
+            break;
+        }
+
+    }
+
+    /**
+     * Triggered when a key was depressed
+     * 
+     * @param event
+     *            The KeyUpEvent of the key depressed
+     */
+
+    @Override
+    public void onKeyUp(KeyUpEvent event) {
+        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) {
+                    filterOptions(currentPage);
+                }
+                break;
+            }
+        }
+    }
+
+    /**
+     * Resets the Select to its initial state
+     */
+    private void reset() {
+        if (currentSuggestion != null) {
+            String text = currentSuggestion.getReplacementString();
+            setPromptingOff(text);
+            selectedOptionKey = currentSuggestion.key;
+        } else {
+            if (focused) {
+                setPromptingOff("");
+            } else {
+                setPromptingOn();
+            }
+            selectedOptionKey = null;
+        }
+        lastFilter = "";
+        suggestionPopup.hide();
+    }
+
+    /**
+     * Listener for popupopener
+     */
+
+    @Override
+    public void onClick(ClickEvent event) {
+        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();
+        }
+    }
+
+    /**
+     * 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) {
+
+        /*
+         * 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);
+        }
+    }
+
+    /**
+     * 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) {
+
+        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 = Util.getIEFocusedElement();
+            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) {
+            // much of the TAB handling takes place here
+            if (tabPressedWhenPopupOpen) {
+                tabPressedWhenPopupOpen = false;
+                suggestionPopup.menu.doSelectedItemAction();
+                suggestionPopup.hide();
+            } else if (!suggestionPopup.isAttached()
+                    || suggestionPopup.isJustClosed()) {
+                suggestionPopup.menu.doSelectedItemAction();
+            }
+            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);
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see com.vaadin.client.Focusable#focus()
+     */
+
+    @Override
+    public void 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 = Util.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);
+                int offset = w - Util.getRequiredWidth(this);
+                style.setProperty("padding", originalPadding);
+                style.setProperty("borderWidth", originalBorder);
+
+                setWidth(suggestionPopupMinWidth + offset + "px");
+            }
+
+            /*
+             * Lock the textbox width to its current value if it's not already
+             * locked
+             */
+            if (!tb.getElement().getStyle().getWidth().endsWith("px")) {
+                tb.setWidth((tb.getOffsetWidth() - selectedItemIcon
+                        .getOffsetWidth()) + "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;
+            }
+        }
+    }
+
+    @Override
+    protected void onDetach() {
+        super.onDetach();
+        suggestionPopup.hide();
+    }
+
+    @Override
+    public Element getSubPartElement(String subPart) {
+        if ("textbox".equals(subPart)) {
+            return tb.getElement();
+        } else if ("button".equals(subPart)) {
+            return popupOpener.getElement();
+        }
+        return null;
+    }
+
+    @Override
+    public String getSubPartName(Element subElement) {
+        if (tb.getElement().isOrHasChild(subElement)) {
+            return "textbox";
+        } else if (popupOpener.getElement().isOrHasChild(subElement)) {
+            return "button";
+        }
+        return null;
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VGridLayout.java b/client/src/com/vaadin/client/ui/VGridLayout.java
new file mode 100644 (file)
index 0000000..be8353a
--- /dev/null
@@ -0,0 +1,737 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ConnectorMap;
+import com.vaadin.client.LayoutManager;
+import com.vaadin.client.StyleConstants;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.Util;
+import com.vaadin.client.VCaption;
+import com.vaadin.client.ui.gridlayout.GridLayoutConnector;
+import com.vaadin.client.ui.layout.ComponentConnectorLayoutSlot;
+import com.vaadin.client.ui.layout.VLayoutSlot;
+import com.vaadin.shared.ui.AlignmentInfo;
+import com.vaadin.shared.ui.MarginInfo;
+
+public class VGridLayout extends ComplexPanel {
+
+    public static final String CLASSNAME = "v-gridlayout";
+
+    /** 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 HashMap<Widget, Cell> widgetToCell = new HashMap<Widget, Cell>();
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public int[] columnWidths;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public int[] rowHeights;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public int[] colExpandRatioArray;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public int[] rowExpandRatioArray;
+
+    int[] minColumnWidths;
+
+    private int[] minRowHeights;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public DivElement spacingMeasureElement;
+
+    public VGridLayout() {
+        super();
+        setElement(Document.get().createDivElement());
+
+        spacingMeasureElement = Document.get().createDivElement();
+        Style spacingStyle = spacingMeasureElement.getStyle();
+        spacingStyle.setPosition(Position.ABSOLUTE);
+        getElement().appendChild(spacingMeasureElement);
+
+        setStyleName(CLASSNAME);
+        addStyleName(StyleConstants.UI_LAYOUT);
+    }
+
+    private GridLayoutConnector getConnector() {
+        return (GridLayoutConnector) ConnectorMap.get(client)
+                .getConnector(this);
+    }
+
+    /**
+     * Returns the column widths measured in pixels
+     * 
+     * @return
+     */
+    protected int[] getColumnWidths() {
+        return columnWidths;
+    }
+
+    /**
+     * Returns the row heights measured in pixels
+     * 
+     * @return
+     */
+    protected int[] getRowHeights() {
+        return rowHeights;
+    }
+
+    /**
+     * Returns the spacing between the cells horizontally in pixels
+     * 
+     * @return
+     */
+    protected int getHorizontalSpacing() {
+        return LayoutManager.get(client).getOuterWidth(spacingMeasureElement);
+    }
+
+    /**
+     * Returns the spacing between the cells vertically in pixels
+     * 
+     * @return
+     */
+    protected int getVerticalSpacing() {
+        return LayoutManager.get(client).getOuterHeight(spacingMeasureElement);
+    }
+
+    static int[] cloneArray(int[] toBeCloned) {
+        int[] clone = new int[toBeCloned.length];
+        for (int i = 0; i < clone.length; i++) {
+            clone[i] = toBeCloned[i] * 1;
+        }
+        return clone;
+    }
+
+    void expandRows() {
+        if (!isUndefinedHeight()) {
+            int usedSpace = minRowHeights[0];
+            int verticalSpacing = getVerticalSpacing();
+            for (int i = 1; i < minRowHeights.length; i++) {
+                usedSpace += verticalSpacing + minRowHeights[i];
+            }
+            int availableSpace = LayoutManager.get(client).getInnerHeight(
+                    getElement());
+            int excessSpace = availableSpace - usedSpace;
+            int distributed = 0;
+            if (excessSpace > 0) {
+                for (int i = 0; i < rowHeights.length; i++) {
+                    int ew = excessSpace * rowExpandRatioArray[i] / 1000;
+                    rowHeights[i] = minRowHeights[i] + ew;
+                    distributed += ew;
+                }
+                excessSpace -= distributed;
+                int c = 0;
+                while (excessSpace > 0) {
+                    rowHeights[c % rowHeights.length]++;
+                    excessSpace--;
+                    c++;
+                }
+            }
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void updateHeight() {
+        // Detect minimum heights & calculate spans
+        detectRowHeights();
+
+        // Expand
+        expandRows();
+
+        // Position
+        layoutCellsVertically();
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void updateWidth() {
+        // Detect widths & calculate spans
+        detectColWidths();
+        // Expand
+        expandColumns();
+        // Position
+        layoutCellsHorizontally();
+
+    }
+
+    void expandColumns() {
+        if (!isUndefinedWidth()) {
+            int usedSpace = minColumnWidths[0];
+            int horizontalSpacing = getHorizontalSpacing();
+            for (int i = 1; i < minColumnWidths.length; i++) {
+                usedSpace += horizontalSpacing + minColumnWidths[i];
+            }
+
+            int availableSpace = LayoutManager.get(client).getInnerWidth(
+                    getElement());
+            int excessSpace = availableSpace - usedSpace;
+            int distributed = 0;
+            if (excessSpace > 0) {
+                for (int i = 0; i < columnWidths.length; i++) {
+                    int ew = excessSpace * colExpandRatioArray[i] / 1000;
+                    columnWidths[i] = minColumnWidths[i] + ew;
+                    distributed += ew;
+                }
+                excessSpace -= distributed;
+                int c = 0;
+                while (excessSpace > 0) {
+                    columnWidths[c % columnWidths.length]++;
+                    excessSpace--;
+                    c++;
+                }
+            }
+        }
+    }
+
+    void layoutCellsVertically() {
+        int verticalSpacing = getVerticalSpacing();
+        LayoutManager layoutManager = LayoutManager.get(client);
+        Element element = getElement();
+        int paddingTop = layoutManager.getPaddingTop(element);
+        int paddingBottom = layoutManager.getPaddingBottom(element);
+        int y = paddingTop;
+
+        for (int i = 0; i < cells.length; i++) {
+            y = paddingTop;
+            for (int j = 0; j < cells[i].length; j++) {
+                Cell cell = cells[i][j];
+                if (cell != null) {
+                    int reservedMargin;
+                    if (cell.rowspan + j >= cells[i].length) {
+                        // Make room for layout padding for cells reaching the
+                        // bottom of the layout
+                        reservedMargin = paddingBottom;
+                    } else {
+                        reservedMargin = 0;
+                    }
+                    cell.layoutVertically(y, reservedMargin);
+                }
+                y += rowHeights[j] + verticalSpacing;
+            }
+        }
+
+        if (isUndefinedHeight()) {
+            int outerHeight = y - verticalSpacing
+                    + layoutManager.getPaddingBottom(element)
+                    + layoutManager.getBorderHeight(element);
+            element.getStyle().setHeight(outerHeight, Unit.PX);
+            getConnector().getLayoutManager().reportOuterHeight(getConnector(),
+                    outerHeight);
+        }
+    }
+
+    void layoutCellsHorizontally() {
+        LayoutManager layoutManager = LayoutManager.get(client);
+        Element element = getElement();
+        int x = layoutManager.getPaddingLeft(element);
+        int paddingRight = layoutManager.getPaddingRight(element);
+        int horizontalSpacing = getHorizontalSpacing();
+        for (int i = 0; i < cells.length; i++) {
+            for (int j = 0; j < cells[i].length; j++) {
+                Cell cell = cells[i][j];
+                if (cell != null) {
+                    int reservedMargin;
+                    // Make room for layout padding for cells reaching the
+                    // right edge of the layout
+                    if (i + cell.colspan >= cells.length) {
+                        reservedMargin = paddingRight;
+                    } else {
+                        reservedMargin = 0;
+                    }
+                    cell.layoutHorizontally(x, reservedMargin);
+                }
+            }
+            x += columnWidths[i] + horizontalSpacing;
+        }
+
+        if (isUndefinedWidth()) {
+            int outerWidth = x - horizontalSpacing
+                    + layoutManager.getPaddingRight(element)
+                    + layoutManager.getBorderWidth(element);
+            element.getStyle().setWidth(outerWidth, Unit.PX);
+            getConnector().getLayoutManager().reportOuterWidth(getConnector(),
+                    outerWidth);
+        }
+    }
+
+    private boolean isUndefinedHeight() {
+        return getConnector().isUndefinedHeight();
+    }
+
+    private boolean isUndefinedWidth() {
+        return getConnector().isUndefinedWidth();
+    }
+
+    private void detectRowHeights() {
+        for (int i = 0; i < rowHeights.length; i++) {
+            rowHeights[i] = 0;
+        }
+
+        // collect min rowheight from non-rowspanned cells
+        for (int i = 0; i < cells.length; i++) {
+            for (int j = 0; j < cells[i].length; j++) {
+                Cell cell = cells[i][j];
+                if (cell != null) {
+                    if (cell.rowspan == 1) {
+                        if (!cell.hasRelativeHeight()
+                                && rowHeights[j] < cell.getHeight()) {
+                            rowHeights[j] = cell.getHeight();
+                        }
+                    } else {
+                        storeRowSpannedCell(cell);
+                    }
+                }
+            }
+        }
+
+        distributeRowSpanHeights();
+
+        minRowHeights = cloneArray(rowHeights);
+    }
+
+    private void detectColWidths() {
+        // collect min colwidths from non-colspanned cells
+        for (int i = 0; i < columnWidths.length; i++) {
+            columnWidths[i] = 0;
+        }
+
+        for (int i = 0; i < cells.length; i++) {
+            for (int j = 0; j < cells[i].length; j++) {
+                Cell cell = cells[i][j];
+                if (cell != null) {
+                    if (cell.colspan == 1) {
+                        if (!cell.hasRelativeWidth()
+                                && columnWidths[i] < cell.getWidth()) {
+                            columnWidths[i] = cell.getWidth();
+                        }
+                    } else {
+                        storeColSpannedCell(cell);
+                    }
+                }
+            }
+        }
+
+        distributeColSpanWidths();
+
+        minColumnWidths = cloneArray(columnWidths);
+    }
+
+    private void storeRowSpannedCell(Cell cell) {
+        SpanList l = null;
+        for (SpanList list : rowSpans) {
+            if (list.span < cell.rowspan) {
+                continue;
+            } else {
+                // insert before this
+                l = list;
+                break;
+            }
+        }
+        if (l == null) {
+            l = new SpanList(cell.rowspan);
+            rowSpans.add(l);
+        } else if (l.span != cell.rowspan) {
+            SpanList newL = new SpanList(cell.rowspan);
+            rowSpans.add(rowSpans.indexOf(l), newL);
+            l = newL;
+        }
+        l.cells.add(cell);
+    }
+
+    /**
+     * Iterates colspanned cells, ensures cols have enough space to accommodate
+     * them
+     */
+    void distributeColSpanWidths() {
+        for (SpanList list : colSpans) {
+            for (Cell cell : list.cells) {
+                // cells with relative content may return non 0 here if on
+                // subsequent renders
+                int width = cell.hasRelativeWidth() ? 0 : cell.getWidth();
+                distributeSpanSize(columnWidths, cell.col, cell.colspan,
+                        getHorizontalSpacing(), width, colExpandRatioArray);
+            }
+        }
+    }
+
+    /**
+     * Iterates rowspanned cells, ensures rows have enough space to accommodate
+     * them
+     */
+    private void distributeRowSpanHeights() {
+        for (SpanList list : rowSpans) {
+            for (Cell cell : list.cells) {
+                // cells with relative content may return non 0 here if on
+                // subsequent renders
+                int height = cell.hasRelativeHeight() ? 0 : cell.getHeight();
+                distributeSpanSize(rowHeights, cell.row, cell.rowspan,
+                        getVerticalSpacing(), height, rowExpandRatioArray);
+            }
+        }
+    }
+
+    private static void distributeSpanSize(int[] dimensions,
+            int spanStartIndex, int spanSize, int spacingSize, int size,
+            int[] expansionRatios) {
+        int allocated = dimensions[spanStartIndex];
+        for (int i = 1; i < spanSize; i++) {
+            allocated += spacingSize + dimensions[spanStartIndex + i];
+        }
+        if (allocated < size) {
+            // dimensions needs to be expanded due spanned cell
+            int neededExtraSpace = size - allocated;
+            int allocatedExtraSpace = 0;
+
+            // Divide space according to expansion ratios if any span has a
+            // ratio
+            int totalExpansion = 0;
+            for (int i = 0; i < spanSize; i++) {
+                int itemIndex = spanStartIndex + i;
+                totalExpansion += expansionRatios[itemIndex];
+            }
+
+            for (int i = 0; i < spanSize; i++) {
+                int itemIndex = spanStartIndex + i;
+                int expansion;
+                if (totalExpansion == 0) {
+                    // Divide equally among all cells if there are no
+                    // expansion ratios
+                    expansion = neededExtraSpace / spanSize;
+                } else {
+                    expansion = neededExtraSpace * expansionRatios[itemIndex]
+                            / totalExpansion;
+                }
+                dimensions[itemIndex] += expansion;
+                allocatedExtraSpace += expansion;
+            }
+
+            // We might still miss a couple of pixels because of
+            // rounding errors...
+            if (neededExtraSpace > allocatedExtraSpace) {
+                for (int i = 0; i < spanSize; i++) {
+                    // Add one pixel to every cell until we have
+                    // compensated for any rounding error
+                    int itemIndex = spanStartIndex + i;
+                    dimensions[itemIndex] += 1;
+                    allocatedExtraSpace += 1;
+                    if (neededExtraSpace == allocatedExtraSpace) {
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    private LinkedList<SpanList> colSpans = new LinkedList<SpanList>();
+    private LinkedList<SpanList> rowSpans = new LinkedList<SpanList>();
+
+    private class SpanList {
+        final int span;
+        List<Cell> cells = new LinkedList<Cell>();
+
+        public SpanList(int span) {
+            this.span = span;
+        }
+    }
+
+    void storeColSpannedCell(Cell cell) {
+        SpanList l = null;
+        for (SpanList list : colSpans) {
+            if (list.span < cell.colspan) {
+                continue;
+            } else {
+                // insert before this
+                l = list;
+                break;
+            }
+        }
+        if (l == null) {
+            l = new SpanList(cell.colspan);
+            colSpans.add(l);
+        } else if (l.span != cell.colspan) {
+
+            SpanList newL = new SpanList(cell.colspan);
+            colSpans.add(colSpans.indexOf(l), newL);
+            l = newL;
+        }
+        l.cells.add(cell);
+    }
+
+    Cell[][] cells;
+
+    /**
+     * Private helper class.
+     */
+    /** For internal use only. May be removed or replaced in the future. */
+    public class Cell {
+        public Cell(int row, int col) {
+            this.row = row;
+            this.col = col;
+        }
+
+        public boolean hasContent() {
+            return hasContent;
+        }
+
+        public boolean hasRelativeHeight() {
+            if (slot != null) {
+                return slot.getChild().isRelativeHeight();
+            } else {
+                return true;
+            }
+        }
+
+        /**
+         * @return total of spanned cols
+         */
+        private int getAvailableWidth() {
+            int width = columnWidths[col];
+            for (int i = 1; i < colspan; i++) {
+                width += getHorizontalSpacing() + columnWidths[col + i];
+            }
+            return width;
+        }
+
+        /**
+         * @return total of spanned rows
+         */
+        private int getAvailableHeight() {
+            int height = rowHeights[row];
+            for (int i = 1; i < rowspan; i++) {
+                height += getVerticalSpacing() + rowHeights[row + i];
+            }
+            return height;
+        }
+
+        public void layoutHorizontally(int x, int marginRight) {
+            if (slot != null) {
+                slot.positionHorizontally(x, getAvailableWidth(), marginRight);
+            }
+        }
+
+        public void layoutVertically(int y, int marginBottom) {
+            if (slot != null) {
+                slot.positionVertically(y, getAvailableHeight(), marginBottom);
+            }
+        }
+
+        public int getWidth() {
+            if (slot != null) {
+                return slot.getUsedWidth();
+            } else {
+                return 0;
+            }
+        }
+
+        public int getHeight() {
+            if (slot != null) {
+                return slot.getUsedHeight();
+            } else {
+                return 0;
+            }
+        }
+
+        protected boolean hasRelativeWidth() {
+            if (slot != null) {
+                return slot.getChild().isRelativeWidth();
+            } else {
+                return true;
+            }
+        }
+
+        final int row;
+        final int col;
+        int colspan = 1;
+        int rowspan = 1;
+
+        private boolean hasContent;
+
+        private AlignmentInfo alignment;
+
+        /** For internal use only. May be removed or replaced in the future. */
+        public ComponentConnectorLayoutSlot slot;
+
+        public void updateFromUidl(UIDL cellUidl) {
+            // Set cell width
+            colspan = cellUidl.hasAttribute("w") ? cellUidl
+                    .getIntAttribute("w") : 1;
+            // Set cell height
+            rowspan = cellUidl.hasAttribute("h") ? cellUidl
+                    .getIntAttribute("h") : 1;
+            // ensure we will lose reference to old cells, now overlapped by
+            // this cell
+            for (int i = 0; i < colspan; i++) {
+                for (int j = 0; j < rowspan; j++) {
+                    if (i > 0 || j > 0) {
+                        cells[col + i][row + j] = null;
+                    }
+                }
+            }
+
+            UIDL childUidl = cellUidl.getChildUIDL(0); // we are interested
+                                                       // about childUidl
+            hasContent = childUidl != null;
+            if (hasContent) {
+                ComponentConnector childConnector = client
+                        .getPaintable(childUidl);
+
+                if (slot == null || slot.getChild() != childConnector) {
+                    slot = new ComponentConnectorLayoutSlot(CLASSNAME,
+                            childConnector, getConnector());
+                    if (childConnector.isRelativeWidth()) {
+                        slot.getWrapperElement().getStyle()
+                                .setWidth(100, Unit.PCT);
+                    }
+                    Element slotWrapper = slot.getWrapperElement();
+                    getElement().appendChild(slotWrapper);
+
+                    Widget widget = childConnector.getWidget();
+                    insert(widget, slotWrapper, getWidgetCount(), false);
+                    Cell oldCell = widgetToCell.put(widget, this);
+                    if (oldCell != null) {
+                        oldCell.slot.getWrapperElement().removeFromParent();
+                        oldCell.slot = null;
+                    }
+                }
+
+            }
+        }
+
+        public void setAlignment(AlignmentInfo alignmentInfo) {
+            slot.setAlignment(alignmentInfo);
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public Cell getCell(int row, int col) {
+        return cells[col][row];
+    }
+
+    /**
+     * Creates a new Cell with the given coordinates. If an existing cell was
+     * found, returns that one.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     * 
+     * @param row
+     * @param col
+     * @return
+     */
+    public Cell createCell(int row, int col) {
+        Cell cell = getCell(row, col);
+        if (cell == null) {
+            cell = new Cell(row, col);
+            cells[col][row] = cell;
+        }
+        return cell;
+    }
+
+    /**
+     * Returns the deepest nested child component which contains "element". The
+     * child component is also returned if "element" is part of its caption.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     * 
+     * @param element
+     *            An element that is a nested sub element of the root element in
+     *            this layout
+     * @return The Paintable which the element is a part of. Null if the element
+     *         belongs to the layout and not to a child.
+     */
+    public ComponentConnector getComponent(Element element) {
+        return Util.getConnectorForElement(client, this, element);
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void setCaption(Widget widget, VCaption caption) {
+        VLayoutSlot slot = widgetToCell.get(widget).slot;
+
+        if (caption != null) {
+            // Logical attach.
+            getChildren().add(caption);
+        }
+
+        // Physical attach if not null, also removes old caption
+        slot.setCaption(caption);
+
+        if (caption != null) {
+            // Adopt.
+            adopt(caption);
+        }
+    }
+
+    private void togglePrefixedStyleName(String name, boolean enabled) {
+        if (enabled) {
+            addStyleDependentName(name);
+        } else {
+            removeStyleDependentName(name);
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void updateMarginStyleNames(MarginInfo marginInfo) {
+        togglePrefixedStyleName("margin-top", marginInfo.hasTop());
+        togglePrefixedStyleName("margin-right", marginInfo.hasRight());
+        togglePrefixedStyleName("margin-bottom", marginInfo.hasBottom());
+        togglePrefixedStyleName("margin-left", marginInfo.hasLeft());
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void updateSpacingStyleName(boolean spacingEnabled) {
+        String styleName = getStylePrimaryName();
+        if (spacingEnabled) {
+            spacingMeasureElement.addClassName(styleName + "-spacing-on");
+            spacingMeasureElement.removeClassName(styleName + "-spacing-off");
+        } else {
+            spacingMeasureElement.removeClassName(styleName + "-spacing-on");
+            spacingMeasureElement.addClassName(styleName + "-spacing-off");
+        }
+    }
+
+    public void setSize(int rows, int cols) {
+        if (cells == null) {
+            cells = new Cell[cols][rows];
+        } else if (cells.length != cols || cells[0].length != rows) {
+            Cell[][] newCells = new Cell[cols][rows];
+            for (int i = 0; i < cells.length; i++) {
+                for (int j = 0; j < cells[i].length; j++) {
+                    if (i < cols && j < rows) {
+                        newCells[i][j] = cells[i][j];
+                    }
+                }
+            }
+            cells = newCells;
+        }
+    }
+
+}
diff --git a/client/src/com/vaadin/client/ui/VHorizontalLayout.java b/client/src/com/vaadin/client/ui/VHorizontalLayout.java
new file mode 100644 (file)
index 0000000..53a933e
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui;
+
+import com.vaadin.client.StyleConstants;
+
+/**
+ * Represents a layout where the children is ordered vertically
+ */
+public class VHorizontalLayout extends VOrderedLayout {
+
+    public static final String CLASSNAME = "v-horizontallayout";
+
+    /**
+     * Default constructor
+     */
+    public VHorizontalLayout() {
+        super(false);
+        setStyleName(CLASSNAME);
+    }
+
+    @Override
+    public void setStyleName(String style) {
+        super.setStyleName(style);
+        addStyleName(StyleConstants.UI_LAYOUT);
+        addStyleName("v-horizontal");
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VImage.java b/client/src/com/vaadin/client/ui/VImage.java
new file mode 100644 (file)
index 0000000..1b7da97
--- /dev/null
@@ -0,0 +1,12 @@
+package com.vaadin.client.ui;
+
+import com.google.gwt.user.client.ui.Image;
+
+public class VImage extends Image {
+
+    public static final String CLASSNAME = "v-image";
+
+    public VImage() {
+        setStylePrimaryName(CLASSNAME);
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VLabel.java b/client/src/com/vaadin/client/ui/VLabel.java
new file mode 100644 (file)
index 0000000..a0a6901
--- /dev/null
@@ -0,0 +1,82 @@
+/* 
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.HTML;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.Util;
+import com.vaadin.client.VTooltip;
+
+public class VLabel extends HTML {
+
+    public static final String CLASSNAME = "v-label";
+    private static final String CLASSNAME_UNDEFINED_WIDTH = "v-label-undef-w";
+
+    private ApplicationConnection connection;
+
+    public VLabel() {
+        super();
+        setStyleName(CLASSNAME);
+        sinkEvents(VTooltip.TOOLTIP_EVENTS);
+    }
+
+    public VLabel(String text) {
+        super(text);
+        setStyleName(CLASSNAME);
+    }
+
+    @Override
+    public void onBrowserEvent(Event event) {
+        super.onBrowserEvent(event);
+        if (event.getTypeInt() == Event.ONLOAD) {
+            Util.notifyParentOfSizeChange(this, true);
+            event.stopPropagation();
+            return;
+        }
+    }
+
+    @Override
+    public void setWidth(String width) {
+        super.setWidth(width);
+        if (width == null || width.equals("")) {
+            setStyleName(getElement(), CLASSNAME_UNDEFINED_WIDTH, true);
+            getElement().getStyle().setDisplay(Display.INLINE_BLOCK);
+        } else {
+            setStyleName(getElement(), CLASSNAME_UNDEFINED_WIDTH, false);
+            getElement().getStyle().clearDisplay();
+        }
+    }
+
+    @Override
+    public void setText(String text) {
+        if (BrowserInfo.get().isIE8()) {
+            // #3983 - IE8 incorrectly replaces \n with <br> so we do the
+            // escaping manually and set as HTML
+            super.setHTML(Util.escapeHTML(text));
+        } else {
+            super.setText(text);
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void setConnection(ApplicationConnection client) {
+        connection = client;
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VLink.java b/client/src/com/vaadin/client/ui/VLink.java
new file mode 100644 (file)
index 0000000..a4a4e78
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.HTML;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.Util;
+import com.vaadin.shared.ui.BorderStyle;
+
+public class VLink extends HTML implements ClickHandler {
+
+    public static final String CLASSNAME = "v-link";
+
+    @Deprecated
+    protected static final BorderStyle BORDER_STYLE_DEFAULT = BorderStyle.DEFAULT;
+
+    @Deprecated
+    protected static final BorderStyle BORDER_STYLE_MINIMAL = BorderStyle.MINIMAL;
+
+    @Deprecated
+    protected static final BorderStyle BORDER_STYLE_NONE = BorderStyle.NONE;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public String src;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public String target;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public BorderStyle borderStyle = BorderStyle.DEFAULT;
+
+    /** 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 int targetWidth;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public int targetHeight;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public Element errorIndicatorElement;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public final Element anchor = DOM.createAnchor();
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public final Element captionElement = DOM.createSpan();
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public Icon icon;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public ApplicationConnection client;
+
+    public VLink() {
+        super();
+        getElement().appendChild(anchor);
+        anchor.appendChild(captionElement);
+        addClickHandler(this);
+        setStyleName(CLASSNAME);
+    }
+
+    @Override
+    public void onClick(ClickEvent event) {
+        if (enabled) {
+            if (target == null) {
+                target = "_self";
+            }
+            String features;
+            switch (borderStyle) {
+            case NONE:
+                features = "menubar=no,location=no,status=no";
+                break;
+            case MINIMAL:
+                features = "menubar=yes,location=no,status=no";
+                break;
+            default:
+                features = "";
+                break;
+            }
+
+            if (targetWidth > 0) {
+                features += (features.length() > 0 ? "," : "") + "width="
+                        + targetWidth;
+            }
+            if (targetHeight > 0) {
+                features += (features.length() > 0 ? "," : "") + "height="
+                        + targetHeight;
+            }
+
+            if (features.length() > 0) {
+                // if 'special features' are set, use window.open(), unless
+                // a modifier key is held (ctrl to open in new tab etc)
+                Event e = DOM.eventGetCurrentEvent();
+                if (!e.getCtrlKey() && !e.getAltKey() && !e.getShiftKey()
+                        && !e.getMetaKey()) {
+                    Window.open(src, target, features);
+                    e.preventDefault();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onBrowserEvent(Event event) {
+        final Element target = DOM.eventGetTarget(event);
+        if (event.getTypeInt() == Event.ONLOAD) {
+            Util.notifyParentOfSizeChange(this, true);
+        }
+        if (target == captionElement || target == anchor
+                || (icon != null && target == icon.getElement())) {
+            super.onBrowserEvent(event);
+        }
+        if (!enabled) {
+            event.preventDefault();
+        }
+
+    }
+
+}
diff --git a/client/src/com/vaadin/client/ui/VListSelect.java b/client/src/com/vaadin/client/ui/VListSelect.java
new file mode 100644 (file)
index 0000000..e88e188
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.user.client.ui.ListBox;
+import com.vaadin.client.UIDL;
+
+public class VListSelect extends VOptionGroupBase {
+
+    public static final String CLASSNAME = "v-select";
+
+    private static final int VISIBLE_COUNT = 10;
+
+    protected ListBox select;
+
+    private int lastSelectedIndex = -1;
+
+    public VListSelect() {
+        super(new ListBox(true), CLASSNAME);
+        select = getOptionsContainer();
+        select.addChangeHandler(this);
+        select.addClickHandler(this);
+        select.setVisibleItemCount(VISIBLE_COUNT);
+        setStyleName(CLASSNAME);
+    }
+
+    @Override
+    public void setStyleName(String style) {
+        super.setStyleName(style);
+        updateStyleNames();
+    }
+
+    @Override
+    public void setStylePrimaryName(String style) {
+        super.setStylePrimaryName(style);
+        updateStyleNames();
+    }
+
+    protected void updateStyleNames() {
+        container.setStyleName(getStylePrimaryName());
+        select.setStyleName(getStylePrimaryName() + "-select");
+    }
+
+    protected ListBox getOptionsContainer() {
+        return (ListBox) optionsContainer;
+    }
+
+    @Override
+    public void buildOptions(UIDL uidl) {
+        select.setMultipleSelect(isMultiselect());
+        select.setEnabled(!isDisabled() && !isReadonly());
+        select.clear();
+        if (!isMultiselect() && isNullSelectionAllowed()
+                && !isNullSelectionItemAvailable()) {
+            // can't unselect last item in singleselect mode
+            select.addItem("", (String) null);
+        }
+        for (final Iterator<?> i = uidl.getChildIterator(); i.hasNext();) {
+            final UIDL optionUidl = (UIDL) i.next();
+            select.addItem(optionUidl.getStringAttribute("caption"),
+                    optionUidl.getStringAttribute("key"));
+            if (optionUidl.hasAttribute("selected")) {
+                int itemIndex = select.getItemCount() - 1;
+                select.setItemSelected(itemIndex, true);
+                lastSelectedIndex = itemIndex;
+            }
+        }
+        if (getRows() > 0) {
+            select.setVisibleItemCount(getRows());
+        }
+    }
+
+    @Override
+    protected String[] getSelectedItems() {
+        final ArrayList<String> selectedItemKeys = new ArrayList<String>();
+        for (int i = 0; i < select.getItemCount(); i++) {
+            if (select.isItemSelected(i)) {
+                selectedItemKeys.add(select.getValue(i));
+            }
+        }
+        return selectedItemKeys.toArray(new String[selectedItemKeys.size()]);
+    }
+
+    @Override
+    public void onChange(ChangeEvent event) {
+        final int si = select.getSelectedIndex();
+        if (si == -1 && !isNullSelectionAllowed()) {
+            select.setSelectedIndex(lastSelectedIndex);
+        } else {
+            lastSelectedIndex = si;
+            if (isMultiselect()) {
+                client.updateVariable(paintableId, "selected",
+                        getSelectedItems(), isImmediate());
+            } else {
+                client.updateVariable(paintableId, "selected",
+                        new String[] { "" + getSelectedItem() }, isImmediate());
+            }
+        }
+    }
+
+    @Override
+    public void setHeight(String height) {
+        select.setHeight(height);
+        super.setHeight(height);
+    }
+
+    @Override
+    public void setWidth(String width) {
+        select.setWidth(width);
+        super.setWidth(width);
+    }
+
+    @Override
+    public void setTabIndex(int tabIndex) {
+        getOptionsContainer().setTabIndex(tabIndex);
+    }
+
+    @Override
+    public void focus() {
+        select.setFocus(true);
+    }
+}
\ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/VMenuBar.java b/client/src/com/vaadin/client/ui/VMenuBar.java
new file mode 100644 (file)
index 0000000..825d7e6
--- /dev/null
@@ -0,0 +1,1560 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.HasHTML;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.LayoutManager;
+import com.vaadin.client.TooltipInfo;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.Util;
+import com.vaadin.shared.ui.menubar.MenuBarConstants;
+
+public class VMenuBar extends SimpleFocusablePanel implements
+        CloseHandler<PopupPanel>, KeyPressHandler, KeyDownHandler,
+        FocusHandler, SubPartAware {
+
+    // The hierarchy of VMenuBar is a bit weird as VMenuBar is the Paintable,
+    // used for the root menu but also used for the sub menus.
+
+    /** Set the CSS class name to allow styling. */
+    public static final String CLASSNAME = "v-menubar";
+    public static final String SUBMENU_CLASSNAME_PREFIX = "-submenu";
+
+    /**
+     * For server connections.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public String uidlId;
+
+    /** 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 final VMenuBar hostReference = this;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public CustomMenuItem moreItem = null;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public VMenuBar collapsedRootItems;
+
+    /**
+     * An empty command to be used when the item has no command associated
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public static final Command emptyCommand = null;
+
+    /** Widget fields **/
+    protected boolean subMenu;
+    protected ArrayList<CustomMenuItem> items;
+    protected Element containerElement;
+    protected VOverlay popup;
+    protected VMenuBar visibleChildMenu;
+    protected boolean menuVisible = false;
+    protected VMenuBar parentMenu;
+    protected CustomMenuItem selected;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean enabled = true;
+
+    private VLazyExecutor iconLoadedExecutioner = new VLazyExecutor(100,
+            new ScheduledCommand() {
+
+                @Override
+                public void execute() {
+                    iLayout(true);
+                }
+            });
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean openRootOnHover;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean htmlContentAllowed;
+
+    public VMenuBar() {
+        // Create an empty horizontal menubar
+        this(false, null);
+
+        // Navigation is only handled by the root bar
+        addFocusHandler(this);
+
+        /*
+         * Firefox auto-repeat works correctly only if we use a key press
+         * handler, other browsers handle it correctly when using a key down
+         * handler
+         */
+        if (BrowserInfo.get().isGecko()) {
+            addKeyPressHandler(this);
+        } else {
+            addKeyDownHandler(this);
+        }
+    }
+
+    public VMenuBar(boolean subMenu, VMenuBar parentMenu) {
+
+        items = new ArrayList<CustomMenuItem>();
+        popup = null;
+        visibleChildMenu = null;
+        this.subMenu = subMenu;
+
+        containerElement = getElement();
+
+        sinkEvents(Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT
+                | Event.ONLOAD);
+
+        if (parentMenu == null) {
+            // Root menu
+            setStyleName(CLASSNAME);
+        } else {
+            // Child menus inherits style name
+            setStyleName(parentMenu.getStyleName());
+        }
+    }
+
+    @Override
+    public void setStyleName(String style) {
+        super.setStyleName(style);
+        updateStyleNames();
+    }
+
+    @Override
+    public void setStylePrimaryName(String style) {
+        super.setStylePrimaryName(style);
+        updateStyleNames();
+    }
+
+    protected void updateStyleNames() {
+        String primaryStyleName = getParentMenu() != null ? getParentMenu()
+                .getStylePrimaryName() : getStylePrimaryName();
+
+        // Reset the style name for all the items
+        for (CustomMenuItem item : items) {
+            item.setStyleName(primaryStyleName + "-menuitem");
+        }
+
+        if (subMenu
+                && !getStylePrimaryName().endsWith(SUBMENU_CLASSNAME_PREFIX)) {
+            /*
+             * Sub-menus should get the sub-menu prefix
+             */
+            super.setStylePrimaryName(primaryStyleName
+                    + SUBMENU_CLASSNAME_PREFIX);
+        }
+    }
+
+    @Override
+    protected void onDetach() {
+        super.onDetach();
+        if (!subMenu) {
+            setSelected(null);
+            hideChildren();
+            menuVisible = false;
+        }
+    }
+
+    void updateSize() {
+        // Take from setWidth
+        if (!subMenu) {
+            // Only needed for root level menu
+            hideChildren();
+            setSelected(null);
+            menuVisible = false;
+        }
+    }
+
+    /**
+     * Build the HTML content for a menu item.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public String buildItemHTML(UIDL item) {
+        // Construct html from the text and the optional icon
+        StringBuffer itemHTML = new StringBuffer();
+        if (item.hasAttribute("separator")) {
+            itemHTML.append("<span>---</span>");
+        } else {
+            // Add submenu indicator
+            if (item.getChildCount() > 0) {
+                String bgStyle = "";
+                itemHTML.append("<span class=\"" + getStylePrimaryName()
+                        + "-submenu-indicator\"" + bgStyle + ">&#x25BA;</span>");
+            }
+
+            itemHTML.append("<span class=\"" + getStylePrimaryName()
+                    + "-menuitem-caption\">");
+            if (item.hasAttribute("icon")) {
+                itemHTML.append("<img src=\""
+                        + Util.escapeAttribute(client.translateVaadinUri(item
+                                .getStringAttribute("icon"))) + "\" class=\""
+                        + Icon.CLASSNAME + "\" alt=\"\" />");
+            }
+            String itemText = item.getStringAttribute("text");
+            if (!htmlContentAllowed) {
+                itemText = Util.escapeHTML(itemText);
+            }
+            itemHTML.append(itemText);
+            itemHTML.append("</span>");
+        }
+        return itemHTML.toString();
+    }
+
+    /**
+     * This is called by the items in the menu and it communicates the
+     * information to the server
+     * 
+     * @param clickedItemId
+     *            id of the item that was clicked
+     */
+    public void onMenuClick(int clickedItemId) {
+        // Updating the state to the server can not be done before
+        // the server connection is known, i.e., before updateFromUIDL()
+        // has been called.
+        if (uidlId != null && client != null) {
+            // Communicate the user interaction parameters to server. This call
+            // will initiate an AJAX request to the server.
+            client.updateVariable(uidlId, "clickedId", clickedItemId, true);
+        }
+    }
+
+    /** Widget methods **/
+
+    /**
+     * Returns a list of items in this menu
+     */
+    public List<CustomMenuItem> getItems() {
+        return items;
+    }
+
+    /**
+     * Remove all the items in this menu
+     */
+    public void clearItems() {
+        Element e = getContainerElement();
+        while (DOM.getChildCount(e) > 0) {
+            DOM.removeChild(e, DOM.getChild(e, 0));
+        }
+        items.clear();
+    }
+
+    /**
+     * Returns the containing element of the menu
+     * 
+     * @return
+     */
+    @Override
+    public Element getContainerElement() {
+        return containerElement;
+    }
+
+    /**
+     * Add a new item to this menu
+     * 
+     * @param html
+     *            items text
+     * @param cmd
+     *            items command
+     * @return the item created
+     */
+    public CustomMenuItem addItem(String html, Command cmd) {
+        CustomMenuItem item = GWT.create(CustomMenuItem.class);
+        item.setHTML(html);
+        item.setCommand(cmd);
+        addItem(item);
+        return item;
+    }
+
+    /**
+     * Add a new item to this menu
+     * 
+     * @param item
+     */
+    public void addItem(CustomMenuItem item) {
+        if (items.contains(item)) {
+            return;
+        }
+        DOM.appendChild(getContainerElement(), item.getElement());
+        item.setParentMenu(this);
+        item.setSelected(false);
+        items.add(item);
+    }
+
+    public void addItem(CustomMenuItem item, int index) {
+        if (items.contains(item)) {
+            return;
+        }
+        DOM.insertChild(getContainerElement(), item.getElement(), index);
+        item.setParentMenu(this);
+        item.setSelected(false);
+        items.add(index, item);
+    }
+
+    /**
+     * Remove the given item from this menu
+     * 
+     * @param item
+     */
+    public void removeItem(CustomMenuItem item) {
+        if (items.contains(item)) {
+            int index = items.indexOf(item);
+
+            DOM.removeChild(getContainerElement(),
+                    DOM.getChild(getContainerElement(), index));
+            items.remove(index);
+        }
+    }
+
+    /*
+     * @see
+     * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt.user
+     * .client.Event)
+     */
+    @Override
+    public void onBrowserEvent(Event e) {
+        super.onBrowserEvent(e);
+
+        // Handle onload events (icon loaded, size changes)
+        if (DOM.eventGetType(e) == Event.ONLOAD) {
+            VMenuBar parent = getParentMenu();
+            if (parent != null) {
+                // The onload event for an image in a popup should be sent to
+                // the parent, which owns the popup
+                parent.iconLoaded();
+            } else {
+                // Onload events for images in the root menu are handled by the
+                // root menu itself
+                iconLoaded();
+            }
+            return;
+        }
+
+        Element targetElement = DOM.eventGetTarget(e);
+        CustomMenuItem targetItem = null;
+        for (int i = 0; i < items.size(); i++) {
+            CustomMenuItem item = items.get(i);
+            if (DOM.isOrHasChild(item.getElement(), targetElement)) {
+                targetItem = item;
+            }
+        }
+
+        if (targetItem != null) {
+            switch (DOM.eventGetType(e)) {
+
+            case Event.ONCLICK:
+                if (isEnabled() && targetItem.isEnabled()) {
+                    itemClick(targetItem);
+                }
+                if (subMenu) {
+                    // Prevent moving keyboard focus to child menus
+                    VMenuBar parent = parentMenu;
+                    while (parent.getParentMenu() != null) {
+                        parent = parent.getParentMenu();
+                    }
+                    parent.setFocus(true);
+                }
+
+                break;
+
+            case Event.ONMOUSEOVER:
+                LazyCloser.cancelClosing();
+
+                if (isEnabled() && targetItem.isEnabled()) {
+                    itemOver(targetItem);
+                }
+                break;
+
+            case Event.ONMOUSEOUT:
+                itemOut(targetItem);
+                LazyCloser.schedule();
+                break;
+            }
+        } else if (subMenu && DOM.eventGetType(e) == Event.ONCLICK && subMenu) {
+            // Prevent moving keyboard focus to child menus
+            VMenuBar parent = parentMenu;
+            while (parent.getParentMenu() != null) {
+                parent = parent.getParentMenu();
+            }
+            parent.setFocus(true);
+        }
+    }
+
+    private boolean isEnabled() {
+        return enabled;
+    }
+
+    private void iconLoaded() {
+        iconLoadedExecutioner.trigger();
+    }
+
+    /**
+     * When an item is clicked
+     * 
+     * @param item
+     */
+    public void itemClick(CustomMenuItem item) {
+        if (item.getCommand() != null) {
+            setSelected(null);
+
+            if (visibleChildMenu != null) {
+                visibleChildMenu.hideChildren();
+            }
+
+            hideParents(true);
+            menuVisible = false;
+            Scheduler.get().scheduleDeferred(item.getCommand());
+
+        } else {
+            if (item.getSubMenu() != null
+                    && item.getSubMenu() != visibleChildMenu) {
+                setSelected(item);
+                showChildMenu(item);
+                menuVisible = true;
+            } else if (!subMenu) {
+                setSelected(null);
+                hideChildren();
+                menuVisible = false;
+            }
+        }
+    }
+
+    /**
+     * When the user hovers the mouse over the item
+     * 
+     * @param item
+     */
+    public void itemOver(CustomMenuItem item) {
+        if ((openRootOnHover || subMenu || menuVisible) && !item.isSeparator()) {
+            setSelected(item);
+            if (!subMenu && openRootOnHover && !menuVisible) {
+                menuVisible = true; // start opening menus
+                LazyCloser.prepare(this);
+            }
+        }
+
+        if (menuVisible && visibleChildMenu != item.getSubMenu()
+                && popup != null) {
+            popup.hide();
+        }
+
+        if (menuVisible && item.getSubMenu() != null
+                && visibleChildMenu != item.getSubMenu()) {
+            showChildMenu(item);
+        }
+    }
+
+    /**
+     * When the mouse is moved away from an item
+     * 
+     * @param item
+     */
+    public void itemOut(CustomMenuItem item) {
+        if (visibleChildMenu != item.getSubMenu()) {
+            hideChildMenu(item);
+            setSelected(null);
+        } else if (visibleChildMenu == null) {
+            setSelected(null);
+        }
+    }
+
+    /**
+     * Used to autoclose submenus when they the menu is in a mode which opens
+     * root menus on mouse hover.
+     */
+    private static class LazyCloser extends Timer {
+        static LazyCloser INSTANCE;
+        private VMenuBar activeRoot;
+
+        @Override
+        public void run() {
+            activeRoot.hideChildren();
+            activeRoot.setSelected(null);
+            activeRoot.menuVisible = false;
+            activeRoot = null;
+        }
+
+        public static void cancelClosing() {
+            if (INSTANCE != null) {
+                INSTANCE.cancel();
+            }
+        }
+
+        public static void prepare(VMenuBar vMenuBar) {
+            if (INSTANCE == null) {
+                INSTANCE = new LazyCloser();
+            }
+            if (INSTANCE.activeRoot == vMenuBar) {
+                INSTANCE.cancel();
+            } else if (INSTANCE.activeRoot != null) {
+                INSTANCE.cancel();
+                INSTANCE.run();
+            }
+            INSTANCE.activeRoot = vMenuBar;
+        }
+
+        public static void schedule() {
+            if (INSTANCE != null && INSTANCE.activeRoot != null) {
+                INSTANCE.schedule(750);
+            }
+        }
+
+    }
+
+    /**
+     * Shows the child menu of an item. The caller must ensure that the item has
+     * a submenu.
+     * 
+     * @param item
+     */
+    public void showChildMenu(CustomMenuItem item) {
+
+        int left = 0;
+        int top = 0;
+        if (subMenu) {
+            left = item.getParentMenu().getAbsoluteLeft()
+                    + item.getParentMenu().getOffsetWidth();
+            top = item.getAbsoluteTop();
+        } else {
+            left = item.getAbsoluteLeft();
+            top = item.getParentMenu().getAbsoluteTop()
+                    + item.getParentMenu().getOffsetHeight();
+        }
+        showChildMenuAt(item, top, left);
+    }
+
+    protected void showChildMenuAt(CustomMenuItem item, int top, int left) {
+        final int shadowSpace = 10;
+
+        popup = new VOverlay(true, false, true);
+        popup.setOwner(this);
+
+        /*
+         * Use parents primary style name if possible and remove the submenu
+         * prefix if needed
+         */
+        String primaryStyleName = parentMenu != null ? parentMenu
+                .getStylePrimaryName() : getStylePrimaryName();
+        if (subMenu) {
+            primaryStyleName = primaryStyleName.replace(
+                    SUBMENU_CLASSNAME_PREFIX, "");
+        }
+        popup.setStyleName(primaryStyleName + "-popup");
+
+        // Setting owner and handlers to support tooltips. Needed for tooltip
+        // handling of overlay widgets (will direct queries to parent menu)
+        if (parentMenu == null) {
+            popup.setOwner(this);
+        } else {
+            VMenuBar parent = parentMenu;
+            while (parent.getParentMenu() != null) {
+                parent = parent.getParentMenu();
+            }
+            popup.setOwner(parent);
+        }
+        if (client != null) {
+            client.getVTooltip().connectHandlersToWidget(popup);
+        }
+
+        popup.setWidget(item.getSubMenu());
+        popup.addCloseHandler(this);
+        popup.addAutoHidePartner(item.getElement());
+
+        // at 0,0 because otherwise IE7 add extra scrollbars (#5547)
+        popup.setPopupPosition(0, 0);
+
+        item.getSubMenu().onShow();
+        visibleChildMenu = item.getSubMenu();
+        item.getSubMenu().setParentMenu(this);
+
+        popup.show();
+
+        if (left + popup.getOffsetWidth() >= RootPanel.getBodyElement()
+                .getOffsetWidth() - shadowSpace) {
+            if (subMenu) {
+                left = item.getParentMenu().getAbsoluteLeft()
+                        - popup.getOffsetWidth() - shadowSpace;
+            } else {
+                left = RootPanel.getBodyElement().getOffsetWidth()
+                        - popup.getOffsetWidth() - shadowSpace;
+            }
+            // Accommodate space for shadow
+            if (left < shadowSpace) {
+                left = shadowSpace;
+            }
+        }
+
+        top = adjustPopupHeight(top, shadowSpace);
+
+        popup.setPopupPosition(left, top);
+
+    }
+
+    private int adjustPopupHeight(int top, final int shadowSpace) {
+        // Check that the popup will fit the screen
+        int availableHeight = RootPanel.getBodyElement().getOffsetHeight()
+                - top - shadowSpace;
+        int missingHeight = popup.getOffsetHeight() - availableHeight;
+        if (missingHeight > 0) {
+            // First move the top of the popup to get more space
+            // Don't move above top of screen, don't move more than needed
+            int moveUpBy = Math.min(top - shadowSpace, missingHeight);
+
+            // Update state
+            top -= moveUpBy;
+            missingHeight -= moveUpBy;
+            availableHeight += moveUpBy;
+
+            if (missingHeight > 0) {
+                int contentWidth = visibleChildMenu.getOffsetWidth();
+
+                // If there's still not enough room, limit height to fit and add
+                // a scroll bar
+                Style style = popup.getElement().getStyle();
+                style.setHeight(availableHeight, Unit.PX);
+                style.setOverflowY(Overflow.SCROLL);
+
+                // Make room for the scroll bar by adjusting the width of the
+                // popup
+                style.setWidth(contentWidth + Util.getNativeScrollbarSize(),
+                        Unit.PX);
+                popup.positionOrSizeUpdated();
+            }
+        }
+        return top;
+    }
+
+    /**
+     * Hides the submenu of an item
+     * 
+     * @param item
+     */
+    public void hideChildMenu(CustomMenuItem item) {
+        if (visibleChildMenu != null
+                && !(visibleChildMenu == item.getSubMenu())) {
+            popup.hide();
+        }
+    }
+
+    /**
+     * When the menu is shown.
+     */
+    public void onShow() {
+        // remove possible previous selection
+        if (selected != null) {
+            selected.setSelected(false);
+            selected = null;
+        }
+        menuVisible = true;
+    }
+
+    /**
+     * Listener method, fired when this menu is closed
+     */
+    @Override
+    public void onClose(CloseEvent<PopupPanel> event) {
+        hideChildren();
+        if (event.isAutoClosed()) {
+            hideParents(true);
+            menuVisible = false;
+        }
+        visibleChildMenu = null;
+        popup = null;
+    }
+
+    /**
+     * Recursively hide all child menus
+     */
+    public void hideChildren() {
+        if (visibleChildMenu != null) {
+            visibleChildMenu.hideChildren();
+            popup.hide();
+        }
+    }
+
+    /**
+     * Recursively hide all parent menus
+     */
+    public void hideParents(boolean autoClosed) {
+        if (visibleChildMenu != null) {
+            popup.hide();
+            setSelected(null);
+            menuVisible = !autoClosed;
+        }
+
+        if (getParentMenu() != null) {
+            getParentMenu().hideParents(autoClosed);
+        }
+    }
+
+    /**
+     * Returns the parent menu of this menu, or null if this is the top-level
+     * menu
+     * 
+     * @return
+     */
+    public VMenuBar getParentMenu() {
+        return parentMenu;
+    }
+
+    /**
+     * Set the parent menu of this menu
+     * 
+     * @param parent
+     */
+    public void setParentMenu(VMenuBar parent) {
+        parentMenu = parent;
+    }
+
+    /**
+     * Returns the currently selected item of this menu, or null if nothing is
+     * selected
+     * 
+     * @return
+     */
+    public CustomMenuItem getSelected() {
+        return selected;
+    }
+
+    /**
+     * Set the currently selected item of this menu
+     * 
+     * @param item
+     */
+    public void setSelected(CustomMenuItem item) {
+        // If we had something selected, unselect
+        if (item != selected && selected != null) {
+            selected.setSelected(false);
+        }
+        // If we have a valid selection, select it
+        if (item != null) {
+            item.setSelected(true);
+        }
+
+        selected = item;
+    }
+
+    /**
+     * 
+     * A class to hold information on menu items
+     * 
+     */
+    public static class CustomMenuItem extends Widget implements HasHTML {
+
+        protected String html = null;
+        protected Command command = null;
+        protected VMenuBar subMenu = null;
+        protected VMenuBar parentMenu = null;
+        protected boolean enabled = true;
+        protected boolean isSeparator = false;
+        protected boolean checkable = false;
+        protected boolean checked = false;
+        protected boolean selected = false;
+        protected String description = null;
+
+        private String styleName;
+
+        /**
+         * Default menu item {@link Widget} constructor for GWT.create().
+         * 
+         * Use {@link #setHTML(String)} and {@link #setCommand(Command)} after
+         * constructing a menu item.
+         */
+        public CustomMenuItem() {
+            this("", null);
+        }
+
+        /**
+         * Creates a menu item {@link Widget}.
+         * 
+         * @param html
+         * @param cmd
+         * @deprecated use the default constructor and {@link #setHTML(String)}
+         *             and {@link #setCommand(Command)} instead
+         */
+        @Deprecated
+        public CustomMenuItem(String html, Command cmd) {
+            // We need spans to allow inline-block in IE
+            setElement(DOM.createSpan());
+
+            setHTML(html);
+            setCommand(cmd);
+            setSelected(false);
+        }
+
+        @Override
+        public void setStyleName(String style) {
+            super.setStyleName(style);
+            updateStyleNames();
+
+            // Pass stylename down to submenus
+            if (getSubMenu() != null) {
+                getSubMenu().setStyleName(style);
+            }
+        }
+
+        public void setSelected(boolean selected) {
+            this.selected = selected;
+            updateStyleNames();
+        }
+
+        public void setChecked(boolean checked) {
+            if (checkable && !isSeparator) {
+                this.checked = checked;
+            } else {
+                this.checked = false;
+            }
+            updateStyleNames();
+        }
+
+        public boolean isChecked() {
+            return checked;
+        }
+
+        public void setCheckable(boolean checkable) {
+            if (checkable && !isSeparator) {
+                this.checkable = true;
+            } else {
+                setChecked(false);
+                this.checkable = false;
+            }
+        }
+
+        public boolean isCheckable() {
+            return checkable;
+        }
+
+        /*
+         * setters and getters for the fields
+         */
+
+        public void setSubMenu(VMenuBar subMenu) {
+            this.subMenu = subMenu;
+        }
+
+        public VMenuBar getSubMenu() {
+            return subMenu;
+        }
+
+        public void setParentMenu(VMenuBar parentMenu) {
+            this.parentMenu = parentMenu;
+            updateStyleNames();
+        }
+
+        protected void updateStyleNames() {
+            if (parentMenu == null) {
+                // Style names depend on the parent menu's primary style name so
+                // don't do updates until the item has a parent
+                return;
+            }
+
+            String primaryStyleName = parentMenu.getStylePrimaryName();
+            if (parentMenu.subMenu) {
+                primaryStyleName = primaryStyleName.replace(
+                        SUBMENU_CLASSNAME_PREFIX, "");
+            }
+
+            if (isSeparator) {
+                super.setStyleName(primaryStyleName + "-separator");
+            } else {
+                super.setStyleName(primaryStyleName + "-menuitem");
+            }
+
+            if (styleName != null) {
+                addStyleDependentName(styleName);
+            }
+
+            if (enabled) {
+                removeStyleDependentName("disabled");
+            } else {
+                addStyleDependentName("disabled");
+            }
+
+            if (selected && isSelectable()) {
+                addStyleDependentName("selected");
+                // needed for IE6 to have a single style name to match for an
+                // element
+                // TODO Can be optimized now that IE6 is not supported any more
+                if (checkable) {
+                    if (checked) {
+                        removeStyleDependentName("selected-unchecked");
+                        addStyleDependentName("selected-checked");
+                    } else {
+                        removeStyleDependentName("selected-checked");
+                        addStyleDependentName("selected-unchecked");
+                    }
+                }
+            } else {
+                removeStyleDependentName("selected");
+                // needed for IE6 to have a single style name to match for an
+                // element
+                removeStyleDependentName("selected-checked");
+                removeStyleDependentName("selected-unchecked");
+            }
+
+            if (checkable && !isSeparator) {
+                if (checked) {
+                    addStyleDependentName("checked");
+                    removeStyleDependentName("unchecked");
+                } else {
+                    addStyleDependentName("unchecked");
+                    removeStyleDependentName("checked");
+                }
+            }
+        }
+
+        public VMenuBar getParentMenu() {
+            return parentMenu;
+        }
+
+        public void setCommand(Command command) {
+            this.command = command;
+        }
+
+        public Command getCommand() {
+            return command;
+        }
+
+        @Override
+        public String getHTML() {
+            return html;
+        }
+
+        @Override
+        public void setHTML(String html) {
+            this.html = html;
+            DOM.setInnerHTML(getElement(), html);
+
+            // Sink the onload event for any icons. The onload
+            // events are handled by the parent VMenuBar.
+            Util.sinkOnloadForImages(getElement());
+        }
+
+        @Override
+        public String getText() {
+            return html;
+        }
+
+        @Override
+        public void setText(String text) {
+            setHTML(Util.escapeHTML(text));
+        }
+
+        public void setEnabled(boolean enabled) {
+            this.enabled = enabled;
+            updateStyleNames();
+        }
+
+        public boolean isEnabled() {
+            return enabled;
+        }
+
+        private void setSeparator(boolean separator) {
+            isSeparator = separator;
+            updateStyleNames();
+            if (!separator) {
+                setEnabled(enabled);
+            }
+        }
+
+        public boolean isSeparator() {
+            return isSeparator;
+        }
+
+        public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+            setSeparator(uidl.hasAttribute("separator"));
+            setEnabled(!uidl
+                    .hasAttribute(MenuBarConstants.ATTRIBUTE_ITEM_DISABLED));
+
+            if (!isSeparator()
+                    && uidl.hasAttribute(MenuBarConstants.ATTRIBUTE_CHECKED)) {
+                // if the selected attribute is present (either true or false),
+                // the item is selectable
+                setCheckable(true);
+                setChecked(uidl
+                        .getBooleanAttribute(MenuBarConstants.ATTRIBUTE_CHECKED));
+            } else {
+                setCheckable(false);
+            }
+
+            if (uidl.hasAttribute(MenuBarConstants.ATTRIBUTE_ITEM_STYLE)) {
+                styleName = uidl
+                        .getStringAttribute(MenuBarConstants.ATTRIBUTE_ITEM_STYLE);
+            }
+
+            if (uidl.hasAttribute(MenuBarConstants.ATTRIBUTE_ITEM_DESCRIPTION)) {
+                description = uidl
+                        .getStringAttribute(MenuBarConstants.ATTRIBUTE_ITEM_DESCRIPTION);
+            }
+
+            updateStyleNames();
+        }
+
+        public TooltipInfo getTooltip() {
+            if (description == null) {
+                return null;
+            }
+
+            return new TooltipInfo(description);
+        }
+
+        /**
+         * Checks if the item can be selected.
+         * 
+         * @return true if it is possible to select this item, false otherwise
+         */
+        public boolean isSelectable() {
+            return !isSeparator() && isEnabled();
+        }
+
+    }
+
+    /**
+     * @author Jouni Koivuviita / Vaadin Ltd.
+     */
+    public void iLayout() {
+        iLayout(false);
+        updateSize();
+    }
+
+    public void iLayout(boolean iconLoadEvent) {
+        // Only collapse if there is more than one item in the root menu and the
+        // menu has an explicit size
+        if ((getItems().size() > 1 || (collapsedRootItems != null && collapsedRootItems
+                .getItems().size() > 0))
+                && getElement().getStyle().getProperty("width") != null
+                && moreItem != null) {
+
+            // Measure the width of the "more" item
+            final boolean morePresent = getItems().contains(moreItem);
+            addItem(moreItem);
+            final int moreItemWidth = moreItem.getOffsetWidth();
+            if (!morePresent) {
+                removeItem(moreItem);
+            }
+
+            int availableWidth = LayoutManager.get(client).getInnerWidth(
+                    getElement());
+
+            // Used width includes the "more" item if present
+            int usedWidth = getConsumedWidth();
+            int diff = availableWidth - usedWidth;
+            removeItem(moreItem);
+
+            if (diff < 0) {
+                // Too many items: collapse last items from root menu
+                int widthNeeded = usedWidth - availableWidth;
+                if (!morePresent) {
+                    widthNeeded += moreItemWidth;
+                }
+                int widthReduced = 0;
+
+                while (widthReduced < widthNeeded && getItems().size() > 0) {
+                    // Move last root menu item to collapsed menu
+                    CustomMenuItem collapse = getItems().get(
+                            getItems().size() - 1);
+                    widthReduced += collapse.getOffsetWidth();
+                    removeItem(collapse);
+                    collapsedRootItems.addItem(collapse, 0);
+                }
+            } else if (collapsedRootItems.getItems().size() > 0) {
+                // Space available for items: expand first items from collapsed
+                // menu
+                int widthAvailable = diff + moreItemWidth;
+                int widthGrowth = 0;
+
+                while (widthAvailable > widthGrowth
+                        && collapsedRootItems.getItems().size() > 0) {
+                    // Move first item from collapsed menu to the root menu
+                    CustomMenuItem expand = collapsedRootItems.getItems()
+                            .get(0);
+                    collapsedRootItems.removeItem(expand);
+                    addItem(expand);
+                    widthGrowth += expand.getOffsetWidth();
+                    if (collapsedRootItems.getItems().size() > 0) {
+                        widthAvailable -= moreItemWidth;
+                    }
+                    if (widthGrowth > widthAvailable) {
+                        removeItem(expand);
+                        collapsedRootItems.addItem(expand, 0);
+                    } else {
+                        widthAvailable = diff + moreItemWidth;
+                    }
+                }
+            }
+            if (collapsedRootItems.getItems().size() > 0) {
+                addItem(moreItem);
+            }
+        }
+
+        // If a popup is open we might need to adjust the shadow as well if an
+        // icon shown in that popup was loaded
+        if (popup != null) {
+            // Forces a recalculation of the shadow size
+            popup.show();
+        }
+        if (iconLoadEvent) {
+            // Size have changed if the width is undefined
+            Util.notifyParentOfSizeChange(this, false);
+        }
+    }
+
+    private int getConsumedWidth() {
+        int w = 0;
+        for (CustomMenuItem item : getItems()) {
+            if (!collapsedRootItems.getItems().contains(item)) {
+                w += item.getOffsetWidth();
+            }
+        }
+        return w;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google
+     * .gwt.event.dom.client.KeyPressEvent)
+     */
+    @Override
+    public void onKeyPress(KeyPressEvent event) {
+        if (handleNavigation(event.getNativeEvent().getKeyCode(),
+                event.isControlKeyDown() || event.isMetaKeyDown(),
+                event.isShiftKeyDown())) {
+            event.preventDefault();
+        }
+    }
+
+    /*
+     * (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 (handleNavigation(event.getNativeEvent().getKeyCode(),
+                event.isControlKeyDown() || event.isMetaKeyDown(),
+                event.isShiftKeyDown())) {
+            event.preventDefault();
+        }
+    }
+
+    /**
+     * Get the key that moves the selection upwards. By default it is the up
+     * arrow key but by overriding this you can change the key to whatever you
+     * want.
+     * 
+     * @return The keycode of the key
+     */
+    protected int getNavigationUpKey() {
+        return KeyCodes.KEY_UP;
+    }
+
+    /**
+     * Get the key that moves the selection downwards. By default it is the down
+     * arrow key but by overriding this you can change the key to whatever you
+     * want.
+     * 
+     * @return The keycode of the key
+     */
+    protected int getNavigationDownKey() {
+        return KeyCodes.KEY_DOWN;
+    }
+
+    /**
+     * Get the key that moves the selection left. By default it is the left
+     * arrow key but by overriding this you can change the key to whatever you
+     * want.
+     * 
+     * @return The keycode of the key
+     */
+    protected int getNavigationLeftKey() {
+        return KeyCodes.KEY_LEFT;
+    }
+
+    /**
+     * Get the key that moves the selection right. By default it is the right
+     * arrow key but by overriding this you can change the key to whatever you
+     * want.
+     * 
+     * @return The keycode of the key
+     */
+    protected int getNavigationRightKey() {
+        return KeyCodes.KEY_RIGHT;
+    }
+
+    /**
+     * Get the key that selects a menu item. By default it is the Enter key but
+     * by overriding this you can change the key to whatever you want.
+     * 
+     * @return
+     */
+    protected int getNavigationSelectKey() {
+        return KeyCodes.KEY_ENTER;
+    }
+
+    /**
+     * Get the key that closes the menu. By default it is the escape key but by
+     * overriding this yoy can change the key to whatever you want.
+     * 
+     * @return
+     */
+    protected int getCloseMenuKey() {
+        return KeyCodes.KEY_ESCAPE;
+    }
+
+    /**
+     * Handles the keyboard events handled by the MenuBar
+     * 
+     * @param event
+     *            The keyboard event received
+     * @return true iff the navigation event was handled
+     */
+    public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
+
+        // If tab or shift+tab close menus
+        if (keycode == KeyCodes.KEY_TAB) {
+            setSelected(null);
+            hideChildren();
+            menuVisible = false;
+            return false;
+        }
+
+        if (ctrl || shift || !isEnabled()) {
+            // Do not handle tab key, nor ctrl keys
+            return false;
+        }
+
+        if (keycode == getNavigationLeftKey()) {
+            if (getSelected() == null) {
+                // If nothing is selected then select the last item
+                setSelected(items.get(items.size() - 1));
+                if (!getSelected().isSelectable()) {
+                    handleNavigation(keycode, ctrl, shift);
+                }
+            } else if (visibleChildMenu == null && getParentMenu() == null) {
+                // If this is the root menu then move to the left
+                int idx = items.indexOf(getSelected());
+                if (idx > 0) {
+                    setSelected(items.get(idx - 1));
+                } else {
+                    setSelected(items.get(items.size() - 1));
+                }
+
+                if (!getSelected().isSelectable()) {
+                    handleNavigation(keycode, ctrl, shift);
+                }
+            } else if (visibleChildMenu != null) {
+                // Redirect all navigation to the submenu
+                visibleChildMenu.handleNavigation(keycode, ctrl, shift);
+
+            } else if (getParentMenu().getParentMenu() == null) {
+                // Inside a sub menu, whose parent is a root menu item
+                VMenuBar root = getParentMenu();
+
+                root.getSelected().getSubMenu().setSelected(null);
+                root.hideChildren();
+
+                // Get the root menus items and select the previous one
+                int idx = root.getItems().indexOf(root.getSelected());
+                idx = idx > 0 ? idx : root.getItems().size();
+                CustomMenuItem selected = root.getItems().get(--idx);
+
+                while (selected.isSeparator() || !selected.isEnabled()) {
+                    idx = idx > 0 ? idx : root.getItems().size();
+                    selected = root.getItems().get(--idx);
+                }
+
+                root.setSelected(selected);
+                openMenuAndFocusFirstIfPossible(selected);
+            } else {
+                getParentMenu().getSelected().getSubMenu().setSelected(null);
+                getParentMenu().hideChildren();
+            }
+
+            return true;
+
+        } else if (keycode == getNavigationRightKey()) {
+
+            if (getSelected() == null) {
+                // If nothing is selected then select the first item
+                setSelected(items.get(0));
+                if (!getSelected().isSelectable()) {
+                    handleNavigation(keycode, ctrl, shift);
+                }
+            } else if (visibleChildMenu == null && getParentMenu() == null) {
+                // If this is the root menu then move to the right
+                int idx = items.indexOf(getSelected());
+
+                if (idx < items.size() - 1) {
+                    setSelected(items.get(idx + 1));
+                } else {
+                    setSelected(items.get(0));
+                }
+
+                if (!getSelected().isSelectable()) {
+                    handleNavigation(keycode, ctrl, shift);
+                }
+            } else if (visibleChildMenu == null
+                    && getSelected().getSubMenu() != null) {
+                // If the item has a submenu then show it and move the selection
+                // there
+                showChildMenu(getSelected());
+                menuVisible = true;
+                visibleChildMenu.handleNavigation(keycode, ctrl, shift);
+            } else if (visibleChildMenu == null) {
+
+                // Get the root menu
+                VMenuBar root = getParentMenu();
+                while (root.getParentMenu() != null) {
+                    root = root.getParentMenu();
+                }
+
+                // Hide the submenu
+                root.hideChildren();
+
+                // Get the root menus items and select the next one
+                int idx = root.getItems().indexOf(root.getSelected());
+                idx = idx < root.getItems().size() - 1 ? idx : -1;
+                CustomMenuItem selected = root.getItems().get(++idx);
+
+                while (selected.isSeparator() || !selected.isEnabled()) {
+                    idx = idx < root.getItems().size() - 1 ? idx : -1;
+                    selected = root.getItems().get(++idx);
+                }
+
+                root.setSelected(selected);
+                openMenuAndFocusFirstIfPossible(selected);
+            } else if (visibleChildMenu != null) {
+                // Redirect all navigation to the submenu
+                visibleChildMenu.handleNavigation(keycode, ctrl, shift);
+            }
+
+            return true;
+
+        } else if (keycode == getNavigationUpKey()) {
+
+            if (getSelected() == null) {
+                // If nothing is selected then select the last item
+                setSelected(items.get(items.size() - 1));
+                if (!getSelected().isSelectable()) {
+                    handleNavigation(keycode, ctrl, shift);
+                }
+            } else if (visibleChildMenu != null) {
+                // Redirect all navigation to the submenu
+                visibleChildMenu.handleNavigation(keycode, ctrl, shift);
+            } else {
+                // Select the previous item if possible or loop to the last item
+                int idx = items.indexOf(getSelected());
+                if (idx > 0) {
+                    setSelected(items.get(idx - 1));
+                } else {
+                    setSelected(items.get(items.size() - 1));
+                }
+
+                if (!getSelected().isSelectable()) {
+                    handleNavigation(keycode, ctrl, shift);
+                }
+            }
+
+            return true;
+
+        } else if (keycode == getNavigationDownKey()) {
+
+            if (getSelected() == null) {
+                // If nothing is selected then select the first item
+                selectFirstItem();
+            } else if (visibleChildMenu == null && getParentMenu() == null) {
+                // If this is the root menu the show the child menu with arrow
+                // down, if there is a child menu
+                openMenuAndFocusFirstIfPossible(getSelected());
+            } else if (visibleChildMenu != null) {
+                // Redirect all navigation to the submenu
+                visibleChildMenu.handleNavigation(keycode, ctrl, shift);
+            } else {
+                // Select the next item if possible or loop to the first item
+                int idx = items.indexOf(getSelected());
+                if (idx < items.size() - 1) {
+                    setSelected(items.get(idx + 1));
+                } else {
+                    setSelected(items.get(0));
+                }
+
+                if (!getSelected().isSelectable()) {
+                    handleNavigation(keycode, ctrl, shift);
+                }
+            }
+            return true;
+
+        } else if (keycode == getCloseMenuKey()) {
+            setSelected(null);
+            hideChildren();
+            menuVisible = false;
+
+        } else if (keycode == getNavigationSelectKey()) {
+            if (getSelected() == null) {
+                // If nothing is selected then select the first item
+                selectFirstItem();
+            } else if (visibleChildMenu != null) {
+                // Redirect all navigation to the submenu
+                visibleChildMenu.handleNavigation(keycode, ctrl, shift);
+                menuVisible = false;
+            } else if (visibleChildMenu == null
+                    && getSelected().getSubMenu() != null) {
+                // If the item has a sub menu then show it and move the
+                // selection there
+                openMenuAndFocusFirstIfPossible(getSelected());
+            } else {
+                Command command = getSelected().getCommand();
+                if (command != null) {
+                    command.execute();
+                }
+
+                setSelected(null);
+                hideParents(true);
+            }
+        }
+
+        return false;
+    }
+
+    private void selectFirstItem() {
+        for (int i = 0; i < items.size(); i++) {
+            CustomMenuItem item = items.get(i);
+            if (item.isSelectable()) {
+                setSelected(item);
+                break;
+            }
+        }
+    }
+
+    private void openMenuAndFocusFirstIfPossible(CustomMenuItem menuItem) {
+        VMenuBar subMenu = menuItem.getSubMenu();
+        if (subMenu == null) {
+            // No child menu? Nothing to do
+            return;
+        }
+
+        VMenuBar parentMenu = menuItem.getParentMenu();
+        parentMenu.showChildMenu(menuItem);
+
+        menuVisible = true;
+        // Select the first item in the newly open submenu
+        subMenu.selectFirstItem();
+
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
+     * .dom.client.FocusEvent)
+     */
+    @Override
+    public void onFocus(FocusEvent event) {
+
+    }
+
+    private final String SUBPART_PREFIX = "item";
+
+    @Override
+    public Element getSubPartElement(String subPart) {
+        int index = Integer
+                .parseInt(subPart.substring(SUBPART_PREFIX.length()));
+        CustomMenuItem item = getItems().get(index);
+
+        return item.getElement();
+    }
+
+    @Override
+    public String getSubPartName(Element subElement) {
+        if (!getElement().isOrHasChild(subElement)) {
+            return null;
+        }
+
+        Element menuItemRoot = subElement;
+        while (menuItemRoot != null && menuItemRoot.getParentElement() != null
+                && menuItemRoot.getParentElement() != getElement()) {
+            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;
+    }
+
+    /**
+     * Get menu item with given DOM element
+     * 
+     * @param element
+     *            Element used in search
+     * @return Menu item or null if not found
+     */
+    public CustomMenuItem getMenuItemWithElement(Element element) {
+        for (int i = 0; i < items.size(); i++) {
+            CustomMenuItem item = items.get(i);
+            if (DOM.isOrHasChild(item.getElement(), element)) {
+                return item;
+            }
+
+            if (item.getSubMenu() != null) {
+                item = item.getSubMenu().getMenuItemWithElement(element);
+                if (item != null) {
+                    return item;
+                }
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VNativeButton.java b/client/src/com/vaadin/client/ui/VNativeButton.java
new file mode 100644 (file)
index 0000000..f68326c
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.Button;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.MouseEventDetailsBuilder;
+import com.vaadin.client.Util;
+import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.ui.button.ButtonServerRpc;
+
+public class VNativeButton extends Button implements ClickHandler {
+
+    public static final String CLASSNAME = "v-nativebutton";
+
+    protected String width = null;
+
+    /** 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 ApplicationConnection client;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public ButtonServerRpc buttonRpcProxy;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public Element errorIndicatorElement;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public final Element captionElement = DOM.createSpan();
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public Icon icon;
+
+    /**
+     * Helper flag to handle special-case where the button is moved from under
+     * mouse while clicking it. In this case mouse leaves the button without
+     * moving.
+     */
+    private boolean clickPending;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean disableOnClick = false;
+
+    public VNativeButton() {
+        setStyleName(CLASSNAME);
+
+        getElement().appendChild(captionElement);
+        captionElement.setClassName(getStyleName() + "-caption");
+
+        addClickHandler(this);
+
+        sinkEvents(Event.ONMOUSEDOWN);
+        sinkEvents(Event.ONMOUSEUP);
+    }
+
+    @Override
+    public void setText(String text) {
+        captionElement.setInnerText(text);
+    }
+
+    @Override
+    public void setHTML(String html) {
+        captionElement.setInnerHTML(html);
+    }
+
+    @Override
+    public void onBrowserEvent(Event event) {
+        super.onBrowserEvent(event);
+
+        if (DOM.eventGetType(event) == Event.ONLOAD) {
+            Util.notifyParentOfSizeChange(this, true);
+
+        } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN
+                && event.getButton() == Event.BUTTON_LEFT) {
+            clickPending = true;
+        } else if (DOM.eventGetType(event) == Event.ONMOUSEMOVE) {
+            clickPending = false;
+        } else if (DOM.eventGetType(event) == Event.ONMOUSEOUT) {
+            if (clickPending) {
+                click();
+            }
+            clickPending = false;
+        }
+    }
+
+    @Override
+    public void setWidth(String width) {
+        this.width = width;
+        super.setWidth(width);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event
+     * .dom.client.ClickEvent)
+     */
+    @Override
+    public void onClick(ClickEvent event) {
+        if (paintableId == null || client == null) {
+            return;
+        }
+
+        if (BrowserInfo.get().isSafari()) {
+            VNativeButton.this.setFocus(true);
+        }
+        if (disableOnClick) {
+            setEnabled(false);
+            // FIXME: This should be moved to NativeButtonConnector along with
+            // buttonRpcProxy
+            addStyleName(ApplicationConnection.DISABLED_CLASSNAME);
+            buttonRpcProxy.disableOnClick();
+        }
+
+        // Add mouse details
+        MouseEventDetails details = MouseEventDetailsBuilder
+                .buildMouseEventDetails(event.getNativeEvent(), getElement());
+        buttonRpcProxy.click(details);
+
+        clickPending = false;
+    }
+
+}
diff --git a/client/src/com/vaadin/client/ui/VNativeSelect.java b/client/src/com/vaadin/client/ui/VNativeSelect.java
new file mode 100644 (file)
index 0000000..5a64c39
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.user.client.ui.ListBox;
+import com.vaadin.client.UIDL;
+
+public class VNativeSelect extends VOptionGroupBase implements Field {
+
+    public static final String CLASSNAME = "v-select";
+
+    protected ListBox select;
+
+    private boolean firstValueIsTemporaryNullItem = false;
+
+    public VNativeSelect() {
+        super(new ListBox(false), CLASSNAME);
+        select = getOptionsContainer();
+        select.setVisibleItemCount(1);
+        select.addChangeHandler(this);
+        select.setStyleName(CLASSNAME + "-select");
+
+    }
+
+    protected ListBox getOptionsContainer() {
+        return (ListBox) optionsContainer;
+    }
+
+    @Override
+    public void buildOptions(UIDL uidl) {
+        select.setEnabled(!isDisabled() && !isReadonly());
+        select.clear();
+        firstValueIsTemporaryNullItem = false;
+
+        if (isNullSelectionAllowed() && !isNullSelectionItemAvailable()) {
+            // can't unselect last item in singleselect mode
+            select.addItem("", (String) null);
+        }
+        boolean selected = false;
+        for (final Iterator<?> i = uidl.getChildIterator(); i.hasNext();) {
+            final UIDL optionUidl = (UIDL) i.next();
+            select.addItem(optionUidl.getStringAttribute("caption"),
+                    optionUidl.getStringAttribute("key"));
+            if (optionUidl.hasAttribute("selected")) {
+                select.setItemSelected(select.getItemCount() - 1, true);
+                selected = true;
+            }
+        }
+        if (!selected && !isNullSelectionAllowed()) {
+            // null-select not allowed, but value not selected yet; add null and
+            // remove when something is selected
+            select.insertItem("", (String) null, 0);
+            select.setItemSelected(0, true);
+            firstValueIsTemporaryNullItem = true;
+        }
+    }
+
+    @Override
+    protected String[] getSelectedItems() {
+        final ArrayList<String> selectedItemKeys = new ArrayList<String>();
+        for (int i = 0; i < select.getItemCount(); i++) {
+            if (select.isItemSelected(i)) {
+                selectedItemKeys.add(select.getValue(i));
+            }
+        }
+        return selectedItemKeys.toArray(new String[selectedItemKeys.size()]);
+    }
+
+    @Override
+    public void onChange(ChangeEvent event) {
+
+        if (select.isMultipleSelect()) {
+            client.updateVariable(paintableId, "selected", getSelectedItems(),
+                    isImmediate());
+        } else {
+            client.updateVariable(paintableId, "selected", new String[] { ""
+                    + getSelectedItem() }, isImmediate());
+        }
+        if (firstValueIsTemporaryNullItem) {
+            // remove temporary empty item
+            select.removeItem(0);
+            firstValueIsTemporaryNullItem = false;
+        }
+    }
+
+    @Override
+    public void setHeight(String height) {
+        select.setHeight(height);
+        super.setHeight(height);
+    }
+
+    @Override
+    public void setWidth(String width) {
+        select.setWidth(width);
+        super.setWidth(width);
+    }
+
+    @Override
+    public void setTabIndex(int tabIndex) {
+        getOptionsContainer().setTabIndex(tabIndex);
+    }
+
+    @Override
+    public void focus() {
+        select.setFocus(true);
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VNotification.java b/client/src/com/vaadin/client/ui/VNotification.java
new file mode 100644 (file)
index 0000000..16ae698
--- /dev/null
@@ -0,0 +1,472 @@
+/* 
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.EventObject;
+import java.util.Iterator;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.Util;
+import com.vaadin.shared.Position;
+import com.vaadin.shared.ui.ui.UIConstants;
+
+public class VNotification extends VOverlay {
+
+    public static final Position CENTERED = Position.MIDDLE_CENTER;
+    public static final Position CENTERED_TOP = Position.TOP_CENTER;
+    public static final Position CENTERED_BOTTOM = Position.BOTTOM_CENTER;
+    public static final Position TOP_LEFT = Position.TOP_LEFT;
+    public static final Position TOP_RIGHT = Position.TOP_RIGHT;
+    public static final Position BOTTOM_LEFT = Position.BOTTOM_LEFT;
+    public static final Position BOTTOM_RIGHT = Position.BOTTOM_RIGHT;
+
+    public static final int DELAY_FOREVER = -1;
+    public static final int DELAY_NONE = 0;
+
+    private static final String STYLENAME = "v-Notification";
+    private static final int mouseMoveThreshold = 7;
+    private static final int Z_INDEX_BASE = 20000;
+    public static final String STYLE_SYSTEM = "system";
+    private static final int FADE_ANIMATION_INTERVAL = 50; // == 20 fps
+
+    private static final ArrayList<VNotification> notifications = new ArrayList<VNotification>();
+
+    private int startOpacity = 90;
+    private int fadeMsec = 400;
+    private int delayMsec = 1000;
+
+    private Timer fader;
+    private Timer delay;
+
+    private int x = -1;
+    private int y = -1;
+
+    private String temporaryStyle;
+
+    private ArrayList<EventListener> listeners;
+    private static final int TOUCH_DEVICE_IDLE_DELAY = 1000;
+
+    /**
+     * Default constructor. You should use GWT.create instead.
+     */
+    public VNotification() {
+        setStyleName(STYLENAME);
+        sinkEvents(Event.ONCLICK);
+        DOM.setStyleAttribute(getElement(), "zIndex", "" + Z_INDEX_BASE);
+    }
+
+    /**
+     * @deprecated Use static {@link #createNotification(int)} instead to enable
+     *             GWT deferred binding.
+     * 
+     * @param delayMsec
+     */
+    @Deprecated
+    public VNotification(int delayMsec) {
+        this();
+        this.delayMsec = delayMsec;
+        if (BrowserInfo.get().isTouchDevice()) {
+            new Timer() {
+                @Override
+                public void run() {
+                    if (isAttached()) {
+                        fade();
+                    }
+                }
+            }.schedule(delayMsec + TOUCH_DEVICE_IDLE_DELAY);
+        }
+    }
+
+    /**
+     * @deprecated Use static {@link #createNotification(int, int, int)} instead
+     *             to enable GWT deferred binding.
+     * 
+     * @param delayMsec
+     * @param fadeMsec
+     * @param startOpacity
+     */
+    @Deprecated
+    public VNotification(int delayMsec, int fadeMsec, int startOpacity) {
+        this(delayMsec);
+        this.fadeMsec = fadeMsec;
+        this.startOpacity = startOpacity;
+    }
+
+    public void startDelay() {
+        DOM.removeEventPreview(this);
+        if (delayMsec > 0) {
+            if (delay == null) {
+                delay = new Timer() {
+                    @Override
+                    public void run() {
+                        fade();
+                    }
+                };
+                delay.schedule(delayMsec);
+            }
+        } else if (delayMsec == 0) {
+            fade();
+        }
+    }
+
+    @Override
+    public void show() {
+        show(CENTERED);
+    }
+
+    public void show(String style) {
+        show(CENTERED, style);
+    }
+
+    public void show(com.vaadin.shared.Position position) {
+        show(position, null);
+    }
+
+    public void show(Widget widget, Position position, String style) {
+        setWidget(widget);
+        show(position, style);
+    }
+
+    public void show(String html, Position position, String style) {
+        setWidget(new HTML(html));
+        show(position, style);
+    }
+
+    public void show(Position position, String style) {
+        setOpacity(getElement(), startOpacity);
+        if (style != null) {
+            temporaryStyle = style;
+            addStyleName(style);
+            addStyleDependentName(style);
+        }
+        super.show();
+        notifications.add(this);
+        setPosition(position);
+        positionOrSizeUpdated();
+        /**
+         * Android 4 fails to render notifications correctly without a little
+         * nudge (#8551)
+         */
+        if (BrowserInfo.get().isAndroid()) {
+            Util.setStyleTemporarily(getElement(), "display", "none");
+        }
+    }
+
+    @Override
+    public void hide() {
+        DOM.removeEventPreview(this);
+        cancelDelay();
+        cancelFade();
+        if (temporaryStyle != null) {
+            removeStyleName(temporaryStyle);
+            removeStyleDependentName(temporaryStyle);
+            temporaryStyle = null;
+        }
+        super.hide();
+        notifications.remove(this);
+        fireEvent(new HideEvent(this));
+    }
+
+    public void fade() {
+        DOM.removeEventPreview(this);
+        cancelDelay();
+        if (fader == null) {
+            fader = new Timer() {
+                private final long start = new Date().getTime();
+
+                @Override
+                public void run() {
+                    /*
+                     * To make animation smooth, don't count that event happens
+                     * on time. Reduce opacity according to the actual time
+                     * spent instead of fixed decrement.
+                     */
+                    long now = new Date().getTime();
+                    long timeEplaced = now - start;
+                    float remainingFraction = 1 - timeEplaced
+                            / (float) fadeMsec;
+                    int opacity = (int) (startOpacity * remainingFraction);
+                    if (opacity <= 0) {
+                        cancel();
+                        hide();
+                        if (BrowserInfo.get().isOpera()) {
+                            // tray notification on opera needs to explicitly
+                            // define
+                            // size, reset it
+                            DOM.setStyleAttribute(getElement(), "width", "");
+                            DOM.setStyleAttribute(getElement(), "height", "");
+                        }
+                    } else {
+                        setOpacity(getElement(), opacity);
+                    }
+                }
+            };
+            fader.scheduleRepeating(FADE_ANIMATION_INTERVAL);
+        }
+    }
+
+    public void setPosition(com.vaadin.shared.Position position) {
+        final Element el = getElement();
+        DOM.setStyleAttribute(el, "top", "");
+        DOM.setStyleAttribute(el, "left", "");
+        DOM.setStyleAttribute(el, "bottom", "");
+        DOM.setStyleAttribute(el, "right", "");
+        switch (position) {
+        case TOP_LEFT:
+            DOM.setStyleAttribute(el, "top", "0px");
+            DOM.setStyleAttribute(el, "left", "0px");
+            break;
+        case TOP_RIGHT:
+            DOM.setStyleAttribute(el, "top", "0px");
+            DOM.setStyleAttribute(el, "right", "0px");
+            break;
+        case BOTTOM_RIGHT:
+            DOM.setStyleAttribute(el, "position", "absolute");
+            if (BrowserInfo.get().isOpera()) {
+                // tray notification on opera needs explicitly defined size
+                DOM.setStyleAttribute(el, "width", getOffsetWidth() + "px");
+                DOM.setStyleAttribute(el, "height", getOffsetHeight() + "px");
+            }
+            DOM.setStyleAttribute(el, "bottom", "0px");
+            DOM.setStyleAttribute(el, "right", "0px");
+            break;
+        case BOTTOM_LEFT:
+            DOM.setStyleAttribute(el, "bottom", "0px");
+            DOM.setStyleAttribute(el, "left", "0px");
+            break;
+        case TOP_CENTER:
+            center();
+            DOM.setStyleAttribute(el, "top", "0px");
+            break;
+        case BOTTOM_CENTER:
+            center();
+            DOM.setStyleAttribute(el, "top", "");
+            DOM.setStyleAttribute(el, "bottom", "0px");
+            break;
+        default:
+        case MIDDLE_CENTER:
+            center();
+            break;
+        }
+    }
+
+    private void cancelFade() {
+        if (fader != null) {
+            fader.cancel();
+            fader = null;
+        }
+    }
+
+    private void cancelDelay() {
+        if (delay != null) {
+            delay.cancel();
+            delay = null;
+        }
+    }
+
+    private void setOpacity(Element el, int opacity) {
+        DOM.setStyleAttribute(el, "opacity", "" + (opacity / 100.0));
+        if (BrowserInfo.get().isIE()) {
+            DOM.setStyleAttribute(el, "filter", "Alpha(opacity=" + opacity
+                    + ")");
+        }
+    }
+
+    @Override
+    public void onBrowserEvent(Event event) {
+        DOM.removeEventPreview(this);
+        if (fader == null) {
+            fade();
+        }
+    }
+
+    @Override
+    public boolean onEventPreview(Event event) {
+        int type = DOM.eventGetType(event);
+        // "modal"
+        if (delayMsec == -1 || temporaryStyle == STYLE_SYSTEM) {
+            if (type == Event.ONCLICK) {
+                if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) {
+                    fade();
+                    return false;
+                }
+            } else if (type == Event.ONKEYDOWN
+                    && event.getKeyCode() == KeyCodes.KEY_ESCAPE) {
+                fade();
+                return false;
+            }
+            if (temporaryStyle == STYLE_SYSTEM) {
+                return true;
+            } else {
+                return false;
+            }
+        }
+        // default
+        switch (type) {
+        case Event.ONMOUSEMOVE:
+
+            if (x < 0) {
+                x = DOM.eventGetClientX(event);
+                y = DOM.eventGetClientY(event);
+            } else if (Math.abs(DOM.eventGetClientX(event) - x) > mouseMoveThreshold
+                    || Math.abs(DOM.eventGetClientY(event) - y) > mouseMoveThreshold) {
+                startDelay();
+            }
+            break;
+        case Event.ONMOUSEDOWN:
+        case Event.ONMOUSEWHEEL:
+        case Event.ONSCROLL:
+            startDelay();
+            break;
+        case Event.ONKEYDOWN:
+            if (event.getRepeat()) {
+                return true;
+            }
+            startDelay();
+            break;
+        default:
+            break;
+        }
+        return true;
+    }
+
+    public void addEventListener(EventListener listener) {
+        if (listeners == null) {
+            listeners = new ArrayList<EventListener>();
+        }
+        listeners.add(listener);
+    }
+
+    public void removeEventListener(EventListener listener) {
+        if (listeners == null) {
+            return;
+        }
+        listeners.remove(listener);
+    }
+
+    private void fireEvent(HideEvent event) {
+        if (listeners != null) {
+            for (Iterator<EventListener> it = listeners.iterator(); it
+                    .hasNext();) {
+                EventListener l = it.next();
+                l.notificationHidden(event);
+            }
+        }
+    }
+
+    public static void showNotification(ApplicationConnection client,
+            final UIDL notification) {
+        boolean onlyPlainText = notification
+                .hasAttribute(UIConstants.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED);
+        String html = "";
+        if (notification.hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON)) {
+            final String parsedUri = client
+                    .translateVaadinUri(notification
+                            .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON));
+            html += "<img src=\"" + Util.escapeAttribute(parsedUri) + "\" />";
+        }
+        if (notification
+                .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION)) {
+            String caption = notification
+                    .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION);
+            if (onlyPlainText) {
+                caption = Util.escapeHTML(caption);
+                caption = caption.replaceAll("\\n", "<br />");
+            }
+            html += "<h1>" + caption + "</h1>";
+        }
+        if (notification
+                .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_MESSAGE)) {
+            String message = notification
+                    .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_MESSAGE);
+            if (onlyPlainText) {
+                message = Util.escapeHTML(message);
+                message = message.replaceAll("\\n", "<br />");
+            }
+            html += "<p>" + message + "</p>";
+        }
+
+        final String style = notification
+                .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_STYLE) ? notification
+                .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_STYLE)
+                : null;
+
+        final int pos = notification
+                .getIntAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_POSITION);
+        Position position = Position.values()[pos];
+
+        final int delay = notification
+                .getIntAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_DELAY);
+        createNotification(delay, client.getUIConnector().getWidget()).show(
+                html, position, style);
+    }
+
+    public static VNotification createNotification(int delayMsec, Widget owner) {
+        final VNotification notification = GWT.create(VNotification.class);
+        notification.delayMsec = delayMsec;
+        if (BrowserInfo.get().isTouchDevice()) {
+            new Timer() {
+                @Override
+                public void run() {
+                    if (notification.isAttached()) {
+                        notification.fade();
+                    }
+                }
+            }.schedule(notification.delayMsec + TOUCH_DEVICE_IDLE_DELAY);
+        }
+        notification.setOwner(owner);
+        return notification;
+    }
+
+    public class HideEvent extends EventObject {
+
+        public HideEvent(Object source) {
+            super(source);
+        }
+    }
+
+    public interface EventListener extends java.util.EventListener {
+        public void notificationHidden(HideEvent event);
+    }
+
+    /**
+     * Moves currently visible notifications to the top of the event preview
+     * stack. Can be called when opening other overlays such as subwindows to
+     * ensure the notifications receive the events they need and don't linger
+     * indefinitely. See #7136.
+     * 
+     * TODO Should this be a generic Overlay feature instead?
+     */
+    public static void bringNotificationsToFront() {
+        for (VNotification notification : notifications) {
+            DOM.removeEventPreview(notification);
+            DOM.addEventPreview(notification);
+        }
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VOptionGroup.java b/client/src/com/vaadin/client/ui/VOptionGroup.java
new file mode 100644 (file)
index 0000000..b928956
--- /dev/null
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import com.google.gwt.core.client.Scheduler;
+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.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.LoadEvent;
+import com.google.gwt.event.dom.client.LoadHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.FocusWidget;
+import com.google.gwt.user.client.ui.Focusable;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.RadioButton;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.Util;
+import com.vaadin.shared.EventId;
+import com.vaadin.shared.ui.optiongroup.OptionGroupConstants;
+
+public class VOptionGroup extends VOptionGroupBase implements FocusHandler,
+        BlurHandler {
+
+    public static final String CLASSNAME = "v-select-optiongroup";
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public final Panel panel;
+
+    private final Map<CheckBox, String> optionsToKeys;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean sendFocusEvents = false;
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean sendBlurEvents = false;
+    /** For internal use only. May be removed or replaced in the future. */
+    public List<HandlerRegistration> focusHandlers = null;
+    /** For internal use only. May be removed or replaced in the future. */
+    public List<HandlerRegistration> blurHandlers = null;
+
+    private final LoadHandler iconLoadHandler = new LoadHandler() {
+        @Override
+        public void onLoad(LoadEvent event) {
+            Util.notifyParentOfSizeChange(VOptionGroup.this, true);
+        }
+    };
+
+    /**
+     * used to check whether a blur really was a blur of the complete
+     * optiongroup: if a control inside this optiongroup gains focus right after
+     * blur of another control inside this optiongroup (meaning: if onFocus
+     * fires after onBlur has fired), the blur and focus won't be sent to the
+     * server side as only a focus change inside this optiongroup occured
+     */
+    private boolean blurOccured = false;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean htmlContentAllowed = false;
+
+    public VOptionGroup() {
+        super(CLASSNAME);
+        panel = (Panel) optionsContainer;
+        optionsToKeys = new HashMap<CheckBox, String>();
+    }
+
+    /*
+     * Return true if no elements were changed, false otherwise.
+     */
+    @Override
+    public void buildOptions(UIDL uidl) {
+        panel.clear();
+        for (final Iterator<?> it = uidl.getChildIterator(); it.hasNext();) {
+            final UIDL opUidl = (UIDL) it.next();
+            CheckBox op;
+
+            String itemHtml = opUidl.getStringAttribute("caption");
+            if (!htmlContentAllowed) {
+                itemHtml = Util.escapeHTML(itemHtml);
+            }
+
+            String icon = opUidl.getStringAttribute("icon");
+            if (icon != null && icon.length() != 0) {
+                String iconUrl = client.translateVaadinUri(icon);
+                itemHtml = "<img src=\"" + iconUrl + "\" class=\""
+                        + Icon.CLASSNAME + "\" alt=\"\" />" + itemHtml;
+            }
+
+            if (isMultiselect()) {
+                op = new VCheckBox();
+                op.setHTML(itemHtml);
+            } else {
+                op = new RadioButton(paintableId, itemHtml, true);
+                op.setStyleName("v-radiobutton");
+            }
+
+            if (icon != null && icon.length() != 0) {
+                Util.sinkOnloadForImages(op.getElement());
+                op.addHandler(iconLoadHandler, LoadEvent.getType());
+            }
+
+            op.addStyleName(CLASSNAME_OPTION);
+            op.setValue(opUidl.getBooleanAttribute("selected"));
+            boolean enabled = !opUidl
+                    .getBooleanAttribute(OptionGroupConstants.ATTRIBUTE_OPTION_DISABLED)
+                    && !isReadonly() && !isDisabled();
+            op.setEnabled(enabled);
+            setStyleName(op.getElement(),
+                    ApplicationConnection.DISABLED_CLASSNAME, !enabled);
+            op.addClickHandler(this);
+            optionsToKeys.put(op, opUidl.getStringAttribute("key"));
+            panel.add(op);
+        }
+    }
+
+    @Override
+    protected String[] getSelectedItems() {
+        return selectedKeys.toArray(new String[selectedKeys.size()]);
+    }
+
+    @Override
+    public void onClick(ClickEvent event) {
+        super.onClick(event);
+        if (event.getSource() instanceof CheckBox) {
+            final boolean selected = ((CheckBox) event.getSource()).getValue();
+            final String key = optionsToKeys.get(event.getSource());
+            if (!isMultiselect()) {
+                selectedKeys.clear();
+            }
+            if (selected) {
+                selectedKeys.add(key);
+            } else {
+                selectedKeys.remove(key);
+            }
+            client.updateVariable(paintableId, "selected", getSelectedItems(),
+                    isImmediate());
+        }
+    }
+
+    @Override
+    public void setTabIndex(int tabIndex) {
+        for (Iterator<Widget> iterator = panel.iterator(); iterator.hasNext();) {
+            FocusWidget widget = (FocusWidget) iterator.next();
+            widget.setTabIndex(tabIndex);
+        }
+    }
+
+    @Override
+    public void focus() {
+        Iterator<Widget> iterator = panel.iterator();
+        if (iterator.hasNext()) {
+            ((Focusable) iterator.next()).setFocus(true);
+        }
+    }
+
+    @Override
+    public void onFocus(FocusEvent arg0) {
+        if (!blurOccured) {
+            // no blur occured before this focus event
+            // panel was blurred => fire the event to the server side if
+            // requested by server side
+            if (sendFocusEvents) {
+                client.updateVariable(paintableId, EventId.FOCUS, "", true);
+            }
+        } else {
+            // blur occured before this focus event
+            // another control inside the panel (checkbox / radio box) was
+            // blurred => do not fire the focus and set blurOccured to false, so
+            // blur will not be fired, too
+            blurOccured = false;
+        }
+    }
+
+    @Override
+    public void onBlur(BlurEvent arg0) {
+        blurOccured = true;
+        if (sendBlurEvents) {
+            Scheduler.get().scheduleDeferred(new Command() {
+                @Override
+                public void execute() {
+                    // check whether blurOccured still is true and then send the
+                    // event out to the server
+                    if (blurOccured) {
+                        client.updateVariable(paintableId, EventId.BLUR, "",
+                                true);
+                        blurOccured = false;
+                    }
+                }
+            });
+        }
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VOptionGroupBase.java b/client/src/com/vaadin/client/ui/VOptionGroupBase.java
new file mode 100644 (file)
index 0000000..d372f4c
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.Set;
+
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.Focusable;
+import com.vaadin.client.UIDL;
+
+public abstract class VOptionGroupBase extends Composite implements Field,
+        ClickHandler, ChangeHandler, KeyPressHandler, Focusable {
+
+    public static final String CLASSNAME_OPTION = "v-select-option";
+
+    /** 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 Set<String> selectedKeys;
+
+    /** 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 boolean multiselect;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean disabled;
+
+    /** 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 int cols = 0;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public int rows = 0;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean nullSelectionAllowed = true;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean nullSelectionItemAvailable = false;
+
+    /**
+     * Widget holding the different options (e.g. ListBox or Panel for radio
+     * buttons) (optional, fallbacks to container Panel)
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public Widget optionsContainer;
+
+    /**
+     * Panel containing the component.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public final Panel container;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public VTextField newItemField;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public VNativeButton newItemButton;
+
+    public VOptionGroupBase(String classname) {
+        container = new FlowPanel();
+        initWidget(container);
+        optionsContainer = container;
+        container.setStyleName(classname);
+        immediate = false;
+        multiselect = false;
+    }
+
+    /*
+     * Call this if you wish to specify your own container for the option
+     * elements (e.g. SELECT)
+     */
+    public VOptionGroupBase(Widget w, String classname) {
+        this(classname);
+        optionsContainer = w;
+        container.add(optionsContainer);
+    }
+
+    protected boolean isImmediate() {
+        return immediate;
+    }
+
+    protected boolean isMultiselect() {
+        return multiselect;
+    }
+
+    protected boolean isDisabled() {
+        return disabled;
+    }
+
+    protected boolean isReadonly() {
+        return readonly;
+    }
+
+    protected boolean isNullSelectionAllowed() {
+        return nullSelectionAllowed;
+    }
+
+    protected boolean isNullSelectionItemAvailable() {
+        return nullSelectionItemAvailable;
+    }
+
+    /**
+     * For internal use only. May be removed or replaced in the future.
+     * 
+     * @return "cols" specified in uidl, 0 if not specified
+     */
+    public int getColumns() {
+        return cols;
+    }
+
+    /**
+     * For internal use only. May be removed or replaced in the future.
+     * 
+     * @return "rows" specified in uidl, 0 if not specified
+     */
+    public int getRows() {
+        return rows;
+    }
+
+    public abstract void setTabIndex(int tabIndex);
+
+    @Override
+    public void onClick(ClickEvent event) {
+        if (event.getSource() == newItemButton
+                && !newItemField.getText().equals("")) {
+            client.updateVariable(paintableId, "newitem",
+                    newItemField.getText(), true);
+            newItemField.setText("");
+        }
+    }
+
+    @Override
+    public void onChange(ChangeEvent event) {
+        if (multiselect) {
+            client.updateVariable(paintableId, "selected", getSelectedItems(),
+                    immediate);
+        } else {
+            client.updateVariable(paintableId, "selected", new String[] { ""
+                    + getSelectedItem() }, immediate);
+        }
+    }
+
+    @Override
+    public void onKeyPress(KeyPressEvent event) {
+        if (event.getSource() == newItemField
+                && event.getCharCode() == KeyCodes.KEY_ENTER) {
+            newItemButton.click();
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public abstract void buildOptions(UIDL uidl);
+
+    protected abstract String[] getSelectedItems();
+
+    protected String getSelectedItem() {
+        final String[] sel = getSelectedItems();
+        if (sel.length > 0) {
+            return sel[0];
+        } else {
+            return null;
+        }
+    }
+
+}
diff --git a/client/src/com/vaadin/client/ui/VOrderedLayout.java b/client/src/com/vaadin/client/ui/VOrderedLayout.java
new file mode 100644 (file)
index 0000000..bbcb2f8
--- /dev/null
@@ -0,0 +1,1196 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.regexp.shared.MatchResult;
+import com.google.gwt.regexp.shared.RegExp;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.UIObject;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.LayoutManager;
+import com.vaadin.client.StyleConstants;
+import com.vaadin.client.Util;
+import com.vaadin.client.ui.layout.ElementResizeListener;
+import com.vaadin.shared.ui.AlignmentInfo;
+import com.vaadin.shared.ui.MarginInfo;
+
+/**
+ * Base class for ordered layouts
+ */
+public class VOrderedLayout extends FlowPanel {
+
+    private static final String ALIGN_CLASS_PREFIX = "v-align-";
+
+    protected boolean spacing = false;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean vertical = true;
+
+    protected boolean definedHeight = false;
+
+    private Map<Widget, Slot> widgetToSlot = new HashMap<Widget, Slot>();
+
+    private Element expandWrapper;
+
+    private LayoutManager layoutManager;
+
+    public VOrderedLayout(boolean vertical) {
+        this.vertical = vertical;
+    }
+
+    /**
+     * Add or move a slot to another index.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     * <p>
+     * You should note that the index does not refer to the DOM index if
+     * spacings are used. If spacings are used then the index will be adjusted
+     * to include the spacings when inserted.
+     * <p>
+     * For instance when using spacing the index converts to DOM index in the
+     * following way:
+     * 
+     * <pre>
+     * index : 0 -> DOM index: 0
+     * index : 1 -> DOM index: 1
+     * index : 2 -> DOM index: 3
+     * index : 3 -> DOM index: 5
+     * index : 4 -> DOM index: 7
+     * </pre>
+     * 
+     * When using this method never account for spacings.
+     * </p>
+     * 
+     * @param slot
+     *            The slot to move or add
+     * @param index
+     *            The index where the slot should be placed.
+     */
+    public void addOrMoveSlot(Slot slot, int index) {
+        if (slot.getParent() == this) {
+            int currentIndex = getWidgetIndex(slot);
+            if (index == currentIndex) {
+                return;
+            }
+        }
+
+        insert(slot, index);
+
+        /*
+         * We need to confirm spacings are correctly applied after each insert.
+         */
+        setSpacing(spacing);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void insert(Widget child, Element container, int beforeIndex,
+            boolean domInsert) {
+        // Validate index; adjust if the widget is already a child of this
+        // panel.
+        beforeIndex = adjustIndex(child, beforeIndex);
+
+        // Detach new child.
+        child.removeFromParent();
+
+        // Logical attach.
+        getChildren().insert(child, beforeIndex);
+
+        // Physical attach.
+        container = expandWrapper != null ? expandWrapper : getElement();
+        if (domInsert) {
+            if (spacing) {
+                if (beforeIndex != 0) {
+                    /*
+                     * Since the spacing elements are located at the same DOM
+                     * level as the slots we need to take them into account when
+                     * calculating the slot position.
+                     * 
+                     * The spacing elements are always located before the actual
+                     * slot except for the first slot which do not have a
+                     * spacing element like this
+                     * 
+                     * |<slot1><spacing2><slot2><spacing3><slot3>...|
+                     */
+                    beforeIndex = beforeIndex * 2 - 1;
+                }
+            }
+            DOM.insertChild(container, child.getElement(), beforeIndex);
+        } else {
+            DOM.appendChild(container, child.getElement());
+        }
+
+        // Adopt.
+        adopt(child);
+    }
+
+    /**
+     * Remove a slot from the layout
+     * 
+     * @param widget
+     * @return
+     */
+    public void removeWidget(Widget widget) {
+        Slot slot = widgetToSlot.get(widget);
+        remove(slot);
+        widgetToSlot.remove(widget);
+    }
+
+    /**
+     * Get the containing slot for a widget. If no slot is found a new slot is
+     * created and returned.
+     * 
+     * @param widget
+     *            The widget whose slot you want to get
+     * 
+     * @return
+     */
+    public Slot getSlot(Widget widget) {
+        Slot slot = widgetToSlot.get(widget);
+        if (slot == null) {
+            slot = new Slot(widget);
+            widgetToSlot.put(widget, slot);
+        }
+        return slot;
+    }
+
+    /**
+     * Gets a slot based on the widget element. If no slot is found then null is
+     * returned.
+     * 
+     * @param widgetElement
+     *            The element of the widget ( Same as getWidget().getElement() )
+     * @return
+     */
+    public Slot getSlot(Element widgetElement) {
+        for (Map.Entry<Widget, Slot> entry : widgetToSlot.entrySet()) {
+            if (entry.getKey().getElement() == widgetElement) {
+                return entry.getValue();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Defines where the caption should be placed
+     */
+    public enum CaptionPosition {
+        TOP, RIGHT, BOTTOM, LEFT
+    }
+
+    /**
+     * Represents a slot which contains the actual widget in the layout.
+     */
+    public final class Slot extends SimplePanel {
+
+        public static final String SLOT_CLASSNAME = "v-slot";
+
+        private Element spacer;
+        private Element captionWrap;
+        private Element caption;
+        private Element captionText;
+        private Icon icon;
+        private Element errorIcon;
+        private Element requiredIcon;
+
+        private ElementResizeListener captionResizeListener;
+
+        private ElementResizeListener widgetResizeListener;
+
+        private ElementResizeListener spacingResizeListener;
+
+        // Caption is placed after component unless there is some part which
+        // moves it above.
+        private CaptionPosition captionPosition = CaptionPosition.RIGHT;
+
+        private AlignmentInfo alignment;
+
+        private double expandRatio = -1;
+
+        /**
+         * Constructor
+         * 
+         * @param widget
+         *            The widget to put in the slot
+         * 
+         * @param layoutManager
+         *            The layout manager used by the layout
+         */
+        private Slot(Widget widget) {
+            setStyleName(SLOT_CLASSNAME);
+            setWidget(widget);
+        }
+
+        /*
+         * (non-Javadoc)
+         * 
+         * @see
+         * com.google.gwt.user.client.ui.SimplePanel#remove(com.google.gwt.user
+         * .client.ui.Widget)
+         */
+        @Override
+        public boolean remove(Widget w) {
+            detachListeners();
+            return super.remove(w);
+        }
+
+        /*
+         * (non-Javadoc)
+         * 
+         * @see
+         * com.google.gwt.user.client.ui.SimplePanel#setWidget(com.google.gwt
+         * .user.client.ui.Widget)
+         */
+        @Override
+        public void setWidget(Widget w) {
+            detachListeners();
+            super.setWidget(w);
+            attachListeners();
+        }
+
+        /**
+         * Attached resize listeners to the widget, caption and spacing elements
+         */
+        private void attachListeners() {
+            if (getWidget() != null && getLayoutManager() != null) {
+                LayoutManager lm = getLayoutManager();
+                if (getCaptionElement() != null
+                        && captionResizeListener != null) {
+                    lm.addElementResizeListener(getCaptionElement(),
+                            captionResizeListener);
+                }
+                if (widgetResizeListener != null) {
+                    lm.addElementResizeListener(getWidget().getElement(),
+                            widgetResizeListener);
+                }
+                if (getSpacingElement() != null
+                        && spacingResizeListener != null) {
+                    lm.addElementResizeListener(getSpacingElement(),
+                            spacingResizeListener);
+                }
+            }
+        }
+
+        /**
+         * Detaches resize listeners from the widget, caption and spacing
+         * elements
+         */
+        private void detachListeners() {
+            if (getWidget() != null && getLayoutManager() != null) {
+                LayoutManager lm = getLayoutManager();
+                if (getCaptionElement() != null
+                        && captionResizeListener != null) {
+                    lm.removeElementResizeListener(getCaptionElement(),
+                            captionResizeListener);
+                }
+                if (widgetResizeListener != null) {
+                    lm.removeElementResizeListener(getWidget().getElement(),
+                            widgetResizeListener);
+                }
+                if (getSpacingElement() != null
+                        && spacingResizeListener != null) {
+                    lm.removeElementResizeListener(getSpacingElement(),
+                            spacingResizeListener);
+                }
+            }
+        }
+
+        public ElementResizeListener getCaptionResizeListener() {
+            return captionResizeListener;
+        }
+
+        public void setCaptionResizeListener(
+                ElementResizeListener captionResizeListener) {
+            detachListeners();
+            this.captionResizeListener = captionResizeListener;
+            attachListeners();
+        }
+
+        public ElementResizeListener getWidgetResizeListener() {
+            return widgetResizeListener;
+        }
+
+        public void setWidgetResizeListener(
+                ElementResizeListener widgetResizeListener) {
+            detachListeners();
+            this.widgetResizeListener = widgetResizeListener;
+            attachListeners();
+        }
+
+        public ElementResizeListener getSpacingResizeListener() {
+            return spacingResizeListener;
+        }
+
+        public void setSpacingResizeListener(
+                ElementResizeListener spacingResizeListener) {
+            detachListeners();
+            this.spacingResizeListener = spacingResizeListener;
+            attachListeners();
+        }
+
+        /**
+         * Returns the alignment for the slot
+         * 
+         */
+        public AlignmentInfo getAlignment() {
+            return alignment;
+        }
+
+        /**
+         * Sets the style names for the slot containing the widget
+         * 
+         * @param stylenames
+         *            The style names for the slot
+         */
+        protected void setStyleNames(String... stylenames) {
+            setStyleName(SLOT_CLASSNAME);
+            if (stylenames != null) {
+                for (String stylename : stylenames) {
+                    addStyleDependentName(stylename);
+                }
+            }
+
+            // Ensure alignment style names are correct
+            setAlignment(alignment);
+        }
+
+        /**
+         * Sets how the widget is aligned inside the slot
+         * 
+         * @param alignment
+         *            The alignment inside the slot
+         */
+        public void setAlignment(AlignmentInfo alignment) {
+            this.alignment = alignment;
+
+            if (alignment != null && alignment.isHorizontalCenter()) {
+                addStyleName(ALIGN_CLASS_PREFIX + "center");
+                removeStyleName(ALIGN_CLASS_PREFIX + "right");
+            } else if (alignment != null && alignment.isRight()) {
+                addStyleName(ALIGN_CLASS_PREFIX + "right");
+                removeStyleName(ALIGN_CLASS_PREFIX + "center");
+            } else {
+                removeStyleName(ALIGN_CLASS_PREFIX + "right");
+                removeStyleName(ALIGN_CLASS_PREFIX + "center");
+            }
+
+            if (alignment != null && alignment.isVerticalCenter()) {
+                addStyleName(ALIGN_CLASS_PREFIX + "middle");
+                removeStyleName(ALIGN_CLASS_PREFIX + "bottom");
+            } else if (alignment != null && alignment.isBottom()) {
+                addStyleName(ALIGN_CLASS_PREFIX + "bottom");
+                removeStyleName(ALIGN_CLASS_PREFIX + "middle");
+            } else {
+                removeStyleName(ALIGN_CLASS_PREFIX + "middle");
+                removeStyleName(ALIGN_CLASS_PREFIX + "bottom");
+            }
+        }
+
+        /**
+         * Set how the slot should be expanded relative to the other slots
+         * 
+         * @param expandRatio
+         *            The ratio of the space the slot should occupy
+         * 
+         */
+        public void setExpandRatio(double expandRatio) {
+            this.expandRatio = expandRatio;
+        }
+
+        /**
+         * Get the expand ratio for the slot. The expand ratio describes how the
+         * slot should be resized compared to other slots in the layout
+         * 
+         * @return
+         */
+        public double getExpandRatio() {
+            return expandRatio;
+        }
+
+        /**
+         * Set the spacing for the slot. The spacing determines if there should
+         * be empty space around the slot when the slot.
+         * 
+         * @param spacing
+         *            Should spacing be enabled
+         */
+        public void setSpacing(boolean spacing) {
+            if (spacing && spacer == null) {
+                spacer = DOM.createDiv();
+                spacer.addClassName("v-spacing");
+
+                /*
+                 * This has to be done here for the initial render. In other
+                 * cases where the spacer already exists onAttach will handle
+                 * it.
+                 */
+                getElement().getParentElement().insertBefore(spacer,
+                        getElement());
+            } else if (!spacing && spacer != null) {
+                spacer.removeFromParent();
+                spacer = null;
+            }
+        }
+
+        /**
+         * Get the element which is added to make the spacing
+         * 
+         * @return
+         */
+        public Element getSpacingElement() {
+            return spacer;
+        }
+
+        /**
+         * Does the slot have spacing
+         */
+        public boolean hasSpacing() {
+            return getSpacingElement() != null;
+        }
+
+        /**
+         * Get the vertical amount in pixels of the spacing
+         */
+        protected int getVerticalSpacing() {
+            if (spacer == null) {
+                return 0;
+            } else if (getLayoutManager() != null) {
+                return getLayoutManager().getOuterHeight(spacer);
+            }
+            return spacer.getOffsetHeight();
+        }
+
+        /**
+         * Get the horizontal amount of pixels of the spacing
+         * 
+         * @return
+         */
+        protected int getHorizontalSpacing() {
+            if (spacer == null) {
+                return 0;
+            } else if (getLayoutManager() != null) {
+                return getLayoutManager().getOuterWidth(spacer);
+            }
+            return spacer.getOffsetWidth();
+        }
+
+        /**
+         * Set the position of the caption relative to the slot
+         * 
+         * @param captionPosition
+         *            The position of the caption
+         */
+        public void setCaptionPosition(CaptionPosition captionPosition) {
+            if (caption == null) {
+                return;
+            }
+            captionWrap.removeClassName("v-caption-on-"
+                    + this.captionPosition.name().toLowerCase());
+
+            this.captionPosition = captionPosition;
+            if (captionPosition == CaptionPosition.BOTTOM
+                    || captionPosition == CaptionPosition.RIGHT) {
+                captionWrap.appendChild(caption);
+            } else {
+                captionWrap.insertFirst(caption);
+            }
+
+            captionWrap.addClassName("v-caption-on-"
+                    + captionPosition.name().toLowerCase());
+        }
+
+        /**
+         * Get the position of the caption relative to the slot
+         */
+        public CaptionPosition getCaptionPosition() {
+            return captionPosition;
+        }
+
+        /**
+         * Set the caption of the slot
+         * 
+         * @param captionText
+         *            The text of the caption
+         * @param iconUrl
+         *            The icon URL
+         * @param styles
+         *            The style names
+         * @param error
+         *            The error message
+         * @param showError
+         *            Should the error message be shown
+         * @param required
+         *            Is the (field) required
+         * @param enabled
+         *            Is the component enabled
+         */
+        public void setCaption(String captionText, String iconUrl,
+                List<String> styles, String error, boolean showError,
+                boolean required, boolean enabled) {
+
+            // TODO place for optimization: check if any of these have changed
+            // since last time, and only run those changes
+
+            // Caption wrappers
+            if (captionText != null || iconUrl != null || error != null
+                    || required) {
+                if (caption == null) {
+                    caption = DOM.createDiv();
+                    captionWrap = DOM.createDiv();
+                    captionWrap.addClassName(StyleConstants.UI_WIDGET);
+                    captionWrap.addClassName("v-has-caption");
+                    getElement().appendChild(captionWrap);
+                    captionWrap.appendChild(getWidget().getElement());
+                }
+            } else if (caption != null) {
+                getElement().appendChild(getWidget().getElement());
+                captionWrap.removeFromParent();
+                caption = null;
+                captionWrap = null;
+            }
+
+            // Caption text
+            if (captionText != null) {
+                if (this.captionText == null) {
+                    this.captionText = DOM.createSpan();
+                    this.captionText.addClassName("v-captiontext");
+                    caption.appendChild(this.captionText);
+                }
+                if (captionText.trim().equals("")) {
+                    this.captionText.setInnerHTML("&nbsp;");
+                } else {
+                    this.captionText.setInnerText(captionText);
+                }
+            } else if (this.captionText != null) {
+                this.captionText.removeFromParent();
+                this.captionText = null;
+            }
+
+            // Icon
+            if (iconUrl != null) {
+                if (icon == null) {
+                    icon = new Icon();
+                    caption.insertFirst(icon.getElement());
+                }
+                icon.setUri(iconUrl);
+            } else if (icon != null) {
+                icon.getElement().removeFromParent();
+                icon = null;
+            }
+
+            // Required
+            if (required) {
+                if (requiredIcon == null) {
+                    requiredIcon = DOM.createSpan();
+                    // TODO decide something better (e.g. use CSS to insert the
+                    // character)
+                    requiredIcon.setInnerHTML("*");
+                    requiredIcon.setClassName("v-required-field-indicator");
+                }
+                caption.appendChild(requiredIcon);
+            } else if (requiredIcon != null) {
+                requiredIcon.removeFromParent();
+                requiredIcon = null;
+            }
+
+            // Error
+            if (error != null && showError) {
+                if (errorIcon == null) {
+                    errorIcon = DOM.createSpan();
+                    errorIcon.setClassName("v-errorindicator");
+                }
+                caption.appendChild(errorIcon);
+            } else if (errorIcon != null) {
+                errorIcon.removeFromParent();
+                errorIcon = null;
+            }
+
+            if (caption != null) {
+                // Styles
+                caption.setClassName("v-caption");
+
+                if (styles != null) {
+                    for (String style : styles) {
+                        caption.addClassName("v-caption-" + style);
+                    }
+                }
+
+                if (enabled) {
+                    caption.removeClassName("v-disabled");
+                } else {
+                    caption.addClassName("v-disabled");
+                }
+
+                // Caption position
+                if (captionText != null || iconUrl != null) {
+                    setCaptionPosition(CaptionPosition.TOP);
+                } else {
+                    setCaptionPosition(CaptionPosition.RIGHT);
+                }
+            }
+        }
+
+        /**
+         * Does the slot have a caption
+         */
+        public boolean hasCaption() {
+            return caption != null;
+        }
+
+        /**
+         * Get the slots caption element
+         */
+        public Element getCaptionElement() {
+            return caption;
+        }
+
+        /**
+         * Set if the slot has a relative width
+         * 
+         * @param relativeWidth
+         *            True if slot uses relative width, false if the slot has a
+         *            static width
+         */
+        private boolean relativeWidth = false;
+
+        public void setRelativeWidth(boolean relativeWidth) {
+            this.relativeWidth = relativeWidth;
+            updateRelativeSize(relativeWidth, "width");
+        }
+
+        /**
+         * Set if the slot has a relative height
+         * 
+         * @param relativeHeight
+         *            Trie if the slot uses a relative height, false if the slot
+         *            has a static height
+         */
+        private boolean relativeHeight = false;
+
+        public void setRelativeHeight(boolean relativeHeight) {
+            this.relativeHeight = relativeHeight;
+            updateRelativeSize(relativeHeight, "height");
+        }
+
+        /**
+         * Updates the captions size if the slot is relative
+         * 
+         * @param isRelativeSize
+         *            Is the slot relatived sized
+         * @param direction
+         *            The directorion of the caption
+         */
+        private void updateRelativeSize(boolean isRelativeSize, String direction) {
+            if (isRelativeSize && hasCaption()) {
+                captionWrap.getStyle().setProperty(
+                        direction,
+                        getWidget().getElement().getStyle()
+                                .getProperty(direction));
+                captionWrap.addClassName("v-has-" + direction);
+            } else if (hasCaption()) {
+                if (direction.equals("height")) {
+                    captionWrap.getStyle().clearHeight();
+                } else {
+                    captionWrap.getStyle().clearWidth();
+                }
+                captionWrap.removeClassName("v-has-" + direction);
+                captionWrap.getStyle().clearPaddingTop();
+                captionWrap.getStyle().clearPaddingRight();
+                captionWrap.getStyle().clearPaddingBottom();
+                captionWrap.getStyle().clearPaddingLeft();
+                caption.getStyle().clearMarginTop();
+                caption.getStyle().clearMarginRight();
+                caption.getStyle().clearMarginBottom();
+                caption.getStyle().clearMarginLeft();
+            }
+        }
+
+        /*
+         * (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);
+            if (DOM.eventGetType(event) == Event.ONLOAD
+                    && icon.getElement() == DOM.eventGetTarget(event)) {
+                if (getLayoutManager() != null) {
+                    getLayoutManager().layoutLater();
+                } else {
+                    updateCaptionOffset(caption);
+                }
+            }
+        }
+
+        /*
+         * (non-Javadoc)
+         * 
+         * @see com.google.gwt.user.client.ui.SimplePanel#getContainerElement()
+         */
+        @Override
+        protected Element getContainerElement() {
+            if (captionWrap == null) {
+                return getElement();
+            } else {
+                return captionWrap;
+            }
+        }
+
+        /*
+         * (non-Javadoc)
+         * 
+         * @see com.google.gwt.user.client.ui.Widget#onDetach()
+         */
+        @Override
+        protected void onDetach() {
+            if (spacer != null) {
+                spacer.removeFromParent();
+            }
+            super.onDetach();
+        }
+
+        /*
+         * (non-Javadoc)
+         * 
+         * @see com.google.gwt.user.client.ui.Widget#onAttach()
+         */
+        @Override
+        protected void onAttach() {
+            super.onAttach();
+            if (spacer != null) {
+                getElement().getParentElement().insertBefore(spacer,
+                        getElement());
+            }
+        }
+    }
+
+    /**
+     * The icon for each widget. Located in the caption of the slot.
+     */
+    public static class Icon extends UIObject {
+
+        public static final String CLASSNAME = "v-icon";
+
+        private String myUrl;
+
+        /**
+         * Constructor
+         */
+        public Icon() {
+            setElement(DOM.createImg());
+            DOM.setElementProperty(getElement(), "alt", "");
+            setStyleName(CLASSNAME);
+        }
+
+        /**
+         * Set the URL where the icon is located
+         * 
+         * @param url
+         *            A fully qualified URL
+         */
+        public void setUri(String url) {
+            if (!url.equals(myUrl)) {
+                /*
+                 * Start sinking onload events, widgets responsibility to react.
+                 * We must do this BEFORE we set src as IE fires the event
+                 * immediately if the image is found in cache (#2592).
+                 */
+                sinkEvents(Event.ONLOAD);
+
+                DOM.setElementProperty(getElement(), "src", url);
+                myUrl = url;
+            }
+        }
+    }
+
+    /**
+     * Set the layout manager for the layout
+     * 
+     * @param manager
+     *            The layout manager to use
+     */
+    public void setLayoutManager(LayoutManager manager) {
+        layoutManager = manager;
+    }
+
+    /**
+     * Get the layout manager used by this layout
+     * 
+     */
+    public LayoutManager getLayoutManager() {
+        return layoutManager;
+    }
+
+    /**
+     * Deducts the caption position by examining the wrapping element.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     * 
+     * @param captionWrap
+     *            The wrapping element
+     * 
+     * @return The caption position
+     */
+    public CaptionPosition getCaptionPositionFromElement(Element captionWrap) {
+        RegExp captionPositionRegexp = RegExp.compile("v-caption-on-(\\S+)");
+
+        // Get caption position from the classname
+        MatchResult matcher = captionPositionRegexp.exec(captionWrap
+                .getClassName());
+        if (matcher == null || matcher.getGroupCount() < 2) {
+            return CaptionPosition.TOP;
+        }
+        String captionClass = matcher.getGroup(1);
+        CaptionPosition captionPosition = CaptionPosition.valueOf(
+                CaptionPosition.class, captionClass.toUpperCase());
+        return captionPosition;
+    }
+
+    /**
+     * Update the offset off the caption relative to the slot
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     * 
+     * @param caption
+     *            The caption element
+     */
+    public void updateCaptionOffset(Element caption) {
+
+        Element captionWrap = caption.getParentElement().cast();
+
+        Style captionWrapStyle = captionWrap.getStyle();
+        captionWrapStyle.clearPaddingTop();
+        captionWrapStyle.clearPaddingRight();
+        captionWrapStyle.clearPaddingBottom();
+        captionWrapStyle.clearPaddingLeft();
+
+        Style captionStyle = caption.getStyle();
+        captionStyle.clearMarginTop();
+        captionStyle.clearMarginRight();
+        captionStyle.clearMarginBottom();
+        captionStyle.clearMarginLeft();
+
+        // Get caption position from the classname
+        CaptionPosition captionPosition = getCaptionPositionFromElement(captionWrap);
+
+        if (captionPosition == CaptionPosition.LEFT
+                || captionPosition == CaptionPosition.RIGHT) {
+            int captionWidth;
+            if (layoutManager != null) {
+                captionWidth = layoutManager.getOuterWidth(caption)
+                        - layoutManager.getMarginWidth(caption);
+            } else {
+                captionWidth = caption.getOffsetWidth();
+            }
+            if (captionWidth > 0) {
+                if (captionPosition == CaptionPosition.LEFT) {
+                    captionWrapStyle.setPaddingLeft(captionWidth, Unit.PX);
+                    captionStyle.setMarginLeft(-captionWidth, Unit.PX);
+                } else {
+                    captionWrapStyle.setPaddingRight(captionWidth, Unit.PX);
+                    captionStyle.setMarginRight(-captionWidth, Unit.PX);
+                }
+            }
+        }
+        if (captionPosition == CaptionPosition.TOP
+                || captionPosition == CaptionPosition.BOTTOM) {
+            int captionHeight;
+            if (layoutManager != null) {
+                captionHeight = layoutManager.getOuterHeight(caption)
+                        - layoutManager.getMarginHeight(caption);
+            } else {
+                captionHeight = caption.getOffsetHeight();
+            }
+            if (captionHeight > 0) {
+                if (captionPosition == CaptionPosition.TOP) {
+                    captionWrapStyle.setPaddingTop(captionHeight, Unit.PX);
+                    captionStyle.setMarginTop(-captionHeight, Unit.PX);
+                } else {
+                    captionWrapStyle.setPaddingBottom(captionHeight, Unit.PX);
+                    captionStyle.setMarginBottom(-captionHeight, Unit.PX);
+                }
+            }
+        }
+    }
+
+    /**
+     * Set the margin of the layout
+     * 
+     * @param marginInfo
+     *            The margin information
+     */
+    public void setMargin(MarginInfo marginInfo) {
+        if (marginInfo != null) {
+            setStyleName("v-margin-top", marginInfo.hasTop());
+            setStyleName("v-margin-right", marginInfo.hasRight());
+            setStyleName("v-margin-bottom", marginInfo.hasBottom());
+            setStyleName("v-margin-left", marginInfo.hasLeft());
+        }
+    }
+
+    /**
+     * Turn on or off spacing in the layout
+     * 
+     * @param spacing
+     *            True if spacing should be used, false if not
+     */
+    public void setSpacing(boolean spacing) {
+        this.spacing = spacing;
+        for (Slot slot : widgetToSlot.values()) {
+            if (getWidgetIndex(slot) > 0) {
+                slot.setSpacing(spacing);
+            } else {
+                slot.setSpacing(false);
+            }
+        }
+    }
+
+    /**
+     * Triggers a recalculation of the expand width and heights
+     */
+    private void recalculateExpands() {
+        double total = 0;
+        for (Slot slot : widgetToSlot.values()) {
+            if (slot.getExpandRatio() > -1) {
+                total += slot.getExpandRatio();
+            } else {
+                if (vertical) {
+                    slot.getElement().getStyle().clearHeight();
+                } else {
+                    slot.getElement().getStyle().clearWidth();
+                }
+            }
+        }
+        for (Slot slot : widgetToSlot.values()) {
+            if (slot.getExpandRatio() > -1) {
+                if (vertical) {
+                    slot.setHeight((100 * (slot.getExpandRatio() / total))
+                            + "%");
+                    if (slot.relativeHeight) {
+                        Util.notifyParentOfSizeChange(this, true);
+                    }
+                } else {
+                    slot.setWidth((100 * (slot.getExpandRatio() / total)) + "%");
+                    if (slot.relativeWidth) {
+                        Util.notifyParentOfSizeChange(this, true);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Removes elements used to expand a slot.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public void clearExpand() {
+        if (expandWrapper != null) {
+            for (; expandWrapper.getChildCount() > 0;) {
+                Element el = expandWrapper.getChild(0).cast();
+                getElement().appendChild(el);
+                if (vertical) {
+                    el.getStyle().clearHeight();
+                    el.getStyle().clearMarginTop();
+                } else {
+                    el.getStyle().clearWidth();
+                    el.getStyle().clearMarginLeft();
+                }
+            }
+            expandWrapper.removeFromParent();
+            expandWrapper = null;
+        }
+    }
+
+    /**
+     * Adds elements used to expand a slot
+     */
+    public void updateExpand() {
+        boolean isExpanding = false;
+        for (Widget slot : getChildren()) {
+            if (((Slot) slot).getExpandRatio() > -1) {
+                isExpanding = true;
+            } else {
+                if (vertical) {
+                    slot.getElement().getStyle().clearHeight();
+                } else {
+                    slot.getElement().getStyle().clearWidth();
+                }
+            }
+            slot.getElement().getStyle().clearMarginLeft();
+            slot.getElement().getStyle().clearMarginTop();
+        }
+
+        if (isExpanding) {
+            if (expandWrapper == null) {
+                expandWrapper = DOM.createDiv();
+                expandWrapper.setClassName("v-expand");
+                for (; getElement().getChildCount() > 0;) {
+                    Node el = getElement().getChild(0);
+                    expandWrapper.appendChild(el);
+                }
+                getElement().appendChild(expandWrapper);
+            }
+
+            int totalSize = 0;
+            for (Widget w : getChildren()) {
+                Slot slot = (Slot) w;
+                if (slot.getExpandRatio() == -1) {
+
+                    if (layoutManager != null) {
+                        // TODO check caption position
+                        if (vertical) {
+                            int size = layoutManager.getOuterHeight(slot
+                                    .getWidget().getElement())
+                                    - layoutManager.getMarginHeight(slot
+                                            .getWidget().getElement());
+                            if (slot.hasCaption()) {
+                                size += layoutManager.getOuterHeight(slot
+                                        .getCaptionElement())
+                                        - layoutManager.getMarginHeight(slot
+                                                .getCaptionElement());
+                            }
+                            if (size > 0) {
+                                totalSize += size;
+                            }
+                        } else {
+                            int max = -1;
+                            max = layoutManager.getOuterWidth(slot.getWidget()
+                                    .getElement())
+                                    - layoutManager.getMarginWidth(slot
+                                            .getWidget().getElement());
+                            if (slot.hasCaption()) {
+                                int max2 = layoutManager.getOuterWidth(slot
+                                        .getCaptionElement())
+                                        - layoutManager.getMarginWidth(slot
+                                                .getCaptionElement());
+                                max = Math.max(max, max2);
+                            }
+                            if (max > 0) {
+                                totalSize += max;
+                            }
+                        }
+                    } else {
+                        totalSize += vertical ? slot.getOffsetHeight() : slot
+                                .getOffsetWidth();
+                    }
+                }
+                // TODO fails in Opera, always returns 0
+                int spacingSize = vertical ? slot.getVerticalSpacing() : slot
+                        .getHorizontalSpacing();
+                if (spacingSize > 0) {
+                    totalSize += spacingSize;
+                }
+            }
+
+            // When we set the margin to the first child, we don't need
+            // overflow:hidden in the layout root element, since the wrapper
+            // would otherwise be placed outside of the layout root element
+            // and block events on elements below it.
+            if (vertical) {
+                expandWrapper.getStyle().setPaddingTop(totalSize, Unit.PX);
+                expandWrapper.getFirstChildElement().getStyle()
+                        .setMarginTop(-totalSize, Unit.PX);
+            } else {
+                expandWrapper.getStyle().setPaddingLeft(totalSize, Unit.PX);
+                expandWrapper.getFirstChildElement().getStyle()
+                        .setMarginLeft(-totalSize, Unit.PX);
+            }
+
+            recalculateExpands();
+        }
+    }
+
+    /**
+     * Perform a recalculation of the layout height
+     */
+    public void recalculateLayoutHeight() {
+        // Only needed if a horizontal layout is undefined high, and contains
+        // relative height children or vertical alignments
+        if (vertical || definedHeight) {
+            return;
+        }
+
+        boolean hasRelativeHeightChildren = false;
+        boolean hasVAlign = false;
+
+        for (Widget slot : getChildren()) {
+            Widget widget = ((Slot) slot).getWidget();
+            String h = widget.getElement().getStyle().getHeight();
+            if (h != null && h.indexOf("%") > -1) {
+                hasRelativeHeightChildren = true;
+            }
+            AlignmentInfo a = ((Slot) slot).getAlignment();
+            if (a != null && (a.isVerticalCenter() || a.isBottom())) {
+                hasVAlign = true;
+            }
+        }
+
+        if (hasRelativeHeightChildren || hasVAlign) {
+            int newHeight;
+            if (layoutManager != null) {
+                newHeight = layoutManager.getOuterHeight(getElement())
+                        - layoutManager.getMarginHeight(getElement());
+            } else {
+                newHeight = getElement().getOffsetHeight();
+            }
+            VOrderedLayout.this.getElement().getStyle()
+                    .setHeight(newHeight, Unit.PX);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setHeight(String height) {
+        super.setHeight(height);
+        definedHeight = (height != null && !"".equals(height));
+    }
+
+    /**
+     * Sets the slots style names. The style names will be prefixed with the
+     * v-slot prefix.
+     * 
+     * @param stylenames
+     *            The style names of the slot.
+     */
+    public void setSlotStyleNames(Widget widget, String... stylenames) {
+        Slot slot = getSlot(widget);
+        if (slot == null) {
+            throw new IllegalArgumentException(
+                    "A slot for the widget could not be found. Has the widget been added to the layout?");
+        }
+        slot.setStyleNames(stylenames);
+    }
+
+}
diff --git a/client/src/com/vaadin/client/ui/VPanel.java b/client/src/com/vaadin/client/ui/VPanel.java
new file mode 100644 (file)
index 0000000..b4927d2
--- /dev/null
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.Focusable;
+import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
+import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
+
+public class VPanel extends SimplePanel implements ShortcutActionHandlerOwner,
+        Focusable {
+
+    public static final String CLASSNAME = "v-panel";
+
+    /** 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 id;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public final Element captionNode = DOM.createDiv();
+
+    private final Element captionText = DOM.createSpan();
+
+    private Icon icon;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public final Element bottomDecoration = DOM.createDiv();
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public final Element contentNode = DOM.createDiv();
+
+    private Element errorIndicatorElement;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public ShortcutActionHandler shortcutHandler;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public int scrollTop;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public int scrollLeft;
+
+    private TouchScrollHandler touchScrollHandler;
+
+    public VPanel() {
+        super();
+        DivElement captionWrap = Document.get().createDivElement();
+        captionWrap.appendChild(captionNode);
+        captionNode.appendChild(captionText);
+
+        captionWrap.setClassName(CLASSNAME + "-captionwrap");
+        captionNode.setClassName(CLASSNAME + "-caption");
+        contentNode.setClassName(CLASSNAME + "-content");
+        bottomDecoration.setClassName(CLASSNAME + "-deco");
+
+        getElement().appendChild(captionWrap);
+
+        /*
+         * Make contentNode focusable only by using the setFocus() method. This
+         * behaviour can be changed by invoking setTabIndex() in the serverside
+         * implementation
+         */
+        contentNode.setTabIndex(-1);
+
+        getElement().appendChild(contentNode);
+
+        getElement().appendChild(bottomDecoration);
+        setStyleName(CLASSNAME);
+        DOM.sinkEvents(getElement(), Event.ONKEYDOWN);
+        DOM.sinkEvents(contentNode, Event.ONSCROLL | Event.TOUCHEVENTS);
+
+        contentNode.getStyle().setProperty("position", "relative");
+        getElement().getStyle().setProperty("overflow", "hidden");
+
+        makeScrollable();
+    }
+
+    /**
+     * Sets the keyboard focus on the Panel
+     * 
+     * @param focus
+     *            Should the panel have focus or not.
+     */
+    public void setFocus(boolean focus) {
+        if (focus) {
+            getContainerElement().focus();
+        } else {
+            getContainerElement().blur();
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see com.vaadin.client.Focusable#focus()
+     */
+
+    @Override
+    public void focus() {
+        setFocus(true);
+
+    }
+
+    @Override
+    protected Element getContainerElement() {
+        return contentNode;
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void setCaption(String text) {
+        DOM.setInnerHTML(captionText, text);
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void setErrorIndicatorVisible(boolean showError) {
+        if (showError) {
+            if (errorIndicatorElement == null) {
+                errorIndicatorElement = DOM.createSpan();
+                DOM.setElementProperty(errorIndicatorElement, "className",
+                        "v-errorindicator");
+                DOM.sinkEvents(errorIndicatorElement, Event.MOUSEEVENTS);
+                sinkEvents(Event.MOUSEEVENTS);
+            }
+            DOM.insertBefore(captionNode, errorIndicatorElement, captionText);
+        } else if (errorIndicatorElement != null) {
+            DOM.removeChild(captionNode, errorIndicatorElement);
+            errorIndicatorElement = null;
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void setIconUri(String iconUri, ApplicationConnection client) {
+        if (iconUri == null) {
+            if (icon != null) {
+                DOM.removeChild(captionNode, icon.getElement());
+                icon = null;
+            }
+        } else {
+            if (icon == null) {
+                icon = new Icon(client);
+                DOM.insertChild(captionNode, icon.getElement(), 0);
+            }
+            icon.setUri(iconUri);
+        }
+    }
+
+    @Override
+    public void onBrowserEvent(Event event) {
+        super.onBrowserEvent(event);
+
+        final Element target = DOM.eventGetTarget(event);
+        final int type = DOM.eventGetType(event);
+        if (type == Event.ONKEYDOWN && shortcutHandler != null) {
+            shortcutHandler.handleKeyboardEvent(event);
+            return;
+        }
+        if (type == Event.ONSCROLL) {
+            int newscrollTop = DOM.getElementPropertyInt(contentNode,
+                    "scrollTop");
+            int newscrollLeft = DOM.getElementPropertyInt(contentNode,
+                    "scrollLeft");
+            if (client != null
+                    && (newscrollLeft != scrollLeft || newscrollTop != scrollTop)) {
+                scrollLeft = newscrollLeft;
+                scrollTop = newscrollTop;
+                client.updateVariable(id, "scrollTop", scrollTop, false);
+                client.updateVariable(id, "scrollLeft", scrollLeft, false);
+            }
+        }
+    }
+
+    @Override
+    public ShortcutActionHandler getShortcutActionHandler() {
+        return shortcutHandler;
+    }
+
+    /**
+     * Ensures the panel is scrollable eg. after style name changes.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public void makeScrollable() {
+        if (touchScrollHandler == null) {
+            touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this);
+        }
+        touchScrollHandler.addElement(contentNode);
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VPasswordField.java b/client/src/com/vaadin/client/ui/VPasswordField.java
new file mode 100644 (file)
index 0000000..de27ef1
--- /dev/null
@@ -0,0 +1,33 @@
+/* 
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.user.client.DOM;
+
+/**
+ * This class represents a password field.
+ * 
+ * @author Vaadin Ltd.
+ * 
+ */
+public class VPasswordField extends VTextField {
+
+    public VPasswordField() {
+        super(DOM.createInputPassword());
+    }
+
+}
diff --git a/client/src/com/vaadin/client/ui/VPopupCalendar.java b/client/src/com/vaadin/client/ui/VPopupCalendar.java
new file mode 100644 (file)
index 0000000..941978d
--- /dev/null
@@ -0,0 +1,409 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.Date;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.DomEvent;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+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.Button;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.VConsole;
+import com.vaadin.client.ui.VCalendarPanel.FocusOutListener;
+import com.vaadin.client.ui.VCalendarPanel.SubmitListener;
+import com.vaadin.shared.ui.datefield.Resolution;
+
+/**
+ * Represents a date selection component with a text field and a popup date
+ * selector.
+ * 
+ * <b>Note:</b> To change the keyboard assignments used in the popup dialog you
+ * should extend <code>com.vaadin.client.ui.VCalendarPanel</code> and then pass
+ * set it by calling the <code>setCalendarPanel(VCalendarPanel panel)</code>
+ * method.
+ * 
+ */
+public class VPopupCalendar extends VTextualDate implements Field,
+        ClickHandler, CloseHandler<PopupPanel>, SubPartAware {
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public final Button calendarToggle = new Button();
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public VCalendarPanel calendar;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public final VOverlay popup;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean parsable = true;
+
+    private boolean open = false;
+
+    public VPopupCalendar() {
+        super();
+
+        calendarToggle.setText("");
+        calendarToggle.addClickHandler(this);
+        // -2 instead of -1 to avoid FocusWidget.onAttach to reset it
+        calendarToggle.getElement().setTabIndex(-2);
+        add(calendarToggle);
+
+        calendar = GWT.create(VCalendarPanel.class);
+        calendar.setParentField(this);
+        calendar.setFocusOutListener(new FocusOutListener() {
+            @Override
+            public boolean onFocusOut(DomEvent<?> event) {
+                event.preventDefault();
+                closeCalendarPanel();
+                return true;
+            }
+        });
+
+        calendar.setSubmitListener(new SubmitListener() {
+            @Override
+            public void onSubmit() {
+                // Update internal value and send valuechange event if immediate
+                updateValue(calendar.getDate());
+
+                // Update text field (a must when not immediate).
+                buildDate(true);
+
+                closeCalendarPanel();
+            }
+
+            @Override
+            public void onCancel() {
+                closeCalendarPanel();
+            }
+        });
+
+        popup = new VOverlay(true, true, true);
+        popup.setOwner(this);
+
+        popup.setWidget(calendar);
+        popup.addCloseHandler(this);
+
+        DOM.setElementProperty(calendar.getElement(), "id",
+                "PID_VAADIN_POPUPCAL");
+
+        sinkEvents(Event.ONKEYDOWN);
+
+        updateStyleNames();
+    }
+
+    @SuppressWarnings("deprecation")
+    public void updateValue(Date newDate) {
+        Date currentDate = getCurrentDate();
+        if (currentDate == null || newDate.getTime() != currentDate.getTime()) {
+            setCurrentDate((Date) newDate.clone());
+            getClient().updateVariable(getId(), "year",
+                    newDate.getYear() + 1900, false);
+            if (getCurrentResolution().getCalendarField() > Resolution.YEAR
+                    .getCalendarField()) {
+                getClient().updateVariable(getId(), "month",
+                        newDate.getMonth() + 1, false);
+                if (getCurrentResolution().getCalendarField() > Resolution.MONTH
+                        .getCalendarField()) {
+                    getClient().updateVariable(getId(), "day",
+                            newDate.getDate(), false);
+                    if (getCurrentResolution().getCalendarField() > Resolution.DAY
+                            .getCalendarField()) {
+                        getClient().updateVariable(getId(), "hour",
+                                newDate.getHours(), false);
+                        if (getCurrentResolution().getCalendarField() > Resolution.HOUR
+                                .getCalendarField()) {
+                            getClient().updateVariable(getId(), "min",
+                                    newDate.getMinutes(), false);
+                            if (getCurrentResolution().getCalendarField() > Resolution.MINUTE
+                                    .getCalendarField()) {
+                                getClient().updateVariable(getId(), "sec",
+                                        newDate.getSeconds(), false);
+                            }
+                        }
+                    }
+                }
+            }
+            if (isImmediate()) {
+                getClient().sendPendingVariableChanges();
+            }
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.user.client.ui.UIObject#setStyleName(java.lang.String)
+     */
+    @Override
+    public void setStyleName(String style) {
+        super.setStyleName(style);
+        updateStyleNames();
+    }
+
+    @Override
+    public void setStylePrimaryName(String style) {
+        removeStyleName(getStylePrimaryName() + "-popupcalendar");
+        super.setStylePrimaryName(style);
+        updateStyleNames();
+    }
+
+    @Override
+    protected void updateStyleNames() {
+        super.updateStyleNames();
+        if (getStylePrimaryName() != null && calendarToggle != null) {
+            addStyleName(getStylePrimaryName() + "-popupcalendar");
+            calendarToggle.setStyleName(getStylePrimaryName() + "-button");
+            popup.setStyleName(getStylePrimaryName() + "-popup");
+            calendar.setStyleName(getStylePrimaryName() + "-calendarpanel");
+        }
+    }
+
+    /**
+     * Opens the calendar panel popup
+     */
+    public void openCalendarPanel() {
+
+        if (!open && !readonly) {
+            open = true;
+
+            if (getCurrentDate() != null) {
+                calendar.setDate((Date) getCurrentDate().clone());
+            } else {
+                calendar.setDate(new Date());
+            }
+
+            // clear previous values
+            popup.setWidth("");
+            popup.setHeight("");
+            popup.setPopupPositionAndShow(new PositionCallback() {
+                @Override
+                public void setPosition(int offsetWidth, int offsetHeight) {
+                    final int w = offsetWidth;
+                    final int h = offsetHeight;
+                    final int browserWindowWidth = Window.getClientWidth()
+                            + Window.getScrollLeft();
+                    final int browserWindowHeight = Window.getClientHeight()
+                            + Window.getScrollTop();
+                    int t = calendarToggle.getAbsoluteTop();
+                    int l = calendarToggle.getAbsoluteLeft();
+
+                    // Add a little extra space to the right to avoid
+                    // problems with IE7 scrollbars and to make it look
+                    // nicer.
+                    int extraSpace = 30;
+
+                    boolean overflowRight = false;
+                    if (l + +w + extraSpace > browserWindowWidth) {
+                        overflowRight = true;
+                        // Part of the popup is outside the browser window
+                        // (to the right)
+                        l = browserWindowWidth - w - extraSpace;
+                    }
+
+                    if (t + h + calendarToggle.getOffsetHeight() + 30 > browserWindowHeight) {
+                        // Part of the popup is outside the browser window
+                        // (below)
+                        t = browserWindowHeight - h
+                                - calendarToggle.getOffsetHeight() - 30;
+                        if (!overflowRight) {
+                            // Show to the right of the popup button unless we
+                            // are in the lower right corner of the screen
+                            l += calendarToggle.getOffsetWidth();
+                        }
+                    }
+
+                    // fix size
+                    popup.setWidth(w + "px");
+                    popup.setHeight(h + "px");
+
+                    popup.setPopupPosition(l,
+                            t + calendarToggle.getOffsetHeight() + 2);
+
+                    /*
+                     * We have to wait a while before focusing since the popup
+                     * needs to be opened before we can focus
+                     */
+                    Timer focusTimer = new Timer() {
+                        @Override
+                        public void run() {
+                            setFocus(true);
+                        }
+                    };
+
+                    focusTimer.schedule(100);
+                }
+            });
+        } else {
+            VConsole.error("Cannot reopen popup, it is already open!");
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event
+     * .dom.client.ClickEvent)
+     */
+    @Override
+    public void onClick(ClickEvent event) {
+        if (event.getSource() == calendarToggle && isEnabled()) {
+            openCalendarPanel();
+        }
+    }
+
+    /*
+     * (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 (event.getSource() == popup) {
+            buildDate();
+            if (!BrowserInfo.get().isTouchDevice()) {
+                /*
+                 * Move focus to textbox, unless on touch device (avoids opening
+                 * virtual keyboard).
+                 */
+                focus();
+            }
+
+            // TODO resolve what the "Sigh." is all about and document it here
+            // Sigh.
+            Timer t = new Timer() {
+                @Override
+                public void run() {
+                    open = false;
+                }
+            };
+            t.schedule(100);
+        }
+    }
+
+    /**
+     * Sets focus to Calendar panel.
+     * 
+     * @param focus
+     */
+    public void setFocus(boolean focus) {
+        calendar.setFocus(focus);
+    }
+
+    /**
+     * For internal use only. May be removed or replaced in the future.
+     * 
+     * @see com.vaadin.client.ui.VTextualDate#buildDate()
+     */
+    @Override
+    public void buildDate() {
+        // Save previous value
+        String previousValue = getText();
+        super.buildDate();
+
+        // Restore previous value if the input could not be parsed
+        if (!parsable) {
+            setText(previousValue);
+        }
+    }
+
+    /**
+     * Update the text field contents from the date. See {@link #buildDate()}.
+     * 
+     * @param forceValid
+     *            true to force the text field to be updated, false to only
+     *            update if the parsable flag is true.
+     */
+    protected void buildDate(boolean forceValid) {
+        if (forceValid) {
+            parsable = true;
+        }
+        buildDate();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see com.vaadin.client.ui.VDateField#onBrowserEvent(com.google
+     * .gwt.user.client.Event)
+     */
+    @Override
+    public void onBrowserEvent(com.google.gwt.user.client.Event event) {
+        super.onBrowserEvent(event);
+        if (DOM.eventGetType(event) == Event.ONKEYDOWN
+                && event.getKeyCode() == getOpenCalenderPanelKey()) {
+            openCalendarPanel();
+            event.preventDefault();
+        }
+    }
+
+    /**
+     * Get the key code that opens the calendar panel. By default it is the down
+     * key but you can override this to be whatever you like
+     * 
+     * @return
+     */
+    protected int getOpenCalenderPanelKey() {
+        return KeyCodes.KEY_DOWN;
+    }
+
+    /**
+     * Closes the open popup panel
+     */
+    public void closeCalendarPanel() {
+        if (open) {
+            popup.hide(true);
+        }
+    }
+
+    private final String CALENDAR_TOGGLE_ID = "popupButton";
+
+    @Override
+    public Element getSubPartElement(String subPart) {
+        if (subPart.equals(CALENDAR_TOGGLE_ID)) {
+            return calendarToggle.getElement();
+        }
+
+        return super.getSubPartElement(subPart);
+    }
+
+    @Override
+    public String getSubPartName(Element subElement) {
+        if (calendarToggle.getElement().isOrHasChild(subElement)) {
+            return CALENDAR_TOGGLE_ID;
+        }
+
+        return super.getSubPartName(subElement);
+    }
+
+}
diff --git a/client/src/com/vaadin/client/ui/VPopupView.java b/client/src/com/vaadin/client/ui/VPopupView.java
new file mode 100644 (file)
index 0000000..4e0a9bc
--- /dev/null
@@ -0,0 +1,393 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+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.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.Focusable;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.HasWidgets;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.VCaptionWrapper;
+import com.vaadin.client.VConsole;
+import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
+import com.vaadin.client.ui.popupview.VisibilityChangeEvent;
+import com.vaadin.client.ui.popupview.VisibilityChangeHandler;
+
+public class VPopupView extends HTML {
+
+    public static final String CLASSNAME = "v-popupview";
+
+    /**
+     * For server-client communication.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public String uidlId;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public ApplicationConnection client;
+
+    /**
+     * Helps to communicate popup visibility to the server.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public boolean hostPopupVisible;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public final CustomPopup popup;
+    private final Label loading = new Label();
+
+    /**
+     * loading constructor
+     */
+    public VPopupView() {
+        super();
+        popup = new CustomPopup();
+
+        setStyleName(CLASSNAME);
+        popup.setStyleName(CLASSNAME + "-popup");
+        loading.setStyleName(CLASSNAME + "-loading");
+
+        setHTML("");
+        popup.setWidget(loading);
+
+        // When we click to open the popup...
+        addClickHandler(new ClickHandler() {
+            @Override
+            public void onClick(ClickEvent event) {
+                fireEvent(new VisibilityChangeEvent(true));
+            }
+        });
+
+        // ..and when we close it
+        popup.addCloseHandler(new CloseHandler<PopupPanel>() {
+            @Override
+            public void onClose(CloseEvent<PopupPanel> event) {
+                fireEvent(new VisibilityChangeEvent(false));
+            }
+        });
+
+        popup.setAnimationEnabled(true);
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void preparePopup(final CustomPopup popup) {
+        popup.setVisible(false);
+        popup.show();
+    }
+
+    /**
+     * Determines the correct position for a popup and displays the popup at
+     * that position.
+     * 
+     * By default, the popup is shown centered relative to its host component,
+     * ensuring it is visible on the screen if possible.
+     * 
+     * Can be overridden to customize the popup position.
+     * 
+     * @param popup
+     */
+    public void showPopup(final CustomPopup popup) {
+        popup.setPopupPosition(0, 0);
+
+        popup.setVisible(true);
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void center() {
+        int windowTop = RootPanel.get().getAbsoluteTop();
+        int windowLeft = RootPanel.get().getAbsoluteLeft();
+        int windowRight = windowLeft + RootPanel.get().getOffsetWidth();
+        int windowBottom = windowTop + RootPanel.get().getOffsetHeight();
+
+        int offsetWidth = popup.getOffsetWidth();
+        int offsetHeight = popup.getOffsetHeight();
+
+        int hostHorizontalCenter = VPopupView.this.getAbsoluteLeft()
+                + VPopupView.this.getOffsetWidth() / 2;
+        int hostVerticalCenter = VPopupView.this.getAbsoluteTop()
+                + VPopupView.this.getOffsetHeight() / 2;
+
+        int left = hostHorizontalCenter - offsetWidth / 2;
+        int top = hostVerticalCenter - offsetHeight / 2;
+
+        // Don't show the popup outside the screen.
+        if ((left + offsetWidth) > windowRight) {
+            left -= (left + offsetWidth) - windowRight;
+        }
+
+        if ((top + offsetHeight) > windowBottom) {
+            top -= (top + offsetHeight) - windowBottom;
+        }
+
+        if (left < 0) {
+            left = 0;
+        }
+
+        if (top < 0) {
+            top = 0;
+        }
+
+        popup.setPopupPosition(left, top);
+    }
+
+    /**
+     * Make sure that we remove the popup when the main widget is removed.
+     * 
+     * @see com.google.gwt.user.client.ui.Widget#onUnload()
+     */
+    @Override
+    protected void onDetach() {
+        popup.hide();
+        super.onDetach();
+    }
+
+    private static native void nativeBlur(Element e)
+    /*-{
+        if(e && e.blur) {
+            e.blur();
+        }
+    }-*/;
+
+    /**
+     * This class is only public to enable overriding showPopup, and is
+     * currently not intended to be extended or otherwise used directly. Its API
+     * (other than it being a VOverlay) is to be considered private and
+     * potentially subject to change.
+     */
+    public class CustomPopup extends VOverlay {
+
+        private ComponentConnector popupComponentConnector = null;
+
+        /** For internal use only. May be removed or replaced in the future. */
+        public Widget popupComponentWidget = null;
+
+        /** For internal use only. May be removed or replaced in the future. */
+        public VCaptionWrapper captionWrapper = null;
+
+        private boolean hasHadMouseOver = false;
+        private boolean hideOnMouseOut = true;
+        private final Set<Element> activeChildren = new HashSet<Element>();
+        private boolean hiding = false;
+
+        private ShortcutActionHandler shortcutActionHandler;
+
+        public CustomPopup() {
+            super(true, false, true); // autoHide, not modal, dropshadow
+            setOwner(VPopupView.this);
+            // Delegate popup keyboard events to the relevant handler. The
+            // events do not propagate automatically because the popup is
+            // directly attached to the RootPanel.
+            addDomHandler(new KeyDownHandler() {
+                @Override
+                public void onKeyDown(KeyDownEvent event) {
+                    if (shortcutActionHandler != null) {
+                        shortcutActionHandler.handleKeyboardEvent(Event
+                                .as(event.getNativeEvent()));
+                    }
+                }
+            }, KeyDownEvent.getType());
+        }
+
+        // For some reason ONMOUSEOUT events are not always received, so we have
+        // to use ONMOUSEMOVE that doesn't target the popup
+        @Override
+        public boolean onEventPreview(Event event) {
+            Element target = DOM.eventGetTarget(event);
+            boolean eventTargetsPopup = DOM.isOrHasChild(getElement(), target);
+            int type = DOM.eventGetType(event);
+
+            // Catch children that use keyboard, so we can unfocus them when
+            // hiding
+            if (eventTargetsPopup && type == Event.ONKEYPRESS) {
+                activeChildren.add(target);
+            }
+
+            if (eventTargetsPopup && type == Event.ONMOUSEMOVE) {
+                hasHadMouseOver = true;
+            }
+
+            if (!eventTargetsPopup && type == Event.ONMOUSEMOVE) {
+                if (hasHadMouseOver && hideOnMouseOut) {
+                    hide();
+                    return true;
+                }
+            }
+
+            // Was the TAB key released outside of our popup?
+            if (!eventTargetsPopup && type == Event.ONKEYUP
+                    && event.getKeyCode() == KeyCodes.KEY_TAB) {
+                // Should we hide on focus out (mouse out)?
+                if (hideOnMouseOut) {
+                    hide();
+                    return true;
+                }
+            }
+
+            return super.onEventPreview(event);
+        }
+
+        @Override
+        public void hide(boolean autoClosed) {
+            VConsole.log("Hiding popupview");
+            hiding = true;
+            syncChildren();
+            if (popupComponentWidget != null && popupComponentWidget != loading) {
+                remove(popupComponentWidget);
+            }
+            hasHadMouseOver = false;
+            shortcutActionHandler = null;
+            super.hide(autoClosed);
+        }
+
+        @Override
+        public void show() {
+            hiding = false;
+
+            // Find the shortcut action handler that should handle keyboard
+            // events from the popup. The events do not propagate automatically
+            // because the popup is directly attached to the RootPanel.
+            Widget widget = VPopupView.this;
+            while (shortcutActionHandler == null && widget != null) {
+                if (widget instanceof ShortcutActionHandlerOwner) {
+                    shortcutActionHandler = ((ShortcutActionHandlerOwner) widget)
+                            .getShortcutActionHandler();
+                }
+                widget = widget.getParent();
+            }
+
+            super.show();
+        }
+
+        /**
+         * Try to sync all known active child widgets to server
+         */
+        public void syncChildren() {
+            // Notify children with focus
+            if ((popupComponentWidget instanceof Focusable)) {
+                ((Focusable) popupComponentWidget).setFocus(false);
+            } else {
+
+                checkForRTE(popupComponentWidget);
+            }
+
+            // Notify children that have used the keyboard
+            for (Element e : activeChildren) {
+                try {
+                    nativeBlur(e);
+                } catch (Exception ignored) {
+                }
+            }
+            activeChildren.clear();
+        }
+
+        private void checkForRTE(Widget popupComponentWidget2) {
+            if (popupComponentWidget2 instanceof VRichTextArea) {
+                ((VRichTextArea) popupComponentWidget2)
+                        .synchronizeContentToServer();
+            } else if (popupComponentWidget2 instanceof HasWidgets) {
+                HasWidgets hw = (HasWidgets) popupComponentWidget2;
+                Iterator<Widget> iterator = hw.iterator();
+                while (iterator.hasNext()) {
+                    checkForRTE(iterator.next());
+                }
+            }
+        }
+
+        @Override
+        public boolean remove(Widget w) {
+
+            popupComponentConnector = null;
+            popupComponentWidget = null;
+            captionWrapper = null;
+
+            return super.remove(w);
+        }
+
+        public void setPopupConnector(ComponentConnector newPopupComponent) {
+
+            if (newPopupComponent != popupComponentConnector) {
+                Widget newWidget = newPopupComponent.getWidget();
+                setWidget(newWidget);
+                popupComponentWidget = newWidget;
+                popupComponentConnector = newPopupComponent;
+            }
+
+        }
+
+        public void setHideOnMouseOut(boolean hideOnMouseOut) {
+            this.hideOnMouseOut = hideOnMouseOut;
+        }
+
+        /*
+         * 
+         * We need a hack make popup act as a child of VPopupView in Vaadin's
+         * component tree, but work in default GWT manner when closing or
+         * opening.
+         * 
+         * (non-Javadoc)
+         * 
+         * @see com.google.gwt.user.client.ui.Widget#getParent()
+         */
+        @Override
+        public Widget getParent() {
+            if (!isAttached() || hiding) {
+                return super.getParent();
+            } else {
+                return VPopupView.this;
+            }
+        }
+
+        @Override
+        protected void onDetach() {
+            super.onDetach();
+            hiding = false;
+        }
+
+        @Override
+        public Element getContainerElement() {
+            return super.getContainerElement();
+        }
+
+    }// class CustomPopup
+
+    public HandlerRegistration addVisibilityChangeHandler(
+            final VisibilityChangeHandler visibilityChangeHandler) {
+        return addHandler(visibilityChangeHandler,
+                VisibilityChangeEvent.getType());
+    }
+
+}// class VPopupView
diff --git a/client/src/com/vaadin/client/ui/VProgressIndicator.java b/client/src/com/vaadin/client/ui/VProgressIndicator.java
new file mode 100644 (file)
index 0000000..0428104
--- /dev/null
@@ -0,0 +1,74 @@
+/* 
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.HasEnabled;
+import com.google.gwt.user.client.ui.Widget;
+
+public class VProgressIndicator extends Widget implements HasEnabled {
+
+    public static final String CLASSNAME = "v-progressindicator";
+    Element wrapper = DOM.createDiv();
+    Element indicator = DOM.createDiv();
+
+    protected boolean indeterminate = false;
+    protected float state = 0.0f;
+    private boolean enabled;
+
+    public VProgressIndicator() {
+        setElement(DOM.createDiv());
+        getElement().appendChild(wrapper);
+        setStyleName(CLASSNAME);
+        wrapper.appendChild(indicator);
+        indicator.setClassName(CLASSNAME + "-indicator");
+        wrapper.setClassName(CLASSNAME + "-wrapper");
+    }
+
+    public void setIndeterminate(boolean indeterminate) {
+        this.indeterminate = indeterminate;
+        setStyleName(CLASSNAME + "-indeterminate", indeterminate);
+    }
+
+    public void setState(float state) {
+        final int size = Math.round(100 * state);
+        indicator.getStyle().setWidth(size, Unit.PCT);
+    }
+
+    public boolean isIndeterminate() {
+        return indeterminate;
+    }
+
+    public float getState() {
+        return state;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+        setStyleName("v-disabled", !enabled);
+
+    }
+
+}
diff --git a/client/src/com/vaadin/client/ui/VRichTextArea.java b/client/src/com/vaadin/client/ui/VRichTextArea.java
new file mode 100644 (file)
index 0000000..2576d02
--- /dev/null
@@ -0,0 +1,379 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Focusable;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.RichTextArea;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.ConnectorMap;
+import com.vaadin.client.Util;
+import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
+
+/**
+ * This class implements a basic client side rich text editor component.
+ * 
+ * @author Vaadin Ltd.
+ * 
+ */
+public class VRichTextArea extends Composite implements Field, ChangeHandler,
+        BlurHandler, KeyPressHandler, KeyDownHandler, Focusable {
+
+    /**
+     * The input node CSS classname.
+     */
+    public static final String CLASSNAME = "v-richtextarea";
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public String id;
+
+    /** 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 boolean immediate = false;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public RichTextArea rta;
+
+    private VRichTextToolbar formatter;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public HTML html = new HTML();
+
+    private final FlowPanel fp = new FlowPanel();
+
+    private boolean enabled = true;
+
+    private int extraHorizontalPixels = -1;
+    private int extraVerticalPixels = -1;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public int maxLength = -1;
+
+    private int toolbarNaturalWidth = 500;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public HandlerRegistration keyPressHandler;
+
+    private ShortcutActionHandlerOwner hasShortcutActionHandler;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public String currentValue = "";
+
+    private boolean readOnly = false;
+
+    public VRichTextArea() {
+        createRTAComponents();
+        fp.add(formatter);
+        fp.add(rta);
+
+        initWidget(fp);
+        setStyleName(CLASSNAME);
+
+        TouchScrollDelegate.enableTouchScrolling(html, html.getElement());
+    }
+
+    private void createRTAComponents() {
+        rta = new RichTextArea();
+        rta.setWidth("100%");
+        rta.addBlurHandler(this);
+        rta.addKeyDownHandler(this);
+        formatter = new VRichTextToolbar(rta);
+    }
+
+    public void setEnabled(boolean enabled) {
+        if (this.enabled != enabled) {
+            // rta.setEnabled(enabled);
+            swapEditableArea();
+            this.enabled = enabled;
+        }
+    }
+
+    /**
+     * Swaps html to rta and visa versa.
+     */
+    private void swapEditableArea() {
+        if (html.isAttached()) {
+            fp.remove(html);
+            if (BrowserInfo.get().isWebkit()) {
+                fp.remove(formatter);
+                createRTAComponents(); // recreate new RTA to bypass #5379
+                fp.add(formatter);
+            }
+            rta.setHTML(currentValue);
+            fp.add(rta);
+        } else {
+            html.setHTML(currentValue);
+            fp.remove(rta);
+            fp.add(html);
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void selectAll() {
+        /*
+         * There is a timing issue if trying to select all immediately on first
+         * render. Simple deferred command is not enough. Using Timer with
+         * moderated timeout. If this appears to fail on many (most likely slow)
+         * environments, consider increasing the timeout.
+         * 
+         * FF seems to require the most time to stabilize its RTA. On Vaadin
+         * tiergarden test machines, 200ms was not enough always (about 50%
+         * success rate) - 300 ms was 100% successful. This however was not
+         * enough on a sluggish old non-virtualized XP test machine. A bullet
+         * proof solution would be nice, GWT 2.1 might however solve these. At
+         * least setFocus has a workaround for this kind of issue.
+         */
+        new Timer() {
+            @Override
+            public void run() {
+                rta.getFormatter().selectAll();
+            }
+        }.schedule(320);
+    }
+
+    public void setReadOnly(boolean b) {
+        if (isReadOnly() != b) {
+            swapEditableArea();
+            readOnly = b;
+        }
+        // reset visibility in case enabled state changed and the formatter was
+        // recreated
+        formatter.setVisible(!readOnly);
+    }
+
+    private boolean isReadOnly() {
+        return readOnly;
+    }
+
+    // TODO is this really used, or does everything go via onBlur() only?
+    @Override
+    public void onChange(ChangeEvent event) {
+        synchronizeContentToServer();
+    }
+
+    /**
+     * Method is public to let popupview force synchronization on close.
+     */
+    public void synchronizeContentToServer() {
+        if (client != null && id != null) {
+            final String html = rta.getHTML();
+            if (!html.equals(currentValue)) {
+                client.updateVariable(id, "text", html, immediate);
+                currentValue = html;
+            }
+        }
+    }
+
+    @Override
+    public void onBlur(BlurEvent event) {
+        synchronizeContentToServer();
+        // TODO notify possible server side blur/focus listeners
+    }
+
+    /**
+     * @return space used by components paddings and borders
+     */
+    private int getExtraHorizontalPixels() {
+        if (extraHorizontalPixels < 0) {
+            detectExtraSizes();
+        }
+        return extraHorizontalPixels;
+    }
+
+    /**
+     * @return space used by components paddings and borders
+     */
+    private int getExtraVerticalPixels() {
+        if (extraVerticalPixels < 0) {
+            detectExtraSizes();
+        }
+        return extraVerticalPixels;
+    }
+
+    /**
+     * Detects space used by components paddings and borders.
+     */
+    private void detectExtraSizes() {
+        Element clone = Util.cloneNode(getElement(), false);
+        DOM.setElementAttribute(clone, "id", "");
+        DOM.setStyleAttribute(clone, "visibility", "hidden");
+        DOM.setStyleAttribute(clone, "position", "absolute");
+        // due FF3 bug set size to 10px and later subtract it from extra pixels
+        DOM.setStyleAttribute(clone, "width", "10px");
+        DOM.setStyleAttribute(clone, "height", "10px");
+        DOM.appendChild(DOM.getParent(getElement()), clone);
+        extraHorizontalPixels = DOM.getElementPropertyInt(clone, "offsetWidth") - 10;
+        extraVerticalPixels = DOM.getElementPropertyInt(clone, "offsetHeight") - 10;
+
+        DOM.removeChild(DOM.getParent(getElement()), clone);
+    }
+
+    @Override
+    public void setHeight(String height) {
+        if (height.endsWith("px")) {
+            int h = Integer.parseInt(height.substring(0, height.length() - 2));
+            h -= getExtraVerticalPixels();
+            if (h < 0) {
+                h = 0;
+            }
+
+            super.setHeight(h + "px");
+        } else {
+            super.setHeight(height);
+        }
+
+        if (height == null || height.equals("")) {
+            rta.setHeight("");
+        } else {
+            /*
+             * The formatter height will be initially calculated wrong so we
+             * delay the height setting so the DOM has had time to stabilize.
+             */
+            Scheduler.get().scheduleDeferred(new Command() {
+                @Override
+                public void execute() {
+                    int editorHeight = getOffsetHeight()
+                            - getExtraVerticalPixels()
+                            - formatter.getOffsetHeight();
+                    if (editorHeight < 0) {
+                        editorHeight = 0;
+                    }
+                    rta.setHeight(editorHeight + "px");
+                }
+            });
+        }
+    }
+
+    @Override
+    public void setWidth(String width) {
+        if (width.endsWith("px")) {
+            int w = Integer.parseInt(width.substring(0, width.length() - 2));
+            w -= getExtraHorizontalPixels();
+            if (w < 0) {
+                w = 0;
+            }
+
+            super.setWidth(w + "px");
+        } else if (width.equals("")) {
+            /*
+             * IE cannot calculate the width of the 100% iframe correctly if
+             * there is no width specified for the parent. In this case we would
+             * use the toolbar but IE cannot calculate the width of that one
+             * correctly either in all cases. So we end up using a default width
+             * for a RichTextArea with no width definition in all browsers (for
+             * compatibility).
+             */
+
+            super.setWidth(toolbarNaturalWidth + "px");
+        } else {
+            super.setWidth(width);
+        }
+    }
+
+    @Override
+    public void onKeyPress(KeyPressEvent event) {
+        if (maxLength >= 0) {
+            Scheduler.get().scheduleDeferred(new Command() {
+                @Override
+                public void execute() {
+                    if (rta.getHTML().length() > maxLength) {
+                        rta.setHTML(rta.getHTML().substring(0, maxLength));
+                    }
+                }
+            });
+        }
+    }
+
+    @Override
+    public void onKeyDown(KeyDownEvent event) {
+        // delegate to closest shortcut action handler
+        // throw event from the iframe forward to the shortcuthandler
+        ShortcutActionHandler shortcutHandler = getShortcutHandlerOwner()
+                .getShortcutActionHandler();
+        if (shortcutHandler != null) {
+            shortcutHandler
+                    .handleKeyboardEvent(com.google.gwt.user.client.Event
+                            .as(event.getNativeEvent()),
+                            ConnectorMap.get(client).getConnector(this));
+        }
+    }
+
+    private ShortcutActionHandlerOwner getShortcutHandlerOwner() {
+        if (hasShortcutActionHandler == null) {
+            Widget parent = getParent();
+            while (parent != null) {
+                if (parent instanceof ShortcutActionHandlerOwner) {
+                    break;
+                }
+                parent = parent.getParent();
+            }
+            hasShortcutActionHandler = (ShortcutActionHandlerOwner) parent;
+        }
+        return hasShortcutActionHandler;
+    }
+
+    @Override
+    public int getTabIndex() {
+        return rta.getTabIndex();
+    }
+
+    @Override
+    public void setAccessKey(char key) {
+        rta.setAccessKey(key);
+    }
+
+    @Override
+    public void setFocus(boolean focused) {
+        /*
+         * Similar issue as with selectAll. Focusing must happen before possible
+         * selectall, so keep the timeout here lower.
+         */
+        new Timer() {
+
+            @Override
+            public void run() {
+                rta.setFocus(true);
+            }
+        }.schedule(300);
+    }
+
+    @Override
+    public void setTabIndex(int index) {
+        rta.setTabIndex(index);
+    }
+
+}
diff --git a/client/src/com/vaadin/client/ui/VRichTextToolbar.java b/client/src/com/vaadin/client/ui/VRichTextToolbar.java
new file mode 100644 (file)
index 0000000..4d002e9
--- /dev/null
@@ -0,0 +1,476 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+/*
+ * Copyright 2007 Google Inc.
+ *
+ * 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 com.google.gwt.core.client.GWT;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.i18n.client.Constants;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.ImageResource;
+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.Image;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.PushButton;
+import com.google.gwt.user.client.ui.RichTextArea;
+import com.google.gwt.user.client.ui.ToggleButton;
+
+/**
+ * A modified version of sample toolbar for use with {@link RichTextArea}. It
+ * provides a simple UI for all rich text formatting, dynamically displayed only
+ * for the available functionality.
+ */
+public class VRichTextToolbar extends Composite {
+
+    /**
+     * This {@link ClientBundle} is used for all the button icons. Using a
+     * bundle allows all of these images to be packed into a single image, which
+     * saves a lot of HTTP requests, drastically improving startup time.
+     */
+    public interface Images extends ClientBundle {
+
+        ImageResource bold();
+
+        ImageResource createLink();
+
+        ImageResource hr();
+
+        ImageResource indent();
+
+        ImageResource insertImage();
+
+        ImageResource italic();
+
+        ImageResource justifyCenter();
+
+        ImageResource justifyLeft();
+
+        ImageResource justifyRight();
+
+        ImageResource ol();
+
+        ImageResource outdent();
+
+        ImageResource removeFormat();
+
+        ImageResource removeLink();
+
+        ImageResource strikeThrough();
+
+        ImageResource subscript();
+
+        ImageResource superscript();
+
+        ImageResource ul();
+
+        ImageResource underline();
+    }
+
+    /**
+     * This {@link Constants} interface is used to make the toolbar's strings
+     * internationalizable.
+     */
+    public interface Strings extends Constants {
+
+        String black();
+
+        String blue();
+
+        String bold();
+
+        String color();
+
+        String createLink();
+
+        String font();
+
+        String green();
+
+        String hr();
+
+        String indent();
+
+        String insertImage();
+
+        String italic();
+
+        String justifyCenter();
+
+        String justifyLeft();
+
+        String justifyRight();
+
+        String large();
+
+        String medium();
+
+        String normal();
+
+        String ol();
+
+        String outdent();
+
+        String red();
+
+        String removeFormat();
+
+        String removeLink();
+
+        String size();
+
+        String small();
+
+        String strikeThrough();
+
+        String subscript();
+
+        String superscript();
+
+        String ul();
+
+        String underline();
+
+        String white();
+
+        String xlarge();
+
+        String xsmall();
+
+        String xxlarge();
+
+        String xxsmall();
+
+        String yellow();
+    }
+
+    /**
+     * We use an inner EventHandler class to avoid exposing event methods on the
+     * RichTextToolbar itself.
+     */
+    private class EventHandler implements ClickHandler, ChangeHandler,
+            KeyUpHandler {
+
+        @Override
+        @SuppressWarnings("deprecation")
+        public void onChange(ChangeEvent event) {
+            Object sender = event.getSource();
+            if (sender == backColors) {
+                basic.setBackColor(backColors.getValue(backColors
+                        .getSelectedIndex()));
+                backColors.setSelectedIndex(0);
+            } else if (sender == foreColors) {
+                basic.setForeColor(foreColors.getValue(foreColors
+                        .getSelectedIndex()));
+                foreColors.setSelectedIndex(0);
+            } else if (sender == fonts) {
+                basic.setFontName(fonts.getValue(fonts.getSelectedIndex()));
+                fonts.setSelectedIndex(0);
+            } else if (sender == fontSizes) {
+                basic.setFontSize(fontSizesConstants[fontSizes
+                        .getSelectedIndex() - 1]);
+                fontSizes.setSelectedIndex(0);
+            }
+        }
+
+        @Override
+        @SuppressWarnings("deprecation")
+        public void onClick(ClickEvent event) {
+            Object sender = event.getSource();
+            if (sender == bold) {
+                basic.toggleBold();
+            } else if (sender == italic) {
+                basic.toggleItalic();
+            } else if (sender == underline) {
+                basic.toggleUnderline();
+            } else if (sender == subscript) {
+                basic.toggleSubscript();
+            } else if (sender == superscript) {
+                basic.toggleSuperscript();
+            } else if (sender == strikethrough) {
+                extended.toggleStrikethrough();
+            } else if (sender == indent) {
+                extended.rightIndent();
+            } else if (sender == outdent) {
+                extended.leftIndent();
+            } else if (sender == justifyLeft) {
+                basic.setJustification(RichTextArea.Justification.LEFT);
+            } else if (sender == justifyCenter) {
+                basic.setJustification(RichTextArea.Justification.CENTER);
+            } else if (sender == justifyRight) {
+                basic.setJustification(RichTextArea.Justification.RIGHT);
+            } else if (sender == insertImage) {
+                final String url = Window.prompt("Enter an image URL:",
+                        "http://");
+                if (url != null) {
+                    extended.insertImage(url);
+                }
+            } else if (sender == createLink) {
+                final String url = Window
+                        .prompt("Enter a link URL:", "http://");
+                if (url != null) {
+                    extended.createLink(url);
+                }
+            } else if (sender == removeLink) {
+                extended.removeLink();
+            } else if (sender == hr) {
+                extended.insertHorizontalRule();
+            } else if (sender == ol) {
+                extended.insertOrderedList();
+            } else if (sender == ul) {
+                extended.insertUnorderedList();
+            } else if (sender == removeFormat) {
+                extended.removeFormat();
+            } else if (sender == richText) {
+                // We use the RichTextArea's onKeyUp event to update the toolbar
+                // status. This will catch any cases where the user moves the
+                // cursur using the keyboard, or uses one of the browser's
+                // built-in keyboard shortcuts.
+                updateStatus();
+            }
+        }
+
+        @Override
+        public void onKeyUp(KeyUpEvent event) {
+            if (event.getSource() == richText) {
+                // We use the RichTextArea's onKeyUp event to update the toolbar
+                // status. This will catch any cases where the user moves the
+                // cursor using the keyboard, or uses one of the browser's
+                // built-in keyboard shortcuts.
+                updateStatus();
+            }
+        }
+    }
+
+    private static final RichTextArea.FontSize[] fontSizesConstants = new RichTextArea.FontSize[] {
+            RichTextArea.FontSize.XX_SMALL, RichTextArea.FontSize.X_SMALL,
+            RichTextArea.FontSize.SMALL, RichTextArea.FontSize.MEDIUM,
+            RichTextArea.FontSize.LARGE, RichTextArea.FontSize.X_LARGE,
+            RichTextArea.FontSize.XX_LARGE };
+
+    private final Images images = (Images) GWT.create(Images.class);
+    private final Strings strings = (Strings) GWT.create(Strings.class);
+    private final EventHandler handler = new EventHandler();
+
+    private final RichTextArea richText;
+    @SuppressWarnings("deprecation")
+    private final RichTextArea.BasicFormatter basic;
+    @SuppressWarnings("deprecation")
+    private final RichTextArea.ExtendedFormatter extended;
+
+    private final FlowPanel outer = new FlowPanel();
+    private final FlowPanel topPanel = new FlowPanel();
+    private final FlowPanel bottomPanel = new FlowPanel();
+    private ToggleButton bold;
+    private ToggleButton italic;
+    private ToggleButton underline;
+    private ToggleButton subscript;
+    private ToggleButton superscript;
+    private ToggleButton strikethrough;
+    private PushButton indent;
+    private PushButton outdent;
+    private PushButton justifyLeft;
+    private PushButton justifyCenter;
+    private PushButton justifyRight;
+    private PushButton hr;
+    private PushButton ol;
+    private PushButton ul;
+    private PushButton insertImage;
+    private PushButton createLink;
+    private PushButton removeLink;
+    private PushButton removeFormat;
+
+    private ListBox backColors;
+    private ListBox foreColors;
+    private ListBox fonts;
+    private ListBox fontSizes;
+
+    /**
+     * Creates a new toolbar that drives the given rich text area.
+     * 
+     * @param richText
+     *            the rich text area to be controlled
+     */
+    @SuppressWarnings("deprecation")
+    public VRichTextToolbar(RichTextArea richText) {
+        this.richText = richText;
+        basic = richText.getBasicFormatter();
+        extended = richText.getExtendedFormatter();
+
+        outer.add(topPanel);
+        outer.add(bottomPanel);
+        topPanel.setStyleName("gwt-RichTextToolbar-top");
+        bottomPanel.setStyleName("gwt-RichTextToolbar-bottom");
+
+        initWidget(outer);
+        setStyleName("gwt-RichTextToolbar");
+
+        if (basic != null) {
+            topPanel.add(bold = createToggleButton(images.bold(),
+                    strings.bold()));
+            topPanel.add(italic = createToggleButton(images.italic(),
+                    strings.italic()));
+            topPanel.add(underline = createToggleButton(images.underline(),
+                    strings.underline()));
+            topPanel.add(subscript = createToggleButton(images.subscript(),
+                    strings.subscript()));
+            topPanel.add(superscript = createToggleButton(images.superscript(),
+                    strings.superscript()));
+            topPanel.add(justifyLeft = createPushButton(images.justifyLeft(),
+                    strings.justifyLeft()));
+            topPanel.add(justifyCenter = createPushButton(
+                    images.justifyCenter(), strings.justifyCenter()));
+            topPanel.add(justifyRight = createPushButton(images.justifyRight(),
+                    strings.justifyRight()));
+        }
+
+        if (extended != null) {
+            topPanel.add(strikethrough = createToggleButton(
+                    images.strikeThrough(), strings.strikeThrough()));
+            topPanel.add(indent = createPushButton(images.indent(),
+                    strings.indent()));
+            topPanel.add(outdent = createPushButton(images.outdent(),
+                    strings.outdent()));
+            topPanel.add(hr = createPushButton(images.hr(), strings.hr()));
+            topPanel.add(ol = createPushButton(images.ol(), strings.ol()));
+            topPanel.add(ul = createPushButton(images.ul(), strings.ul()));
+            topPanel.add(insertImage = createPushButton(images.insertImage(),
+                    strings.insertImage()));
+            topPanel.add(createLink = createPushButton(images.createLink(),
+                    strings.createLink()));
+            topPanel.add(removeLink = createPushButton(images.removeLink(),
+                    strings.removeLink()));
+            topPanel.add(removeFormat = createPushButton(images.removeFormat(),
+                    strings.removeFormat()));
+        }
+
+        if (basic != null) {
+            bottomPanel.add(backColors = createColorList("Background"));
+            bottomPanel.add(foreColors = createColorList("Foreground"));
+            bottomPanel.add(fonts = createFontList());
+            bottomPanel.add(fontSizes = createFontSizes());
+
+            // We only use these handlers for updating status, so don't hook
+            // them up unless at least basic editing is supported.
+            richText.addKeyUpHandler(handler);
+            richText.addClickHandler(handler);
+        }
+    }
+
+    private ListBox createColorList(String caption) {
+        final ListBox lb = new ListBox();
+        lb.addChangeHandler(handler);
+        lb.setVisibleItemCount(1);
+
+        lb.addItem(caption);
+        lb.addItem(strings.white(), "white");
+        lb.addItem(strings.black(), "black");
+        lb.addItem(strings.red(), "red");
+        lb.addItem(strings.green(), "green");
+        lb.addItem(strings.yellow(), "yellow");
+        lb.addItem(strings.blue(), "blue");
+        lb.setTabIndex(-1);
+        return lb;
+    }
+
+    private ListBox createFontList() {
+        final ListBox lb = new ListBox();
+        lb.addChangeHandler(handler);
+        lb.setVisibleItemCount(1);
+
+        lb.addItem(strings.font(), "");
+        lb.addItem(strings.normal(), "inherit");
+        lb.addItem("Times New Roman", "Times New Roman");
+        lb.addItem("Arial", "Arial");
+        lb.addItem("Courier New", "Courier New");
+        lb.addItem("Georgia", "Georgia");
+        lb.addItem("Trebuchet", "Trebuchet");
+        lb.addItem("Verdana", "Verdana");
+        lb.setTabIndex(-1);
+        return lb;
+    }
+
+    private ListBox createFontSizes() {
+        final ListBox lb = new ListBox();
+        lb.addChangeHandler(handler);
+        lb.setVisibleItemCount(1);
+
+        lb.addItem(strings.size());
+        lb.addItem(strings.xxsmall());
+        lb.addItem(strings.xsmall());
+        lb.addItem(strings.small());
+        lb.addItem(strings.medium());
+        lb.addItem(strings.large());
+        lb.addItem(strings.xlarge());
+        lb.addItem(strings.xxlarge());
+        lb.setTabIndex(-1);
+        return lb;
+    }
+
+    private PushButton createPushButton(ImageResource img, String tip) {
+        final PushButton pb = new PushButton(new Image(img));
+        pb.addClickHandler(handler);
+        pb.setTitle(tip);
+        pb.setTabIndex(-1);
+        return pb;
+    }
+
+    private ToggleButton createToggleButton(ImageResource img, String tip) {
+        final ToggleButton tb = new ToggleButton(new Image(img));
+        tb.addClickHandler(handler);
+        tb.setTitle(tip);
+        tb.setTabIndex(-1);
+        return tb;
+    }
+
+    /**
+     * Updates the status of all the stateful buttons.
+     */
+    @SuppressWarnings("deprecation")
+    private void updateStatus() {
+        if (basic != null) {
+            bold.setDown(basic.isBold());
+            italic.setDown(basic.isItalic());
+            underline.setDown(basic.isUnderlined());
+            subscript.setDown(basic.isSubscript());
+            superscript.setDown(basic.isSuperscript());
+        }
+
+        if (extended != null) {
+            strikethrough.setDown(extended.isStrikethrough());
+        }
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VScrollTable.java b/client/src/com/vaadin/client/ui/VScrollTable.java
new file mode 100644 (file)
index 0000000..2af58d8
--- /dev/null
@@ -0,0 +1,7156 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+import java.util.Collection;
+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.Set;
+
+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.Document;
+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.Overflow;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.dom.client.Style.Visibility;
+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.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.ContextMenuEvent;
+import com.google.gwt.event.dom.client.ContextMenuHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.event.dom.client.ScrollEvent;
+import com.google.gwt.event.dom.client.ScrollHandler;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+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.FlowPanel;
+import com.google.gwt.user.client.ui.HasWidgets;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.PopupPanel;
+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.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ConnectorMap;
+import com.vaadin.client.Focusable;
+import com.vaadin.client.MouseEventDetailsBuilder;
+import com.vaadin.client.TooltipInfo;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.Util;
+import com.vaadin.client.VConsole;
+import com.vaadin.client.VTooltip;
+import com.vaadin.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow;
+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.VHasDropHandler;
+import com.vaadin.client.ui.dd.VTransferable;
+import com.vaadin.client.ui.embedded.VEmbedded;
+import com.vaadin.shared.ComponentState;
+import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.ui.dd.VerticalDropLocation;
+import com.vaadin.shared.ui.table.TableConstants;
+
+/**
+ * VScrollTable
+ * 
+ * VScrollTable is a FlowPanel having two widgets in it: * TableHead component *
+ * ScrollPanel
+ * 
+ * TableHead contains table's header and widgets + logic for resizing,
+ * reordering and hiding columns.
+ * 
+ * ScrollPanel contains VScrollTableBody object which handles content. To save
+ * some bandwidth and to improve clients responsiveness with loads of data, in
+ * VScrollTableBody all rows are not necessary rendered. There are "spacers" in
+ * VScrollTableBody to use the exact same space as non-rendered rows would use.
+ * This way we can use seamlessly traditional scrollbars and scrolling to fetch
+ * more rows instead of "paging".
+ * 
+ * In VScrollTable we listen to scroll events. On horizontal scrolling we also
+ * update TableHeads scroll position which has its scrollbars hidden. On
+ * vertical scroll events we will check if we are reaching the end of area where
+ * we have rows rendered and
+ * 
+ * TODO implement unregistering for child components in Cells
+ */
+public class VScrollTable extends FlowPanel implements HasWidgets,
+        ScrollHandler, VHasDropHandler, FocusHandler, BlurHandler, Focusable,
+        ActionOwner {
+
+    public static final String STYLENAME = "v-table";
+
+    public enum SelectMode {
+        NONE(0), SINGLE(1), MULTI(2);
+        private int id;
+
+        private SelectMode(int id) {
+            this.id = id;
+        }
+
+        public int getId() {
+            return id;
+        }
+    }
+
+    private static final String ROW_HEADER_COLUMN_KEY = "0";
+
+    private static final double CACHE_RATE_DEFAULT = 2;
+
+    /**
+     * The default multi select mode where simple left clicks only selects one
+     * item, CTRL+left click selects multiple items and SHIFT-left click selects
+     * a range of items.
+     */
+    private static final int MULTISELECT_MODE_DEFAULT = 0;
+
+    /**
+     * The simple multiselect mode is what the table used to have before
+     * ctrl/shift selections were added. That is that when this is set clicking
+     * on an item selects/deselects the item and no ctrl/shift selections are
+     * available.
+     */
+    private static final int MULTISELECT_MODE_SIMPLE = 1;
+
+    /**
+     * multiple of pagelength which component will cache when requesting more
+     * rows
+     */
+    private double cache_rate = CACHE_RATE_DEFAULT;
+    /**
+     * fraction of pageLenght which can be scrolled without making new request
+     */
+    private double cache_react_rate = 0.75 * cache_rate;
+
+    public static final char ALIGN_CENTER = 'c';
+    public static final char ALIGN_LEFT = 'b';
+    public static final char ALIGN_RIGHT = 'e';
+    private static final int CHARCODE_SPACE = 32;
+    private int firstRowInViewPort = 0;
+    private int pageLength = 15;
+    private int lastRequestedFirstvisible = 0; // to detect "serverside scroll"
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean showRowHeaders = false;
+
+    private String[] columnOrder;
+
+    protected 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 boolean immediate;
+
+    private boolean nullSelectionAllowed = true;
+
+    private SelectMode selectMode = SelectMode.NONE;
+
+    private final HashSet<String> selectedRowKeys = new HashSet<String>();
+
+    /*
+     * When scrolling and selecting at the same time, the selections are not in
+     * sync with the server while retrieving new rows (until key is released).
+     */
+    private HashSet<Object> unSyncedselectionsBeforeRowFetch;
+
+    /*
+     * These are used when jumping between pages when pressing Home and End
+     */
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean selectLastItemInNextRender = false;
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean selectFirstItemInNextRender = false;
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean focusFirstItemInNextRender = false;
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean focusLastItemInNextRender = false;
+
+    /**
+     * The currently focused row.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public VScrollTableRow focusedRow;
+
+    /**
+     * Helper to store selection range start in when using the keyboard
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public VScrollTableRow selectionRangeStart;
+
+    /**
+     * Flag for notifying when the selection has changed and should be sent to
+     * the server
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public boolean selectionChanged = false;
+
+    /*
+     * The speed (in pixels) which the scrolling scrolls vertically/horizontally
+     */
+    private int scrollingVelocity = 10;
+
+    private Timer scrollingVelocityTimer = null;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public String[] bodyActionKeys;
+
+    private boolean enableDebug = false;
+
+    private static final boolean hasNativeTouchScrolling = BrowserInfo.get()
+            .isTouchDevice()
+            && !BrowserInfo.get().requiresTouchScrollDelegate();
+
+    private Set<String> noncollapsibleColumns;
+
+    /**
+     * The last known row height used to preserve the height of a table with
+     * custom row heights and a fixed page length after removing the last row
+     * from the table.
+     * 
+     * A new VScrollTableBody instance is created every time the number of rows
+     * changes causing {@link VScrollTableBody#rowHeight} to be discarded and
+     * the height recalculated by {@link VScrollTableBody#getRowHeight(boolean)}
+     * to avoid some rounding problems, e.g. round(2 * 19.8) / 2 = 20 but
+     * round(3 * 19.8) / 3 = 19.66.
+     */
+    private double lastKnownRowHeight = Double.NaN;
+
+    /**
+     * Represents a select range of rows
+     */
+    private class SelectionRange {
+        private VScrollTableRow startRow;
+        private final int length;
+
+        /**
+         * Constuctor.
+         */
+        public SelectionRange(VScrollTableRow row1, VScrollTableRow row2) {
+            VScrollTableRow endRow;
+            if (row2.isBefore(row1)) {
+                startRow = row2;
+                endRow = row1;
+            } else {
+                startRow = row1;
+                endRow = row2;
+            }
+            length = endRow.getIndex() - startRow.getIndex() + 1;
+        }
+
+        public SelectionRange(VScrollTableRow row, int length) {
+            startRow = row;
+            this.length = length;
+        }
+
+        /*
+         * (non-Javadoc)
+         * 
+         * @see java.lang.Object#toString()
+         */
+
+        @Override
+        public String toString() {
+            return startRow.getKey() + "-" + length;
+        }
+
+        private boolean inRange(VScrollTableRow row) {
+            return row.getIndex() >= startRow.getIndex()
+                    && row.getIndex() < startRow.getIndex() + length;
+        }
+
+        public Collection<SelectionRange> split(VScrollTableRow row) {
+            assert row.isAttached();
+            ArrayList<SelectionRange> ranges = new ArrayList<SelectionRange>(2);
+
+            int endOfFirstRange = row.getIndex() - 1;
+            if (!(endOfFirstRange - startRow.getIndex() < 0)) {
+                // create range of first part unless its length is < 1
+                ranges.add(new SelectionRange(startRow, endOfFirstRange
+                        - startRow.getIndex() + 1));
+            }
+            int startOfSecondRange = row.getIndex() + 1;
+            if (!(getEndIndex() - startOfSecondRange < 0)) {
+                // create range of second part unless its length is < 1
+                VScrollTableRow startOfRange = scrollBody
+                        .getRowByRowIndex(startOfSecondRange);
+                ranges.add(new SelectionRange(startOfRange, getEndIndex()
+                        - startOfSecondRange + 1));
+            }
+            return ranges;
+        }
+
+        private int getEndIndex() {
+            return startRow.getIndex() + length - 1;
+        }
+
+    };
+
+    private final HashSet<SelectionRange> selectedRowRanges = new HashSet<SelectionRange>();
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean initializedAndAttached = false;
+
+    /**
+     * Flag to indicate if a column width recalculation is needed due update.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public boolean headerChangedDuringUpdate = false;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public final TableHead tHead = new TableHead();
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public final TableFooter tFoot = new TableFooter();
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public final FocusableScrollPanel scrollBodyPanel = new FocusableScrollPanel(
+            true);
+
+    private KeyPressHandler navKeyPressHandler = new KeyPressHandler() {
+
+        @Override
+        public void onKeyPress(KeyPressEvent keyPressEvent) {
+            // This is used for Firefox only, since Firefox auto-repeat
+            // works correctly only if we use a key press handler, other
+            // browsers handle it correctly when using a key down handler
+            if (!BrowserInfo.get().isGecko()) {
+                return;
+            }
+
+            NativeEvent event = keyPressEvent.getNativeEvent();
+            if (!enabled) {
+                // Cancel default keyboard events on a disabled Table
+                // (prevents scrolling)
+                event.preventDefault();
+            } else if (hasFocus) {
+                // Key code in Firefox/onKeyPress is present only for
+                // special keys, otherwise 0 is returned
+                int keyCode = event.getKeyCode();
+                if (keyCode == 0 && event.getCharCode() == ' ') {
+                    // Provide a keyCode for space to be compatible with
+                    // FireFox keypress event
+                    keyCode = CHARCODE_SPACE;
+                }
+
+                if (handleNavigation(keyCode,
+                        event.getCtrlKey() || event.getMetaKey(),
+                        event.getShiftKey())) {
+                    event.preventDefault();
+                }
+
+                startScrollingVelocityTimer();
+            }
+        }
+
+    };
+
+    private KeyUpHandler navKeyUpHandler = new KeyUpHandler() {
+
+        @Override
+        public void onKeyUp(KeyUpEvent keyUpEvent) {
+            NativeEvent event = keyUpEvent.getNativeEvent();
+            int keyCode = event.getKeyCode();
+
+            if (!isFocusable()) {
+                cancelScrollingVelocityTimer();
+            } else if (isNavigationKey(keyCode)) {
+                if (keyCode == getNavigationDownKey()
+                        || keyCode == getNavigationUpKey()) {
+                    /*
+                     * in multiselect mode the server may still have value from
+                     * previous page. Clear it unless doing multiselection or
+                     * just moving focus.
+                     */
+                    if (!event.getShiftKey() && !event.getCtrlKey()) {
+                        instructServerToForgetPreviousSelections();
+                    }
+                    sendSelectedRows();
+                }
+                cancelScrollingVelocityTimer();
+                navKeyDown = false;
+            }
+        }
+    };
+
+    private KeyDownHandler navKeyDownHandler = new KeyDownHandler() {
+
+        @Override
+        public void onKeyDown(KeyDownEvent keyDownEvent) {
+            NativeEvent event = keyDownEvent.getNativeEvent();
+            // This is not used for Firefox
+            if (BrowserInfo.get().isGecko()) {
+                return;
+            }
+
+            if (!enabled) {
+                // Cancel default keyboard events on a disabled Table
+                // (prevents scrolling)
+                event.preventDefault();
+            } else if (hasFocus) {
+                if (handleNavigation(event.getKeyCode(), event.getCtrlKey()
+                        || event.getMetaKey(), event.getShiftKey())) {
+                    navKeyDown = true;
+                    event.preventDefault();
+                }
+
+                startScrollingVelocityTimer();
+            }
+        }
+    };
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public int totalRows;
+
+    private Set<String> collapsedColumns;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public final RowRequestHandler rowRequestHandler;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public VScrollTableBody scrollBody;
+
+    private int firstvisible = 0;
+    private boolean sortAscending;
+    private String sortColumn;
+    private String oldSortColumn;
+    private boolean columnReordering;
+
+    /**
+     * This map contains captions and icon urls for actions like: * "33_c" ->
+     * "Edit" * "33_i" -> "http://dom.com/edit.png"
+     */
+    private final HashMap<Object, String> actionMap = new HashMap<Object, String>();
+    private String[] visibleColOrder;
+    private boolean initialContentReceived = false;
+    private Element scrollPositionElement;
+
+    /** 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 showColHeaders;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean showColFooters;
+
+    /** flag to indicate that table body has changed */
+    private boolean isNewBody = true;
+
+    /**
+     * Read from the "recalcWidths" -attribute. When it is true, the table will
+     * recalculate the widths for columns - desirable in some cases. For #1983,
+     * marked experimental.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public boolean recalcWidths = false;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean rendering = false;
+
+    private boolean hasFocus = false;
+    private int dragmode;
+
+    private int multiselectmode;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public int tabIndex;
+
+    private TouchScrollDelegate touchScrollDelegate;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public int lastRenderedHeight;
+
+    /**
+     * Values (serverCacheFirst+serverCacheLast) sent by server that tells which
+     * rows (indexes) are in the server side cache (page buffer). -1 means
+     * unknown. The server side cache row MUST MATCH the client side cache rows.
+     * 
+     * If the client side cache contains additional rows with e.g. buttons, it
+     * will cause out of sync when such a button is pressed.
+     * 
+     * If the server side cache contains additional rows with e.g. buttons,
+     * scrolling in the client will cause empty buttons to be rendered
+     * (cached=true request for non-existing components)
+     * 
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public int serverCacheFirst = -1;
+    public int serverCacheLast = -1;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean sizeNeedsInit = true;
+
+    /**
+     * Used to recall the position of an open context menu if we need to close
+     * and reopen it during a row update.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public class ContextMenuDetails {
+        public String rowKey;
+        public int left;
+        public int top;
+
+        public ContextMenuDetails(String rowKey, int left, int top) {
+            this.rowKey = rowKey;
+            this.left = left;
+            this.top = top;
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public ContextMenuDetails contextMenu = null;
+
+    public VScrollTable() {
+        setMultiSelectMode(MULTISELECT_MODE_DEFAULT);
+
+        scrollBodyPanel.addFocusHandler(this);
+        scrollBodyPanel.addBlurHandler(this);
+
+        scrollBodyPanel.addScrollHandler(this);
+
+        /*
+         * Firefox auto-repeat works correctly only if we use a key press
+         * handler, other browsers handle it correctly when using a key down
+         * handler
+         */
+        if (BrowserInfo.get().isGecko()) {
+            scrollBodyPanel.addKeyPressHandler(navKeyPressHandler);
+        } else {
+            scrollBodyPanel.addKeyDownHandler(navKeyDownHandler);
+        }
+        scrollBodyPanel.addKeyUpHandler(navKeyUpHandler);
+
+        scrollBodyPanel.sinkEvents(Event.TOUCHEVENTS);
+
+        scrollBodyPanel.sinkEvents(Event.ONCONTEXTMENU);
+        scrollBodyPanel.addDomHandler(new ContextMenuHandler() {
+
+            @Override
+            public void onContextMenu(ContextMenuEvent event) {
+                handleBodyContextMenu(event);
+            }
+        }, ContextMenuEvent.getType());
+
+        setStyleName(STYLENAME);
+
+        add(tHead);
+        add(scrollBodyPanel);
+        add(tFoot);
+
+        rowRequestHandler = new RowRequestHandler();
+    }
+
+    @Override
+    public void setStyleName(String style) {
+        updateStyleNames(style, false);
+    }
+
+    @Override
+    public void setStylePrimaryName(String style) {
+        updateStyleNames(style, true);
+    }
+
+    private void updateStyleNames(String newStyle, boolean isPrimary) {
+        scrollBodyPanel
+                .removeStyleName(getStylePrimaryName() + "-body-wrapper");
+        scrollBodyPanel.removeStyleName(getStylePrimaryName() + "-body");
+
+        if (scrollBody != null) {
+            scrollBody.removeStyleName(getStylePrimaryName()
+                    + "-body-noselection");
+        }
+
+        if (isPrimary) {
+            super.setStylePrimaryName(newStyle);
+        } else {
+            super.setStyleName(newStyle);
+        }
+
+        scrollBodyPanel.addStyleName(getStylePrimaryName() + "-body-wrapper");
+        scrollBodyPanel.addStyleName(getStylePrimaryName() + "-body");
+
+        tHead.updateStyleNames(getStylePrimaryName());
+        tFoot.updateStyleNames(getStylePrimaryName());
+
+        if (scrollBody != null) {
+            scrollBody.updateStyleNames(getStylePrimaryName());
+        }
+    }
+
+    public void init(ApplicationConnection client) {
+        this.client = client;
+        // Add a handler to clear saved context menu details when the menu
+        // closes. See #8526.
+        client.getContextMenu().addCloseHandler(new CloseHandler<PopupPanel>() {
+
+            @Override
+            public void onClose(CloseEvent<PopupPanel> event) {
+                contextMenu = null;
+            }
+        });
+    }
+
+    private void handleBodyContextMenu(ContextMenuEvent event) {
+        if (enabled && bodyActionKeys != null) {
+            int left = Util.getTouchOrMouseClientX(event.getNativeEvent());
+            int top = Util.getTouchOrMouseClientY(event.getNativeEvent());
+            top += Window.getScrollTop();
+            left += Window.getScrollLeft();
+            client.getContextMenu().showAt(this, left, top);
+
+            // Only prevent browser context menu if there are action handlers
+            // registered
+            event.stopPropagation();
+            event.preventDefault();
+        }
+    }
+
+    /**
+     * Fires a column resize event which sends the resize information to the
+     * server.
+     * 
+     * @param columnId
+     *            The columnId of the column which was resized
+     * @param originalWidth
+     *            The width in pixels of the column before the resize event
+     * @param newWidth
+     *            The width in pixels of the column after the resize event
+     */
+    private void fireColumnResizeEvent(String columnId, int originalWidth,
+            int newWidth) {
+        client.updateVariable(paintableId, "columnResizeEventColumn", columnId,
+                false);
+        client.updateVariable(paintableId, "columnResizeEventPrev",
+                originalWidth, false);
+        client.updateVariable(paintableId, "columnResizeEventCurr", newWidth,
+                immediate);
+
+    }
+
+    /**
+     * Non-immediate variable update of column widths for a collection of
+     * columns.
+     * 
+     * @param columns
+     *            the columns to trigger the events for.
+     */
+    private void sendColumnWidthUpdates(Collection<HeaderCell> columns) {
+        String[] newSizes = new String[columns.size()];
+        int ix = 0;
+        for (HeaderCell cell : columns) {
+            newSizes[ix++] = cell.getColKey() + ":" + cell.getWidth();
+        }
+        client.updateVariable(paintableId, "columnWidthUpdates", newSizes,
+                false);
+    }
+
+    /**
+     * Moves the focus one step down
+     * 
+     * @return Returns true if succeeded
+     */
+    private boolean moveFocusDown() {
+        return moveFocusDown(0);
+    }
+
+    /**
+     * Moves the focus down by 1+offset rows
+     * 
+     * @return Returns true if succeeded, else false if the selection could not
+     *         be move downwards
+     */
+    private boolean moveFocusDown(int offset) {
+        if (isSelectable()) {
+            if (focusedRow == null && scrollBody.iterator().hasNext()) {
+                // FIXME should focus first visible from top, not first rendered
+                // ??
+                return setRowFocus((VScrollTableRow) scrollBody.iterator()
+                        .next());
+            } else {
+                VScrollTableRow next = getNextRow(focusedRow, offset);
+                if (next != null) {
+                    return setRowFocus(next);
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Moves the selection one step up
+     * 
+     * @return Returns true if succeeded
+     */
+    private boolean moveFocusUp() {
+        return moveFocusUp(0);
+    }
+
+    /**
+     * Moves the focus row upwards
+     * 
+     * @return Returns true if succeeded, else false if the selection could not
+     *         be move upwards
+     * 
+     */
+    private boolean moveFocusUp(int offset) {
+        if (isSelectable()) {
+            if (focusedRow == null && scrollBody.iterator().hasNext()) {
+                // FIXME logic is exactly the same as in moveFocusDown, should
+                // be the opposite??
+                return setRowFocus((VScrollTableRow) scrollBody.iterator()
+                        .next());
+            } else {
+                VScrollTableRow prev = getPreviousRow(focusedRow, offset);
+                if (prev != null) {
+                    return setRowFocus(prev);
+                } else {
+                    VConsole.log("no previous available");
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Selects a row where the current selection head is
+     * 
+     * @param ctrlSelect
+     *            Is the selection a ctrl+selection
+     * @param shiftSelect
+     *            Is the selection a shift+selection
+     * @return Returns truw
+     */
+    private void selectFocusedRow(boolean ctrlSelect, boolean shiftSelect) {
+        if (focusedRow != null) {
+            // Arrows moves the selection and clears previous selections
+            if (isSelectable() && !ctrlSelect && !shiftSelect) {
+                deselectAll();
+                focusedRow.toggleSelection();
+                selectionRangeStart = focusedRow;
+            } else if (isSelectable() && ctrlSelect && !shiftSelect) {
+                // Ctrl+arrows moves selection head
+                selectionRangeStart = focusedRow;
+                // No selection, only selection head is moved
+            } else if (isMultiSelectModeAny() && !ctrlSelect && shiftSelect) {
+                // Shift+arrows selection selects a range
+                focusedRow.toggleShiftSelection(shiftSelect);
+            }
+        }
+    }
+
+    /**
+     * Sends the selection to the server if changed since the last update/visit.
+     */
+    protected void sendSelectedRows() {
+        sendSelectedRows(immediate);
+    }
+
+    /**
+     * Sends the selection to the server if it has been changed since the last
+     * update/visit.
+     * 
+     * @param immediately
+     *            set to true to immediately send the rows
+     */
+    protected void sendSelectedRows(boolean immediately) {
+        // Don't send anything if selection has not changed
+        if (!selectionChanged) {
+            return;
+        }
+
+        // Reset selection changed flag
+        selectionChanged = false;
+
+        // Note: changing the immediateness of this might require changes to
+        // "clickEvent" immediateness also.
+        if (isMultiSelectModeDefault()) {
+            // Convert ranges to a set of strings
+            Set<String> ranges = new HashSet<String>();
+            for (SelectionRange range : selectedRowRanges) {
+                ranges.add(range.toString());
+            }
+
+            // Send the selected row ranges
+            client.updateVariable(paintableId, "selectedRanges",
+                    ranges.toArray(new String[selectedRowRanges.size()]), false);
+
+            // clean selectedRowKeys so that they don't contain excess values
+            for (Iterator<String> iterator = selectedRowKeys.iterator(); iterator
+                    .hasNext();) {
+                String key = iterator.next();
+                VScrollTableRow renderedRowByKey = getRenderedRowByKey(key);
+                if (renderedRowByKey != null) {
+                    for (SelectionRange range : selectedRowRanges) {
+                        if (range.inRange(renderedRowByKey)) {
+                            iterator.remove();
+                        }
+                    }
+                } else {
+                    // orphaned selected key, must be in a range, ignore
+                    iterator.remove();
+                }
+
+            }
+        }
+
+        // Send the selected rows
+        client.updateVariable(paintableId, "selected",
+                selectedRowKeys.toArray(new String[selectedRowKeys.size()]),
+                immediately);
+
+    }
+
+    /**
+     * Get the key that moves the selection head upwards. By default it is the
+     * up arrow key but by overriding this you can change the key to whatever
+     * you want.
+     * 
+     * @return The keycode of the key
+     */
+    protected int getNavigationUpKey() {
+        return KeyCodes.KEY_UP;
+    }
+
+    /**
+     * Get the key that moves the selection head downwards. By default it is the
+     * down arrow key but by overriding this you can change the key to whatever
+     * you want.
+     * 
+     * @return The keycode of the key
+     */
+    protected int getNavigationDownKey() {
+        return KeyCodes.KEY_DOWN;
+    }
+
+    /**
+     * Get the key that scrolls to the left in the table. By default it is the
+     * left arrow key but by overriding this you can change the key to whatever
+     * you want.
+     * 
+     * @return The keycode of the key
+     */
+    protected int getNavigationLeftKey() {
+        return KeyCodes.KEY_LEFT;
+    }
+
+    /**
+     * Get the key that scroll to the right on the table. By default it is the
+     * right arrow key but by overriding this you can change the key to whatever
+     * you want.
+     * 
+     * @return The keycode of the key
+     */
+    protected int getNavigationRightKey() {
+        return KeyCodes.KEY_RIGHT;
+    }
+
+    /**
+     * Get the key that selects an item in the table. By default it is the space
+     * bar key but by overriding this you can change the key to whatever you
+     * want.
+     * 
+     * @return
+     */
+    protected int getNavigationSelectKey() {
+        return CHARCODE_SPACE;
+    }
+
+    /**
+     * Get the key the moves the selection one page up in the table. By default
+     * this is the Page Up key but by overriding this you can change the key to
+     * whatever you want.
+     * 
+     * @return
+     */
+    protected int getNavigationPageUpKey() {
+        return KeyCodes.KEY_PAGEUP;
+    }
+
+    /**
+     * Get the key the moves the selection one page down in the table. By
+     * default this is the Page Down key but by overriding this you can change
+     * the key to whatever you want.
+     * 
+     * @return
+     */
+    protected int getNavigationPageDownKey() {
+        return KeyCodes.KEY_PAGEDOWN;
+    }
+
+    /**
+     * Get the key the moves the selection to the beginning of the table. By
+     * default this is the Home key but by overriding this you can change the
+     * key to whatever you want.
+     * 
+     * @return
+     */
+    protected int getNavigationStartKey() {
+        return KeyCodes.KEY_HOME;
+    }
+
+    /**
+     * Get the key the moves the selection to the end of the table. By default
+     * this is the End key but by overriding this you can change the key to
+     * whatever you want.
+     * 
+     * @return
+     */
+    protected int getNavigationEndKey() {
+        return KeyCodes.KEY_END;
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void initializeRows(UIDL uidl, UIDL rowData) {
+        if (scrollBody != null) {
+            scrollBody.removeFromParent();
+        }
+        scrollBody = createScrollBody();
+
+        scrollBody.renderInitialRows(rowData, uidl.getIntAttribute("firstrow"),
+                uidl.getIntAttribute("rows"));
+        scrollBodyPanel.add(scrollBody);
+
+        // New body starts scrolled to the left, make sure the header and footer
+        // are also scrolled to the left
+        tHead.setHorizontalScrollPosition(0);
+        tFoot.setHorizontalScrollPosition(0);
+
+        initialContentReceived = true;
+        sizeNeedsInit = true;
+        scrollBody.restoreRowVisibility();
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void updateColumnProperties(UIDL uidl) {
+        updateColumnOrder(uidl);
+
+        updateCollapsedColumns(uidl);
+
+        UIDL vc = uidl.getChildByTagName("visiblecolumns");
+        if (vc != null) {
+            tHead.updateCellsFromUIDL(vc);
+            tFoot.updateCellsFromUIDL(vc);
+        }
+
+        updateHeader(uidl.getStringArrayAttribute("vcolorder"));
+        updateFooter(uidl.getStringArrayAttribute("vcolorder"));
+        if (uidl.hasVariable("noncollapsiblecolumns")) {
+            noncollapsibleColumns = uidl
+                    .getStringArrayVariableAsSet("noncollapsiblecolumns");
+        }
+    }
+
+    private void updateCollapsedColumns(UIDL uidl) {
+        if (uidl.hasVariable("collapsedcolumns")) {
+            tHead.setColumnCollapsingAllowed(true);
+            collapsedColumns = uidl
+                    .getStringArrayVariableAsSet("collapsedcolumns");
+        } else {
+            tHead.setColumnCollapsingAllowed(false);
+        }
+    }
+
+    private void updateColumnOrder(UIDL uidl) {
+        if (uidl.hasVariable("columnorder")) {
+            columnReordering = true;
+            columnOrder = uidl.getStringArrayVariable("columnorder");
+        } else {
+            columnReordering = false;
+            columnOrder = null;
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean selectSelectedRows(UIDL uidl) {
+        boolean keyboardSelectionOverRowFetchInProgress = false;
+
+        if (uidl.hasVariable("selected")) {
+            final Set<String> selectedKeys = uidl
+                    .getStringArrayVariableAsSet("selected");
+            if (scrollBody != null) {
+                Iterator<Widget> iterator = scrollBody.iterator();
+                while (iterator.hasNext()) {
+                    /*
+                     * Make the focus reflect to the server side state unless we
+                     * are currently selecting multiple rows with keyboard.
+                     */
+                    VScrollTableRow row = (VScrollTableRow) iterator.next();
+                    boolean selected = selectedKeys.contains(row.getKey());
+                    if (!selected
+                            && unSyncedselectionsBeforeRowFetch != null
+                            && unSyncedselectionsBeforeRowFetch.contains(row
+                                    .getKey())) {
+                        selected = true;
+                        keyboardSelectionOverRowFetchInProgress = true;
+                    }
+                    if (selected != row.isSelected()) {
+                        row.toggleSelection();
+                        if (!isSingleSelectMode() && !selected) {
+                            // Update selection range in case a row is
+                            // unselected from the middle of a range - #8076
+                            removeRowFromUnsentSelectionRanges(row);
+                        }
+                    }
+                }
+            }
+        }
+        unSyncedselectionsBeforeRowFetch = null;
+        return keyboardSelectionOverRowFetchInProgress;
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void updateSortingProperties(UIDL uidl) {
+        oldSortColumn = sortColumn;
+        if (uidl.hasVariable("sortascending")) {
+            sortAscending = uidl.getBooleanVariable("sortascending");
+            sortColumn = uidl.getStringVariable("sortcolumn");
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void resizeSortedColumnForSortIndicator() {
+        // Force recalculation of the captionContainer element inside the header
+        // cell to accomodate for the size of the sort arrow.
+        HeaderCell sortedHeader = tHead.getHeaderCell(sortColumn);
+        if (sortedHeader != null) {
+            tHead.resizeCaptionContainer(sortedHeader);
+        }
+        // Also recalculate the width of the captionContainer element in the
+        // previously sorted header, since this now has more room.
+        HeaderCell oldSortedHeader = tHead.getHeaderCell(oldSortColumn);
+        if (oldSortedHeader != null) {
+            tHead.resizeCaptionContainer(oldSortedHeader);
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void updateFirstVisibleAndScrollIfNeeded(UIDL uidl) {
+        firstvisible = uidl.hasVariable("firstvisible") ? uidl
+                .getIntVariable("firstvisible") : 0;
+        if (firstvisible != lastRequestedFirstvisible && scrollBody != null) {
+            // received 'surprising' firstvisible from server: scroll there
+            firstRowInViewPort = firstvisible;
+            scrollBodyPanel
+                    .setScrollPosition(measureRowHeightOffset(firstvisible));
+        }
+    }
+
+    protected int measureRowHeightOffset(int rowIx) {
+        return (int) (rowIx * scrollBody.getRowHeight());
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void updatePageLength(UIDL uidl) {
+        int oldPageLength = pageLength;
+        if (uidl.hasAttribute("pagelength")) {
+            pageLength = uidl.getIntAttribute("pagelength");
+        } else {
+            // pagelenght is "0" meaning scrolling is turned off
+            pageLength = totalRows;
+        }
+
+        if (oldPageLength != pageLength && initializedAndAttached) {
+            // page length changed, need to update size
+            sizeNeedsInit = true;
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void updateSelectionProperties(UIDL uidl, ComponentState state,
+            boolean readOnly) {
+        setMultiSelectMode(uidl.hasAttribute("multiselectmode") ? uidl
+                .getIntAttribute("multiselectmode") : MULTISELECT_MODE_DEFAULT);
+
+        nullSelectionAllowed = uidl.hasAttribute("nsa") ? uidl
+                .getBooleanAttribute("nsa") : true;
+
+        if (uidl.hasAttribute("selectmode")) {
+            if (readOnly) {
+                selectMode = SelectMode.NONE;
+            } else if (uidl.getStringAttribute("selectmode").equals("multi")) {
+                selectMode = SelectMode.MULTI;
+            } else if (uidl.getStringAttribute("selectmode").equals("single")) {
+                selectMode = SelectMode.SINGLE;
+            } else {
+                selectMode = SelectMode.NONE;
+            }
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void updateDragMode(UIDL uidl) {
+        dragmode = uidl.hasAttribute("dragmode") ? uidl
+                .getIntAttribute("dragmode") : 0;
+        if (BrowserInfo.get().isIE()) {
+            if (dragmode > 0) {
+                getElement().setPropertyJSO("onselectstart",
+                        getPreventTextSelectionIEHack());
+            } else {
+                getElement().setPropertyJSO("onselectstart", null);
+            }
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void updateTotalRows(UIDL uidl) {
+        int newTotalRows = uidl.getIntAttribute("totalrows");
+        if (newTotalRows != getTotalRows()) {
+            if (scrollBody != null) {
+                if (getTotalRows() == 0) {
+                    tHead.clear();
+                    tFoot.clear();
+                }
+                initializedAndAttached = false;
+                initialContentReceived = false;
+                isNewBody = true;
+            }
+            setTotalRows(newTotalRows);
+        }
+    }
+
+    protected void setTotalRows(int newTotalRows) {
+        totalRows = newTotalRows;
+    }
+
+    public int getTotalRows() {
+        return totalRows;
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void focusRowFromBody() {
+        if (selectedRowKeys.size() == 1) {
+            // try to focus a row currently selected and in viewport
+            String selectedRowKey = selectedRowKeys.iterator().next();
+            if (selectedRowKey != null) {
+                VScrollTableRow renderedRow = getRenderedRowByKey(selectedRowKey);
+                if (renderedRow == null || !renderedRow.isInViewPort()) {
+                    setRowFocus(scrollBody.getRowByRowIndex(firstRowInViewPort));
+                } else {
+                    setRowFocus(renderedRow);
+                }
+            }
+        } else {
+            // multiselect mode
+            setRowFocus(scrollBody.getRowByRowIndex(firstRowInViewPort));
+        }
+    }
+
+    protected VScrollTableBody createScrollBody() {
+        return new VScrollTableBody();
+    }
+
+    /**
+     * Selects the last row visible in the table
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     * 
+     * @param focusOnly
+     *            Should the focus only be moved to the last row
+     */
+    public void selectLastRenderedRowInViewPort(boolean focusOnly) {
+        int index = firstRowInViewPort + getFullyVisibleRowCount();
+        VScrollTableRow lastRowInViewport = scrollBody.getRowByRowIndex(index);
+        if (lastRowInViewport == null) {
+            // this should not happen in normal situations (white space at the
+            // end of viewport). Select the last rendered as a fallback.
+            lastRowInViewport = scrollBody.getRowByRowIndex(scrollBody
+                    .getLastRendered());
+            if (lastRowInViewport == null) {
+                return; // empty table
+            }
+        }
+        setRowFocus(lastRowInViewport);
+        if (!focusOnly) {
+            selectFocusedRow(false, multiselectPending);
+            sendSelectedRows();
+        }
+    }
+
+    /**
+     * Selects the first row visible in the table
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     * 
+     * @param focusOnly
+     *            Should the focus only be moved to the first row
+     */
+    public void selectFirstRenderedRowInViewPort(boolean focusOnly) {
+        int index = firstRowInViewPort;
+        VScrollTableRow firstInViewport = scrollBody.getRowByRowIndex(index);
+        if (firstInViewport == null) {
+            // this should not happen in normal situations
+            return;
+        }
+        setRowFocus(firstInViewport);
+        if (!focusOnly) {
+            selectFocusedRow(false, multiselectPending);
+            sendSelectedRows();
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void setCacheRateFromUIDL(UIDL uidl) {
+        setCacheRate(uidl.hasAttribute("cr") ? uidl.getDoubleAttribute("cr")
+                : CACHE_RATE_DEFAULT);
+    }
+
+    private void setCacheRate(double d) {
+        if (cache_rate != d) {
+            cache_rate = d;
+            cache_react_rate = 0.75 * d;
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void updateActionMap(UIDL mainUidl) {
+        UIDL actionsUidl = mainUidl.getChildByTagName("actions");
+        if (actionsUidl == null) {
+            return;
+        }
+
+        final Iterator<?> it = actionsUidl.getChildIterator();
+        while (it.hasNext()) {
+            final UIDL action = (UIDL) it.next();
+            final String key = action.getStringAttribute("key");
+            final String caption = action.getStringAttribute("caption");
+            actionMap.put(key + "_c", caption);
+            if (action.hasAttribute("icon")) {
+                // TODO need some uri handling ??
+                actionMap.put(key + "_i", client.translateVaadinUri(action
+                        .getStringAttribute("icon")));
+            } else {
+                actionMap.remove(key + "_i");
+            }
+        }
+
+    }
+
+    public String getActionCaption(String actionKey) {
+        return actionMap.get(actionKey + "_c");
+    }
+
+    public String getActionIcon(String actionKey) {
+        return actionMap.get(actionKey + "_i");
+    }
+
+    private void updateHeader(String[] strings) {
+        if (strings == null) {
+            return;
+        }
+
+        int visibleCols = strings.length;
+        int colIndex = 0;
+        if (showRowHeaders) {
+            tHead.enableColumn(ROW_HEADER_COLUMN_KEY, colIndex);
+            visibleCols++;
+            visibleColOrder = new String[visibleCols];
+            visibleColOrder[colIndex] = ROW_HEADER_COLUMN_KEY;
+            colIndex++;
+        } else {
+            visibleColOrder = new String[visibleCols];
+            tHead.removeCell(ROW_HEADER_COLUMN_KEY);
+        }
+
+        int i;
+        for (i = 0; i < strings.length; i++) {
+            final String cid = strings[i];
+            visibleColOrder[colIndex] = cid;
+            tHead.enableColumn(cid, colIndex);
+            colIndex++;
+        }
+
+        tHead.setVisible(showColHeaders);
+        setContainerHeight();
+
+    }
+
+    /**
+     * Updates footers.
+     * <p>
+     * Update headers whould be called before this method is called!
+     * </p>
+     * 
+     * @param strings
+     */
+    private void updateFooter(String[] strings) {
+        if (strings == null) {
+            return;
+        }
+
+        // Add dummy column if row headers are present
+        int colIndex = 0;
+        if (showRowHeaders) {
+            tFoot.enableColumn(ROW_HEADER_COLUMN_KEY, colIndex);
+            colIndex++;
+        } else {
+            tFoot.removeCell(ROW_HEADER_COLUMN_KEY);
+        }
+
+        int i;
+        for (i = 0; i < strings.length; i++) {
+            final String cid = strings[i];
+            tFoot.enableColumn(cid, colIndex);
+            colIndex++;
+        }
+
+        tFoot.setVisible(showColFooters);
+    }
+
+    /**
+     * For internal use only. May be removed or replaced in the future.
+     * 
+     * @param uidl
+     *            which contains row data
+     * @param firstRow
+     *            first row in data set
+     * @param reqRows
+     *            amount of rows in data set
+     */
+    public void updateBody(UIDL uidl, int firstRow, int reqRows) {
+        if (uidl == null || reqRows < 1) {
+            // container is empty, remove possibly existing rows
+            if (firstRow <= 0) {
+                while (scrollBody.getLastRendered() > scrollBody.firstRendered) {
+                    scrollBody.unlinkRow(false);
+                }
+                scrollBody.unlinkRow(false);
+            }
+            return;
+        }
+
+        scrollBody.renderRows(uidl, firstRow, reqRows);
+
+        discardRowsOutsideCacheWindow();
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void updateRowsInBody(UIDL partialRowUpdates) {
+        if (partialRowUpdates == null) {
+            return;
+        }
+        int firstRowIx = partialRowUpdates.getIntAttribute("firsturowix");
+        int count = partialRowUpdates.getIntAttribute("numurows");
+        scrollBody.unlinkRows(firstRowIx, count);
+        scrollBody.insertRows(partialRowUpdates, firstRowIx, count);
+    }
+
+    /**
+     * Updates the internal cache by unlinking rows that fall outside of the
+     * caching window.
+     */
+    protected void discardRowsOutsideCacheWindow() {
+        int firstRowToKeep = (int) (firstRowInViewPort - pageLength
+                * cache_rate);
+        int lastRowToKeep = (int) (firstRowInViewPort + pageLength + pageLength
+                * cache_rate);
+        debug("Client side calculated cache rows to keep: " + firstRowToKeep
+                + "-" + lastRowToKeep);
+
+        if (serverCacheFirst != -1) {
+            firstRowToKeep = serverCacheFirst;
+            lastRowToKeep = serverCacheLast;
+            debug("Server cache rows that override: " + serverCacheFirst + "-"
+                    + serverCacheLast);
+            if (firstRowToKeep < scrollBody.getFirstRendered()
+                    || lastRowToKeep > scrollBody.getLastRendered()) {
+                debug("*** Server wants us to keep " + serverCacheFirst + "-"
+                        + serverCacheLast + " but we only have rows "
+                        + scrollBody.getFirstRendered() + "-"
+                        + scrollBody.getLastRendered() + " rendered!");
+            }
+        }
+        discardRowsOutsideOf(firstRowToKeep, lastRowToKeep);
+
+        scrollBody.fixSpacers();
+
+        scrollBody.restoreRowVisibility();
+    }
+
+    private void discardRowsOutsideOf(int optimalFirstRow, int optimalLastRow) {
+        /*
+         * firstDiscarded and lastDiscarded are only calculated for debug
+         * purposes
+         */
+        int firstDiscarded = -1, lastDiscarded = -1;
+        boolean cont = true;
+        while (cont && scrollBody.getLastRendered() > optimalFirstRow
+                && scrollBody.getFirstRendered() < optimalFirstRow) {
+            if (firstDiscarded == -1) {
+                firstDiscarded = scrollBody.getFirstRendered();
+            }
+
+            // removing row from start
+            cont = scrollBody.unlinkRow(true);
+        }
+        if (firstDiscarded != -1) {
+            lastDiscarded = scrollBody.getFirstRendered() - 1;
+            debug("Discarded rows " + firstDiscarded + "-" + lastDiscarded);
+        }
+        firstDiscarded = lastDiscarded = -1;
+
+        cont = true;
+        while (cont && scrollBody.getLastRendered() > optimalLastRow) {
+            if (lastDiscarded == -1) {
+                lastDiscarded = scrollBody.getLastRendered();
+            }
+
+            // removing row from the end
+            cont = scrollBody.unlinkRow(false);
+        }
+        if (lastDiscarded != -1) {
+            firstDiscarded = scrollBody.getLastRendered() + 1;
+            debug("Discarded rows " + firstDiscarded + "-" + lastDiscarded);
+        }
+
+        debug("Now in cache: " + scrollBody.getFirstRendered() + "-"
+                + scrollBody.getLastRendered());
+    }
+
+    /**
+     * Inserts rows in the table body or removes them from the table body based
+     * on the commands in the UIDL.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     * 
+     * @param partialRowAdditions
+     *            the UIDL containing row updates.
+     */
+    public void addAndRemoveRows(UIDL partialRowAdditions) {
+        if (partialRowAdditions == null) {
+            return;
+        }
+        if (partialRowAdditions.hasAttribute("hide")) {
+            scrollBody.unlinkAndReindexRows(
+                    partialRowAdditions.getIntAttribute("firstprowix"),
+                    partialRowAdditions.getIntAttribute("numprows"));
+            scrollBody.ensureCacheFilled();
+        } else {
+            if (partialRowAdditions.hasAttribute("delbelow")) {
+                scrollBody.insertRowsDeleteBelow(partialRowAdditions,
+                        partialRowAdditions.getIntAttribute("firstprowix"),
+                        partialRowAdditions.getIntAttribute("numprows"));
+            } else {
+                scrollBody.insertAndReindexRows(partialRowAdditions,
+                        partialRowAdditions.getIntAttribute("firstprowix"),
+                        partialRowAdditions.getIntAttribute("numprows"));
+            }
+        }
+
+        discardRowsOutsideCacheWindow();
+    }
+
+    /**
+     * Gives correct column index for given column key ("cid" in UIDL).
+     * 
+     * @param colKey
+     * @return column index of visible columns, -1 if column not visible
+     */
+    private int getColIndexByKey(String colKey) {
+        // return 0 if asked for rowHeaders
+        if (ROW_HEADER_COLUMN_KEY.equals(colKey)) {
+            return 0;
+        }
+        for (int i = 0; i < visibleColOrder.length; i++) {
+            if (visibleColOrder[i].equals(colKey)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private boolean isMultiSelectModeSimple() {
+        return selectMode == SelectMode.MULTI
+                && multiselectmode == MULTISELECT_MODE_SIMPLE;
+    }
+
+    private boolean isSingleSelectMode() {
+        return selectMode == SelectMode.SINGLE;
+    }
+
+    private boolean isMultiSelectModeAny() {
+        return selectMode == SelectMode.MULTI;
+    }
+
+    private boolean isMultiSelectModeDefault() {
+        return selectMode == SelectMode.MULTI
+                && multiselectmode == MULTISELECT_MODE_DEFAULT;
+    }
+
+    private void setMultiSelectMode(int multiselectmode) {
+        if (BrowserInfo.get().isTouchDevice()) {
+            // Always use the simple mode for touch devices that do not have
+            // shift/ctrl keys
+            this.multiselectmode = MULTISELECT_MODE_SIMPLE;
+        } else {
+            this.multiselectmode = multiselectmode;
+        }
+
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean isSelectable() {
+        return selectMode.getId() > SelectMode.NONE.getId();
+    }
+
+    private boolean isCollapsedColumn(String colKey) {
+        if (collapsedColumns == null) {
+            return false;
+        }
+        if (collapsedColumns.contains(colKey)) {
+            return true;
+        }
+        return false;
+    }
+
+    private String getColKeyByIndex(int index) {
+        return tHead.getHeaderCell(index).getColKey();
+    }
+
+    private void setColWidth(int colIndex, int w, boolean isDefinedWidth) {
+        final HeaderCell hcell = tHead.getHeaderCell(colIndex);
+
+        // Make sure that the column grows to accommodate the sort indicator if
+        // necessary.
+        if (w < hcell.getMinWidth()) {
+            w = hcell.getMinWidth();
+        }
+
+        // Set header column width
+        hcell.setWidth(w, isDefinedWidth);
+
+        // Ensure indicators have been taken into account
+        tHead.resizeCaptionContainer(hcell);
+
+        // Set body column width
+        scrollBody.setColWidth(colIndex, w);
+
+        // Set footer column width
+        FooterCell fcell = tFoot.getFooterCell(colIndex);
+        fcell.setWidth(w, isDefinedWidth);
+    }
+
+    private int getColWidth(String colKey) {
+        return tHead.getHeaderCell(colKey).getWidth();
+    }
+
+    /**
+     * Get a rendered row by its key
+     * 
+     * @param key
+     *            The key to search with
+     * @return
+     */
+    public VScrollTableRow getRenderedRowByKey(String key) {
+        if (scrollBody != null) {
+            final Iterator<Widget> it = scrollBody.iterator();
+            VScrollTableRow r = null;
+            while (it.hasNext()) {
+                r = (VScrollTableRow) it.next();
+                if (r.getKey().equals(key)) {
+                    return r;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the next row to the given row
+     * 
+     * @param row
+     *            The row to calculate from
+     * 
+     * @return The next row or null if no row exists
+     */
+    private VScrollTableRow getNextRow(VScrollTableRow row, int offset) {
+        final Iterator<Widget> it = scrollBody.iterator();
+        VScrollTableRow r = null;
+        while (it.hasNext()) {
+            r = (VScrollTableRow) it.next();
+            if (r == row) {
+                r = null;
+                while (offset >= 0 && it.hasNext()) {
+                    r = (VScrollTableRow) it.next();
+                    offset--;
+                }
+                return r;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns the previous row from the given row
+     * 
+     * @param row
+     *            The row to calculate from
+     * @return The previous row or null if no row exists
+     */
+    private VScrollTableRow getPreviousRow(VScrollTableRow row, int offset) {
+        final Iterator<Widget> it = scrollBody.iterator();
+        final Iterator<Widget> offsetIt = scrollBody.iterator();
+        VScrollTableRow r = null;
+        VScrollTableRow prev = null;
+        while (it.hasNext()) {
+            r = (VScrollTableRow) it.next();
+            if (offset < 0) {
+                prev = (VScrollTableRow) offsetIt.next();
+            }
+            if (r == row) {
+                return prev;
+            }
+            offset--;
+        }
+
+        return null;
+    }
+
+    protected void reOrderColumn(String columnKey, int newIndex) {
+
+        final int oldIndex = getColIndexByKey(columnKey);
+
+        // Change header order
+        tHead.moveCell(oldIndex, newIndex);
+
+        // Change body order
+        scrollBody.moveCol(oldIndex, newIndex);
+
+        // Change footer order
+        tFoot.moveCell(oldIndex, newIndex);
+
+        /*
+         * Build new columnOrder and update it to server Note that columnOrder
+         * also contains collapsed columns so we cannot directly build it from
+         * cells vector Loop the old columnOrder and append in order to new
+         * array unless on moved columnKey. On new index also put the moved key
+         * i == index on columnOrder, j == index on newOrder
+         */
+        final String oldKeyOnNewIndex = visibleColOrder[newIndex];
+        if (showRowHeaders) {
+            newIndex--; // columnOrder don't have rowHeader
+        }
+        // add back hidden rows,
+        for (int i = 0; i < columnOrder.length; i++) {
+            if (columnOrder[i].equals(oldKeyOnNewIndex)) {
+                break; // break loop at target
+            }
+            if (isCollapsedColumn(columnOrder[i])) {
+                newIndex++;
+            }
+        }
+        // finally we can build the new columnOrder for server
+        final String[] newOrder = new String[columnOrder.length];
+        for (int i = 0, j = 0; j < newOrder.length; i++) {
+            if (j == newIndex) {
+                newOrder[j] = columnKey;
+                j++;
+            }
+            if (i == columnOrder.length) {
+                break;
+            }
+            if (columnOrder[i].equals(columnKey)) {
+                continue;
+            }
+            newOrder[j] = columnOrder[i];
+            j++;
+        }
+        columnOrder = newOrder;
+        // also update visibleColumnOrder
+        int i = showRowHeaders ? 1 : 0;
+        for (int j = 0; j < newOrder.length; j++) {
+            final String cid = newOrder[j];
+            if (!isCollapsedColumn(cid)) {
+                visibleColOrder[i++] = cid;
+            }
+        }
+        client.updateVariable(paintableId, "columnorder", columnOrder, false);
+        if (client.hasEventListeners(this,
+                TableConstants.COLUMN_REORDER_EVENT_ID)) {
+            client.sendPendingVariableChanges();
+        }
+    }
+
+    @Override
+    protected void onDetach() {
+        rowRequestHandler.cancel();
+        super.onDetach();
+        // ensure that scrollPosElement will be detached
+        if (scrollPositionElement != null) {
+            final Element parent = DOM.getParent(scrollPositionElement);
+            if (parent != null) {
+                DOM.removeChild(parent, scrollPositionElement);
+            }
+        }
+    }
+
+    /**
+     * Run only once when component is attached and received its initial
+     * content. This function:
+     * 
+     * * Syncs headers and bodys "natural widths and saves the values.
+     * 
+     * * Sets proper width and height
+     * 
+     * * Makes deferred request to get some cache rows
+     * 
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public void sizeInit() {
+        sizeNeedsInit = false;
+
+        scrollBody.setContainerHeight();
+
+        /*
+         * We will use browsers table rendering algorithm to find proper column
+         * widths. If content and header take less space than available, we will
+         * divide extra space relatively to each column which has not width set.
+         * 
+         * Overflow pixels are added to last column.
+         */
+
+        Iterator<Widget> headCells = tHead.iterator();
+        Iterator<Widget> footCells = tFoot.iterator();
+        int i = 0;
+        int totalExplicitColumnsWidths = 0;
+        int total = 0;
+        float expandRatioDivider = 0;
+
+        final int[] widths = new int[tHead.visibleCells.size()];
+
+        tHead.enableBrowserIntelligence();
+        tFoot.enableBrowserIntelligence();
+
+        // first loop: collect natural widths
+        while (headCells.hasNext()) {
+            final HeaderCell hCell = (HeaderCell) headCells.next();
+            final FooterCell fCell = (FooterCell) footCells.next();
+            int w = hCell.getWidth();
+            if (hCell.isDefinedWidth()) {
+                // server has defined column width explicitly
+                totalExplicitColumnsWidths += w;
+            } else {
+                if (hCell.getExpandRatio() > 0) {
+                    expandRatioDivider += hCell.getExpandRatio();
+                    w = 0;
+                } else {
+                    // get and store greater of header width and column width,
+                    // and
+                    // store it as a minimumn natural col width
+                    int headerWidth = hCell.getNaturalColumnWidth(i);
+                    int footerWidth = fCell.getNaturalColumnWidth(i);
+                    w = headerWidth > footerWidth ? headerWidth : footerWidth;
+                }
+                hCell.setNaturalMinimumColumnWidth(w);
+                fCell.setNaturalMinimumColumnWidth(w);
+            }
+            widths[i] = w;
+            total += w;
+            i++;
+        }
+
+        tHead.disableBrowserIntelligence();
+        tFoot.disableBrowserIntelligence();
+
+        boolean willHaveScrollbarz = willHaveScrollbars();
+
+        // fix "natural" width if width not set
+        if (isDynamicWidth()) {
+            int w = total;
+            w += scrollBody.getCellExtraWidth() * visibleColOrder.length;
+            if (willHaveScrollbarz) {
+                w += Util.getNativeScrollbarSize();
+            }
+            setContentWidth(w);
+        }
+
+        int availW = scrollBody.getAvailableWidth();
+        if (BrowserInfo.get().isIE()) {
+            // Hey IE, are you really sure about this?
+            availW = scrollBody.getAvailableWidth();
+        }
+        availW -= scrollBody.getCellExtraWidth() * visibleColOrder.length;
+
+        if (willHaveScrollbarz) {
+            availW -= Util.getNativeScrollbarSize();
+        }
+
+        // TODO refactor this code to be the same as in resize timer
+        boolean needsReLayout = false;
+
+        if (availW > total) {
+            // natural size is smaller than available space
+            final int extraSpace = availW - total;
+            final int totalWidthR = total - totalExplicitColumnsWidths;
+            int checksum = 0;
+            needsReLayout = true;
+
+            if (extraSpace == 1) {
+                // We cannot divide one single pixel so we give it the first
+                // undefined column
+                headCells = tHead.iterator();
+                i = 0;
+                checksum = availW;
+                while (headCells.hasNext()) {
+                    HeaderCell hc = (HeaderCell) headCells.next();
+                    if (!hc.isDefinedWidth()) {
+                        widths[i]++;
+                        break;
+                    }
+                    i++;
+                }
+
+            } else if (expandRatioDivider > 0) {
+                // visible columns have some active expand ratios, excess
+                // space is divided according to them
+                headCells = tHead.iterator();
+                i = 0;
+                while (headCells.hasNext()) {
+                    HeaderCell hCell = (HeaderCell) headCells.next();
+                    if (hCell.getExpandRatio() > 0) {
+                        int w = widths[i];
+                        final int newSpace = Math.round((extraSpace * (hCell
+                                .getExpandRatio() / expandRatioDivider)));
+                        w += newSpace;
+                        widths[i] = w;
+                    }
+                    checksum += widths[i];
+                    i++;
+                }
+            } else if (totalWidthR > 0) {
+                // no expand ratios defined, we will share extra space
+                // relatively to "natural widths" among those without
+                // explicit width
+                headCells = tHead.iterator();
+                i = 0;
+                while (headCells.hasNext()) {
+                    HeaderCell hCell = (HeaderCell) headCells.next();
+                    if (!hCell.isDefinedWidth()) {
+                        int w = widths[i];
+                        final int newSpace = Math.round((float) extraSpace
+                                * (float) w / totalWidthR);
+                        w += newSpace;
+                        widths[i] = w;
+                    }
+                    checksum += widths[i];
+                    i++;
+                }
+            }
+
+            if (extraSpace > 0 && checksum != availW) {
+                /*
+                 * There might be in some cases a rounding error of 1px when
+                 * extra space is divided so if there is one then we give the
+                 * first undefined column 1 more pixel
+                 */
+                headCells = tHead.iterator();
+                i = 0;
+                while (headCells.hasNext()) {
+                    HeaderCell hc = (HeaderCell) headCells.next();
+                    if (!hc.isDefinedWidth()) {
+                        widths[i] += availW - checksum;
+                        break;
+                    }
+                    i++;
+                }
+            }
+
+        } else {
+            // bodys size will be more than available and scrollbar will appear
+        }
+
+        // last loop: set possibly modified values or reset if new tBody
+        i = 0;
+        headCells = tHead.iterator();
+        while (headCells.hasNext()) {
+            final HeaderCell hCell = (HeaderCell) headCells.next();
+            if (isNewBody || hCell.getWidth() == -1) {
+                final int w = widths[i];
+                setColWidth(i, w, false);
+            }
+            i++;
+        }
+
+        initializedAndAttached = true;
+
+        if (needsReLayout) {
+            scrollBody.reLayoutComponents();
+        }
+
+        updatePageLength();
+
+        /*
+         * Fix "natural" height if height is not set. This must be after width
+         * fixing so the components' widths have been adjusted.
+         */
+        if (isDynamicHeight()) {
+            /*
+             * We must force an update of the row height as this point as it
+             * might have been (incorrectly) calculated earlier
+             */
+
+            int bodyHeight;
+            if (pageLength == totalRows) {
+                /*
+                 * A hack to support variable height rows when paging is off.
+                 * Generally this is not supported by scrolltable. We want to
+                 * show all rows so the bodyHeight should be equal to the table
+                 * height.
+                 */
+                // int bodyHeight = scrollBody.getOffsetHeight();
+                bodyHeight = scrollBody.getRequiredHeight();
+            } else {
+                bodyHeight = (int) Math.round(scrollBody.getRowHeight(true)
+                        * pageLength);
+            }
+            boolean needsSpaceForHorizontalSrollbar = (total > availW);
+            if (needsSpaceForHorizontalSrollbar) {
+                bodyHeight += Util.getNativeScrollbarSize();
+            }
+            scrollBodyPanel.setHeight(bodyHeight + "px");
+            Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
+        }
+
+        isNewBody = false;
+
+        if (firstvisible > 0) {
+            // Deferred due to some Firefox oddities
+            Scheduler.get().scheduleDeferred(new Command() {
+
+                @Override
+                public void execute() {
+                    scrollBodyPanel
+                            .setScrollPosition(measureRowHeightOffset(firstvisible));
+                    firstRowInViewPort = firstvisible;
+                }
+            });
+        }
+
+        if (enabled) {
+            // Do we need cache rows
+            if (scrollBody.getLastRendered() + 1 < firstRowInViewPort
+                    + pageLength + (int) cache_react_rate * pageLength) {
+                if (totalRows - 1 > scrollBody.getLastRendered()) {
+                    // fetch cache rows
+                    int firstInNewSet = scrollBody.getLastRendered() + 1;
+                    rowRequestHandler.setReqFirstRow(firstInNewSet);
+                    int lastInNewSet = (int) (firstRowInViewPort + pageLength + cache_rate
+                            * pageLength);
+                    if (lastInNewSet > totalRows - 1) {
+                        lastInNewSet = totalRows - 1;
+                    }
+                    rowRequestHandler.setReqRows(lastInNewSet - firstInNewSet
+                            + 1);
+                    rowRequestHandler.deferRowFetch(1);
+                }
+            }
+        }
+
+        /*
+         * Ensures the column alignments are correct at initial loading. <br/>
+         * (child components widths are correct)
+         */
+        scrollBody.reLayoutComponents();
+        Scheduler.get().scheduleDeferred(new Command() {
+
+            @Override
+            public void execute() {
+                Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
+            }
+        });
+    }
+
+    /**
+     * Note, this method is not official api although declared as protected.
+     * Extend at you own risk.
+     * 
+     * @return true if content area will have scrollbars visible.
+     */
+    protected boolean willHaveScrollbars() {
+        if (isDynamicHeight()) {
+            if (pageLength < totalRows) {
+                return true;
+            }
+        } else {
+            int fakeheight = (int) Math.round(scrollBody.getRowHeight()
+                    * totalRows);
+            int availableHeight = scrollBodyPanel.getElement().getPropertyInt(
+                    "clientHeight");
+            if (fakeheight > availableHeight) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void announceScrollPosition() {
+        if (scrollPositionElement == null) {
+            scrollPositionElement = DOM.createDiv();
+            scrollPositionElement.setClassName(getStylePrimaryName()
+                    + "-scrollposition");
+            scrollPositionElement.getStyle().setPosition(Position.ABSOLUTE);
+            scrollPositionElement.getStyle().setDisplay(Display.NONE);
+            getElement().appendChild(scrollPositionElement);
+        }
+
+        Style style = scrollPositionElement.getStyle();
+        style.setMarginLeft(getElement().getOffsetWidth() / 2 - 80, Unit.PX);
+        style.setMarginTop(-scrollBodyPanel.getOffsetHeight(), Unit.PX);
+
+        // indexes go from 1-totalRows, as rowheaders in index-mode indicate
+        int last = (firstRowInViewPort + pageLength);
+        if (last > totalRows) {
+            last = totalRows;
+        }
+        scrollPositionElement.setInnerHTML("<span>" + (firstRowInViewPort + 1)
+                + " &ndash; " + (last) + "..." + "</span>");
+        style.setDisplay(Display.BLOCK);
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void hideScrollPositionAnnotation() {
+        if (scrollPositionElement != null) {
+            DOM.setStyleAttribute(scrollPositionElement, "display", "none");
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean isScrollPositionVisible() {
+        return scrollPositionElement != null
+                && !scrollPositionElement.getStyle().getDisplay()
+                        .equals(Display.NONE.toString());
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public class RowRequestHandler extends Timer {
+
+        private int reqFirstRow = 0;
+        private int reqRows = 0;
+        private boolean isRunning = false;
+
+        public void deferRowFetch() {
+            deferRowFetch(250);
+        }
+
+        public boolean isRunning() {
+            return isRunning;
+        }
+
+        public void deferRowFetch(int msec) {
+            isRunning = true;
+            if (reqRows > 0 && reqFirstRow < totalRows) {
+                schedule(msec);
+
+                // tell scroll position to user if currently "visible" rows are
+                // not rendered
+                if (totalRows > pageLength
+                        && ((firstRowInViewPort + pageLength > scrollBody
+                                .getLastRendered()) || (firstRowInViewPort < scrollBody
+                                .getFirstRendered()))) {
+                    announceScrollPosition();
+                } else {
+                    hideScrollPositionAnnotation();
+                }
+            }
+        }
+
+        public void setReqFirstRow(int reqFirstRow) {
+            if (reqFirstRow < 0) {
+                reqFirstRow = 0;
+            } else if (reqFirstRow >= totalRows) {
+                reqFirstRow = totalRows - 1;
+            }
+            this.reqFirstRow = reqFirstRow;
+        }
+
+        public void setReqRows(int reqRows) {
+            this.reqRows = reqRows;
+        }
+
+        @Override
+        public void run() {
+            if (client.hasActiveRequest() || navKeyDown) {
+                // if client connection is busy, don't bother loading it more
+                VConsole.log("Postponed rowfetch");
+                schedule(250);
+            } else {
+
+                int firstToBeRendered = scrollBody.firstRendered;
+                if (reqFirstRow < firstToBeRendered) {
+                    firstToBeRendered = reqFirstRow;
+                } else if (firstRowInViewPort - (int) (cache_rate * pageLength) > firstToBeRendered) {
+                    firstToBeRendered = firstRowInViewPort
+                            - (int) (cache_rate * pageLength);
+                    if (firstToBeRendered < 0) {
+                        firstToBeRendered = 0;
+                    }
+                }
+
+                int lastToBeRendered = scrollBody.lastRendered;
+
+                if (reqFirstRow + reqRows - 1 > lastToBeRendered) {
+                    lastToBeRendered = reqFirstRow + reqRows - 1;
+                } else if (firstRowInViewPort + pageLength + pageLength
+                        * cache_rate < lastToBeRendered) {
+                    lastToBeRendered = (firstRowInViewPort + pageLength + (int) (pageLength * cache_rate));
+                    if (lastToBeRendered >= totalRows) {
+                        lastToBeRendered = totalRows - 1;
+                    }
+                    // due Safari 3.1 bug (see #2607), verify reqrows, original
+                    // problem unknown, but this should catch the issue
+                    if (reqFirstRow + reqRows - 1 > lastToBeRendered) {
+                        reqRows = lastToBeRendered - reqFirstRow;
+                    }
+                }
+
+                client.updateVariable(paintableId, "firstToBeRendered",
+                        firstToBeRendered, false);
+
+                client.updateVariable(paintableId, "lastToBeRendered",
+                        lastToBeRendered, false);
+                // remember which firstvisible we requested, in case the server
+                // has
+                // a differing opinion
+                lastRequestedFirstvisible = firstRowInViewPort;
+                client.updateVariable(paintableId, "firstvisible",
+                        firstRowInViewPort, false);
+                client.updateVariable(paintableId, "reqfirstrow", reqFirstRow,
+                        false);
+                client.updateVariable(paintableId, "reqrows", reqRows, true);
+
+                if (selectionChanged) {
+                    unSyncedselectionsBeforeRowFetch = new HashSet<Object>(
+                            selectedRowKeys);
+                }
+                isRunning = false;
+            }
+        }
+
+        public int getReqFirstRow() {
+            return reqFirstRow;
+        }
+
+        /**
+         * Sends request to refresh content at this position.
+         */
+        public void refreshContent() {
+            isRunning = true;
+            int first = (int) (firstRowInViewPort - pageLength * cache_rate);
+            int reqRows = (int) (2 * pageLength * cache_rate + pageLength);
+            if (first < 0) {
+                reqRows = reqRows + first;
+                first = 0;
+            }
+            setReqFirstRow(first);
+            setReqRows(reqRows);
+            run();
+        }
+    }
+
+    public class HeaderCell extends Widget {
+
+        Element td = DOM.createTD();
+
+        Element captionContainer = DOM.createDiv();
+
+        Element sortIndicator = DOM.createDiv();
+
+        Element colResizeWidget = DOM.createDiv();
+
+        Element floatingCopyOfHeaderCell;
+
+        private boolean sortable = false;
+        private final String cid;
+        private boolean dragging;
+
+        private int dragStartX;
+        private int colIndex;
+        private int originalWidth;
+
+        private boolean isResizing;
+
+        private int headerX;
+
+        private boolean moved;
+
+        private int closestSlot;
+
+        private int width = -1;
+
+        private int naturalWidth = -1;
+
+        private char align = ALIGN_LEFT;
+
+        boolean definedWidth = false;
+
+        private float expandRatio = 0;
+
+        private boolean sorted;
+
+        public void setSortable(boolean b) {
+            sortable = b;
+        }
+
+        /**
+         * Makes room for the sorting indicator in case the column that the
+         * header cell belongs to is sorted. This is done by resizing the width
+         * of the caption container element by the correct amount
+         */
+        public void resizeCaptionContainer(int rightSpacing) {
+            int captionContainerWidth = width
+                    - colResizeWidget.getOffsetWidth() - rightSpacing;
+
+            if (td.getClassName().contains("-asc")
+                    || td.getClassName().contains("-desc")) {
+                // Leave room for the sort indicator
+                captionContainerWidth -= sortIndicator.getOffsetWidth();
+            }
+
+            if (captionContainerWidth < 0) {
+                rightSpacing += captionContainerWidth;
+                captionContainerWidth = 0;
+            }
+
+            captionContainer.getStyle().setPropertyPx("width",
+                    captionContainerWidth);
+
+            // Apply/Remove spacing if defined
+            if (rightSpacing > 0) {
+                colResizeWidget.getStyle().setMarginLeft(rightSpacing, Unit.PX);
+            } else {
+                colResizeWidget.getStyle().clearMarginLeft();
+            }
+        }
+
+        public void setNaturalMinimumColumnWidth(int w) {
+            naturalWidth = w;
+        }
+
+        public HeaderCell(String colId, String headerText) {
+            cid = colId;
+
+            setText(headerText);
+
+            td.appendChild(colResizeWidget);
+
+            // ensure no clipping initially (problem on column additions)
+            captionContainer.getStyle().setOverflow(Overflow.VISIBLE);
+
+            td.appendChild(sortIndicator);
+            td.appendChild(captionContainer);
+
+            DOM.sinkEvents(td, Event.MOUSEEVENTS | Event.ONDBLCLICK
+                    | Event.ONCONTEXTMENU | Event.TOUCHEVENTS);
+
+            setElement(td);
+
+            setAlign(ALIGN_LEFT);
+        }
+
+        protected void updateStyleNames(String primaryStyleName) {
+            colResizeWidget.setClassName(primaryStyleName + "-resizer");
+            sortIndicator.setClassName(primaryStyleName + "-sort-indicator");
+            captionContainer.setClassName(primaryStyleName
+                    + "-caption-container");
+            if (sorted) {
+                if (sortAscending) {
+                    setStyleName(primaryStyleName + "-header-cell-asc");
+                } else {
+                    setStyleName(primaryStyleName + "-header-cell-desc");
+                }
+            } else {
+                setStyleName(primaryStyleName + "-header-cell");
+            }
+
+            final String ALIGN_PREFIX = primaryStyleName
+                    + "-caption-container-align-";
+
+            switch (align) {
+            case ALIGN_CENTER:
+                captionContainer.addClassName(ALIGN_PREFIX + "center");
+                break;
+            case ALIGN_RIGHT:
+                captionContainer.addClassName(ALIGN_PREFIX + "right");
+                break;
+            default:
+                captionContainer.addClassName(ALIGN_PREFIX + "left");
+                break;
+            }
+
+        }
+
+        public void disableAutoWidthCalculation() {
+            definedWidth = true;
+            expandRatio = 0;
+        }
+
+        public void setWidth(int w, boolean ensureDefinedWidth) {
+            if (ensureDefinedWidth) {
+                definedWidth = true;
+                // on column resize expand ratio becomes zero
+                expandRatio = 0;
+            }
+            if (width == -1) {
+                // go to default mode, clip content if necessary
+                DOM.setStyleAttribute(captionContainer, "overflow", "");
+            }
+            width = w;
+            if (w == -1) {
+                DOM.setStyleAttribute(captionContainer, "width", "");
+                setWidth("");
+            } else {
+                tHead.resizeCaptionContainer(this);
+
+                /*
+                 * if we already have tBody, set the header width properly, if
+                 * not defer it. IE will fail with complex float in table header
+                 * unless TD width is not explicitly set.
+                 */
+                if (scrollBody != null) {
+                    int tdWidth = width + scrollBody.getCellExtraWidth();
+                    setWidth(tdWidth + "px");
+                } else {
+                    Scheduler.get().scheduleDeferred(new Command() {
+
+                        @Override
+                        public void execute() {
+                            int tdWidth = width
+                                    + scrollBody.getCellExtraWidth();
+                            setWidth(tdWidth + "px");
+                        }
+                    });
+                }
+            }
+        }
+
+        public void setUndefinedWidth() {
+            definedWidth = false;
+            setWidth(-1, false);
+        }
+
+        /**
+         * Detects if width is fixed by developer on server side or resized to
+         * current width by user.
+         * 
+         * @return true if defined, false if "natural" width
+         */
+        public boolean isDefinedWidth() {
+            return definedWidth && width >= 0;
+        }
+
+        public int getWidth() {
+            return width;
+        }
+
+        public void setText(String headerText) {
+            DOM.setInnerHTML(captionContainer, headerText);
+        }
+
+        public String getColKey() {
+            return cid;
+        }
+
+        private void setSorted(boolean sorted) {
+            this.sorted = sorted;
+            updateStyleNames(VScrollTable.this.getStylePrimaryName());
+        }
+
+        /**
+         * Handle column reordering.
+         */
+
+        @Override
+        public void onBrowserEvent(Event event) {
+            if (enabled && event != null) {
+                if (isResizing
+                        || event.getEventTarget().cast() == colResizeWidget) {
+                    if (dragging
+                            && (event.getTypeInt() == Event.ONMOUSEUP || event
+                                    .getTypeInt() == Event.ONTOUCHEND)) {
+                        // Handle releasing column header on spacer #5318
+                        handleCaptionEvent(event);
+                    } else {
+                        onResizeEvent(event);
+                    }
+                } else {
+                    /*
+                     * Ensure focus before handling caption event. Otherwise
+                     * variables changed from caption event may be before
+                     * variables from other components that fire variables when
+                     * they lose focus.
+                     */
+                    if (event.getTypeInt() == Event.ONMOUSEDOWN
+                            || event.getTypeInt() == Event.ONTOUCHSTART) {
+                        scrollBodyPanel.setFocus(true);
+                    }
+                    handleCaptionEvent(event);
+                    boolean stopPropagation = true;
+                    if (event.getTypeInt() == Event.ONCONTEXTMENU
+                            && !client.hasEventListeners(VScrollTable.this,
+                                    TableConstants.HEADER_CLICK_EVENT_ID)) {
+                        // Prevent showing the browser's context menu only when
+                        // there is a header click listener.
+                        stopPropagation = false;
+                    }
+                    if (stopPropagation) {
+                        event.stopPropagation();
+                        event.preventDefault();
+                    }
+                }
+            }
+        }
+
+        private void createFloatingCopy() {
+            floatingCopyOfHeaderCell = DOM.createDiv();
+            DOM.setInnerHTML(floatingCopyOfHeaderCell, DOM.getInnerHTML(td));
+            floatingCopyOfHeaderCell = DOM
+                    .getChild(floatingCopyOfHeaderCell, 2);
+            DOM.setElementProperty(floatingCopyOfHeaderCell, "className",
+                    VScrollTable.this.getStylePrimaryName() + "-header-drag");
+            // otherwise might wrap or be cut if narrow column
+            DOM.setStyleAttribute(floatingCopyOfHeaderCell, "width", "auto");
+            updateFloatingCopysPosition(DOM.getAbsoluteLeft(td),
+                    DOM.getAbsoluteTop(td));
+            DOM.appendChild(RootPanel.get().getElement(),
+                    floatingCopyOfHeaderCell);
+        }
+
+        private void updateFloatingCopysPosition(int x, int y) {
+            x -= DOM.getElementPropertyInt(floatingCopyOfHeaderCell,
+                    "offsetWidth") / 2;
+            DOM.setStyleAttribute(floatingCopyOfHeaderCell, "left", x + "px");
+            if (y > 0) {
+                DOM.setStyleAttribute(floatingCopyOfHeaderCell, "top", (y + 7)
+                        + "px");
+            }
+        }
+
+        private void hideFloatingCopy() {
+            DOM.removeChild(RootPanel.get().getElement(),
+                    floatingCopyOfHeaderCell);
+            floatingCopyOfHeaderCell = null;
+        }
+
+        /**
+         * Fires a header click event after the user has clicked a column header
+         * cell
+         * 
+         * @param event
+         *            The click event
+         */
+        private void fireHeaderClickedEvent(Event event) {
+            if (client.hasEventListeners(VScrollTable.this,
+                    TableConstants.HEADER_CLICK_EVENT_ID)) {
+                MouseEventDetails details = MouseEventDetailsBuilder
+                        .buildMouseEventDetails(event);
+                client.updateVariable(paintableId, "headerClickEvent",
+                        details.toString(), false);
+                client.updateVariable(paintableId, "headerClickCID", cid, true);
+            }
+        }
+
+        protected void handleCaptionEvent(Event event) {
+            switch (DOM.eventGetType(event)) {
+            case Event.ONTOUCHSTART:
+            case Event.ONMOUSEDOWN:
+                if (columnReordering
+                        && Util.isTouchEventOrLeftMouseButton(event)) {
+                    if (event.getTypeInt() == Event.ONTOUCHSTART) {
+                        /*
+                         * prevent using this event in e.g. scrolling
+                         */
+                        event.stopPropagation();
+                    }
+                    dragging = true;
+                    moved = false;
+                    colIndex = getColIndexByKey(cid);
+                    DOM.setCapture(getElement());
+                    headerX = tHead.getAbsoluteLeft();
+                    event.preventDefault(); // prevent selecting text &&
+                                            // generated touch events
+                }
+                break;
+            case Event.ONMOUSEUP:
+            case Event.ONTOUCHEND:
+            case Event.ONTOUCHCANCEL:
+                if (columnReordering
+                        && Util.isTouchEventOrLeftMouseButton(event)) {
+                    dragging = false;
+                    DOM.releaseCapture(getElement());
+                    if (moved) {
+                        hideFloatingCopy();
+                        tHead.removeSlotFocus();
+                        if (closestSlot != colIndex
+                                && closestSlot != (colIndex + 1)) {
+                            if (closestSlot > colIndex) {
+                                reOrderColumn(cid, closestSlot - 1);
+                            } else {
+                                reOrderColumn(cid, closestSlot);
+                            }
+                        }
+                    }
+                    if (Util.isTouchEvent(event)) {
+                        /*
+                         * Prevent using in e.g. scrolling and prevent generated
+                         * events.
+                         */
+                        event.preventDefault();
+                        event.stopPropagation();
+                    }
+                }
+
+                if (!moved) {
+                    // mouse event was a click to header -> sort column
+                    if (sortable && Util.isTouchEventOrLeftMouseButton(event)) {
+                        if (sortColumn.equals(cid)) {
+                            // just toggle order
+                            client.updateVariable(paintableId, "sortascending",
+                                    !sortAscending, false);
+                        } else {
+                            // set table sorted by this column
+                            client.updateVariable(paintableId, "sortcolumn",
+                                    cid, false);
+                        }
+                        // get also cache columns at the same request
+                        scrollBodyPanel.setScrollPosition(0);
+                        firstvisible = 0;
+                        rowRequestHandler.setReqFirstRow(0);
+                        rowRequestHandler.setReqRows((int) (2 * pageLength
+                                * cache_rate + pageLength));
+                        rowRequestHandler.deferRowFetch(); // some validation +
+                                                           // defer 250ms
+                        rowRequestHandler.cancel(); // instead of waiting
+                        rowRequestHandler.run(); // run immediately
+                    }
+                    fireHeaderClickedEvent(event);
+                    if (Util.isTouchEvent(event)) {
+                        /*
+                         * Prevent using in e.g. scrolling and prevent generated
+                         * events.
+                         */
+                        event.preventDefault();
+                        event.stopPropagation();
+                    }
+                    break;
+                }
+                break;
+            case Event.ONDBLCLICK:
+                fireHeaderClickedEvent(event);
+                break;
+            case Event.ONTOUCHMOVE:
+            case Event.ONMOUSEMOVE:
+                if (dragging && Util.isTouchEventOrLeftMouseButton(event)) {
+                    if (event.getTypeInt() == Event.ONTOUCHMOVE) {
+                        /*
+                         * prevent using this event in e.g. scrolling
+                         */
+                        event.stopPropagation();
+                    }
+                    if (!moved) {
+                        createFloatingCopy();
+                        moved = true;
+                    }
+
+                    final int clientX = Util.getTouchOrMouseClientX(event);
+                    final int x = clientX + tHead.hTableWrapper.getScrollLeft();
+                    int slotX = headerX;
+                    closestSlot = colIndex;
+                    int closestDistance = -1;
+                    int start = 0;
+                    if (showRowHeaders) {
+                        start++;
+                    }
+                    final int visibleCellCount = tHead.getVisibleCellCount();
+                    for (int i = start; i <= visibleCellCount; i++) {
+                        if (i > 0) {
+                            final String colKey = getColKeyByIndex(i - 1);
+                            slotX += getColWidth(colKey);
+                        }
+                        final int dist = Math.abs(x - slotX);
+                        if (closestDistance == -1 || dist < closestDistance) {
+                            closestDistance = dist;
+                            closestSlot = i;
+                        }
+                    }
+                    tHead.focusSlot(closestSlot);
+
+                    updateFloatingCopysPosition(clientX, -1);
+                }
+                break;
+            default:
+                break;
+            }
+        }
+
+        private void onResizeEvent(Event event) {
+            switch (DOM.eventGetType(event)) {
+            case Event.ONMOUSEDOWN:
+                if (!Util.isTouchEventOrLeftMouseButton(event)) {
+                    return;
+                }
+                isResizing = true;
+                DOM.setCapture(getElement());
+                dragStartX = DOM.eventGetClientX(event);
+                colIndex = getColIndexByKey(cid);
+                originalWidth = getWidth();
+                DOM.eventPreventDefault(event);
+                break;
+            case Event.ONMOUSEUP:
+                if (!Util.isTouchEventOrLeftMouseButton(event)) {
+                    return;
+                }
+                isResizing = false;
+                DOM.releaseCapture(getElement());
+                tHead.disableAutoColumnWidthCalculation(this);
+
+                // Ensure last header cell is taking into account possible
+                // column selector
+                HeaderCell lastCell = tHead.getHeaderCell(tHead
+                        .getVisibleCellCount() - 1);
+                tHead.resizeCaptionContainer(lastCell);
+                triggerLazyColumnAdjustment(true);
+
+                fireColumnResizeEvent(cid, originalWidth, getColWidth(cid));
+                break;
+            case Event.ONMOUSEMOVE:
+                if (!Util.isTouchEventOrLeftMouseButton(event)) {
+                    return;
+                }
+                if (isResizing) {
+                    final int deltaX = DOM.eventGetClientX(event) - dragStartX;
+                    if (deltaX == 0) {
+                        return;
+                    }
+                    tHead.disableAutoColumnWidthCalculation(this);
+
+                    int newWidth = originalWidth + deltaX;
+                    if (newWidth < getMinWidth()) {
+                        newWidth = getMinWidth();
+                    }
+                    setColWidth(colIndex, newWidth, true);
+                    triggerLazyColumnAdjustment(false);
+                    forceRealignColumnHeaders();
+                }
+                break;
+            default:
+                break;
+            }
+        }
+
+        public int getMinWidth() {
+            int cellExtraWidth = 0;
+            if (scrollBody != null) {
+                cellExtraWidth += scrollBody.getCellExtraWidth();
+            }
+            return cellExtraWidth + sortIndicator.getOffsetWidth();
+        }
+
+        public String getCaption() {
+            return DOM.getInnerText(captionContainer);
+        }
+
+        public boolean isEnabled() {
+            return getParent() != null;
+        }
+
+        public void setAlign(char c) {
+            align = c;
+            updateStyleNames(VScrollTable.this.getStylePrimaryName());
+        }
+
+        public char getAlign() {
+            return align;
+        }
+
+        /**
+         * Detects the natural minimum width for the column of this header cell.
+         * If column is resized by user or the width is defined by server the
+         * actual width is returned. Else the natural min width is returned.
+         * 
+         * @param columnIndex
+         *            column index hint, if -1 (unknown) it will be detected
+         * 
+         * @return
+         */
+        public int getNaturalColumnWidth(int columnIndex) {
+            if (isDefinedWidth()) {
+                return width;
+            } else {
+                if (naturalWidth < 0) {
+                    // This is recently revealed column. Try to detect a proper
+                    // value (greater of header and data
+                    // cols)
+
+                    int hw = captionContainer.getOffsetWidth()
+                            + scrollBody.getCellExtraWidth();
+                    if (BrowserInfo.get().isGecko()) {
+                        hw += sortIndicator.getOffsetWidth();
+                    }
+                    if (columnIndex < 0) {
+                        columnIndex = 0;
+                        for (Iterator<Widget> it = tHead.iterator(); it
+                                .hasNext(); columnIndex++) {
+                            if (it.next() == this) {
+                                break;
+                            }
+                        }
+                    }
+                    final int cw = scrollBody.getColWidth(columnIndex);
+                    naturalWidth = (hw > cw ? hw : cw);
+                }
+                return naturalWidth;
+            }
+        }
+
+        public void setExpandRatio(float floatAttribute) {
+            if (floatAttribute != expandRatio) {
+                triggerLazyColumnAdjustment(false);
+            }
+            expandRatio = floatAttribute;
+        }
+
+        public float getExpandRatio() {
+            return expandRatio;
+        }
+
+        public boolean isSorted() {
+            return sorted;
+        }
+    }
+
+    /**
+     * HeaderCell that is header cell for row headers.
+     * 
+     * Reordering disabled and clicking on it resets sorting.
+     */
+    public class RowHeadersHeaderCell extends HeaderCell {
+
+        RowHeadersHeaderCell() {
+            super(ROW_HEADER_COLUMN_KEY, "");
+            updateStyleNames(VScrollTable.this.getStylePrimaryName());
+        }
+
+        @Override
+        protected void updateStyleNames(String primaryStyleName) {
+            super.updateStyleNames(primaryStyleName);
+            setStyleName(primaryStyleName + "-header-cell-rowheader");
+        }
+
+        @Override
+        protected void handleCaptionEvent(Event event) {
+            // NOP: RowHeaders cannot be reordered
+            // TODO It'd be nice to reset sorting here
+        }
+    }
+
+    public class TableHead extends Panel implements ActionOwner {
+
+        private static final int WRAPPER_WIDTH = 900000;
+
+        ArrayList<Widget> visibleCells = new ArrayList<Widget>();
+
+        HashMap<String, HeaderCell> availableCells = new HashMap<String, HeaderCell>();
+
+        Element div = DOM.createDiv();
+        Element hTableWrapper = DOM.createDiv();
+        Element hTableContainer = DOM.createDiv();
+        Element table = DOM.createTable();
+        Element headerTableBody = DOM.createTBody();
+        Element tr = DOM.createTR();
+
+        private final Element columnSelector = DOM.createDiv();
+
+        private int focusedSlot = -1;
+
+        public TableHead() {
+            if (BrowserInfo.get().isIE()) {
+                table.setPropertyInt("cellSpacing", 0);
+            }
+
+            DOM.setStyleAttribute(hTableWrapper, "overflow", "hidden");
+            DOM.setStyleAttribute(columnSelector, "display", "none");
+
+            DOM.appendChild(table, headerTableBody);
+            DOM.appendChild(headerTableBody, tr);
+            DOM.appendChild(hTableContainer, table);
+            DOM.appendChild(hTableWrapper, hTableContainer);
+            DOM.appendChild(div, hTableWrapper);
+            DOM.appendChild(div, columnSelector);
+            setElement(div);
+
+            DOM.sinkEvents(columnSelector, Event.ONCLICK);
+
+            availableCells.put(ROW_HEADER_COLUMN_KEY,
+                    new RowHeadersHeaderCell());
+        }
+
+        protected void updateStyleNames(String primaryStyleName) {
+            hTableWrapper.setClassName(primaryStyleName + "-header");
+            columnSelector.setClassName(primaryStyleName + "-column-selector");
+            setStyleName(primaryStyleName + "-header-wrap");
+            for (HeaderCell c : availableCells.values()) {
+                c.updateStyleNames(primaryStyleName);
+            }
+        }
+
+        public void resizeCaptionContainer(HeaderCell cell) {
+            HeaderCell lastcell = getHeaderCell(visibleCells.size() - 1);
+
+            // Measure column widths
+            int columnTotalWidth = 0;
+            for (Widget w : visibleCells) {
+                columnTotalWidth += w.getOffsetWidth();
+            }
+
+            if (cell == lastcell
+                    && columnSelector.getOffsetWidth() > 0
+                    && columnTotalWidth >= div.getOffsetWidth()
+                            - columnSelector.getOffsetWidth()
+                    && !hasVerticalScrollbar()) {
+                // Ensure column caption is visible when placed under the column
+                // selector widget by shifting and resizing the caption.
+                int offset = 0;
+                int diff = div.getOffsetWidth() - columnTotalWidth;
+                if (diff < columnSelector.getOffsetWidth() && diff > 0) {
+                    // If the difference is less than the column selectors width
+                    // then just offset by the
+                    // difference
+                    offset = columnSelector.getOffsetWidth() - diff;
+                } else {
+                    // Else offset by the whole column selector
+                    offset = columnSelector.getOffsetWidth();
+                }
+                lastcell.resizeCaptionContainer(offset);
+            } else {
+                cell.resizeCaptionContainer(0);
+            }
+        }
+
+        @Override
+        public void clear() {
+            for (String cid : availableCells.keySet()) {
+                removeCell(cid);
+            }
+            availableCells.clear();
+            availableCells.put(ROW_HEADER_COLUMN_KEY,
+                    new RowHeadersHeaderCell());
+        }
+
+        public void updateCellsFromUIDL(UIDL uidl) {
+            Iterator<?> it = uidl.getChildIterator();
+            HashSet<String> updated = new HashSet<String>();
+            boolean refreshContentWidths = false;
+            while (it.hasNext()) {
+                final UIDL col = (UIDL) it.next();
+                final String cid = col.getStringAttribute("cid");
+                updated.add(cid);
+
+                String caption = buildCaptionHtmlSnippet(col);
+                HeaderCell c = getHeaderCell(cid);
+                if (c == null) {
+                    c = new HeaderCell(cid, caption);
+                    availableCells.put(cid, c);
+                    if (initializedAndAttached) {
+                        // we will need a column width recalculation
+                        initializedAndAttached = false;
+                        initialContentReceived = false;
+                        isNewBody = true;
+                    }
+                } else {
+                    c.setText(caption);
+                }
+
+                if (col.hasAttribute("sortable")) {
+                    c.setSortable(true);
+                    if (cid.equals(sortColumn)) {
+                        c.setSorted(true);
+                    } else {
+                        c.setSorted(false);
+                    }
+                } else {
+                    c.setSortable(false);
+                }
+
+                if (col.hasAttribute("align")) {
+                    c.setAlign(col.getStringAttribute("align").charAt(0));
+                } else {
+                    c.setAlign(ALIGN_LEFT);
+
+                }
+                if (col.hasAttribute("width")) {
+                    final String widthStr = col.getStringAttribute("width");
+                    // Make sure to accomodate for the sort indicator if
+                    // necessary.
+                    int width = Integer.parseInt(widthStr);
+                    if (width < c.getMinWidth()) {
+                        width = c.getMinWidth();
+                    }
+                    if (width != c.getWidth() && scrollBody != null) {
+                        // Do a more thorough update if a column is resized from
+                        // the server *after* the header has been properly
+                        // initialized
+                        final int colIx = getColIndexByKey(c.cid);
+                        final int newWidth = width;
+                        Scheduler.get().scheduleDeferred(
+                                new ScheduledCommand() {
+
+                                    @Override
+                                    public void execute() {
+                                        setColWidth(colIx, newWidth, true);
+                                    }
+                                });
+                        refreshContentWidths = true;
+                    } else {
+                        c.setWidth(width, true);
+                    }
+                } else if (recalcWidths) {
+                    c.setUndefinedWidth();
+                }
+                if (col.hasAttribute("er")) {
+                    c.setExpandRatio(col.getFloatAttribute("er"));
+                }
+                if (col.hasAttribute("collapsed")) {
+                    // ensure header is properly removed from parent (case when
+                    // collapsing happens via servers side api)
+                    if (c.isAttached()) {
+                        c.removeFromParent();
+                        headerChangedDuringUpdate = true;
+                    }
+                }
+            }
+
+            if (refreshContentWidths) {
+                // Recalculate the column sizings if any column has changed
+                Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+
+                    @Override
+                    public void execute() {
+                        triggerLazyColumnAdjustment(true);
+                    }
+                });
+            }
+
+            // check for orphaned header cells
+            for (Iterator<String> cit = availableCells.keySet().iterator(); cit
+                    .hasNext();) {
+                String cid = cit.next();
+                if (!updated.contains(cid)) {
+                    removeCell(cid);
+                    cit.remove();
+                    // we will need a column width recalculation, since columns
+                    // with expand ratios should expand to fill the void.
+                    initializedAndAttached = false;
+                    initialContentReceived = false;
+                    isNewBody = true;
+                }
+            }
+        }
+
+        public void enableColumn(String cid, int index) {
+            final HeaderCell c = getHeaderCell(cid);
+            if (!c.isEnabled() || getHeaderCell(index) != c) {
+                setHeaderCell(index, c);
+                if (initializedAndAttached) {
+                    headerChangedDuringUpdate = true;
+                }
+            }
+        }
+
+        public int getVisibleCellCount() {
+            return visibleCells.size();
+        }
+
+        public void setHorizontalScrollPosition(int scrollLeft) {
+            hTableWrapper.setScrollLeft(scrollLeft);
+        }
+
+        public void setColumnCollapsingAllowed(boolean cc) {
+            if (cc) {
+                columnSelector.getStyle().setDisplay(Display.BLOCK);
+            } else {
+                columnSelector.getStyle().setDisplay(Display.NONE);
+            }
+        }
+
+        public void disableBrowserIntelligence() {
+            hTableContainer.getStyle().setWidth(WRAPPER_WIDTH, Unit.PX);
+        }
+
+        public void enableBrowserIntelligence() {
+            hTableContainer.getStyle().clearWidth();
+        }
+
+        public void setHeaderCell(int index, HeaderCell cell) {
+            if (cell.isEnabled()) {
+                // we're moving the cell
+                DOM.removeChild(tr, cell.getElement());
+                orphan(cell);
+                visibleCells.remove(cell);
+            }
+            if (index < visibleCells.size()) {
+                // insert to right slot
+                DOM.insertChild(tr, cell.getElement(), index);
+                adopt(cell);
+                visibleCells.add(index, cell);
+            } else if (index == visibleCells.size()) {
+                // simply append
+                DOM.appendChild(tr, cell.getElement());
+                adopt(cell);
+                visibleCells.add(cell);
+            } else {
+                throw new RuntimeException(
+                        "Header cells must be appended in order");
+            }
+        }
+
+        public HeaderCell getHeaderCell(int index) {
+            if (index >= 0 && index < visibleCells.size()) {
+                return (HeaderCell) visibleCells.get(index);
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Get's HeaderCell by it's column Key.
+         * 
+         * Note that this returns HeaderCell even if it is currently collapsed.
+         * 
+         * @param cid
+         *            Column key of accessed HeaderCell
+         * @return HeaderCell
+         */
+        public HeaderCell getHeaderCell(String cid) {
+            return availableCells.get(cid);
+        }
+
+        public void moveCell(int oldIndex, int newIndex) {
+            final HeaderCell hCell = getHeaderCell(oldIndex);
+            final Element cell = hCell.getElement();
+
+            visibleCells.remove(oldIndex);
+            DOM.removeChild(tr, cell);
+
+            DOM.insertChild(tr, cell, newIndex);
+            visibleCells.add(newIndex, hCell);
+        }
+
+        @Override
+        public Iterator<Widget> iterator() {
+            return visibleCells.iterator();
+        }
+
+        @Override
+        public boolean remove(Widget w) {
+            if (visibleCells.contains(w)) {
+                visibleCells.remove(w);
+                orphan(w);
+                DOM.removeChild(DOM.getParent(w.getElement()), w.getElement());
+                return true;
+            }
+            return false;
+        }
+
+        public void removeCell(String colKey) {
+            final HeaderCell c = getHeaderCell(colKey);
+            remove(c);
+        }
+
+        private void focusSlot(int index) {
+            removeSlotFocus();
+            if (index > 0) {
+                Element child = tr.getChild(index - 1).getFirstChild().cast();
+                child.setClassName(VScrollTable.this.getStylePrimaryName()
+                        + "-resizer");
+                child.addClassName(VScrollTable.this.getStylePrimaryName()
+                        + "-focus-slot-right");
+            } else {
+                Element child = tr.getChild(index).getFirstChild().cast();
+                child.setClassName(VScrollTable.this.getStylePrimaryName()
+                        + "-resizer");
+                child.addClassName(VScrollTable.this.getStylePrimaryName()
+                        + "-focus-slot-left");
+            }
+            focusedSlot = index;
+        }
+
+        private void removeSlotFocus() {
+            if (focusedSlot < 0) {
+                return;
+            }
+            if (focusedSlot == 0) {
+                Element child = tr.getChild(focusedSlot).getFirstChild().cast();
+                child.setClassName(VScrollTable.this.getStylePrimaryName()
+                        + "-resizer");
+            } else if (focusedSlot > 0) {
+                Element child = tr.getChild(focusedSlot - 1).getFirstChild()
+                        .cast();
+                child.setClassName(VScrollTable.this.getStylePrimaryName()
+                        + "-resizer");
+            }
+            focusedSlot = -1;
+        }
+
+        @Override
+        public void onBrowserEvent(Event event) {
+            if (enabled) {
+                if (event.getEventTarget().cast() == columnSelector) {
+                    final int left = DOM.getAbsoluteLeft(columnSelector);
+                    final int top = DOM.getAbsoluteTop(columnSelector)
+                            + DOM.getElementPropertyInt(columnSelector,
+                                    "offsetHeight");
+                    client.getContextMenu().showAt(this, left, top);
+                }
+            }
+        }
+
+        @Override
+        protected void onDetach() {
+            super.onDetach();
+            if (client != null) {
+                client.getContextMenu().ensureHidden(this);
+            }
+        }
+
+        class VisibleColumnAction extends Action {
+
+            String colKey;
+            private boolean collapsed;
+            private boolean noncollapsible = false;
+            private VScrollTableRow currentlyFocusedRow;
+
+            public VisibleColumnAction(String colKey) {
+                super(VScrollTable.TableHead.this);
+                this.colKey = colKey;
+                caption = tHead.getHeaderCell(colKey).getCaption();
+                currentlyFocusedRow = focusedRow;
+            }
+
+            @Override
+            public void execute() {
+                if (noncollapsible) {
+                    return;
+                }
+                client.getContextMenu().hide();
+                // toggle selected column
+                if (collapsedColumns.contains(colKey)) {
+                    collapsedColumns.remove(colKey);
+                } else {
+                    tHead.removeCell(colKey);
+                    collapsedColumns.add(colKey);
+                    triggerLazyColumnAdjustment(true);
+                }
+
+                // update variable to server
+                client.updateVariable(paintableId, "collapsedcolumns",
+                        collapsedColumns.toArray(new String[collapsedColumns
+                                .size()]), false);
+                // let rowRequestHandler determine proper rows
+                rowRequestHandler.refreshContent();
+                lazyRevertFocusToRow(currentlyFocusedRow);
+            }
+
+            public void setCollapsed(boolean b) {
+                collapsed = b;
+            }
+
+            public void setNoncollapsible(boolean b) {
+                noncollapsible = b;
+            }
+
+            /**
+             * Override default method to distinguish on/off columns
+             */
+
+            @Override
+            public String getHTML() {
+                final StringBuffer buf = new StringBuffer();
+                buf.append("<span class=\"");
+                if (collapsed) {
+                    buf.append("v-off");
+                } else {
+                    buf.append("v-on");
+                }
+                if (noncollapsible) {
+                    buf.append(" v-disabled");
+                }
+                buf.append("\">");
+
+                buf.append(super.getHTML());
+                buf.append("</span>");
+
+                return buf.toString();
+            }
+
+        }
+
+        /*
+         * Returns columns as Action array for column select popup
+         */
+
+        @Override
+        public Action[] getActions() {
+            Object[] cols;
+            if (columnReordering && columnOrder != null) {
+                cols = columnOrder;
+            } else {
+                // if columnReordering is disabled, we need different way to get
+                // all available columns
+                cols = visibleColOrder;
+                cols = new Object[visibleColOrder.length
+                        + collapsedColumns.size()];
+                int i;
+                for (i = 0; i < visibleColOrder.length; i++) {
+                    cols[i] = visibleColOrder[i];
+                }
+                for (final Iterator<String> it = collapsedColumns.iterator(); it
+                        .hasNext();) {
+                    cols[i++] = it.next();
+                }
+            }
+            final Action[] actions = new Action[cols.length];
+
+            for (int i = 0; i < cols.length; i++) {
+                final String cid = (String) cols[i];
+                final HeaderCell c = getHeaderCell(cid);
+                final VisibleColumnAction a = new VisibleColumnAction(
+                        c.getColKey());
+                a.setCaption(c.getCaption());
+                if (!c.isEnabled()) {
+                    a.setCollapsed(true);
+                }
+                if (noncollapsibleColumns.contains(cid)) {
+                    a.setNoncollapsible(true);
+                }
+                actions[i] = a;
+            }
+            return actions;
+        }
+
+        @Override
+        public ApplicationConnection getClient() {
+            return client;
+        }
+
+        @Override
+        public String getPaintableId() {
+            return paintableId;
+        }
+
+        /**
+         * Returns column alignments for visible columns
+         */
+        public char[] getColumnAlignments() {
+            final Iterator<Widget> it = visibleCells.iterator();
+            final char[] aligns = new char[visibleCells.size()];
+            int colIndex = 0;
+            while (it.hasNext()) {
+                aligns[colIndex++] = ((HeaderCell) it.next()).getAlign();
+            }
+            return aligns;
+        }
+
+        /**
+         * Disables the automatic calculation of all column widths by forcing
+         * the widths to be "defined" thus turning off expand ratios and such.
+         */
+        public void disableAutoColumnWidthCalculation(HeaderCell source) {
+            for (HeaderCell cell : availableCells.values()) {
+                cell.disableAutoWidthCalculation();
+            }
+            // fire column resize events for all columns but the source of the
+            // resize action, since an event will fire separately for this.
+            ArrayList<HeaderCell> columns = new ArrayList<HeaderCell>(
+                    availableCells.values());
+            columns.remove(source);
+            sendColumnWidthUpdates(columns);
+            forceRealignColumnHeaders();
+        }
+    }
+
+    /**
+     * A cell in the footer
+     */
+    public class FooterCell extends Widget {
+        private final Element td = DOM.createTD();
+        private final Element captionContainer = DOM.createDiv();
+        private char align = ALIGN_LEFT;
+        private int width = -1;
+        private float expandRatio = 0;
+        private final String cid;
+        boolean definedWidth = false;
+        private int naturalWidth = -1;
+
+        public FooterCell(String colId, String headerText) {
+            cid = colId;
+
+            setText(headerText);
+
+            // ensure no clipping initially (problem on column additions)
+            DOM.setStyleAttribute(captionContainer, "overflow", "visible");
+
+            DOM.sinkEvents(captionContainer, Event.MOUSEEVENTS);
+
+            DOM.appendChild(td, captionContainer);
+
+            DOM.sinkEvents(td, Event.MOUSEEVENTS | Event.ONDBLCLICK
+                    | Event.ONCONTEXTMENU);
+
+            setElement(td);
+
+            updateStyleNames(VScrollTable.this.getStylePrimaryName());
+        }
+
+        protected void updateStyleNames(String primaryStyleName) {
+            captionContainer.setClassName(primaryStyleName
+                    + "-footer-container");
+        }
+
+        /**
+         * Sets the text of the footer
+         * 
+         * @param footerText
+         *            The text in the footer
+         */
+        public void setText(String footerText) {
+            if (footerText == null || footerText.equals("")) {
+                footerText = "&nbsp;";
+            }
+
+            DOM.setInnerHTML(captionContainer, footerText);
+        }
+
+        /**
+         * Set alignment of the text in the cell
+         * 
+         * @param c
+         *            The alignment which can be ALIGN_CENTER, ALIGN_LEFT,
+         *            ALIGN_RIGHT
+         */
+        public void setAlign(char c) {
+            if (align != c) {
+                switch (c) {
+                case ALIGN_CENTER:
+                    DOM.setStyleAttribute(captionContainer, "textAlign",
+                            "center");
+                    break;
+                case ALIGN_RIGHT:
+                    DOM.setStyleAttribute(captionContainer, "textAlign",
+                            "right");
+                    break;
+                default:
+                    DOM.setStyleAttribute(captionContainer, "textAlign", "");
+                    break;
+                }
+            }
+            align = c;
+        }
+
+        /**
+         * Get the alignment of the text int the cell
+         * 
+         * @return Returns either ALIGN_CENTER, ALIGN_LEFT or ALIGN_RIGHT
+         */
+        public char getAlign() {
+            return align;
+        }
+
+        /**
+         * Sets the width of the cell
+         * 
+         * @param w
+         *            The width of the cell
+         * @param ensureDefinedWidth
+         *            Ensures the the given width is not recalculated
+         */
+        public void setWidth(int w, boolean ensureDefinedWidth) {
+
+            if (ensureDefinedWidth) {
+                definedWidth = true;
+                // on column resize expand ratio becomes zero
+                expandRatio = 0;
+            }
+            if (width == w) {
+                return;
+            }
+            if (width == -1) {
+                // go to default mode, clip content if necessary
+                DOM.setStyleAttribute(captionContainer, "overflow", "");
+            }
+            width = w;
+            if (w == -1) {
+                DOM.setStyleAttribute(captionContainer, "width", "");
+                setWidth("");
+            } else {
+                /*
+                 * Reduce width with one pixel for the right border since the
+                 * footers does not have any spacers between them.
+                 */
+                final int borderWidths = 1;
+
+                // Set the container width (check for negative value)
+                captionContainer.getStyle().setPropertyPx("width",
+                        Math.max(w - borderWidths, 0));
+
+                /*
+                 * if we already have tBody, set the header width properly, if
+                 * not defer it. IE will fail with complex float in table header
+                 * unless TD width is not explicitly set.
+                 */
+                if (scrollBody != null) {
+                    int tdWidth = width + scrollBody.getCellExtraWidth()
+                            - borderWidths;
+                    setWidth(Math.max(tdWidth, 0) + "px");
+                } else {
+                    Scheduler.get().scheduleDeferred(new Command() {
+
+                        @Override
+                        public void execute() {
+                            int tdWidth = width
+                                    + scrollBody.getCellExtraWidth()
+                                    - borderWidths;
+                            setWidth(Math.max(tdWidth, 0) + "px");
+                        }
+                    });
+                }
+            }
+        }
+
+        /**
+         * Sets the width to undefined
+         */
+        public void setUndefinedWidth() {
+            setWidth(-1, false);
+        }
+
+        /**
+         * Detects if width is fixed by developer on server side or resized to
+         * current width by user.
+         * 
+         * @return true if defined, false if "natural" width
+         */
+        public boolean isDefinedWidth() {
+            return definedWidth && width >= 0;
+        }
+
+        /**
+         * Returns the pixels width of the footer cell
+         * 
+         * @return The width in pixels
+         */
+        public int getWidth() {
+            return width;
+        }
+
+        /**
+         * Sets the expand ratio of the cell
+         * 
+         * @param floatAttribute
+         *            The expand ratio
+         */
+        public void setExpandRatio(float floatAttribute) {
+            expandRatio = floatAttribute;
+        }
+
+        /**
+         * Returns the expand ration of the cell
+         * 
+         * @return The expand ratio
+         */
+        public float getExpandRatio() {
+            return expandRatio;
+        }
+
+        /**
+         * Is the cell enabled?
+         * 
+         * @return True if enabled else False
+         */
+        public boolean isEnabled() {
+            return getParent() != null;
+        }
+
+        /**
+         * Handle column clicking
+         */
+
+        @Override
+        public void onBrowserEvent(Event event) {
+            if (enabled && event != null) {
+                handleCaptionEvent(event);
+
+                if (DOM.eventGetType(event) == Event.ONMOUSEUP) {
+                    scrollBodyPanel.setFocus(true);
+                }
+                boolean stopPropagation = true;
+                if (event.getTypeInt() == Event.ONCONTEXTMENU
+                        && !client.hasEventListeners(VScrollTable.this,
+                                TableConstants.FOOTER_CLICK_EVENT_ID)) {
+                    // Show browser context menu if a footer click listener is
+                    // not present
+                    stopPropagation = false;
+                }
+                if (stopPropagation) {
+                    event.stopPropagation();
+                    event.preventDefault();
+                }
+            }
+        }
+
+        /**
+         * Handles a event on the captions
+         * 
+         * @param event
+         *            The event to handle
+         */
+        protected void handleCaptionEvent(Event event) {
+            if (event.getTypeInt() == Event.ONMOUSEUP
+                    || event.getTypeInt() == Event.ONDBLCLICK) {
+                fireFooterClickedEvent(event);
+            }
+        }
+
+        /**
+         * Fires a footer click event after the user has clicked a column footer
+         * cell
+         * 
+         * @param event
+         *            The click event
+         */
+        private void fireFooterClickedEvent(Event event) {
+            if (client.hasEventListeners(VScrollTable.this,
+                    TableConstants.FOOTER_CLICK_EVENT_ID)) {
+                MouseEventDetails details = MouseEventDetailsBuilder
+                        .buildMouseEventDetails(event);
+                client.updateVariable(paintableId, "footerClickEvent",
+                        details.toString(), false);
+                client.updateVariable(paintableId, "footerClickCID", cid, true);
+            }
+        }
+
+        /**
+         * Returns the column key of the column
+         * 
+         * @return The column key
+         */
+        public String getColKey() {
+            return cid;
+        }
+
+        /**
+         * Detects the natural minimum width for the column of this header cell.
+         * If column is resized by user or the width is defined by server the
+         * actual width is returned. Else the natural min width is returned.
+         * 
+         * @param columnIndex
+         *            column index hint, if -1 (unknown) it will be detected
+         * 
+         * @return
+         */
+        public int getNaturalColumnWidth(int columnIndex) {
+            if (isDefinedWidth()) {
+                return width;
+            } else {
+                if (naturalWidth < 0) {
+                    // This is recently revealed column. Try to detect a proper
+                    // value (greater of header and data
+                    // cols)
+
+                    final int hw = ((Element) getElement().getLastChild())
+                            .getOffsetWidth() + scrollBody.getCellExtraWidth();
+                    if (columnIndex < 0) {
+                        columnIndex = 0;
+                        for (Iterator<Widget> it = tHead.iterator(); it
+                                .hasNext(); columnIndex++) {
+                            if (it.next() == this) {
+                                break;
+                            }
+                        }
+                    }
+                    final int cw = scrollBody.getColWidth(columnIndex);
+                    naturalWidth = (hw > cw ? hw : cw);
+                }
+                return naturalWidth;
+            }
+        }
+
+        public void setNaturalMinimumColumnWidth(int w) {
+            naturalWidth = w;
+        }
+    }
+
+    /**
+     * HeaderCell that is header cell for row headers.
+     * 
+     * Reordering disabled and clicking on it resets sorting.
+     */
+    public class RowHeadersFooterCell extends FooterCell {
+
+        RowHeadersFooterCell() {
+            super(ROW_HEADER_COLUMN_KEY, "");
+        }
+
+        @Override
+        protected void handleCaptionEvent(Event event) {
+            // NOP: RowHeaders cannot be reordered
+            // TODO It'd be nice to reset sorting here
+        }
+    }
+
+    /**
+     * The footer of the table which can be seen in the bottom of the Table.
+     */
+    public class TableFooter extends Panel {
+
+        private static final int WRAPPER_WIDTH = 900000;
+
+        ArrayList<Widget> visibleCells = new ArrayList<Widget>();
+        HashMap<String, FooterCell> availableCells = new HashMap<String, FooterCell>();
+
+        Element div = DOM.createDiv();
+        Element hTableWrapper = DOM.createDiv();
+        Element hTableContainer = DOM.createDiv();
+        Element table = DOM.createTable();
+        Element headerTableBody = DOM.createTBody();
+        Element tr = DOM.createTR();
+
+        public TableFooter() {
+
+            DOM.setStyleAttribute(hTableWrapper, "overflow", "hidden");
+
+            DOM.appendChild(table, headerTableBody);
+            DOM.appendChild(headerTableBody, tr);
+            DOM.appendChild(hTableContainer, table);
+            DOM.appendChild(hTableWrapper, hTableContainer);
+            DOM.appendChild(div, hTableWrapper);
+            setElement(div);
+
+            availableCells.put(ROW_HEADER_COLUMN_KEY,
+                    new RowHeadersFooterCell());
+
+            updateStyleNames(VScrollTable.this.getStylePrimaryName());
+        }
+
+        protected void updateStyleNames(String primaryStyleName) {
+            hTableWrapper.setClassName(primaryStyleName + "-footer");
+            setStyleName(primaryStyleName + "-footer-wrap");
+            for (FooterCell c : availableCells.values()) {
+                c.updateStyleNames(primaryStyleName);
+            }
+        }
+
+        @Override
+        public void clear() {
+            for (String cid : availableCells.keySet()) {
+                removeCell(cid);
+            }
+            availableCells.clear();
+            availableCells.put(ROW_HEADER_COLUMN_KEY,
+                    new RowHeadersFooterCell());
+        }
+
+        /*
+         * (non-Javadoc)
+         * 
+         * @see
+         * com.google.gwt.user.client.ui.Panel#remove(com.google.gwt.user.client
+         * .ui.Widget)
+         */
+
+        @Override
+        public boolean remove(Widget w) {
+            if (visibleCells.contains(w)) {
+                visibleCells.remove(w);
+                orphan(w);
+                DOM.removeChild(DOM.getParent(w.getElement()), w.getElement());
+                return true;
+            }
+            return false;
+        }
+
+        /*
+         * (non-Javadoc)
+         * 
+         * @see com.google.gwt.user.client.ui.HasWidgets#iterator()
+         */
+
+        @Override
+        public Iterator<Widget> iterator() {
+            return visibleCells.iterator();
+        }
+
+        /**
+         * Gets a footer cell which represents the given columnId
+         * 
+         * @param cid
+         *            The columnId
+         * 
+         * @return The cell
+         */
+        public FooterCell getFooterCell(String cid) {
+            return availableCells.get(cid);
+        }
+
+        /**
+         * Gets a footer cell by using a column index
+         * 
+         * @param index
+         *            The index of the column
+         * @return The Cell
+         */
+        public FooterCell getFooterCell(int index) {
+            if (index < visibleCells.size()) {
+                return (FooterCell) visibleCells.get(index);
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Updates the cells contents when updateUIDL request is received
+         * 
+         * @param uidl
+         *            The UIDL
+         */
+        public void updateCellsFromUIDL(UIDL uidl) {
+            Iterator<?> columnIterator = uidl.getChildIterator();
+            HashSet<String> updated = new HashSet<String>();
+            while (columnIterator.hasNext()) {
+                final UIDL col = (UIDL) columnIterator.next();
+                final String cid = col.getStringAttribute("cid");
+                updated.add(cid);
+
+                String caption = col.hasAttribute("fcaption") ? col
+                        .getStringAttribute("fcaption") : "";
+                FooterCell c = getFooterCell(cid);
+                if (c == null) {
+                    c = new FooterCell(cid, caption);
+                    availableCells.put(cid, c);
+                    if (initializedAndAttached) {
+                        // we will need a column width recalculation
+                        initializedAndAttached = false;
+                        initialContentReceived = false;
+                        isNewBody = true;
+                    }
+                } else {
+                    c.setText(caption);
+                }
+
+                if (col.hasAttribute("align")) {
+                    c.setAlign(col.getStringAttribute("align").charAt(0));
+                } else {
+                    c.setAlign(ALIGN_LEFT);
+
+                }
+                if (col.hasAttribute("width")) {
+                    if (scrollBody == null) {
+                        // Already updated by setColWidth called from
+                        // TableHeads.updateCellsFromUIDL in case of a server
+                        // side resize
+                        final String width = col.getStringAttribute("width");
+                        c.setWidth(Integer.parseInt(width), true);
+                    }
+                } else if (recalcWidths) {
+                    c.setUndefinedWidth();
+                }
+                if (col.hasAttribute("er")) {
+                    c.setExpandRatio(col.getFloatAttribute("er"));
+                }
+                if (col.hasAttribute("collapsed")) {
+                    // ensure header is properly removed from parent (case when
+                    // collapsing happens via servers side api)
+                    if (c.isAttached()) {
+                        c.removeFromParent();
+                        headerChangedDuringUpdate = true;
+                    }
+                }
+            }
+
+            // check for orphaned header cells
+            for (Iterator<String> cit = availableCells.keySet().iterator(); cit
+                    .hasNext();) {
+                String cid = cit.next();
+                if (!updated.contains(cid)) {
+                    removeCell(cid);
+                    cit.remove();
+                }
+            }
+        }
+
+        /**
+         * Set a footer cell for a specified column index
+         * 
+         * @param index
+         *            The index
+         * @param cell
+         *            The footer cell
+         */
+        public void setFooterCell(int index, FooterCell cell) {
+            if (cell.isEnabled()) {
+                // we're moving the cell
+                DOM.removeChild(tr, cell.getElement());
+                orphan(cell);
+                visibleCells.remove(cell);
+            }
+            if (index < visibleCells.size()) {
+                // insert to right slot
+                DOM.insertChild(tr, cell.getElement(), index);
+                adopt(cell);
+                visibleCells.add(index, cell);
+            } else if (index == visibleCells.size()) {
+                // simply append
+                DOM.appendChild(tr, cell.getElement());
+                adopt(cell);
+                visibleCells.add(cell);
+            } else {
+                throw new RuntimeException(
+                        "Header cells must be appended in order");
+            }
+        }
+
+        /**
+         * Remove a cell by using the columnId
+         * 
+         * @param colKey
+         *            The columnId to remove
+         */
+        public void removeCell(String colKey) {
+            final FooterCell c = getFooterCell(colKey);
+            remove(c);
+        }
+
+        /**
+         * Enable a column (Sets the footer cell)
+         * 
+         * @param cid
+         *            The columnId
+         * @param index
+         *            The index of the column
+         */
+        public void enableColumn(String cid, int index) {
+            final FooterCell c = getFooterCell(cid);
+            if (!c.isEnabled() || getFooterCell(index) != c) {
+                setFooterCell(index, c);
+                if (initializedAndAttached) {
+                    headerChangedDuringUpdate = true;
+                }
+            }
+        }
+
+        /**
+         * Disable browser measurement of the table width
+         */
+        public void disableBrowserIntelligence() {
+            DOM.setStyleAttribute(hTableContainer, "width", WRAPPER_WIDTH
+                    + "px");
+        }
+
+        /**
+         * Enable browser measurement of the table width
+         */
+        public void enableBrowserIntelligence() {
+            DOM.setStyleAttribute(hTableContainer, "width", "");
+        }
+
+        /**
+         * Set the horizontal position in the cell in the footer. This is done
+         * when a horizontal scrollbar is present.
+         * 
+         * @param scrollLeft
+         *            The value of the leftScroll
+         */
+        public void setHorizontalScrollPosition(int scrollLeft) {
+            hTableWrapper.setScrollLeft(scrollLeft);
+        }
+
+        /**
+         * Swap cells when the column are dragged
+         * 
+         * @param oldIndex
+         *            The old index of the cell
+         * @param newIndex
+         *            The new index of the cell
+         */
+        public void moveCell(int oldIndex, int newIndex) {
+            final FooterCell hCell = getFooterCell(oldIndex);
+            final Element cell = hCell.getElement();
+
+            visibleCells.remove(oldIndex);
+            DOM.removeChild(tr, cell);
+
+            DOM.insertChild(tr, cell, newIndex);
+            visibleCells.add(newIndex, hCell);
+        }
+    }
+
+    /**
+     * This Panel can only contain VScrollTableRow type of widgets. This
+     * "simulates" very large table, keeping spacers which take room of
+     * unrendered rows.
+     * 
+     */
+    public class VScrollTableBody extends Panel {
+
+        public static final int DEFAULT_ROW_HEIGHT = 24;
+
+        private double rowHeight = -1;
+
+        private final LinkedList<Widget> renderedRows = new LinkedList<Widget>();
+
+        /**
+         * Due some optimizations row height measuring is deferred and initial
+         * set of rows is rendered detached. Flag set on when table body has
+         * been attached in dom and rowheight has been measured.
+         */
+        private boolean tBodyMeasurementsDone = false;
+
+        Element preSpacer = DOM.createDiv();
+        Element postSpacer = DOM.createDiv();
+
+        Element container = DOM.createDiv();
+
+        TableSectionElement tBodyElement = Document.get().createTBodyElement();
+        Element table = DOM.createTable();
+
+        private int firstRendered;
+        private int lastRendered;
+
+        private char[] aligns;
+
+        protected VScrollTableBody() {
+            constructDOM();
+            setElement(container);
+        }
+
+        public VScrollTableRow getRowByRowIndex(int indexInTable) {
+            int internalIndex = indexInTable - firstRendered;
+            if (internalIndex >= 0 && internalIndex < renderedRows.size()) {
+                return (VScrollTableRow) renderedRows.get(internalIndex);
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * @return the height of scrollable body, subpixels ceiled.
+         */
+        public int getRequiredHeight() {
+            return preSpacer.getOffsetHeight() + postSpacer.getOffsetHeight()
+                    + Util.getRequiredHeight(table);
+        }
+
+        private void constructDOM() {
+            if (BrowserInfo.get().isIE()) {
+                table.setPropertyInt("cellSpacing", 0);
+            }
+
+            table.appendChild(tBodyElement);
+            DOM.appendChild(container, preSpacer);
+            DOM.appendChild(container, table);
+            DOM.appendChild(container, postSpacer);
+            if (BrowserInfo.get().requiresTouchScrollDelegate()) {
+                NodeList<Node> childNodes = container.getChildNodes();
+                for (int i = 0; i < childNodes.getLength(); i++) {
+                    Element item = (Element) childNodes.getItem(i);
+                    item.getStyle().setProperty("webkitTransform",
+                            "translate3d(0,0,0)");
+                }
+            }
+            updateStyleNames(VScrollTable.this.getStylePrimaryName());
+        }
+
+        protected void updateStyleNames(String primaryStyleName) {
+            table.setClassName(primaryStyleName + "-table");
+            preSpacer.setClassName(primaryStyleName + "-row-spacer");
+            postSpacer.setClassName(primaryStyleName + "-row-spacer");
+            for (Widget w : renderedRows) {
+                VScrollTableRow row = (VScrollTableRow) w;
+                row.updateStyleNames(primaryStyleName);
+            }
+        }
+
+        public int getAvailableWidth() {
+            int availW = scrollBodyPanel.getOffsetWidth() - getBorderWidth();
+            return availW;
+        }
+
+        public void renderInitialRows(UIDL rowData, int firstIndex, int rows) {
+            firstRendered = firstIndex;
+            lastRendered = firstIndex + rows - 1;
+            final Iterator<?> it = rowData.getChildIterator();
+            aligns = tHead.getColumnAlignments();
+            while (it.hasNext()) {
+                final VScrollTableRow row = createRow((UIDL) it.next(), aligns);
+                addRow(row);
+            }
+            if (isAttached()) {
+                fixSpacers();
+            }
+        }
+
+        public void renderRows(UIDL rowData, int firstIndex, int rows) {
+            // FIXME REVIEW
+            aligns = tHead.getColumnAlignments();
+            final Iterator<?> it = rowData.getChildIterator();
+            if (firstIndex == lastRendered + 1) {
+                while (it.hasNext()) {
+                    final VScrollTableRow row = prepareRow((UIDL) it.next());
+                    addRow(row);
+                    lastRendered++;
+                }
+                fixSpacers();
+            } else if (firstIndex + rows == firstRendered) {
+                final VScrollTableRow[] rowArray = new VScrollTableRow[rows];
+                int i = rows;
+                while (it.hasNext()) {
+                    i--;
+                    rowArray[i] = prepareRow((UIDL) it.next());
+                }
+                for (i = 0; i < rows; i++) {
+                    addRowBeforeFirstRendered(rowArray[i]);
+                    firstRendered--;
+                }
+            } else {
+                // completely new set of rows
+                while (lastRendered + 1 > firstRendered) {
+                    unlinkRow(false);
+                }
+                final VScrollTableRow row = prepareRow((UIDL) it.next());
+                firstRendered = firstIndex;
+                lastRendered = firstIndex - 1;
+                addRow(row);
+                lastRendered++;
+                setContainerHeight();
+                fixSpacers();
+                while (it.hasNext()) {
+                    addRow(prepareRow((UIDL) it.next()));
+                    lastRendered++;
+                }
+                fixSpacers();
+            }
+
+            // this may be a new set of rows due content change,
+            // ensure we have proper cache rows
+            ensureCacheFilled();
+        }
+
+        /**
+         * Ensure we have the correct set of rows on client side, e.g. if the
+         * content on the server side has changed, or the client scroll position
+         * has changed since the last request.
+         */
+        protected void ensureCacheFilled() {
+            int reactFirstRow = (int) (firstRowInViewPort - pageLength
+                    * cache_react_rate);
+            int reactLastRow = (int) (firstRowInViewPort + pageLength + pageLength
+                    * cache_react_rate);
+            if (reactFirstRow < 0) {
+                reactFirstRow = 0;
+            }
+            if (reactLastRow >= totalRows) {
+                reactLastRow = totalRows - 1;
+            }
+            if (lastRendered < reactFirstRow || firstRendered > reactLastRow) {
+                /*
+                 * #8040 - scroll position is completely changed since the
+                 * latest request, so request a new set of rows.
+                 * 
+                 * TODO: We should probably check whether the fetched rows match
+                 * the current scroll position right when they arrive, so as to
+                 * not waste time rendering a set of rows that will never be
+                 * visible...
+                 */
+                rowRequestHandler.setReqFirstRow(reactFirstRow);
+                rowRequestHandler.setReqRows(reactLastRow - reactFirstRow + 1);
+                rowRequestHandler.deferRowFetch(1);
+            } else if (lastRendered < reactLastRow) {
+                // get some cache rows below visible area
+                rowRequestHandler.setReqFirstRow(lastRendered + 1);
+                rowRequestHandler.setReqRows(reactLastRow - lastRendered);
+                rowRequestHandler.deferRowFetch(1);
+            } else if (firstRendered > reactFirstRow) {
+                /*
+                 * Branch for fetching cache above visible area.
+                 * 
+                 * If cache needed for both before and after visible area, this
+                 * will be rendered after-cache is received and rendered. So in
+                 * some rare situations the table may make two cache visits to
+                 * server.
+                 */
+                rowRequestHandler.setReqFirstRow(reactFirstRow);
+                rowRequestHandler.setReqRows(firstRendered - reactFirstRow);
+                rowRequestHandler.deferRowFetch(1);
+            }
+        }
+
+        /**
+         * Inserts rows as provided in the rowData starting at firstIndex.
+         * 
+         * @param rowData
+         * @param firstIndex
+         * @param rows
+         *            the number of rows
+         * @return a list of the rows added.
+         */
+        protected List<VScrollTableRow> insertRows(UIDL rowData,
+                int firstIndex, int rows) {
+            aligns = tHead.getColumnAlignments();
+            final Iterator<?> it = rowData.getChildIterator();
+            List<VScrollTableRow> insertedRows = new ArrayList<VScrollTableRow>();
+
+            if (firstIndex == lastRendered + 1) {
+                while (it.hasNext()) {
+                    final VScrollTableRow row = prepareRow((UIDL) it.next());
+                    addRow(row);
+                    insertedRows.add(row);
+                    lastRendered++;
+                }
+                fixSpacers();
+            } else if (firstIndex + rows == firstRendered) {
+                final VScrollTableRow[] rowArray = new VScrollTableRow[rows];
+                int i = rows;
+                while (it.hasNext()) {
+                    i--;
+                    rowArray[i] = prepareRow((UIDL) it.next());
+                }
+                for (i = 0; i < rows; i++) {
+                    addRowBeforeFirstRendered(rowArray[i]);
+                    insertedRows.add(rowArray[i]);
+                    firstRendered--;
+                }
+            } else {
+                // insert in the middle
+                int ix = firstIndex;
+                while (it.hasNext()) {
+                    VScrollTableRow row = prepareRow((UIDL) it.next());
+                    insertRowAt(row, ix);
+                    insertedRows.add(row);
+                    lastRendered++;
+                    ix++;
+                }
+                fixSpacers();
+            }
+            return insertedRows;
+        }
+
+        protected List<VScrollTableRow> insertAndReindexRows(UIDL rowData,
+                int firstIndex, int rows) {
+            List<VScrollTableRow> inserted = insertRows(rowData, firstIndex,
+                    rows);
+            int actualIxOfFirstRowAfterInserted = firstIndex + rows
+                    - firstRendered;
+            for (int ix = actualIxOfFirstRowAfterInserted; ix < renderedRows
+                    .size(); ix++) {
+                VScrollTableRow r = (VScrollTableRow) renderedRows.get(ix);
+                r.setIndex(r.getIndex() + rows);
+            }
+            setContainerHeight();
+            return inserted;
+        }
+
+        protected void insertRowsDeleteBelow(UIDL rowData, int firstIndex,
+                int rows) {
+            unlinkAllRowsStartingAt(firstIndex);
+            insertRows(rowData, firstIndex, rows);
+            setContainerHeight();
+        }
+
+        /**
+         * This method is used to instantiate new rows for this table. It
+         * automatically sets correct widths to rows cells and assigns correct
+         * client reference for child widgets.
+         * 
+         * This method can be called only after table has been initialized
+         * 
+         * @param uidl
+         */
+        private VScrollTableRow prepareRow(UIDL uidl) {
+            final VScrollTableRow row = createRow(uidl, aligns);
+            row.initCellWidths();
+            return row;
+        }
+
+        protected VScrollTableRow createRow(UIDL uidl, char[] aligns2) {
+            if (uidl.hasAttribute("gen_html")) {
+                // This is a generated row.
+                return new VScrollTableGeneratedRow(uidl, aligns2);
+            }
+            return new VScrollTableRow(uidl, aligns2);
+        }
+
+        private void addRowBeforeFirstRendered(VScrollTableRow row) {
+            row.setIndex(firstRendered - 1);
+            if (row.isSelected()) {
+                row.addStyleName("v-selected");
+            }
+            tBodyElement.insertBefore(row.getElement(),
+                    tBodyElement.getFirstChild());
+            adopt(row);
+            renderedRows.add(0, row);
+        }
+
+        private void addRow(VScrollTableRow row) {
+            row.setIndex(firstRendered + renderedRows.size());
+            if (row.isSelected()) {
+                row.addStyleName("v-selected");
+            }
+            tBodyElement.appendChild(row.getElement());
+            // Add to renderedRows before adopt so iterator() will return also
+            // this row if called in an attach handler (#9264)
+            renderedRows.add(row);
+            adopt(row);
+        }
+
+        private void insertRowAt(VScrollTableRow row, int index) {
+            row.setIndex(index);
+            if (row.isSelected()) {
+                row.addStyleName("v-selected");
+            }
+            if (index > 0) {
+                VScrollTableRow sibling = getRowByRowIndex(index - 1);
+                tBodyElement
+                        .insertAfter(row.getElement(), sibling.getElement());
+            } else {
+                VScrollTableRow sibling = getRowByRowIndex(index);
+                tBodyElement.insertBefore(row.getElement(),
+                        sibling.getElement());
+            }
+            adopt(row);
+            int actualIx = index - firstRendered;
+            renderedRows.add(actualIx, row);
+        }
+
+        @Override
+        public Iterator<Widget> iterator() {
+            return renderedRows.iterator();
+        }
+
+        /**
+         * @return false if couldn't remove row
+         */
+        protected boolean unlinkRow(boolean fromBeginning) {
+            if (lastRendered - firstRendered < 0) {
+                return false;
+            }
+            int actualIx;
+            if (fromBeginning) {
+                actualIx = 0;
+                firstRendered++;
+            } else {
+                actualIx = renderedRows.size() - 1;
+                lastRendered--;
+            }
+            if (actualIx >= 0) {
+                unlinkRowAtActualIndex(actualIx);
+                fixSpacers();
+                return true;
+            }
+            return false;
+        }
+
+        protected void unlinkRows(int firstIndex, int count) {
+            if (count < 1) {
+                return;
+            }
+            if (firstRendered > firstIndex
+                    && firstRendered < firstIndex + count) {
+                firstIndex = firstRendered;
+            }
+            int lastIndex = firstIndex + count - 1;
+            if (lastRendered < lastIndex) {
+                lastIndex = lastRendered;
+            }
+            for (int ix = lastIndex; ix >= firstIndex; ix--) {
+                unlinkRowAtActualIndex(actualIndex(ix));
+                lastRendered--;
+            }
+            fixSpacers();
+        }
+
+        protected void unlinkAndReindexRows(int firstIndex, int count) {
+            unlinkRows(firstIndex, count);
+            int actualFirstIx = firstIndex - firstRendered;
+            for (int ix = actualFirstIx; ix < renderedRows.size(); ix++) {
+                VScrollTableRow r = (VScrollTableRow) renderedRows.get(ix);
+                r.setIndex(r.getIndex() - count);
+            }
+            setContainerHeight();
+        }
+
+        protected void unlinkAllRowsStartingAt(int index) {
+            if (firstRendered > index) {
+                index = firstRendered;
+            }
+            for (int ix = renderedRows.size() - 1; ix >= index; ix--) {
+                unlinkRowAtActualIndex(actualIndex(ix));
+                lastRendered--;
+            }
+            fixSpacers();
+        }
+
+        private int actualIndex(int index) {
+            return index - firstRendered;
+        }
+
+        private void unlinkRowAtActualIndex(int index) {
+            final VScrollTableRow toBeRemoved = (VScrollTableRow) renderedRows
+                    .get(index);
+            tBodyElement.removeChild(toBeRemoved.getElement());
+            orphan(toBeRemoved);
+            renderedRows.remove(index);
+        }
+
+        @Override
+        public boolean remove(Widget w) {
+            throw new UnsupportedOperationException();
+        }
+
+        /**
+         * Fix container blocks height according to totalRows to avoid
+         * "bouncing" when scrolling
+         */
+        private void setContainerHeight() {
+            fixSpacers();
+            DOM.setStyleAttribute(container, "height",
+                    measureRowHeightOffset(totalRows) + "px");
+        }
+
+        private void fixSpacers() {
+            int prepx = measureRowHeightOffset(firstRendered);
+            if (prepx < 0) {
+                prepx = 0;
+            }
+            preSpacer.getStyle().setPropertyPx("height", prepx);
+            int postpx = measureRowHeightOffset(totalRows - 1)
+                    - measureRowHeightOffset(lastRendered);
+            if (postpx < 0) {
+                postpx = 0;
+            }
+            postSpacer.getStyle().setPropertyPx("height", postpx);
+        }
+
+        public double getRowHeight() {
+            return getRowHeight(false);
+        }
+
+        public double getRowHeight(boolean forceUpdate) {
+            if (tBodyMeasurementsDone && !forceUpdate) {
+                return rowHeight;
+            } else {
+                if (tBodyElement.getRows().getLength() > 0) {
+                    int tableHeight = getTableHeight();
+                    int rowCount = tBodyElement.getRows().getLength();
+                    rowHeight = tableHeight / (double) rowCount;
+                } else {
+                    // Special cases if we can't just measure the current rows
+                    if (!Double.isNaN(lastKnownRowHeight)) {
+                        // Use previous value if available
+                        if (BrowserInfo.get().isIE()) {
+                            /*
+                             * IE needs to reflow the table element at this
+                             * point to work correctly (e.g.
+                             * com.vaadin.tests.components.table.
+                             * ContainerSizeChange) - the other code paths
+                             * already trigger reflows, but here it must be done
+                             * explicitly.
+                             */
+                            getTableHeight();
+                        }
+                        rowHeight = lastKnownRowHeight;
+                    } else if (isAttached()) {
+                        // measure row height by adding a dummy row
+                        VScrollTableRow scrollTableRow = new VScrollTableRow();
+                        tBodyElement.appendChild(scrollTableRow.getElement());
+                        getRowHeight(forceUpdate);
+                        tBodyElement.removeChild(scrollTableRow.getElement());
+                    } else {
+                        // TODO investigate if this can never happen anymore
+                        return DEFAULT_ROW_HEIGHT;
+                    }
+                }
+                lastKnownRowHeight = rowHeight;
+                tBodyMeasurementsDone = true;
+                return rowHeight;
+            }
+        }
+
+        public int getTableHeight() {
+            return table.getOffsetHeight();
+        }
+
+        /**
+         * Returns the width available for column content.
+         * 
+         * @param columnIndex
+         * @return
+         */
+        public int getColWidth(int columnIndex) {
+            if (tBodyMeasurementsDone) {
+                if (renderedRows.isEmpty()) {
+                    // no rows yet rendered
+                    return 0;
+                }
+                for (Widget row : renderedRows) {
+                    if (!(row instanceof VScrollTableGeneratedRow)) {
+                        TableRowElement tr = row.getElement().cast();
+                        Element wrapperdiv = tr.getCells().getItem(columnIndex)
+                                .getFirstChildElement().cast();
+                        return wrapperdiv.getOffsetWidth();
+                    }
+                }
+                return 0;
+            } else {
+                return 0;
+            }
+        }
+
+        /**
+         * Sets the content width of a column.
+         * 
+         * Due IE limitation, we must set the width to a wrapper elements inside
+         * table cells (with overflow hidden, which does not work on td
+         * elements).
+         * 
+         * To get this work properly crossplatform, we will also set the width
+         * of td.
+         * 
+         * @param colIndex
+         * @param w
+         */
+        public void setColWidth(int colIndex, int w) {
+            for (Widget row : renderedRows) {
+                ((VScrollTableRow) row).setCellWidth(colIndex, w);
+            }
+        }
+
+        private int cellExtraWidth = -1;
+
+        /**
+         * Method to return the space used for cell paddings + border.
+         */
+        private int getCellExtraWidth() {
+            if (cellExtraWidth < 0) {
+                detectExtrawidth();
+            }
+            return cellExtraWidth;
+        }
+
+        private void detectExtrawidth() {
+            NodeList<TableRowElement> rows = tBodyElement.getRows();
+            if (rows.getLength() == 0) {
+                /* need to temporary add empty row and detect */
+                VScrollTableRow scrollTableRow = new VScrollTableRow();
+                scrollTableRow.updateStyleNames(VScrollTable.this
+                        .getStylePrimaryName());
+                tBodyElement.appendChild(scrollTableRow.getElement());
+                detectExtrawidth();
+                tBodyElement.removeChild(scrollTableRow.getElement());
+            } else {
+                boolean noCells = false;
+                TableRowElement item = rows.getItem(0);
+                TableCellElement firstTD = item.getCells().getItem(0);
+                if (firstTD == null) {
+                    // content is currently empty, we need to add a fake cell
+                    // for measuring
+                    noCells = true;
+                    VScrollTableRow next = (VScrollTableRow) iterator().next();
+                    boolean sorted = tHead.getHeaderCell(0) != null ? tHead
+                            .getHeaderCell(0).isSorted() : false;
+                    next.addCell(null, "", ALIGN_LEFT, "", true, sorted);
+                    firstTD = item.getCells().getItem(0);
+                }
+                com.google.gwt.dom.client.Element wrapper = firstTD
+                        .getFirstChildElement();
+                cellExtraWidth = firstTD.getOffsetWidth()
+                        - wrapper.getOffsetWidth();
+                if (noCells) {
+                    firstTD.getParentElement().removeChild(firstTD);
+                }
+            }
+        }
+
+        private void reLayoutComponents() {
+            for (Widget w : this) {
+                VScrollTableRow r = (VScrollTableRow) w;
+                for (Widget widget : r) {
+                    client.handleComponentRelativeSize(widget);
+                }
+            }
+        }
+
+        public int getLastRendered() {
+            return lastRendered;
+        }
+
+        public int getFirstRendered() {
+            return firstRendered;
+        }
+
+        public void moveCol(int oldIndex, int newIndex) {
+
+            // loop all rows and move given index to its new place
+            final Iterator<?> rows = iterator();
+            while (rows.hasNext()) {
+                final VScrollTableRow row = (VScrollTableRow) rows.next();
+
+                final Element td = DOM.getChild(row.getElement(), oldIndex);
+                if (td != null) {
+                    DOM.removeChild(row.getElement(), td);
+
+                    DOM.insertChild(row.getElement(), td, newIndex);
+                }
+            }
+
+        }
+
+        /**
+         * Restore row visibility which is set to "none" when the row is
+         * rendered (due a performance optimization).
+         */
+        private void restoreRowVisibility() {
+            for (Widget row : renderedRows) {
+                row.getElement().getStyle().setProperty("visibility", "");
+            }
+        }
+
+        public class VScrollTableRow extends Panel implements ActionOwner {
+
+            private static final int TOUCHSCROLL_TIMEOUT = 100;
+            private static final int DRAGMODE_MULTIROW = 2;
+            protected ArrayList<Widget> childWidgets = new ArrayList<Widget>();
+            private boolean selected = false;
+            protected final int rowKey;
+
+            private String[] actionKeys = null;
+            private final TableRowElement rowElement;
+            private int index;
+            private Event touchStart;
+
+            private static final int TOUCH_CONTEXT_MENU_TIMEOUT = 500;
+            private Timer contextTouchTimeout;
+            private Timer dragTouchTimeout;
+            private int touchStartY;
+            private int touchStartX;
+            private TooltipInfo tooltipInfo = null;
+            private Map<TableCellElement, TooltipInfo> cellToolTips = new HashMap<TableCellElement, TooltipInfo>();
+            private boolean isDragging = false;
+            private String rowStyle = null;
+
+            private VScrollTableRow(int rowKey) {
+                this.rowKey = rowKey;
+                rowElement = Document.get().createTRElement();
+                setElement(rowElement);
+                DOM.sinkEvents(getElement(), Event.MOUSEEVENTS
+                        | Event.TOUCHEVENTS | Event.ONDBLCLICK
+                        | Event.ONCONTEXTMENU | VTooltip.TOOLTIP_EVENTS);
+            }
+
+            public VScrollTableRow(UIDL uidl, char[] aligns) {
+                this(uidl.getIntAttribute("key"));
+
+                /*
+                 * Rendering the rows as hidden improves Firefox and Safari
+                 * performance drastically.
+                 */
+                getElement().getStyle().setProperty("visibility", "hidden");
+
+                rowStyle = uidl.getStringAttribute("rowstyle");
+                updateStyleNames(VScrollTable.this.getStylePrimaryName());
+
+                String rowDescription = uidl.getStringAttribute("rowdescr");
+                if (rowDescription != null && !rowDescription.equals("")) {
+                    tooltipInfo = new TooltipInfo(rowDescription);
+                } else {
+                    tooltipInfo = null;
+                }
+
+                tHead.getColumnAlignments();
+                int col = 0;
+                int visibleColumnIndex = -1;
+
+                // row header
+                if (showRowHeaders) {
+                    boolean sorted = tHead.getHeaderCell(col).isSorted();
+                    addCell(uidl, buildCaptionHtmlSnippet(uidl), aligns[col++],
+                            "rowheader", true, sorted);
+                    visibleColumnIndex++;
+                }
+
+                if (uidl.hasAttribute("al")) {
+                    actionKeys = uidl.getStringArrayAttribute("al");
+                }
+
+                addCellsFromUIDL(uidl, aligns, col, visibleColumnIndex);
+
+                if (uidl.hasAttribute("selected") && !isSelected()) {
+                    toggleSelection();
+                }
+            }
+
+            protected void updateStyleNames(String primaryStyleName) {
+
+                if (getStylePrimaryName().contains("odd")) {
+                    setStyleName(primaryStyleName + "-row-odd");
+                } else {
+                    setStyleName(primaryStyleName + "-row");
+                }
+
+                if (rowStyle != null) {
+                    addStyleName(primaryStyleName + "-row-" + rowStyle);
+                }
+
+                for (int i = 0; i < rowElement.getChildCount(); i++) {
+                    TableCellElement cell = (TableCellElement) rowElement
+                            .getChild(i);
+                    updateCellStyleNames(cell, primaryStyleName);
+                }
+            }
+
+            public TooltipInfo getTooltipInfo() {
+                return tooltipInfo;
+            }
+
+            /**
+             * Add a dummy row, used for measurements if Table is empty.
+             */
+            public VScrollTableRow() {
+                this(0);
+                addCell(null, "_", 'b', "", true, false);
+            }
+
+            protected void initCellWidths() {
+                final int cells = tHead.getVisibleCellCount();
+                for (int i = 0; i < cells; i++) {
+                    int w = VScrollTable.this.getColWidth(getColKeyByIndex(i));
+                    if (w < 0) {
+                        w = 0;
+                    }
+                    setCellWidth(i, w);
+                }
+            }
+
+            protected void setCellWidth(int cellIx, int width) {
+                final Element cell = DOM.getChild(getElement(), cellIx);
+                cell.getFirstChildElement().getStyle()
+                        .setPropertyPx("width", width);
+                cell.getStyle().setPropertyPx("width", width);
+            }
+
+            protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col,
+                    int visibleColumnIndex) {
+                final Iterator<?> cells = uidl.getChildIterator();
+                while (cells.hasNext()) {
+                    final Object cell = cells.next();
+                    visibleColumnIndex++;
+
+                    String columnId = visibleColOrder[visibleColumnIndex];
+
+                    String style = "";
+                    if (uidl.hasAttribute("style-" + columnId)) {
+                        style = uidl.getStringAttribute("style-" + columnId);
+                    }
+
+                    String description = null;
+                    if (uidl.hasAttribute("descr-" + columnId)) {
+                        description = uidl.getStringAttribute("descr-"
+                                + columnId);
+                    }
+
+                    boolean sorted = tHead.getHeaderCell(col).isSorted();
+                    if (cell instanceof String) {
+                        addCell(uidl, cell.toString(), aligns[col++], style,
+                                isRenderHtmlInCells(), sorted, description);
+                    } else {
+                        final ComponentConnector cellContent = client
+                                .getPaintable((UIDL) cell);
+
+                        addCell(uidl, cellContent.getWidget(), aligns[col++],
+                                style, sorted);
+                    }
+                }
+            }
+
+            /**
+             * Overriding this and returning true causes all text cells to be
+             * rendered as HTML.
+             * 
+             * @return always returns false in the default implementation
+             */
+            protected boolean isRenderHtmlInCells() {
+                return false;
+            }
+
+            /**
+             * Detects whether row is visible in tables viewport.
+             * 
+             * @return
+             */
+            public boolean isInViewPort() {
+                int absoluteTop = getAbsoluteTop();
+                int scrollPosition = scrollBodyPanel.getScrollPosition();
+                if (absoluteTop < scrollPosition) {
+                    return false;
+                }
+                int maxVisible = scrollPosition
+                        + scrollBodyPanel.getOffsetHeight() - getOffsetHeight();
+                if (absoluteTop > maxVisible) {
+                    return false;
+                }
+                return true;
+            }
+
+            /**
+             * Makes a check based on indexes whether the row is before the
+             * compared row.
+             * 
+             * @param row1
+             * @return true if this rows index is smaller than in the row1
+             */
+            public boolean isBefore(VScrollTableRow row1) {
+                return getIndex() < row1.getIndex();
+            }
+
+            /**
+             * Sets the index of the row in the whole table. Currently used just
+             * to set even/odd classname
+             * 
+             * @param indexInWholeTable
+             */
+            private void setIndex(int indexInWholeTable) {
+                index = indexInWholeTable;
+                boolean isOdd = indexInWholeTable % 2 == 0;
+                // Inverted logic to be backwards compatible with earlier 6.4.
+                // It is very strange because rows 1,3,5 are considered "even"
+                // and 2,4,6 "odd".
+                //
+                // First remove any old styles so that both styles aren't
+                // applied when indexes are updated.
+                String primaryStyleName = getStylePrimaryName();
+                if (primaryStyleName != null && !primaryStyleName.equals("")) {
+                    removeStyleName(getStylePrimaryName());
+                }
+                if (!isOdd) {
+                    addStyleName(VScrollTable.this.getStylePrimaryName()
+                            + "-row-odd");
+                } else {
+                    addStyleName(VScrollTable.this.getStylePrimaryName()
+                            + "-row");
+                }
+            }
+
+            public int getIndex() {
+                return index;
+            }
+
+            @Override
+            protected void onDetach() {
+                super.onDetach();
+                client.getContextMenu().ensureHidden(this);
+            }
+
+            public String getKey() {
+                return String.valueOf(rowKey);
+            }
+
+            public void addCell(UIDL rowUidl, String text, char align,
+                    String style, boolean textIsHTML, boolean sorted) {
+                addCell(rowUidl, text, align, style, textIsHTML, sorted, null);
+            }
+
+            public void addCell(UIDL rowUidl, String text, char align,
+                    String style, boolean textIsHTML, boolean sorted,
+                    String description) {
+                // String only content is optimized by not using Label widget
+                final TableCellElement td = DOM.createTD().cast();
+                initCellWithText(text, align, style, textIsHTML, sorted,
+                        description, td);
+            }
+
+            protected void initCellWithText(String text, char align,
+                    String style, boolean textIsHTML, boolean sorted,
+                    String description, final TableCellElement td) {
+                final Element container = DOM.createDiv();
+                container.setClassName(VScrollTable.this.getStylePrimaryName()
+                        + "-cell-wrapper");
+
+                td.setClassName(VScrollTable.this.getStylePrimaryName()
+                        + "-cell-content");
+
+                if (style != null && !style.equals("")) {
+                    td.addClassName(VScrollTable.this.getStylePrimaryName()
+                            + "-cell-content-" + style);
+                }
+
+                if (sorted) {
+                    td.addClassName(VScrollTable.this.getStylePrimaryName()
+                            + "-cell-content-sorted");
+                }
+
+                if (textIsHTML) {
+                    container.setInnerHTML(text);
+                } else {
+                    container.setInnerText(text);
+                }
+                if (align != ALIGN_LEFT) {
+                    switch (align) {
+                    case ALIGN_CENTER:
+                        container.getStyle().setProperty("textAlign", "center");
+                        break;
+                    case ALIGN_RIGHT:
+                    default:
+                        container.getStyle().setProperty("textAlign", "right");
+                        break;
+                    }
+                }
+
+                if (description != null && !description.equals("")) {
+                    TooltipInfo info = new TooltipInfo(description);
+                    cellToolTips.put(td, info);
+                } else {
+                    cellToolTips.remove(td);
+                }
+
+                td.appendChild(container);
+                getElement().appendChild(td);
+            }
+
+            protected void updateCellStyleNames(TableCellElement td,
+                    String primaryStyleName) {
+                Element container = td.getFirstChild().cast();
+                container.setClassName(primaryStyleName + "-cell-wrapper");
+
+                /*
+                 * Replace old primary style name with new one
+                 */
+                String className = td.getClassName();
+                String oldPrimaryName = className.split("-cell-content")[0];
+                td.setClassName(className.replaceAll(oldPrimaryName,
+                        primaryStyleName));
+            }
+
+            public void addCell(UIDL rowUidl, Widget w, char align,
+                    String style, boolean sorted) {
+                final TableCellElement td = DOM.createTD().cast();
+                initCellWithWidget(w, align, style, sorted, td);
+            }
+
+            protected void initCellWithWidget(Widget w, char align,
+                    String style, boolean sorted, final TableCellElement td) {
+                final Element container = DOM.createDiv();
+                String className = VScrollTable.this.getStylePrimaryName()
+                        + "-cell-content";
+                if (style != null && !style.equals("")) {
+                    className += " " + VScrollTable.this.getStylePrimaryName()
+                            + "-cell-content-" + style;
+                }
+                if (sorted) {
+                    className += " " + VScrollTable.this.getStylePrimaryName()
+                            + "-cell-content-sorted";
+                }
+                td.setClassName(className);
+                container.setClassName(VScrollTable.this.getStylePrimaryName()
+                        + "-cell-wrapper");
+                // TODO most components work with this, but not all (e.g.
+                // Select)
+                // Old comment: make widget cells respect align.
+                // text-align:center for IE, margin: auto for others
+                if (align != ALIGN_LEFT) {
+                    switch (align) {
+                    case ALIGN_CENTER:
+                        container.getStyle().setProperty("textAlign", "center");
+                        break;
+                    case ALIGN_RIGHT:
+                    default:
+                        container.getStyle().setProperty("textAlign", "right");
+                        break;
+                    }
+                }
+                td.appendChild(container);
+                getElement().appendChild(td);
+                // ensure widget not attached to another element (possible tBody
+                // change)
+                w.removeFromParent();
+                container.appendChild(w.getElement());
+                adopt(w);
+                childWidgets.add(w);
+            }
+
+            @Override
+            public Iterator<Widget> iterator() {
+                return childWidgets.iterator();
+            }
+
+            @Override
+            public boolean remove(Widget w) {
+                if (childWidgets.contains(w)) {
+                    orphan(w);
+                    DOM.removeChild(DOM.getParent(w.getElement()),
+                            w.getElement());
+                    childWidgets.remove(w);
+                    return true;
+                } else {
+                    return false;
+                }
+            }
+
+            /**
+             * If there are registered click listeners, sends a click event and
+             * returns true. Otherwise, does nothing and returns false.
+             * 
+             * @param event
+             * @param targetTdOrTr
+             * @param immediate
+             *            Whether the event is sent immediately
+             * @return Whether a click event was sent
+             */
+            private boolean handleClickEvent(Event event, Element targetTdOrTr,
+                    boolean immediate) {
+                if (!client.hasEventListeners(VScrollTable.this,
+                        TableConstants.ITEM_CLICK_EVENT_ID)) {
+                    // Don't send an event if nobody is listening
+                    return false;
+                }
+
+                // This row was clicked
+                client.updateVariable(paintableId, "clickedKey", "" + rowKey,
+                        false);
+
+                if (getElement() == targetTdOrTr.getParentElement()) {
+                    // A specific column was clicked
+                    int childIndex = DOM.getChildIndex(getElement(),
+                            targetTdOrTr);
+                    String colKey = null;
+                    colKey = tHead.getHeaderCell(childIndex).getColKey();
+                    client.updateVariable(paintableId, "clickedColKey", colKey,
+                            false);
+                }
+
+                MouseEventDetails details = MouseEventDetailsBuilder
+                        .buildMouseEventDetails(event);
+
+                client.updateVariable(paintableId, "clickEvent",
+                        details.toString(), immediate);
+
+                return true;
+            }
+
+            public TooltipInfo getTooltip(
+                    com.google.gwt.dom.client.Element target) {
+
+                TooltipInfo info = null;
+
+                if (target.hasTagName("TD")) {
+
+                    TableCellElement td = (TableCellElement) target.cast();
+                    info = cellToolTips.get(td);
+                }
+
+                if (info == null) {
+                    info = tooltipInfo;
+                }
+
+                return info;
+            }
+
+            /**
+             * Special handler for touch devices that support native scrolling
+             * 
+             * @return Whether the event was handled by this method.
+             */
+            private boolean handleTouchEvent(final Event event) {
+
+                boolean touchEventHandled = false;
+
+                if (enabled && hasNativeTouchScrolling) {
+                    final Element targetTdOrTr = getEventTargetTdOrTr(event);
+                    final int type = event.getTypeInt();
+
+                    switch (type) {
+                    case Event.ONTOUCHSTART:
+                        touchEventHandled = true;
+                        touchStart = event;
+                        isDragging = false;
+                        Touch touch = event.getChangedTouches().get(0);
+                        // save position to fields, touches in events are same
+                        // instance during the operation.
+                        touchStartX = touch.getClientX();
+                        touchStartY = touch.getClientY();
+
+                        if (dragmode != 0) {
+                            if (dragTouchTimeout == null) {
+                                dragTouchTimeout = new Timer() {
+
+                                    @Override
+                                    public void run() {
+                                        if (touchStart != null) {
+                                            // Start a drag if a finger is held
+                                            // in place long enough, then moved
+                                            isDragging = true;
+                                        }
+                                    }
+                                };
+                            }
+                            dragTouchTimeout.schedule(TOUCHSCROLL_TIMEOUT);
+                        }
+
+                        if (actionKeys != null) {
+                            if (contextTouchTimeout == null) {
+                                contextTouchTimeout = new Timer() {
+
+                                    @Override
+                                    public void run() {
+                                        if (touchStart != null) {
+                                            // Open the context menu if finger
+                                            // is held in place long enough.
+                                            showContextMenu(touchStart);
+                                            event.preventDefault();
+                                            touchStart = null;
+                                        }
+                                    }
+                                };
+                            }
+                            contextTouchTimeout
+                                    .schedule(TOUCH_CONTEXT_MENU_TIMEOUT);
+                        }
+                        break;
+                    case Event.ONTOUCHMOVE:
+                        touchEventHandled = true;
+                        if (isSignificantMove(event)) {
+                            if (contextTouchTimeout != null) {
+                                // Moved finger before the context menu timer
+                                // expired, so let the browser handle this as a
+                                // scroll.
+                                contextTouchTimeout.cancel();
+                                contextTouchTimeout = null;
+                            }
+                            if (!isDragging && dragTouchTimeout != null) {
+                                // Moved finger before the drag timer expired,
+                                // so let the browser handle this as a scroll.
+                                dragTouchTimeout.cancel();
+                                dragTouchTimeout = null;
+                            }
+
+                            if (dragmode != 0 && touchStart != null
+                                    && isDragging) {
+                                event.preventDefault();
+                                event.stopPropagation();
+                                startRowDrag(touchStart, type, targetTdOrTr);
+                            }
+                            touchStart = null;
+                        }
+                        break;
+                    case Event.ONTOUCHEND:
+                    case Event.ONTOUCHCANCEL:
+                        touchEventHandled = true;
+                        if (contextTouchTimeout != null) {
+                            contextTouchTimeout.cancel();
+                        }
+                        if (dragTouchTimeout != null) {
+                            dragTouchTimeout.cancel();
+                        }
+                        if (touchStart != null) {
+                            event.preventDefault();
+                            event.stopPropagation();
+                            if (!BrowserInfo.get().isAndroid()) {
+                                Util.simulateClickFromTouchEvent(touchStart,
+                                        this);
+                            }
+                            touchStart = null;
+                        }
+                        isDragging = false;
+                        break;
+                    }
+                }
+                return touchEventHandled;
+            }
+
+            /*
+             * React on click that occur on content cells only
+             */
+
+            @Override
+            public void onBrowserEvent(final Event event) {
+
+                final boolean touchEventHandled = handleTouchEvent(event);
+
+                if (enabled && !touchEventHandled) {
+                    final int type = event.getTypeInt();
+                    final Element targetTdOrTr = getEventTargetTdOrTr(event);
+                    if (type == Event.ONCONTEXTMENU) {
+                        showContextMenu(event);
+                        if (enabled
+                                && (actionKeys != null || client
+                                        .hasEventListeners(
+                                                VScrollTable.this,
+                                                TableConstants.ITEM_CLICK_EVENT_ID))) {
+                            /*
+                             * Prevent browser context menu only if there are
+                             * action handlers or item click listeners
+                             * registered
+                             */
+                            event.stopPropagation();
+                            event.preventDefault();
+                        }
+                        return;
+                    }
+
+                    boolean targetCellOrRowFound = targetTdOrTr != null;
+
+                    switch (type) {
+                    case Event.ONDBLCLICK:
+                        if (targetCellOrRowFound) {
+                            handleClickEvent(event, targetTdOrTr, true);
+                        }
+                        break;
+                    case Event.ONMOUSEUP:
+                        if (targetCellOrRowFound) {
+                            /*
+                             * Queue here, send at the same time as the
+                             * corresponding value change event - see #7127
+                             */
+                            boolean clickEventSent = handleClickEvent(event,
+                                    targetTdOrTr, false);
+
+                            if (event.getButton() == Event.BUTTON_LEFT
+                                    && isSelectable()) {
+
+                                // Ctrl+Shift click
+                                if ((event.getCtrlKey() || event.getMetaKey())
+                                        && event.getShiftKey()
+                                        && isMultiSelectModeDefault()) {
+                                    toggleShiftSelection(false);
+                                    setRowFocus(this);
+
+                                    // Ctrl click
+                                } else if ((event.getCtrlKey() || event
+                                        .getMetaKey())
+                                        && isMultiSelectModeDefault()) {
+                                    boolean wasSelected = isSelected();
+                                    toggleSelection();
+                                    setRowFocus(this);
+                                    /*
+                                     * next possible range select must start on
+                                     * this row
+                                     */
+                                    selectionRangeStart = this;
+                                    if (wasSelected) {
+                                        removeRowFromUnsentSelectionRanges(this);
+                                    }
+
+                                } else if ((event.getCtrlKey() || event
+                                        .getMetaKey()) && isSingleSelectMode()) {
+                                    // Ctrl (or meta) click (Single selection)
+                                    if (!isSelected()
+                                            || (isSelected() && nullSelectionAllowed)) {
+
+                                        if (!isSelected()) {
+                                            deselectAll();
+                                        }
+
+                                        toggleSelection();
+                                        setRowFocus(this);
+                                    }
+
+                                } else if (event.getShiftKey()
+                                        && isMultiSelectModeDefault()) {
+                                    // Shift click
+                                    toggleShiftSelection(true);
+
+                                } else {
+                                    // click
+                                    boolean currentlyJustThisRowSelected = selectedRowKeys
+                                            .size() == 1
+                                            && selectedRowKeys
+                                                    .contains(getKey());
+
+                                    if (!currentlyJustThisRowSelected) {
+                                        if (isSingleSelectMode()
+                                                || isMultiSelectModeDefault()) {
+                                            /*
+                                             * For default multi select mode
+                                             * (ctrl/shift) and for single
+                                             * select mode we need to clear the
+                                             * previous selection before
+                                             * selecting a new one when the user
+                                             * clicks on a row. Only in
+                                             * multiselect/simple mode the old
+                                             * selection should remain after a
+                                             * normal click.
+                                             */
+                                            deselectAll();
+                                        }
+                                        toggleSelection();
+                                    } else if ((isSingleSelectMode() || isMultiSelectModeSimple())
+                                            && nullSelectionAllowed) {
+                                        toggleSelection();
+                                    }/*
+                                      * else NOP to avoid excessive server
+                                      * visits (selection is removed with
+                                      * CTRL/META click)
+                                      */
+
+                                    selectionRangeStart = this;
+                                    setRowFocus(this);
+                                }
+
+                                // Remove IE text selection hack
+                                if (BrowserInfo.get().isIE()) {
+                                    ((Element) event.getEventTarget().cast())
+                                            .setPropertyJSO("onselectstart",
+                                                    null);
+                                }
+                                // Queue value change
+                                sendSelectedRows(false);
+                            }
+                            /*
+                             * Send queued click and value change events if any
+                             * If a click event is sent, send value change with
+                             * it regardless of the immediate flag, see #7127
+                             */
+                            if (immediate || clickEventSent) {
+                                client.sendPendingVariableChanges();
+                            }
+                        }
+                        break;
+                    case Event.ONTOUCHEND:
+                    case Event.ONTOUCHCANCEL:
+                        if (touchStart != null) {
+                            /*
+                             * Touch has not been handled as neither context or
+                             * drag start, handle it as a click.
+                             */
+                            Util.simulateClickFromTouchEvent(touchStart, this);
+                            touchStart = null;
+                        }
+                        if (contextTouchTimeout != null) {
+                            contextTouchTimeout.cancel();
+                        }
+                        break;
+                    case Event.ONTOUCHMOVE:
+                        if (isSignificantMove(event)) {
+                            /*
+                             * TODO figure out scroll delegate don't eat events
+                             * if row is selected. Null check for active
+                             * delegate is as a workaround.
+                             */
+                            if (dragmode != 0
+                                    && touchStart != null
+                                    && (TouchScrollDelegate
+                                            .getActiveScrollDelegate() == null)) {
+                                startRowDrag(touchStart, type, targetTdOrTr);
+                            }
+                            if (contextTouchTimeout != null) {
+                                contextTouchTimeout.cancel();
+                            }
+                            /*
+                             * Avoid clicks and drags by clearing touch start
+                             * flag.
+                             */
+                            touchStart = null;
+                        }
+
+                        break;
+                    case Event.ONTOUCHSTART:
+                        touchStart = event;
+                        Touch touch = event.getChangedTouches().get(0);
+                        // save position to fields, touches in events are same
+                        // isntance during the operation.
+                        touchStartX = touch.getClientX();
+                        touchStartY = touch.getClientY();
+                        /*
+                         * Prevent simulated mouse events.
+                         */
+                        touchStart.preventDefault();
+                        if (dragmode != 0 || actionKeys != null) {
+                            new Timer() {
+
+                                @Override
+                                public void run() {
+                                    TouchScrollDelegate activeScrollDelegate = TouchScrollDelegate
+                                            .getActiveScrollDelegate();
+                                    /*
+                                     * If there's a scroll delegate, check if
+                                     * we're actually scrolling and handle it.
+                                     * If no delegate, do nothing here and let
+                                     * the row handle potential drag'n'drop or
+                                     * context menu.
+                                     */
+                                    if (activeScrollDelegate != null) {
+                                        if (activeScrollDelegate.isMoved()) {
+                                            /*
+                                             * Prevent the row from handling
+                                             * touch move/end events (the
+                                             * delegate handles those) and from
+                                             * doing drag'n'drop or opening a
+                                             * context menu.
+                                             */
+                                            touchStart = null;
+                                        } else {
+                                            /*
+                                             * Scrolling hasn't started, so
+                                             * cancel delegate and let the row
+                                             * handle potential drag'n'drop or
+                                             * context menu.
+                                             */
+                                            activeScrollDelegate
+                                                    .stopScrolling();
+                                        }
+                                    }
+                                }
+                            }.schedule(TOUCHSCROLL_TIMEOUT);
+
+                            if (contextTouchTimeout == null
+                                    && actionKeys != null) {
+                                contextTouchTimeout = new Timer() {
+
+                                    @Override
+                                    public void run() {
+                                        if (touchStart != null) {
+                                            showContextMenu(touchStart);
+                                            touchStart = null;
+                                        }
+                                    }
+                                };
+                            }
+                            if (contextTouchTimeout != null) {
+                                contextTouchTimeout.cancel();
+                                contextTouchTimeout
+                                        .schedule(TOUCH_CONTEXT_MENU_TIMEOUT);
+                            }
+                        }
+                        break;
+                    case Event.ONMOUSEDOWN:
+                        if (targetCellOrRowFound) {
+                            setRowFocus(this);
+                            ensureFocus();
+                            if (dragmode != 0
+                                    && (event.getButton() == NativeEvent.BUTTON_LEFT)) {
+                                startRowDrag(event, type, targetTdOrTr);
+
+                            } else if (event.getCtrlKey()
+                                    || event.getShiftKey()
+                                    || event.getMetaKey()
+                                    && isMultiSelectModeDefault()) {
+
+                                // Prevent default text selection in Firefox
+                                event.preventDefault();
+
+                                // Prevent default text selection in IE
+                                if (BrowserInfo.get().isIE()) {
+                                    ((Element) event.getEventTarget().cast())
+                                            .setPropertyJSO(
+                                                    "onselectstart",
+                                                    getPreventTextSelectionIEHack());
+                                }
+
+                                event.stopPropagation();
+                            }
+                        }
+                        break;
+                    case Event.ONMOUSEOUT:
+                        break;
+                    default:
+                        break;
+                    }
+                }
+                super.onBrowserEvent(event);
+            }
+
+            private boolean isSignificantMove(Event event) {
+                if (touchStart == null) {
+                    // no touch start
+                    return false;
+                }
+                /*
+                 * TODO calculate based on real distance instead of separate
+                 * axis checks
+                 */
+                Touch touch = event.getChangedTouches().get(0);
+                if (Math.abs(touch.getClientX() - touchStartX) > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) {
+                    return true;
+                }
+                if (Math.abs(touch.getClientY() - touchStartY) > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) {
+                    return true;
+                }
+                return false;
+            }
+
+            /**
+             * Checks if the row represented by the row key has been selected
+             * 
+             * @param key
+             *            The generated row key
+             */
+            private boolean rowKeyIsSelected(int rowKey) {
+                // Check single selections
+                if (selectedRowKeys.contains("" + rowKey)) {
+                    return true;
+                }
+
+                // Check range selections
+                for (SelectionRange r : selectedRowRanges) {
+                    if (r.inRange(getRenderedRowByKey("" + rowKey))) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+
+            protected void startRowDrag(Event event, final int type,
+                    Element targetTdOrTr) {
+                VTransferable transferable = new VTransferable();
+                transferable.setDragSource(ConnectorMap.get(client)
+                        .getConnector(VScrollTable.this));
+                transferable.setData("itemId", "" + rowKey);
+                NodeList<TableCellElement> cells = rowElement.getCells();
+                for (int i = 0; i < cells.getLength(); i++) {
+                    if (cells.getItem(i).isOrHasChild(targetTdOrTr)) {
+                        HeaderCell headerCell = tHead.getHeaderCell(i);
+                        transferable.setData("propertyId", headerCell.cid);
+                        break;
+                    }
+                }
+
+                VDragEvent ev = VDragAndDropManager.get().startDrag(
+                        transferable, event, true);
+                if (dragmode == DRAGMODE_MULTIROW && isMultiSelectModeAny()
+                        && rowKeyIsSelected(rowKey)) {
+
+                    // Create a drag image of ALL rows
+                    ev.createDragImage(
+                            (Element) scrollBody.tBodyElement.cast(), true);
+
+                    // Hide rows which are not selected
+                    Element dragImage = ev.getDragImage();
+                    int i = 0;
+                    for (Iterator<Widget> iterator = scrollBody.iterator(); iterator
+                            .hasNext();) {
+                        VScrollTableRow next = (VScrollTableRow) iterator
+                                .next();
+
+                        Element child = (Element) dragImage.getChild(i++);
+
+                        if (!rowKeyIsSelected(next.rowKey)) {
+                            child.getStyle().setVisibility(Visibility.HIDDEN);
+                        }
+                    }
+                } else {
+                    ev.createDragImage(getElement(), true);
+                }
+                if (type == Event.ONMOUSEDOWN) {
+                    event.preventDefault();
+                }
+                event.stopPropagation();
+            }
+
+            /**
+             * Finds the TD that the event interacts with. Returns null if the
+             * target of the event should not be handled. If the event target is
+             * the row directly this method returns the TR element instead of
+             * the TD.
+             * 
+             * @param event
+             * @return TD or TR element that the event targets (the actual event
+             *         target is this element or a child of it)
+             */
+            private Element getEventTargetTdOrTr(Event event) {
+                final Element eventTarget = event.getEventTarget().cast();
+                Widget widget = Util.findWidget(eventTarget, null);
+                final Element thisTrElement = getElement();
+
+                if (widget != this) {
+                    /*
+                     * This is a workaround to make Labels, read only TextFields
+                     * and Embedded in a Table clickable (see #2688). It is
+                     * really not a fix as it does not work with a custom read
+                     * only components (not extending VLabel/VEmbedded).
+                     */
+                    while (widget != null && widget.getParent() != this) {
+                        widget = widget.getParent();
+                    }
+
+                    if (!(widget instanceof VLabel)
+                            && !(widget instanceof VEmbedded)
+                            && !(widget instanceof VTextField && ((VTextField) widget)
+                                    .isReadOnly())) {
+                        return null;
+                    }
+                }
+                if (eventTarget == thisTrElement) {
+                    // This was a click on the TR element
+                    return thisTrElement;
+                }
+
+                // Iterate upwards until we find the TR element
+                Element element = eventTarget;
+                while (element != null
+                        && element.getParentElement().cast() != thisTrElement) {
+                    element = element.getParentElement().cast();
+                }
+                return element;
+            }
+
+            public void showContextMenu(Event event) {
+                if (enabled && actionKeys != null) {
+                    // Show context menu if there are registered action handlers
+                    int left = Util.getTouchOrMouseClientX(event);
+                    int top = Util.getTouchOrMouseClientY(event);
+                    top += Window.getScrollTop();
+                    left += Window.getScrollLeft();
+                    contextMenu = new ContextMenuDetails(getKey(), left, top);
+                    client.getContextMenu().showAt(this, left, top);
+                }
+            }
+
+            /**
+             * Has the row been selected?
+             * 
+             * @return Returns true if selected, else false
+             */
+            public boolean isSelected() {
+                return selected;
+            }
+
+            /**
+             * Toggle the selection of the row
+             */
+            public void toggleSelection() {
+                selected = !selected;
+                selectionChanged = true;
+                if (selected) {
+                    selectedRowKeys.add(String.valueOf(rowKey));
+                    addStyleName("v-selected");
+                } else {
+                    removeStyleName("v-selected");
+                    selectedRowKeys.remove(String.valueOf(rowKey));
+                }
+            }
+
+            /**
+             * Is called when a user clicks an item when holding SHIFT key down.
+             * This will select a new range from the last focused row
+             * 
+             * @param deselectPrevious
+             *            Should the previous selected range be deselected
+             */
+            private void toggleShiftSelection(boolean deselectPrevious) {
+
+                /*
+                 * Ensures that we are in multiselect mode and that we have a
+                 * previous selection which was not a deselection
+                 */
+                if (isSingleSelectMode()) {
+                    // No previous selection found
+                    deselectAll();
+                    toggleSelection();
+                    return;
+                }
+
+                // Set the selectable range
+                VScrollTableRow endRow = this;
+                VScrollTableRow startRow = selectionRangeStart;
+                if (startRow == null) {
+                    startRow = focusedRow;
+                    // If start row is null then we have a multipage selection
+                    // from
+                    // above
+                    if (startRow == null) {
+                        startRow = (VScrollTableRow) scrollBody.iterator()
+                                .next();
+                        setRowFocus(endRow);
+                    }
+                }
+                // Deselect previous items if so desired
+                if (deselectPrevious) {
+                    deselectAll();
+                }
+
+                // we'll ensure GUI state from top down even though selection
+                // was the opposite way
+                if (!startRow.isBefore(endRow)) {
+                    VScrollTableRow tmp = startRow;
+                    startRow = endRow;
+                    endRow = tmp;
+                }
+                SelectionRange range = new SelectionRange(startRow, endRow);
+
+                for (Widget w : scrollBody) {
+                    VScrollTableRow row = (VScrollTableRow) w;
+                    if (range.inRange(row)) {
+                        if (!row.isSelected()) {
+                            row.toggleSelection();
+                        }
+                        selectedRowKeys.add(row.getKey());
+                    }
+                }
+
+                // Add range
+                if (startRow != endRow) {
+                    selectedRowRanges.add(range);
+                }
+            }
+
+            /*
+             * (non-Javadoc)
+             * 
+             * @see com.vaadin.client.ui.IActionOwner#getActions ()
+             */
+
+            @Override
+            public Action[] getActions() {
+                if (actionKeys == null) {
+                    return new Action[] {};
+                }
+                final Action[] actions = new Action[actionKeys.length];
+                for (int i = 0; i < actions.length; i++) {
+                    final String actionKey = actionKeys[i];
+                    final TreeAction a = new TreeAction(this,
+                            String.valueOf(rowKey), actionKey) {
+
+                        @Override
+                        public void execute() {
+                            super.execute();
+                            lazyRevertFocusToRow(VScrollTableRow.this);
+                        }
+                    };
+                    a.setCaption(getActionCaption(actionKey));
+                    a.setIconUrl(getActionIcon(actionKey));
+                    actions[i] = a;
+                }
+                return actions;
+            }
+
+            @Override
+            public ApplicationConnection getClient() {
+                return client;
+            }
+
+            @Override
+            public String getPaintableId() {
+                return paintableId;
+            }
+
+            private int getColIndexOf(Widget child) {
+                com.google.gwt.dom.client.Element widgetCell = child
+                        .getElement().getParentElement().getParentElement();
+                NodeList<TableCellElement> cells = rowElement.getCells();
+                for (int i = 0; i < cells.getLength(); i++) {
+                    if (cells.getItem(i) == widgetCell) {
+                        return i;
+                    }
+                }
+                return -1;
+            }
+
+            public Widget getWidgetForPaintable() {
+                return this;
+            }
+        }
+
+        protected class VScrollTableGeneratedRow extends VScrollTableRow {
+
+            private boolean spanColumns;
+            private boolean htmlContentAllowed;
+
+            public VScrollTableGeneratedRow(UIDL uidl, char[] aligns) {
+                super(uidl, aligns);
+                addStyleName("v-table-generated-row");
+            }
+
+            public boolean isSpanColumns() {
+                return spanColumns;
+            }
+
+            @Override
+            protected void initCellWidths() {
+                if (spanColumns) {
+                    setSpannedColumnWidthAfterDOMFullyInited();
+                } else {
+                    super.initCellWidths();
+                }
+            }
+
+            private void setSpannedColumnWidthAfterDOMFullyInited() {
+                // Defer setting width on spanned columns to make sure that
+                // they are added to the DOM before trying to calculate
+                // widths.
+                Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+
+                    @Override
+                    public void execute() {
+                        if (showRowHeaders) {
+                            setCellWidth(0, tHead.getHeaderCell(0).getWidth());
+                            calcAndSetSpanWidthOnCell(1);
+                        } else {
+                            calcAndSetSpanWidthOnCell(0);
+                        }
+                    }
+                });
+            }
+
+            @Override
+            protected boolean isRenderHtmlInCells() {
+                return htmlContentAllowed;
+            }
+
+            @Override
+            protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col,
+                    int visibleColumnIndex) {
+                htmlContentAllowed = uidl.getBooleanAttribute("gen_html");
+                spanColumns = uidl.getBooleanAttribute("gen_span");
+
+                final Iterator<?> cells = uidl.getChildIterator();
+                if (spanColumns) {
+                    int colCount = uidl.getChildCount();
+                    if (cells.hasNext()) {
+                        final Object cell = cells.next();
+                        if (cell instanceof String) {
+                            addSpannedCell(uidl, cell.toString(), aligns[0],
+                                    "", htmlContentAllowed, false, null,
+                                    colCount);
+                        } else {
+                            addSpannedCell(uidl, (Widget) cell, aligns[0], "",
+                                    false, colCount);
+                        }
+                    }
+                } else {
+                    super.addCellsFromUIDL(uidl, aligns, col,
+                            visibleColumnIndex);
+                }
+            }
+
+            private void addSpannedCell(UIDL rowUidl, Widget w, char align,
+                    String style, boolean sorted, int colCount) {
+                TableCellElement td = DOM.createTD().cast();
+                td.setColSpan(colCount);
+                initCellWithWidget(w, align, style, sorted, td);
+            }
+
+            private void addSpannedCell(UIDL rowUidl, String text, char align,
+                    String style, boolean textIsHTML, boolean sorted,
+                    String description, int colCount) {
+                // String only content is optimized by not using Label widget
+                final TableCellElement td = DOM.createTD().cast();
+                td.setColSpan(colCount);
+                initCellWithText(text, align, style, textIsHTML, sorted,
+                        description, td);
+            }
+
+            @Override
+            protected void setCellWidth(int cellIx, int width) {
+                if (isSpanColumns()) {
+                    if (showRowHeaders) {
+                        if (cellIx == 0) {
+                            super.setCellWidth(0, width);
+                        } else {
+                            // We need to recalculate the spanning TDs width for
+                            // every cellIx in order to support column resizing.
+                            calcAndSetSpanWidthOnCell(1);
+                        }
+                    } else {
+                        // Same as above.
+                        calcAndSetSpanWidthOnCell(0);
+                    }
+                } else {
+                    super.setCellWidth(cellIx, width);
+                }
+            }
+
+            private void calcAndSetSpanWidthOnCell(final int cellIx) {
+                int spanWidth = 0;
+                for (int ix = (showRowHeaders ? 1 : 0); ix < tHead
+                        .getVisibleCellCount(); ix++) {
+                    spanWidth += tHead.getHeaderCell(ix).getOffsetWidth();
+                }
+                Util.setWidthExcludingPaddingAndBorder((Element) getElement()
+                        .getChild(cellIx), spanWidth, 13, false);
+            }
+        }
+
+        /**
+         * Ensure the component has a focus.
+         * 
+         * TODO the current implementation simply always calls focus for the
+         * component. In case the Table at some point implements focus/blur
+         * listeners, this method needs to be evolved to conditionally call
+         * focus only if not currently focused.
+         */
+        protected void ensureFocus() {
+            if (!hasFocus) {
+                scrollBodyPanel.setFocus(true);
+            }
+
+        }
+
+    }
+
+    /**
+     * Deselects all items
+     */
+    public void deselectAll() {
+        for (Widget w : scrollBody) {
+            VScrollTableRow row = (VScrollTableRow) w;
+            if (row.isSelected()) {
+                row.toggleSelection();
+            }
+        }
+        // still ensure all selects are removed from (not necessary rendered)
+        selectedRowKeys.clear();
+        selectedRowRanges.clear();
+        // also notify server that it clears all previous selections (the client
+        // side does not know about the invisible ones)
+        instructServerToForgetPreviousSelections();
+    }
+
+    /**
+     * Used in multiselect mode when the client side knows that all selections
+     * are in the next request.
+     */
+    private void instructServerToForgetPreviousSelections() {
+        client.updateVariable(paintableId, "clearSelections", true, false);
+    }
+
+    /**
+     * Determines the pagelength when the table height is fixed.
+     */
+    public void updatePageLength() {
+        // Only update if visible and enabled
+        if (!isVisible() || !enabled) {
+            return;
+        }
+
+        if (scrollBody == null) {
+            return;
+        }
+
+        if (isDynamicHeight()) {
+            return;
+        }
+
+        int rowHeight = (int) Math.round(scrollBody.getRowHeight());
+        int bodyH = scrollBodyPanel.getOffsetHeight();
+        int rowsAtOnce = bodyH / rowHeight;
+        boolean anotherPartlyVisible = ((bodyH % rowHeight) != 0);
+        if (anotherPartlyVisible) {
+            rowsAtOnce++;
+        }
+        if (pageLength != rowsAtOnce) {
+            pageLength = rowsAtOnce;
+            client.updateVariable(paintableId, "pagelength", pageLength, false);
+
+            if (!rendering) {
+                int currentlyVisible = scrollBody.lastRendered
+                        - scrollBody.firstRendered;
+                if (currentlyVisible < pageLength
+                        && currentlyVisible < totalRows) {
+                    // shake scrollpanel to fill empty space
+                    scrollBodyPanel.setScrollPosition(scrollTop + 1);
+                    scrollBodyPanel.setScrollPosition(scrollTop - 1);
+                }
+
+                sizeNeedsInit = true;
+            }
+        }
+
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void updateWidth() {
+        if (!isVisible()) {
+            /*
+             * Do not update size when the table is hidden as all column widths
+             * will be set to zero and they won't be recalculated when the table
+             * is set visible again (until the size changes again)
+             */
+            return;
+        }
+
+        if (!isDynamicWidth()) {
+            int innerPixels = getOffsetWidth() - getBorderWidth();
+            if (innerPixels < 0) {
+                innerPixels = 0;
+            }
+            setContentWidth(innerPixels);
+
+            // readjust undefined width columns
+            triggerLazyColumnAdjustment(false);
+
+        } else {
+
+            sizeNeedsInit = true;
+
+            // readjust undefined width columns
+            triggerLazyColumnAdjustment(false);
+        }
+
+        /*
+         * setting width may affect wheter the component has scrollbars -> needs
+         * scrolling or not
+         */
+        setProperTabIndex();
+    }
+
+    private static final int LAZY_COLUMN_ADJUST_TIMEOUT = 300;
+
+    private final Timer lazyAdjustColumnWidths = new Timer() {
+        /**
+         * Check for column widths, and available width, to see if we can fix
+         * column widths "optimally". Doing this lazily to avoid expensive
+         * calculation when resizing is not yet finished.
+         */
+
+        @Override
+        public void run() {
+            if (scrollBody == null) {
+                // Try again later if we get here before scrollBody has been
+                // initalized
+                triggerLazyColumnAdjustment(false);
+                return;
+            }
+
+            Iterator<Widget> headCells = tHead.iterator();
+            int usedMinimumWidth = 0;
+            int totalExplicitColumnsWidths = 0;
+            float expandRatioDivider = 0;
+            int colIndex = 0;
+            while (headCells.hasNext()) {
+                final HeaderCell hCell = (HeaderCell) headCells.next();
+                if (hCell.isDefinedWidth()) {
+                    totalExplicitColumnsWidths += hCell.getWidth();
+                    usedMinimumWidth += hCell.getWidth();
+                } else {
+                    usedMinimumWidth += hCell.getNaturalColumnWidth(colIndex);
+                    expandRatioDivider += hCell.getExpandRatio();
+                }
+                colIndex++;
+            }
+
+            int availW = scrollBody.getAvailableWidth();
+            // Hey IE, are you really sure about this?
+            availW = scrollBody.getAvailableWidth();
+            int visibleCellCount = tHead.getVisibleCellCount();
+            int totalExtraWidth = scrollBody.getCellExtraWidth()
+                    * visibleCellCount;
+            if (willHaveScrollbars()) {
+                totalExtraWidth += Util.getNativeScrollbarSize();
+            }
+            availW -= totalExtraWidth;
+            int forceScrollBodyWidth = -1;
+
+            int extraSpace = availW - usedMinimumWidth;
+            if (extraSpace < 0) {
+                if (getTotalRows() == 0) {
+                    /*
+                     * Too wide header combined with no rows in the table.
+                     * 
+                     * No horizontal scrollbars would be displayed because
+                     * there's no rows that grows too wide causing the
+                     * scrollBody container div to overflow. Must explicitely
+                     * force a width to a scrollbar. (see #9187)
+                     */
+                    forceScrollBodyWidth = usedMinimumWidth + totalExtraWidth;
+                }
+                extraSpace = 0;
+            }
+
+            if (forceScrollBodyWidth > 0) {
+                scrollBody.container.getStyle().setWidth(forceScrollBodyWidth,
+                        Unit.PX);
+            } else {
+                // Clear width that might have been set to force horizontal
+                // scrolling if there are no rows
+                scrollBody.container.getStyle().clearWidth();
+            }
+
+            int totalUndefinedNaturalWidths = usedMinimumWidth
+                    - totalExplicitColumnsWidths;
+
+            // we have some space that can be divided optimally
+            HeaderCell hCell;
+            colIndex = 0;
+            headCells = tHead.iterator();
+            int checksum = 0;
+            while (headCells.hasNext()) {
+                hCell = (HeaderCell) headCells.next();
+                if (!hCell.isDefinedWidth()) {
+                    int w = hCell.getNaturalColumnWidth(colIndex);
+                    int newSpace;
+                    if (expandRatioDivider > 0) {
+                        // divide excess space by expand ratios
+                        newSpace = Math.round((w + extraSpace
+                                * hCell.getExpandRatio() / expandRatioDivider));
+                    } else {
+                        if (totalUndefinedNaturalWidths != 0) {
+                            // divide relatively to natural column widths
+                            newSpace = Math.round(w + (float) extraSpace
+                                    * (float) w / totalUndefinedNaturalWidths);
+                        } else {
+                            newSpace = w;
+                        }
+                    }
+                    checksum += newSpace;
+                    setColWidth(colIndex, newSpace, false);
+                } else {
+                    checksum += hCell.getWidth();
+                }
+                colIndex++;
+            }
+
+            if (extraSpace > 0 && checksum != availW) {
+                /*
+                 * There might be in some cases a rounding error of 1px when
+                 * extra space is divided so if there is one then we give the
+                 * first undefined column 1 more pixel
+                 */
+                headCells = tHead.iterator();
+                colIndex = 0;
+                while (headCells.hasNext()) {
+                    HeaderCell hc = (HeaderCell) headCells.next();
+                    if (!hc.isDefinedWidth()) {
+                        setColWidth(colIndex,
+                                hc.getWidth() + availW - checksum, false);
+                        break;
+                    }
+                    colIndex++;
+                }
+            }
+
+            if (isDynamicHeight() && totalRows == pageLength) {
+                // fix body height (may vary if lazy loading is offhorizontal
+                // scrollbar appears/disappears)
+                int bodyHeight = scrollBody.getRequiredHeight();
+                boolean needsSpaceForHorizontalScrollbar = (availW < usedMinimumWidth);
+                if (needsSpaceForHorizontalScrollbar) {
+                    bodyHeight += Util.getNativeScrollbarSize();
+                }
+                int heightBefore = getOffsetHeight();
+                scrollBodyPanel.setHeight(bodyHeight + "px");
+                if (heightBefore != getOffsetHeight()) {
+                    Util.notifyParentOfSizeChange(VScrollTable.this, false);
+                }
+            }
+            scrollBody.reLayoutComponents();
+            Scheduler.get().scheduleDeferred(new Command() {
+
+                @Override
+                public void execute() {
+                    Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
+                }
+            });
+
+            forceRealignColumnHeaders();
+        }
+
+    };
+
+    private void forceRealignColumnHeaders() {
+        if (BrowserInfo.get().isIE()) {
+            /*
+             * IE does not fire onscroll event if scroll position is reverted to
+             * 0 due to the content element size growth. Ensure headers are in
+             * sync with content manually. Safe to use null event as we don't
+             * actually use the event object in listener.
+             */
+            onScroll(null);
+        }
+    }
+
+    /**
+     * helper to set pixel size of head and body part
+     * 
+     * @param pixels
+     */
+    private void setContentWidth(int pixels) {
+        tHead.setWidth(pixels + "px");
+        scrollBodyPanel.setWidth(pixels + "px");
+        tFoot.setWidth(pixels + "px");
+    }
+
+    private int borderWidth = -1;
+
+    /**
+     * @return border left + border right
+     */
+    private int getBorderWidth() {
+        if (borderWidth < 0) {
+            borderWidth = Util.measureHorizontalPaddingAndBorder(
+                    scrollBodyPanel.getElement(), 2);
+            if (borderWidth < 0) {
+                borderWidth = 0;
+            }
+        }
+        return borderWidth;
+    }
+
+    /**
+     * Ensures scrollable area is properly sized. This method is used when fixed
+     * size is used.
+     */
+    private int containerHeight;
+
+    private void setContainerHeight() {
+        if (!isDynamicHeight()) {
+            containerHeight = getOffsetHeight();
+            containerHeight -= showColHeaders ? tHead.getOffsetHeight() : 0;
+            containerHeight -= tFoot.getOffsetHeight();
+            containerHeight -= getContentAreaBorderHeight();
+            if (containerHeight < 0) {
+                containerHeight = 0;
+            }
+            scrollBodyPanel.setHeight(containerHeight + "px");
+        }
+    }
+
+    private int contentAreaBorderHeight = -1;
+    private int scrollLeft;
+    private int scrollTop;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public VScrollTableDropHandler dropHandler;
+
+    private boolean navKeyDown;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean multiselectPending;
+
+    /**
+     * @return border top + border bottom of the scrollable area of table
+     */
+    private int getContentAreaBorderHeight() {
+        if (contentAreaBorderHeight < 0) {
+
+            DOM.setStyleAttribute(scrollBodyPanel.getElement(), "overflow",
+                    "hidden");
+            int oh = scrollBodyPanel.getOffsetHeight();
+            int ch = scrollBodyPanel.getElement()
+                    .getPropertyInt("clientHeight");
+            contentAreaBorderHeight = oh - ch;
+            DOM.setStyleAttribute(scrollBodyPanel.getElement(), "overflow",
+                    "auto");
+        }
+        return contentAreaBorderHeight;
+    }
+
+    @Override
+    public void setHeight(String height) {
+        if (height.length() == 0
+                && getElement().getStyle().getHeight().length() != 0) {
+            /*
+             * Changing from defined to undefined size -> should do a size init
+             * to take page length into account again
+             */
+            sizeNeedsInit = true;
+        }
+        super.setHeight(height);
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void updateHeight() {
+        setContainerHeight();
+
+        if (initializedAndAttached) {
+            updatePageLength();
+        }
+        if (!rendering) {
+            // Webkit may sometimes get an odd rendering bug (white space
+            // between header and body), see bug #3875. Running
+            // overflow hack here to shake body element a bit.
+            // We must run the fix as a deferred command to prevent it from
+            // overwriting the scroll position with an outdated value, see
+            // #7607.
+            Scheduler.get().scheduleDeferred(new Command() {
+
+                @Override
+                public void execute() {
+                    Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
+                }
+            });
+        }
+
+        triggerLazyColumnAdjustment(false);
+
+        /*
+         * setting height may affect wheter the component has scrollbars ->
+         * needs scrolling or not
+         */
+        setProperTabIndex();
+
+    }
+
+    /*
+     * Overridden due Table might not survive of visibility change (scroll pos
+     * lost). Example ITabPanel just set contained components invisible and back
+     * when changing tabs.
+     */
+
+    @Override
+    public void setVisible(boolean visible) {
+        if (isVisible() != visible) {
+            super.setVisible(visible);
+            if (initializedAndAttached) {
+                if (visible) {
+                    Scheduler.get().scheduleDeferred(new Command() {
+
+                        @Override
+                        public void execute() {
+                            scrollBodyPanel
+                                    .setScrollPosition(measureRowHeightOffset(firstRowInViewPort));
+                        }
+                    });
+                }
+            }
+        }
+    }
+
+    /**
+     * Helper function to build html snippet for column or row headers
+     * 
+     * @param uidl
+     *            possibly with values caption and icon
+     * @return html snippet containing possibly an icon + caption text
+     */
+    protected String buildCaptionHtmlSnippet(UIDL uidl) {
+        String s = uidl.hasAttribute("caption") ? uidl
+                .getStringAttribute("caption") : "";
+        if (uidl.hasAttribute("icon")) {
+            s = "<img src=\""
+                    + Util.escapeAttribute(client.translateVaadinUri(uidl
+                            .getStringAttribute("icon")))
+                    + "\" alt=\"icon\" class=\"v-icon\">" + s;
+        }
+        return s;
+    }
+
+    /**
+     * This method has logic which rows needs to be requested from server when
+     * user scrolls
+     */
+
+    @Override
+    public void onScroll(ScrollEvent event) {
+        scrollLeft = scrollBodyPanel.getElement().getScrollLeft();
+        scrollTop = scrollBodyPanel.getScrollPosition();
+        /*
+         * #6970 - IE sometimes fires scroll events for a detached table.
+         * 
+         * FIXME initializedAndAttached should probably be renamed - its name
+         * doesn't seem to reflect its semantics. onDetach() doesn't set it to
+         * false, and changing that might break something else, so we need to
+         * check isAttached() separately.
+         */
+        if (!initializedAndAttached || !isAttached()) {
+            return;
+        }
+        if (!enabled) {
+            scrollBodyPanel
+                    .setScrollPosition(measureRowHeightOffset(firstRowInViewPort));
+            return;
+        }
+
+        rowRequestHandler.cancel();
+
+        if (BrowserInfo.get().isSafari() && event != null && scrollTop == 0) {
+            // due to the webkitoverflowworkaround, top may sometimes report 0
+            // for webkit, although it really is not. Expecting to have the
+            // correct
+            // value available soon.
+            Scheduler.get().scheduleDeferred(new Command() {
+
+                @Override
+                public void execute() {
+                    onScroll(null);
+                }
+            });
+            return;
+        }
+
+        // fix headers horizontal scrolling
+        tHead.setHorizontalScrollPosition(scrollLeft);
+
+        // fix footers horizontal scrolling
+        tFoot.setHorizontalScrollPosition(scrollLeft);
+
+        firstRowInViewPort = calcFirstRowInViewPort();
+        if (firstRowInViewPort > totalRows - pageLength) {
+            firstRowInViewPort = totalRows - pageLength;
+        }
+
+        int postLimit = (int) (firstRowInViewPort + (pageLength - 1) + pageLength
+                * cache_react_rate);
+        if (postLimit > totalRows - 1) {
+            postLimit = totalRows - 1;
+        }
+        int preLimit = (int) (firstRowInViewPort - pageLength
+                * cache_react_rate);
+        if (preLimit < 0) {
+            preLimit = 0;
+        }
+        final int lastRendered = scrollBody.getLastRendered();
+        final int firstRendered = scrollBody.getFirstRendered();
+
+        if (postLimit <= lastRendered && preLimit >= firstRendered) {
+            // we're within no-react area, no need to request more rows
+            // remember which firstvisible we requested, in case the server has
+            // a differing opinion
+            lastRequestedFirstvisible = firstRowInViewPort;
+            client.updateVariable(paintableId, "firstvisible",
+                    firstRowInViewPort, false);
+            return;
+        }
+
+        if (firstRowInViewPort - pageLength * cache_rate > lastRendered
+                || firstRowInViewPort + pageLength + pageLength * cache_rate < firstRendered) {
+            // need a totally new set of rows
+            rowRequestHandler
+                    .setReqFirstRow((firstRowInViewPort - (int) (pageLength * cache_rate)));
+            int last = firstRowInViewPort + (int) (cache_rate * pageLength)
+                    + pageLength - 1;
+            if (last >= totalRows) {
+                last = totalRows - 1;
+            }
+            rowRequestHandler.setReqRows(last
+                    - rowRequestHandler.getReqFirstRow() + 1);
+            rowRequestHandler.deferRowFetch();
+            return;
+        }
+        if (preLimit < firstRendered) {
+            // need some rows to the beginning of the rendered area
+            rowRequestHandler
+                    .setReqFirstRow((int) (firstRowInViewPort - pageLength
+                            * cache_rate));
+            rowRequestHandler.setReqRows(firstRendered
+                    - rowRequestHandler.getReqFirstRow());
+            rowRequestHandler.deferRowFetch();
+
+            return;
+        }
+        if (postLimit > lastRendered) {
+            // need some rows to the end of the rendered area
+            rowRequestHandler.setReqFirstRow(lastRendered + 1);
+            rowRequestHandler.setReqRows((int) ((firstRowInViewPort
+                    + pageLength + pageLength * cache_rate) - lastRendered));
+            rowRequestHandler.deferRowFetch();
+        }
+    }
+
+    protected int calcFirstRowInViewPort() {
+        return (int) Math.ceil(scrollTop / scrollBody.getRowHeight());
+    }
+
+    @Override
+    public VScrollTableDropHandler getDropHandler() {
+        return dropHandler;
+    }
+
+    private static class TableDDDetails {
+        int overkey = -1;
+        VerticalDropLocation dropLocation;
+        String colkey;
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof TableDDDetails) {
+                TableDDDetails other = (TableDDDetails) obj;
+                return dropLocation == other.dropLocation
+                        && overkey == other.overkey
+                        && ((colkey != null && colkey.equals(other.colkey)) || (colkey == null && other.colkey == null));
+            }
+            return false;
+        }
+
+        //
+        // public int hashCode() {
+        // return overkey;
+        // }
+    }
+
+    public class VScrollTableDropHandler extends VAbstractDropHandler {
+
+        private static final String ROWSTYLEBASE = "v-table-row-drag-";
+        private TableDDDetails dropDetails;
+        private TableDDDetails lastEmphasized;
+
+        @Override
+        public void dragEnter(VDragEvent drag) {
+            updateDropDetails(drag);
+            super.dragEnter(drag);
+        }
+
+        private void updateDropDetails(VDragEvent drag) {
+            dropDetails = new TableDDDetails();
+            Element elementOver = drag.getElementOver();
+
+            VScrollTableRow row = Util.findWidget(elementOver, getRowClass());
+            if (row != null) {
+                dropDetails.overkey = row.rowKey;
+                Element tr = row.getElement();
+                Element element = elementOver;
+                while (element != null && element.getParentElement() != tr) {
+                    element = (Element) element.getParentElement();
+                }
+                int childIndex = DOM.getChildIndex(tr, element);
+                dropDetails.colkey = tHead.getHeaderCell(childIndex)
+                        .getColKey();
+                dropDetails.dropLocation = DDUtil.getVerticalDropLocation(
+                        row.getElement(), drag.getCurrentGwtEvent(), 0.2);
+            }
+
+            drag.getDropDetails().put("itemIdOver", dropDetails.overkey + "");
+            drag.getDropDetails().put(
+                    "detail",
+                    dropDetails.dropLocation != null ? dropDetails.dropLocation
+                            .toString() : null);
+
+        }
+
+        private Class<? extends Widget> getRowClass() {
+            // get the row type this way to make dd work in derived
+            // implementations
+            return scrollBody.iterator().next().getClass();
+        }
+
+        @Override
+        public void dragOver(VDragEvent drag) {
+            TableDDDetails oldDetails = dropDetails;
+            updateDropDetails(drag);
+            if (!oldDetails.equals(dropDetails)) {
+                deEmphasis();
+                final TableDDDetails newDetails = dropDetails;
+                VAcceptCallback cb = new VAcceptCallback() {
+
+                    @Override
+                    public void accepted(VDragEvent event) {
+                        if (newDetails.equals(dropDetails)) {
+                            dragAccepted(event);
+                        }
+                        /*
+                         * Else new target slot already defined, ignore
+                         */
+                    }
+                };
+                validate(cb, drag);
+            }
+        }
+
+        @Override
+        public void dragLeave(VDragEvent drag) {
+            deEmphasis();
+            super.dragLeave(drag);
+        }
+
+        @Override
+        public boolean drop(VDragEvent drag) {
+            deEmphasis();
+            return super.drop(drag);
+        }
+
+        private void deEmphasis() {
+            UIObject.setStyleName(getElement(),
+                    VScrollTable.this.getStylePrimaryName() + "-drag", false);
+            if (lastEmphasized == null) {
+                return;
+            }
+            for (Widget w : scrollBody.renderedRows) {
+                VScrollTableRow row = (VScrollTableRow) w;
+                if (lastEmphasized != null
+                        && row.rowKey == lastEmphasized.overkey) {
+                    String stylename = ROWSTYLEBASE
+                            + lastEmphasized.dropLocation.toString()
+                                    .toLowerCase();
+                    VScrollTableRow.setStyleName(row.getElement(), stylename,
+                            false);
+                    lastEmphasized = null;
+                    return;
+                }
+            }
+        }
+
+        /**
+         * TODO needs different drop modes ?? (on cells, on rows), now only
+         * supports rows
+         */
+        private void emphasis(TableDDDetails details) {
+            deEmphasis();
+            UIObject.setStyleName(getElement(),
+                    VScrollTable.this.getStylePrimaryName() + "-drag", true);
+            // iterate old and new emphasized row
+            for (Widget w : scrollBody.renderedRows) {
+                VScrollTableRow row = (VScrollTableRow) w;
+                if (details != null && details.overkey == row.rowKey) {
+                    String stylename = ROWSTYLEBASE
+                            + details.dropLocation.toString().toLowerCase();
+                    VScrollTableRow.setStyleName(row.getElement(), stylename,
+                            true);
+                    lastEmphasized = details;
+                    return;
+                }
+            }
+        }
+
+        @Override
+        protected void dragAccepted(VDragEvent drag) {
+            emphasis(dropDetails);
+        }
+
+        @Override
+        public ComponentConnector getConnector() {
+            return ConnectorMap.get(client).getConnector(VScrollTable.this);
+        }
+
+        @Override
+        public ApplicationConnection getApplicationConnection() {
+            return client;
+        }
+
+    }
+
+    protected VScrollTableRow getFocusedRow() {
+        return focusedRow;
+    }
+
+    /**
+     * Moves the selection head to a specific row
+     * 
+     * @param row
+     *            The row to where the selection head should move
+     * @return Returns true if focus was moved successfully, else false
+     */
+    public boolean setRowFocus(VScrollTableRow row) {
+
+        if (!isSelectable()) {
+            return false;
+        }
+
+        // Remove previous selection
+        if (focusedRow != null && focusedRow != row) {
+            focusedRow.removeStyleName(getStylePrimaryName() + "-focus");
+        }
+
+        if (row != null) {
+
+            // Apply focus style to new selection
+            row.addStyleName(getStylePrimaryName() + "-focus");
+
+            /*
+             * Trying to set focus on already focused row
+             */
+            if (row == focusedRow) {
+                return false;
+            }
+
+            // Set new focused row
+            focusedRow = row;
+
+            ensureRowIsVisible(row);
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Ensures that the row is visible
+     * 
+     * @param row
+     *            The row to ensure is visible
+     */
+    private void ensureRowIsVisible(VScrollTableRow row) {
+        if (BrowserInfo.get().isTouchDevice()) {
+            // Skip due to android devices that have broken scrolltop will may
+            // get odd scrolling here.
+            return;
+        }
+        Util.scrollIntoViewVertically(row.getElement());
+    }
+
+    /**
+     * Handles the keyboard events handled by the table
+     * 
+     * @param event
+     *            The keyboard event received
+     * @return true iff the navigation event was handled
+     */
+    protected boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
+        if (keycode == KeyCodes.KEY_TAB || keycode == KeyCodes.KEY_SHIFT) {
+            // Do not handle tab key
+            return false;
+        }
+
+        // Down navigation
+        if (!isSelectable() && keycode == getNavigationDownKey()) {
+            scrollBodyPanel.setScrollPosition(scrollBodyPanel
+                    .getScrollPosition() + scrollingVelocity);
+            return true;
+        } else if (keycode == getNavigationDownKey()) {
+            if (isMultiSelectModeAny() && moveFocusDown()) {
+                selectFocusedRow(ctrl, shift);
+
+            } else if (isSingleSelectMode() && !shift && moveFocusDown()) {
+                selectFocusedRow(ctrl, shift);
+            }
+            return true;
+        }
+
+        // Up navigation
+        if (!isSelectable() && keycode == getNavigationUpKey()) {
+            scrollBodyPanel.setScrollPosition(scrollBodyPanel
+                    .getScrollPosition() - scrollingVelocity);
+            return true;
+        } else if (keycode == getNavigationUpKey()) {
+            if (isMultiSelectModeAny() && moveFocusUp()) {
+                selectFocusedRow(ctrl, shift);
+            } else if (isSingleSelectMode() && !shift && moveFocusUp()) {
+                selectFocusedRow(ctrl, shift);
+            }
+            return true;
+        }
+
+        if (keycode == getNavigationLeftKey()) {
+            // Left navigation
+            scrollBodyPanel.setHorizontalScrollPosition(scrollBodyPanel
+                    .getHorizontalScrollPosition() - scrollingVelocity);
+            return true;
+
+        } else if (keycode == getNavigationRightKey()) {
+            // Right navigation
+            scrollBodyPanel.setHorizontalScrollPosition(scrollBodyPanel
+                    .getHorizontalScrollPosition() + scrollingVelocity);
+            return true;
+        }
+
+        // Select navigation
+        if (isSelectable() && keycode == getNavigationSelectKey()) {
+            if (isSingleSelectMode()) {
+                boolean wasSelected = focusedRow.isSelected();
+                deselectAll();
+                if (!wasSelected || !nullSelectionAllowed) {
+                    focusedRow.toggleSelection();
+                }
+            } else {
+                focusedRow.toggleSelection();
+                removeRowFromUnsentSelectionRanges(focusedRow);
+            }
+
+            sendSelectedRows();
+            return true;
+        }
+
+        // Page Down navigation
+        if (keycode == getNavigationPageDownKey()) {
+            if (isSelectable()) {
+                /*
+                 * If selectable we plagiate MSW behaviour: first scroll to the
+                 * end of current view. If at the end, scroll down one page
+                 * length and keep the selected row in the bottom part of
+                 * visible area.
+                 */
+                if (!isFocusAtTheEndOfTable()) {
+                    VScrollTableRow lastVisibleRowInViewPort = scrollBody
+                            .getRowByRowIndex(firstRowInViewPort
+                                    + getFullyVisibleRowCount() - 1);
+                    if (lastVisibleRowInViewPort != null
+                            && lastVisibleRowInViewPort != focusedRow) {
+                        // focused row is not at the end of the table, move
+                        // focus and select the last visible row
+                        setRowFocus(lastVisibleRowInViewPort);
+                        selectFocusedRow(ctrl, shift);
+                        sendSelectedRows();
+                    } else {
+                        int indexOfToBeFocused = focusedRow.getIndex()
+                                + getFullyVisibleRowCount();
+                        if (indexOfToBeFocused >= totalRows) {
+                            indexOfToBeFocused = totalRows - 1;
+                        }
+                        VScrollTableRow toBeFocusedRow = scrollBody
+                                .getRowByRowIndex(indexOfToBeFocused);
+
+                        if (toBeFocusedRow != null) {
+                            /*
+                             * if the next focused row is rendered
+                             */
+                            setRowFocus(toBeFocusedRow);
+                            selectFocusedRow(ctrl, shift);
+                            // TODO needs scrollintoview ?
+                            sendSelectedRows();
+                        } else {
+                            // scroll down by pixels and return, to wait for
+                            // new rows, then select the last item in the
+                            // viewport
+                            selectLastItemInNextRender = true;
+                            multiselectPending = shift;
+                            scrollByPagelenght(1);
+                        }
+                    }
+                }
+            } else {
+                /* No selections, go page down by scrolling */
+                scrollByPagelenght(1);
+            }
+            return true;
+        }
+
+        // Page Up navigation
+        if (keycode == getNavigationPageUpKey()) {
+            if (isSelectable()) {
+                /*
+                 * If selectable we plagiate MSW behaviour: first scroll to the
+                 * end of current view. If at the end, scroll down one page
+                 * length and keep the selected row in the bottom part of
+                 * visible area.
+                 */
+                if (!isFocusAtTheBeginningOfTable()) {
+                    VScrollTableRow firstVisibleRowInViewPort = scrollBody
+                            .getRowByRowIndex(firstRowInViewPort);
+                    if (firstVisibleRowInViewPort != null
+                            && firstVisibleRowInViewPort != focusedRow) {
+                        // focus is not at the beginning of the table, move
+                        // focus and select the first visible row
+                        setRowFocus(firstVisibleRowInViewPort);
+                        selectFocusedRow(ctrl, shift);
+                        sendSelectedRows();
+                    } else {
+                        int indexOfToBeFocused = focusedRow.getIndex()
+                                - getFullyVisibleRowCount();
+                        if (indexOfToBeFocused < 0) {
+                            indexOfToBeFocused = 0;
+                        }
+                        VScrollTableRow toBeFocusedRow = scrollBody
+                                .getRowByRowIndex(indexOfToBeFocused);
+
+                        if (toBeFocusedRow != null) { // if the next focused row
+                                                      // is rendered
+                            setRowFocus(toBeFocusedRow);
+                            selectFocusedRow(ctrl, shift);
+                            // TODO needs scrollintoview ?
+                            sendSelectedRows();
+                        } else {
+                            // unless waiting for the next rowset already
+                            // scroll down by pixels and return, to wait for
+                            // new rows, then select the last item in the
+                            // viewport
+                            selectFirstItemInNextRender = true;
+                            multiselectPending = shift;
+                            scrollByPagelenght(-1);
+                        }
+                    }
+                }
+            } else {
+                /* No selections, go page up by scrolling */
+                scrollByPagelenght(-1);
+            }
+
+            return true;
+        }
+
+        // Goto start navigation
+        if (keycode == getNavigationStartKey()) {
+            scrollBodyPanel.setScrollPosition(0);
+            if (isSelectable()) {
+                if (focusedRow != null && focusedRow.getIndex() == 0) {
+                    return false;
+                } else {
+                    VScrollTableRow rowByRowIndex = (VScrollTableRow) scrollBody
+                            .iterator().next();
+                    if (rowByRowIndex.getIndex() == 0) {
+                        setRowFocus(rowByRowIndex);
+                        selectFocusedRow(ctrl, shift);
+                        sendSelectedRows();
+                    } else {
+                        // first row of table will come in next row fetch
+                        if (ctrl) {
+                            focusFirstItemInNextRender = true;
+                        } else {
+                            selectFirstItemInNextRender = true;
+                            multiselectPending = shift;
+                        }
+                    }
+                }
+            }
+            return true;
+        }
+
+        // Goto end navigation
+        if (keycode == getNavigationEndKey()) {
+            scrollBodyPanel.setScrollPosition(scrollBody.getOffsetHeight());
+            if (isSelectable()) {
+                final int lastRendered = scrollBody.getLastRendered();
+                if (lastRendered + 1 == totalRows) {
+                    VScrollTableRow rowByRowIndex = scrollBody
+                            .getRowByRowIndex(lastRendered);
+                    if (focusedRow != rowByRowIndex) {
+                        setRowFocus(rowByRowIndex);
+                        selectFocusedRow(ctrl, shift);
+                        sendSelectedRows();
+                    }
+                } else {
+                    if (ctrl) {
+                        focusLastItemInNextRender = true;
+                    } else {
+                        selectLastItemInNextRender = true;
+                        multiselectPending = shift;
+                    }
+                }
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+    private boolean isFocusAtTheBeginningOfTable() {
+        return focusedRow.getIndex() == 0;
+    }
+
+    private boolean isFocusAtTheEndOfTable() {
+        return focusedRow.getIndex() + 1 >= totalRows;
+    }
+
+    private int getFullyVisibleRowCount() {
+        return (int) (scrollBodyPanel.getOffsetHeight() / scrollBody
+                .getRowHeight());
+    }
+
+    private void scrollByPagelenght(int i) {
+        int pixels = i * scrollBodyPanel.getOffsetHeight();
+        int newPixels = scrollBodyPanel.getScrollPosition() + pixels;
+        if (newPixels < 0) {
+            newPixels = 0;
+        } // else if too high, NOP (all know browsers accept illegally big
+          // values here)
+        scrollBodyPanel.setScrollPosition(newPixels);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
+     * .dom.client.FocusEvent)
+     */
+
+    @Override
+    public void onFocus(FocusEvent event) {
+        if (isFocusable()) {
+            hasFocus = true;
+
+            // Focus a row if no row is in focus
+            if (focusedRow == null) {
+                focusRowFromBody();
+            } else {
+                setRowFocus(focusedRow);
+            }
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event
+     * .dom.client.BlurEvent)
+     */
+
+    @Override
+    public void onBlur(BlurEvent event) {
+        hasFocus = false;
+        navKeyDown = false;
+
+        if (BrowserInfo.get().isIE()) {
+            // IE sometimes moves focus to a clicked table cell...
+            Element focusedElement = Util.getIEFocusedElement();
+            if (Util.getConnectorForElement(client, getParent(), focusedElement) == this) {
+                // ..in that case, steal the focus back to the focus handler
+                // but not if focus is in a child component instead (#7965)
+                focus();
+                return;
+            }
+        }
+
+        if (isFocusable()) {
+            // Unfocus any row
+            setRowFocus(null);
+        }
+    }
+
+    /**
+     * Removes a key from a range if the key is found in a selected range
+     * 
+     * @param key
+     *            The key to remove
+     */
+    private void removeRowFromUnsentSelectionRanges(VScrollTableRow row) {
+        Collection<SelectionRange> newRanges = null;
+        for (Iterator<SelectionRange> iterator = selectedRowRanges.iterator(); iterator
+                .hasNext();) {
+            SelectionRange range = iterator.next();
+            if (range.inRange(row)) {
+                // Split the range if given row is in range
+                Collection<SelectionRange> splitranges = range.split(row);
+                if (newRanges == null) {
+                    newRanges = new ArrayList<SelectionRange>();
+                }
+                newRanges.addAll(splitranges);
+                iterator.remove();
+            }
+        }
+        if (newRanges != null) {
+            selectedRowRanges.addAll(newRanges);
+        }
+    }
+
+    /**
+     * Can the Table be focused?
+     * 
+     * @return True if the table can be focused, else false
+     */
+    public boolean isFocusable() {
+        if (scrollBody != null && enabled) {
+            return !(!hasHorizontalScrollbar() && !hasVerticalScrollbar() && !isSelectable());
+        }
+        return false;
+    }
+
+    private boolean hasHorizontalScrollbar() {
+        return scrollBody.getOffsetWidth() > scrollBodyPanel.getOffsetWidth();
+    }
+
+    private boolean hasVerticalScrollbar() {
+        return scrollBody.getOffsetHeight() > scrollBodyPanel.getOffsetHeight();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see com.vaadin.client.Focusable#focus()
+     */
+
+    @Override
+    public void focus() {
+        if (isFocusable()) {
+            scrollBodyPanel.focus();
+        }
+    }
+
+    /**
+     * Sets the proper tabIndex for scrollBodyPanel (the focusable elemen in the
+     * component).
+     * <p>
+     * If the component has no explicit tabIndex a zero is given (default
+     * tabbing order based on dom hierarchy) or -1 if the component does not
+     * need to gain focus. The component needs no focus if it has no scrollabars
+     * (not scrollable) and not selectable. Note that in the future shortcut
+     * actions may need focus.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public void setProperTabIndex() {
+        int storedScrollTop = 0;
+        int storedScrollLeft = 0;
+
+        if (BrowserInfo.get().getOperaVersion() >= 11) {
+            // Workaround for Opera scroll bug when changing tabIndex (#6222)
+            storedScrollTop = scrollBodyPanel.getScrollPosition();
+            storedScrollLeft = scrollBodyPanel.getHorizontalScrollPosition();
+        }
+
+        if (tabIndex == 0 && !isFocusable()) {
+            scrollBodyPanel.setTabIndex(-1);
+        } else {
+            scrollBodyPanel.setTabIndex(tabIndex);
+        }
+
+        if (BrowserInfo.get().getOperaVersion() >= 11) {
+            // Workaround for Opera scroll bug when changing tabIndex (#6222)
+            scrollBodyPanel.setScrollPosition(storedScrollTop);
+            scrollBodyPanel.setHorizontalScrollPosition(storedScrollLeft);
+        }
+    }
+
+    public void startScrollingVelocityTimer() {
+        if (scrollingVelocityTimer == null) {
+            scrollingVelocityTimer = new Timer() {
+
+                @Override
+                public void run() {
+                    scrollingVelocity++;
+                }
+            };
+            scrollingVelocityTimer.scheduleRepeating(100);
+        }
+    }
+
+    public void cancelScrollingVelocityTimer() {
+        if (scrollingVelocityTimer != null) {
+            // Remove velocityTimer if it exists and the Table is disabled
+            scrollingVelocityTimer.cancel();
+            scrollingVelocityTimer = null;
+            scrollingVelocity = 10;
+        }
+    }
+
+    /**
+     * 
+     * @param keyCode
+     * @return true if the given keyCode is used by the table for navigation
+     */
+    private boolean isNavigationKey(int keyCode) {
+        return keyCode == getNavigationUpKey()
+                || keyCode == getNavigationLeftKey()
+                || keyCode == getNavigationRightKey()
+                || keyCode == getNavigationDownKey()
+                || keyCode == getNavigationPageUpKey()
+                || keyCode == getNavigationPageDownKey()
+                || keyCode == getNavigationEndKey()
+                || keyCode == getNavigationStartKey();
+    }
+
+    public void lazyRevertFocusToRow(final VScrollTableRow currentlyFocusedRow) {
+        Scheduler.get().scheduleFinally(new ScheduledCommand() {
+
+            @Override
+            public void execute() {
+                if (currentlyFocusedRow != null) {
+                    setRowFocus(currentlyFocusedRow);
+                } else {
+                    VConsole.log("no row?");
+                    focusRowFromBody();
+                }
+                scrollBody.ensureFocus();
+            }
+        });
+    }
+
+    @Override
+    public Action[] getActions() {
+        if (bodyActionKeys == null) {
+            return new Action[] {};
+        }
+        final Action[] actions = new Action[bodyActionKeys.length];
+        for (int i = 0; i < actions.length; i++) {
+            final String actionKey = bodyActionKeys[i];
+            Action bodyAction = new TreeAction(this, null, actionKey);
+            bodyAction.setCaption(getActionCaption(actionKey));
+            bodyAction.setIconUrl(getActionIcon(actionKey));
+            actions[i] = bodyAction;
+        }
+        return actions;
+    }
+
+    @Override
+    public ApplicationConnection getClient() {
+        return client;
+    }
+
+    @Override
+    public String getPaintableId() {
+        return paintableId;
+    }
+
+    /**
+     * Add this to the element mouse down event by using element.setPropertyJSO
+     * ("onselectstart",applyDisableTextSelectionIEHack()); Remove it then again
+     * when the mouse is depressed in the mouse up event.
+     * 
+     * @return Returns the JSO preventing text selection
+     */
+    private static native JavaScriptObject getPreventTextSelectionIEHack()
+    /*-{
+            return function(){ return false; };
+    }-*/;
+
+    public void triggerLazyColumnAdjustment(boolean now) {
+        lazyAdjustColumnWidths.cancel();
+        if (now) {
+            lazyAdjustColumnWidths.run();
+        } else {
+            lazyAdjustColumnWidths.schedule(LAZY_COLUMN_ADJUST_TIMEOUT);
+        }
+    }
+
+    private boolean isDynamicWidth() {
+        ComponentConnector paintable = ConnectorMap.get(client).getConnector(
+                this);
+        return paintable.isUndefinedWidth();
+    }
+
+    private boolean isDynamicHeight() {
+        ComponentConnector paintable = ConnectorMap.get(client).getConnector(
+                this);
+        if (paintable == null) {
+            // This should be refactored. As isDynamicHeight can be called from
+            // a timer it is possible that the connector has been unregistered
+            // when this method is called, causing getConnector to return null.
+            return false;
+        }
+        return paintable.isUndefinedHeight();
+    }
+
+    private void debug(String msg) {
+        if (enableDebug) {
+            VConsole.error(msg);
+        }
+    }
+
+    public Widget getWidgetForPaintable() {
+        return this;
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VSlider.java b/client/src/com/vaadin/client/ui/VSlider.java
new file mode 100644 (file)
index 0000000..3fdd8f0
--- /dev/null
@@ -0,0 +1,653 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+//
+package com.vaadin.client.ui;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+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.dom.client.KeyCodes;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.HasValue;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.ContainerResizedListener;
+import com.vaadin.client.Util;
+import com.vaadin.client.VConsole;
+import com.vaadin.shared.ui.slider.SliderOrientation;
+
+public class VSlider extends SimpleFocusablePanel implements Field,
+        ContainerResizedListener, HasValue<Double> {
+
+    public static final String CLASSNAME = "v-slider";
+
+    /**
+     * Minimum size (width or height, depending on orientation) of the slider
+     * base.
+     */
+    private static final int MIN_SIZE = 50;
+
+    protected ApplicationConnection client;
+
+    protected String id;
+
+    protected boolean immediate;
+    protected boolean disabled;
+    protected boolean readonly;
+
+    private int acceleration = 1;
+    protected double min;
+    protected double max;
+    protected int resolution;
+    protected Double value;
+    protected SliderOrientation orientation = SliderOrientation.HORIZONTAL;
+
+    private final HTML feedback = new HTML("", false);
+    private final VOverlay feedbackPopup = new VOverlay(true, false, true) {
+        {
+            setOwner(VSlider.this);
+        }
+
+        @Override
+        public void show() {
+            super.show();
+            updateFeedbackPosition();
+        }
+    };
+
+    /* DOM element for slider's base */
+    private final Element base;
+    private final int BASE_BORDER_WIDTH = 1;
+
+    /* DOM element for slider's handle */
+    private final Element handle;
+
+    /* DOM element for decrement arrow */
+    private final Element smaller;
+
+    /* DOM element for increment arrow */
+    private final Element bigger;
+
+    /* Temporary dragging/animation variables */
+    private boolean dragging = false;
+
+    private VLazyExecutor delayedValueUpdater = new VLazyExecutor(100,
+            new ScheduledCommand() {
+
+                @Override
+                public void execute() {
+                    fireValueChanged();
+                    acceleration = 1;
+                }
+            });
+
+    public VSlider() {
+        super();
+
+        base = DOM.createDiv();
+        handle = DOM.createDiv();
+        smaller = DOM.createDiv();
+        bigger = DOM.createDiv();
+
+        setStyleName(CLASSNAME);
+
+        getElement().appendChild(bigger);
+        getElement().appendChild(smaller);
+        getElement().appendChild(base);
+        base.appendChild(handle);
+
+        // Hide initially
+        smaller.getStyle().setDisplay(Display.NONE);
+        bigger.getStyle().setDisplay(Display.NONE);
+        handle.getStyle().setVisibility(Visibility.HIDDEN);
+
+        sinkEvents(Event.MOUSEEVENTS | Event.ONMOUSEWHEEL | Event.KEYEVENTS
+                | Event.FOCUSEVENTS | Event.TOUCHEVENTS);
+
+        feedbackPopup.setWidget(feedback);
+    }
+
+    @Override
+    public void setStyleName(String style) {
+        updateStyleNames(style, false);
+    }
+
+    @Override
+    public void setStylePrimaryName(String style) {
+        updateStyleNames(style, true);
+    }
+
+    protected void updateStyleNames(String styleName, boolean isPrimaryStyleName) {
+
+        feedbackPopup.removeStyleName(getStylePrimaryName() + "-feedback");
+        removeStyleName(getStylePrimaryName() + "-vertical");
+
+        if (isPrimaryStyleName) {
+            super.setStylePrimaryName(styleName);
+        } else {
+            super.setStyleName(styleName);
+        }
+
+        feedbackPopup.addStyleName(getStylePrimaryName() + "-feedback");
+        base.setClassName(getStylePrimaryName() + "-base");
+        handle.setClassName(getStylePrimaryName() + "-handle");
+        smaller.setClassName(getStylePrimaryName() + "-smaller");
+        bigger.setClassName(getStylePrimaryName() + "-bigger");
+
+        if (isVertical()) {
+            addStyleName(getStylePrimaryName() + "-vertical");
+        }
+    }
+
+    public void setFeedbackValue(double value) {
+        String currentValue = "" + value;
+        if (resolution == 0) {
+            currentValue = "" + new Double(value).intValue();
+        }
+        feedback.setText(currentValue);
+    }
+
+    private void updateFeedbackPosition() {
+        if (isVertical()) {
+            feedbackPopup.setPopupPosition(
+                    handle.getAbsoluteLeft() + handle.getOffsetWidth(),
+                    handle.getAbsoluteTop() + handle.getOffsetHeight() / 2
+                            - feedbackPopup.getOffsetHeight() / 2);
+        } else {
+            feedbackPopup.setPopupPosition(
+                    handle.getAbsoluteLeft() + handle.getOffsetWidth() / 2
+                            - feedbackPopup.getOffsetWidth() / 2,
+                    handle.getAbsoluteTop() - feedbackPopup.getOffsetHeight());
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void buildBase() {
+        final String styleAttribute = isVertical() ? "height" : "width";
+        final String oppositeStyleAttribute = isVertical() ? "width" : "height";
+        final String domProperty = isVertical() ? "offsetHeight"
+                : "offsetWidth";
+
+        // clear unnecessary opposite style attribute
+        base.getStyle().clearProperty(oppositeStyleAttribute);
+
+        final Element p = getElement().getParentElement().cast();
+        if (p.getPropertyInt(domProperty) > 50) {
+            if (isVertical()) {
+                setHeight();
+            } else {
+                base.getStyle().clearProperty(styleAttribute);
+            }
+        } else {
+            // Set minimum size and adjust after all components have
+            // (supposedly) been drawn completely.
+            base.getStyle().setPropertyPx(styleAttribute, MIN_SIZE);
+            Scheduler.get().scheduleDeferred(new Command() {
+
+                @Override
+                public void execute() {
+                    final Element p = getElement().getParentElement().cast();
+                    if (p.getPropertyInt(domProperty) > (MIN_SIZE + 5)) {
+                        if (isVertical()) {
+                            setHeight();
+                        } else {
+                            base.getStyle().clearProperty(styleAttribute);
+                        }
+                        // Ensure correct position
+                        setValue(value, false);
+                    }
+                }
+            });
+        }
+
+        if (!isVertical()) {
+            // Draw handle with a delay to allow base to gain maximum width
+            Scheduler.get().scheduleDeferred(new Command() {
+                @Override
+                public void execute() {
+                    buildHandle();
+                    setValue(value, false);
+                }
+            });
+        } else {
+            buildHandle();
+            setValue(value, false);
+        }
+
+        // TODO attach listeners for focusing and arrow keys
+    }
+
+    void buildHandle() {
+        final String handleAttribute = isVertical() ? "marginTop"
+                : "marginLeft";
+        final String oppositeHandleAttribute = isVertical() ? "marginLeft"
+                : "marginTop";
+
+        handle.getStyle().setProperty(handleAttribute, "0");
+
+        // clear unnecessary opposite handle attribute
+        handle.getStyle().clearProperty(oppositeHandleAttribute);
+
+        // Restore visibility
+        handle.getStyle().setVisibility(Visibility.VISIBLE);
+    }
+
+    @Override
+    public void onBrowserEvent(Event event) {
+        if (disabled || readonly) {
+            return;
+        }
+        final Element targ = DOM.eventGetTarget(event);
+
+        if (DOM.eventGetType(event) == Event.ONMOUSEWHEEL) {
+            processMouseWheelEvent(event);
+        } else if (dragging || targ == handle) {
+            processHandleEvent(event);
+        } else if (targ == smaller) {
+            decreaseValue(true);
+        } else if (targ == bigger) {
+            increaseValue(true);
+        } else if (DOM.eventGetType(event) == Event.MOUSEEVENTS) {
+            processBaseEvent(event);
+        } else if ((BrowserInfo.get().isGecko() && DOM.eventGetType(event) == Event.ONKEYPRESS)
+                || (!BrowserInfo.get().isGecko() && DOM.eventGetType(event) == Event.ONKEYDOWN)) {
+
+            if (handleNavigation(event.getKeyCode(), event.getCtrlKey(),
+                    event.getShiftKey())) {
+
+                feedbackPopup.show();
+
+                delayedValueUpdater.trigger();
+
+                DOM.eventPreventDefault(event);
+                DOM.eventCancelBubble(event, true);
+            }
+        } else if (targ.equals(getElement())
+                && DOM.eventGetType(event) == Event.ONFOCUS) {
+            feedbackPopup.show();
+        } else if (targ.equals(getElement())
+                && DOM.eventGetType(event) == Event.ONBLUR) {
+            feedbackPopup.hide();
+        } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) {
+            feedbackPopup.show();
+        }
+        if (Util.isTouchEvent(event)) {
+            event.preventDefault(); // avoid simulated events
+            event.stopPropagation();
+        }
+    }
+
+    private void processMouseWheelEvent(final Event event) {
+        final int dir = DOM.eventGetMouseWheelVelocityY(event);
+
+        if (dir < 0) {
+            increaseValue(false);
+        } else {
+            decreaseValue(false);
+        }
+
+        delayedValueUpdater.trigger();
+
+        DOM.eventPreventDefault(event);
+        DOM.eventCancelBubble(event, true);
+    }
+
+    private void processHandleEvent(Event event) {
+        switch (DOM.eventGetType(event)) {
+        case Event.ONMOUSEDOWN:
+        case Event.ONTOUCHSTART:
+            if (!disabled && !readonly) {
+                focus();
+                feedbackPopup.show();
+                dragging = true;
+                handle.setClassName(getStylePrimaryName() + "-handle");
+                handle.addClassName(getStylePrimaryName() + "-handle-active");
+
+                DOM.setCapture(getElement());
+                DOM.eventPreventDefault(event); // prevent selecting text
+                DOM.eventCancelBubble(event, true);
+                event.stopPropagation();
+                VConsole.log("Slider move start");
+            }
+            break;
+        case Event.ONMOUSEMOVE:
+        case Event.ONTOUCHMOVE:
+            if (dragging) {
+                VConsole.log("Slider move");
+                setValueByEvent(event, false);
+                updateFeedbackPosition();
+                event.stopPropagation();
+            }
+            break;
+        case Event.ONTOUCHEND:
+            feedbackPopup.hide();
+        case Event.ONMOUSEUP:
+            // feedbackPopup.hide();
+            VConsole.log("Slider move end");
+            dragging = false;
+            handle.setClassName(getStylePrimaryName() + "-handle");
+            DOM.releaseCapture(getElement());
+            setValueByEvent(event, true);
+            event.stopPropagation();
+            break;
+        default:
+            break;
+        }
+    }
+
+    private void processBaseEvent(Event event) {
+        if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) {
+            if (!disabled && !readonly && !dragging) {
+                setValueByEvent(event, true);
+                DOM.eventCancelBubble(event, true);
+            }
+        }
+    }
+
+    private void decreaseValue(boolean updateToServer) {
+        setValue(new Double(value.doubleValue() - Math.pow(10, -resolution)),
+                updateToServer);
+    }
+
+    private void increaseValue(boolean updateToServer) {
+        setValue(new Double(value.doubleValue() + Math.pow(10, -resolution)),
+                updateToServer);
+    }
+
+    private void setValueByEvent(Event event, boolean updateToServer) {
+        double v = min; // Fallback to min
+
+        final int coord = getEventPosition(event);
+
+        final int handleSize, baseSize, baseOffset;
+        if (isVertical()) {
+            handleSize = handle.getOffsetHeight();
+            baseSize = base.getOffsetHeight();
+            baseOffset = base.getAbsoluteTop() - Window.getScrollTop()
+                    - handleSize / 2;
+        } else {
+            handleSize = handle.getOffsetWidth();
+            baseSize = base.getOffsetWidth();
+            baseOffset = base.getAbsoluteLeft() - Window.getScrollLeft()
+                    + handleSize / 2;
+        }
+
+        if (isVertical()) {
+            v = ((baseSize - (coord - baseOffset)) / (double) (baseSize - handleSize))
+                    * (max - min) + min;
+        } else {
+            v = ((coord - baseOffset) / (double) (baseSize - handleSize))
+                    * (max - min) + min;
+        }
+
+        if (v < min) {
+            v = min;
+        } else if (v > max) {
+            v = max;
+        }
+
+        setValue(v, updateToServer);
+    }
+
+    /**
+     * TODO consider extracting touches support to an impl class specific for
+     * webkit (only browser that really supports touches).
+     * 
+     * @param event
+     * @return
+     */
+    protected int getEventPosition(Event event) {
+        if (isVertical()) {
+            return Util.getTouchOrMouseClientY(event);
+        } else {
+            return Util.getTouchOrMouseClientX(event);
+        }
+    }
+
+    @Override
+    public void iLayout() {
+        if (isVertical()) {
+            setHeight();
+        }
+        // Update handle position
+        setValue(value, false);
+    }
+
+    private void setHeight() {
+        // Calculate decoration size
+        base.getStyle().setHeight(0, Unit.PX);
+        base.getStyle().setOverflow(Overflow.HIDDEN);
+        int h = getElement().getOffsetHeight();
+        if (h < MIN_SIZE) {
+            h = MIN_SIZE;
+        }
+        base.getStyle().setHeight(h, Unit.PX);
+        base.getStyle().clearOverflow();
+    }
+
+    private void fireValueChanged() {
+        ValueChangeEvent.fire(VSlider.this, value);
+    }
+
+    /**
+     * Handles the keyboard events handled by the Slider
+     * 
+     * @param event
+     *            The keyboard event received
+     * @return true iff the navigation event was handled
+     */
+    public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
+
+        // No support for ctrl moving
+        if (ctrl) {
+            return false;
+        }
+
+        if ((keycode == getNavigationUpKey() && isVertical())
+                || (keycode == getNavigationRightKey() && !isVertical())) {
+            if (shift) {
+                for (int a = 0; a < acceleration; a++) {
+                    increaseValue(false);
+                }
+                acceleration++;
+            } else {
+                increaseValue(false);
+            }
+            return true;
+        } else if (keycode == getNavigationDownKey() && isVertical()
+                || (keycode == getNavigationLeftKey() && !isVertical())) {
+            if (shift) {
+                for (int a = 0; a < acceleration; a++) {
+                    decreaseValue(false);
+                }
+                acceleration++;
+            } else {
+                decreaseValue(false);
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Get the key that increases the vertical slider. By default it is the up
+     * arrow key but by overriding this you can change the key to whatever you
+     * want.
+     * 
+     * @return The keycode of the key
+     */
+    protected int getNavigationUpKey() {
+        return KeyCodes.KEY_UP;
+    }
+
+    /**
+     * Get the key that decreases the vertical slider. By default it is the down
+     * arrow key but by overriding this you can change the key to whatever you
+     * want.
+     * 
+     * @return The keycode of the key
+     */
+    protected int getNavigationDownKey() {
+        return KeyCodes.KEY_DOWN;
+    }
+
+    /**
+     * Get the key that decreases the horizontal slider. By default it is the
+     * left arrow key but by overriding this you can change the key to whatever
+     * you want.
+     * 
+     * @return The keycode of the key
+     */
+    protected int getNavigationLeftKey() {
+        return KeyCodes.KEY_LEFT;
+    }
+
+    /**
+     * Get the key that increases the horizontal slider. By default it is the
+     * right arrow key but by overriding this you can change the key to whatever
+     * you want.
+     * 
+     * @return The keycode of the key
+     */
+    protected int getNavigationRightKey() {
+        return KeyCodes.KEY_RIGHT;
+    }
+
+    public void setConnection(ApplicationConnection client) {
+        this.client = client;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public void setImmediate(boolean immediate) {
+        this.immediate = immediate;
+    }
+
+    public void setDisabled(boolean disabled) {
+        this.disabled = disabled;
+    }
+
+    public void setReadOnly(boolean readonly) {
+        this.readonly = readonly;
+    }
+
+    private boolean isVertical() {
+        return orientation == SliderOrientation.VERTICAL;
+    }
+
+    public void setOrientation(SliderOrientation orientation) {
+        if (this.orientation != orientation) {
+            this.orientation = orientation;
+            updateStyleNames(getStylePrimaryName(), true);
+        }
+    }
+
+    public void setMinValue(double value) {
+        min = value;
+    }
+
+    public void setMaxValue(double value) {
+        max = value;
+    }
+
+    public void setResolution(int resolution) {
+        this.resolution = resolution;
+    }
+
+    @Override
+    public HandlerRegistration addValueChangeHandler(
+            ValueChangeHandler<Double> handler) {
+        return addHandler(handler, ValueChangeEvent.getType());
+    }
+
+    @Override
+    public Double getValue() {
+        return value;
+    }
+
+    @Override
+    public void setValue(Double value) {
+        if (value < min) {
+            value = min;
+        } else if (value > max) {
+            value = max;
+        }
+
+        // Update handle position
+        final String styleAttribute = isVertical() ? "marginTop" : "marginLeft";
+        final String domProperty = isVertical() ? "offsetHeight"
+                : "offsetWidth";
+        final int handleSize = handle.getPropertyInt(domProperty);
+        final int baseSize = base.getPropertyInt(domProperty)
+                - (2 * BASE_BORDER_WIDTH);
+
+        final int range = baseSize - handleSize;
+        double v = value.doubleValue();
+
+        // Round value to resolution
+        if (resolution > 0) {
+            v = Math.round(v * Math.pow(10, resolution));
+            v = v / Math.pow(10, resolution);
+        } else {
+            v = Math.round(v);
+        }
+        final double valueRange = max - min;
+        double p = 0;
+        if (valueRange > 0) {
+            p = range * ((v - min) / valueRange);
+        }
+        if (p < 0) {
+            p = 0;
+        }
+        if (isVertical()) {
+            p = range - p;
+        }
+        final double pos = p;
+
+        handle.getStyle().setPropertyPx(styleAttribute, (int) Math.round(pos));
+
+        // Update value
+        this.value = new Double(v);
+        setFeedbackValue(v);
+    }
+
+    @Override
+    public void setValue(Double value, boolean fireEvents) {
+        if (value == null) {
+            return;
+        }
+
+        setValue(value);
+
+        if (fireEvents) {
+            fireValueChanged();
+        }
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VSplitPanelHorizontal.java b/client/src/com/vaadin/client/ui/VSplitPanelHorizontal.java
new file mode 100644 (file)
index 0000000..29937a6
--- /dev/null
@@ -0,0 +1,26 @@
+/* 
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import com.vaadin.shared.ui.Orientation;
+
+public class VSplitPanelHorizontal extends VAbstractSplitPanel {
+
+    public VSplitPanelHorizontal() {
+        super(Orientation.HORIZONTAL);
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VSplitPanelVertical.java b/client/src/com/vaadin/client/ui/VSplitPanelVertical.java
new file mode 100644 (file)
index 0000000..1e558fe
--- /dev/null
@@ -0,0 +1,26 @@
+/* 
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import com.vaadin.shared.ui.Orientation;
+
+public class VSplitPanelVertical extends VAbstractSplitPanel {
+
+    public VSplitPanelVertical() {
+        super(Orientation.VERTICAL);
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VTabsheet.java b/client/src/com/vaadin/client/ui/VTabsheet.java
new file mode 100644 (file)
index 0000000..d74665b
--- /dev/null
@@ -0,0 +1,1262 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.Iterator;
+import java.util.List;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Visibility;
+import com.google.gwt.dom.client.TableCellElement;
+import com.google.gwt.dom.client.TableElement;
+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.HasBlurHandlers;
+import com.google.gwt.event.dom.client.HasFocusHandlers;
+import com.google.gwt.event.dom.client.HasKeyDownHandlers;
+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.shared.HandlerRegistration;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.user.client.ui.impl.FocusImpl;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ConnectorMap;
+import com.vaadin.client.Focusable;
+import com.vaadin.client.TooltipInfo;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.Util;
+import com.vaadin.client.VCaption;
+import com.vaadin.shared.ComponentState;
+import com.vaadin.shared.EventId;
+import com.vaadin.shared.ui.ComponentStateUtil;
+import com.vaadin.shared.ui.tabsheet.TabsheetBaseConstants;
+import com.vaadin.shared.ui.tabsheet.TabsheetConstants;
+
+public class VTabsheet extends VTabsheetBase implements Focusable,
+        FocusHandler, BlurHandler, KeyDownHandler {
+
+    private static class VCloseEvent {
+        private Tab tab;
+
+        VCloseEvent(Tab tab) {
+            this.tab = tab;
+        }
+
+        public Tab getTab() {
+            return tab;
+        }
+
+    }
+
+    private interface VCloseHandler {
+        public void onClose(VCloseEvent event);
+    }
+
+    /**
+     * Representation of a single "tab" shown in the TabBar
+     * 
+     */
+    private static class Tab extends SimplePanel implements HasFocusHandlers,
+            HasBlurHandlers, HasKeyDownHandlers {
+        private static final String TD_CLASSNAME = CLASSNAME + "-tabitemcell";
+        private static final String TD_FIRST_CLASSNAME = TD_CLASSNAME
+                + "-first";
+        private static final String TD_SELECTED_CLASSNAME = TD_CLASSNAME
+                + "-selected";
+        private static final String TD_SELECTED_FIRST_CLASSNAME = TD_SELECTED_CLASSNAME
+                + "-first";
+        private static final String TD_DISABLED_CLASSNAME = TD_CLASSNAME
+                + "-disabled";
+
+        private static final String DIV_CLASSNAME = CLASSNAME + "-tabitem";
+        private static final String DIV_SELECTED_CLASSNAME = DIV_CLASSNAME
+                + "-selected";
+
+        private TabCaption tabCaption;
+        Element td = getElement();
+        private VCloseHandler closeHandler;
+
+        private boolean enabledOnServer = true;
+        private Element div;
+        private TabBar tabBar;
+        private boolean hiddenOnServer = false;
+
+        private String styleName;
+
+        private Tab(TabBar tabBar) {
+            super(DOM.createTD());
+            this.tabBar = tabBar;
+            setStyleName(td, TD_CLASSNAME);
+
+            div = DOM.createDiv();
+            focusImpl.setTabIndex(td, -1);
+            setStyleName(div, DIV_CLASSNAME);
+
+            DOM.appendChild(td, div);
+
+            tabCaption = new TabCaption(this, getTabsheet()
+                    .getApplicationConnection());
+            add(tabCaption);
+
+            addFocusHandler(getTabsheet());
+            addBlurHandler(getTabsheet());
+            addKeyDownHandler(getTabsheet());
+        }
+
+        public boolean isHiddenOnServer() {
+            return hiddenOnServer;
+        }
+
+        public void setHiddenOnServer(boolean hiddenOnServer) {
+            this.hiddenOnServer = hiddenOnServer;
+        }
+
+        @Override
+        protected Element getContainerElement() {
+            // Attach caption element to div, not td
+            return div;
+        }
+
+        public boolean isEnabledOnServer() {
+            return enabledOnServer;
+        }
+
+        public void setEnabledOnServer(boolean enabled) {
+            enabledOnServer = enabled;
+            setStyleName(td, TD_DISABLED_CLASSNAME, !enabled);
+            if (!enabled) {
+                focusImpl.setTabIndex(td, -1);
+            }
+        }
+
+        public void addClickHandler(ClickHandler handler) {
+            tabCaption.addClickHandler(handler);
+        }
+
+        public void setCloseHandler(VCloseHandler closeHandler) {
+            this.closeHandler = closeHandler;
+        }
+
+        /**
+         * Toggles the style names for the Tab
+         * 
+         * @param selected
+         *            true if the Tab is selected
+         * @param first
+         *            true if the Tab is the first visible Tab
+         */
+        public void setStyleNames(boolean selected, boolean first) {
+            setStyleName(td, TD_FIRST_CLASSNAME, first);
+            setStyleName(td, TD_SELECTED_CLASSNAME, selected);
+            setStyleName(td, TD_SELECTED_FIRST_CLASSNAME, selected && first);
+            setStyleName(div, DIV_SELECTED_CLASSNAME, selected);
+        }
+
+        public void setTabulatorIndex(int tabIndex) {
+            focusImpl.setTabIndex(td, tabIndex);
+        }
+
+        public boolean isClosable() {
+            return tabCaption.isClosable();
+        }
+
+        public void onClose() {
+            closeHandler.onClose(new VCloseEvent(this));
+        }
+
+        public VTabsheet getTabsheet() {
+            return tabBar.getTabsheet();
+        }
+
+        public void updateFromUIDL(UIDL tabUidl) {
+            tabCaption.updateCaption(tabUidl);
+
+            // Apply the styleName set for the tab
+            String newStyleName = tabUidl
+                    .getStringAttribute(TabsheetConstants.TAB_STYLE_NAME);
+            // Find the nth td element
+            if (newStyleName != null && newStyleName.length() != 0) {
+                if (!newStyleName.equals(styleName)) {
+                    // If we have a new style name
+                    if (styleName != null && styleName.length() != 0) {
+                        // Remove old style name if present
+                        td.removeClassName(TD_CLASSNAME + "-" + styleName);
+                    }
+                    // Set new style name
+                    td.addClassName(TD_CLASSNAME + "-" + newStyleName);
+                    styleName = newStyleName;
+                }
+            } else if (styleName != null) {
+                // Remove the set stylename if no stylename is present in the
+                // uidl
+                td.removeClassName(TD_CLASSNAME + "-" + styleName);
+                styleName = null;
+            }
+        }
+
+        public void recalculateCaptionWidth() {
+            tabCaption.setWidth(tabCaption.getRequiredWidth() + "px");
+        }
+
+        @Override
+        public HandlerRegistration addFocusHandler(FocusHandler handler) {
+            return addDomHandler(handler, FocusEvent.getType());
+        }
+
+        @Override
+        public HandlerRegistration addBlurHandler(BlurHandler handler) {
+            return addDomHandler(handler, BlurEvent.getType());
+        }
+
+        @Override
+        public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) {
+            return addDomHandler(handler, KeyDownEvent.getType());
+        }
+
+        public void focus() {
+            focusImpl.focus(td);
+        }
+
+        public void blur() {
+            focusImpl.blur(td);
+        }
+    }
+
+    public static class TabCaption extends VCaption {
+
+        private boolean closable = false;
+        private Element closeButton;
+        private Tab tab;
+        private ApplicationConnection client;
+
+        TabCaption(Tab tab, ApplicationConnection client) {
+            super(client);
+            this.client = client;
+            this.tab = tab;
+        }
+
+        public boolean updateCaption(UIDL uidl) {
+            if (uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION)) {
+                setTooltipInfo(new TooltipInfo(
+                        uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION),
+                        uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ERROR_MESSAGE)));
+            } else {
+                setTooltipInfo(null);
+            }
+
+            // TODO need to call this instead of super because the caption does
+            // not have an owner
+            boolean ret = updateCaptionWithoutOwner(
+                    uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_CAPTION),
+                    uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DISABLED),
+                    uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION),
+                    uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ERROR_MESSAGE),
+                    uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ICON));
+
+            setClosable(uidl.hasAttribute("closable"));
+
+            return ret;
+        }
+
+        private VTabsheet getTabsheet() {
+            return tab.getTabsheet();
+        }
+
+        @Override
+        public void onBrowserEvent(Event event) {
+            if (closable && event.getTypeInt() == Event.ONCLICK
+                    && event.getEventTarget().cast() == closeButton) {
+                tab.onClose();
+                event.stopPropagation();
+                event.preventDefault();
+            }
+
+            super.onBrowserEvent(event);
+
+            if (event.getTypeInt() == Event.ONLOAD) {
+                getTabsheet().tabSizeMightHaveChanged(getTab());
+            }
+        }
+
+        public Tab getTab() {
+            return tab;
+        }
+
+        public void setClosable(boolean closable) {
+            this.closable = closable;
+            if (closable && closeButton == null) {
+                closeButton = DOM.createSpan();
+                closeButton.setInnerHTML("&times;");
+                closeButton
+                        .setClassName(VTabsheet.CLASSNAME + "-caption-close");
+                getElement().insertBefore(closeButton,
+                        getElement().getLastChild());
+            } else if (!closable && closeButton != null) {
+                getElement().removeChild(closeButton);
+                closeButton = null;
+            }
+            if (closable) {
+                addStyleDependentName("closable");
+            } else {
+                removeStyleDependentName("closable");
+            }
+        }
+
+        public boolean isClosable() {
+            return closable;
+        }
+
+        @Override
+        public int getRequiredWidth() {
+            int width = super.getRequiredWidth();
+            if (closeButton != null) {
+                width += Util.getRequiredWidth(closeButton);
+            }
+            return width;
+        }
+
+        public Element getCloseButton() {
+            return closeButton;
+        }
+
+    }
+
+    static class TabBar extends ComplexPanel implements ClickHandler,
+            VCloseHandler {
+
+        private final Element tr = DOM.createTR();
+
+        private final Element spacerTd = DOM.createTD();
+
+        private Tab selected;
+
+        private VTabsheet tabsheet;
+
+        TabBar(VTabsheet tabsheet) {
+            this.tabsheet = tabsheet;
+
+            Element el = DOM.createTable();
+            Element tbody = DOM.createTBody();
+            DOM.appendChild(el, tbody);
+            DOM.appendChild(tbody, tr);
+            setStyleName(spacerTd, CLASSNAME + "-spacertd");
+            DOM.appendChild(tr, spacerTd);
+            DOM.appendChild(spacerTd, DOM.createDiv());
+            setElement(el);
+        }
+
+        @Override
+        public void onClose(VCloseEvent event) {
+            Tab tab = event.getTab();
+            if (!tab.isEnabledOnServer()) {
+                return;
+            }
+            int tabIndex = getWidgetIndex(tab);
+            getTabsheet().sendTabClosedEvent(tabIndex);
+        }
+
+        protected Element getContainerElement() {
+            return tr;
+        }
+
+        public int getTabCount() {
+            return getWidgetCount();
+        }
+
+        public Tab addTab() {
+            Tab t = new Tab(this);
+            int tabIndex = getTabCount();
+
+            // Logical attach
+            insert(t, tr, tabIndex, true);
+
+            if (tabIndex == 0) {
+                // Set the "first" style
+                t.setStyleNames(false, true);
+            }
+
+            t.addClickHandler(this);
+            t.setCloseHandler(this);
+
+            return t;
+        }
+
+        @Override
+        public void onClick(ClickEvent event) {
+            TabCaption caption = (TabCaption) event.getSource();
+            Element targetElement = event.getNativeEvent().getEventTarget()
+                    .cast();
+            // the tab should not be focused if the close button was clicked
+            if (targetElement == caption.getCloseButton()) {
+                return;
+            }
+
+            int index = getWidgetIndex(caption.getParent());
+            // IE needs explicit focus()
+            if (BrowserInfo.get().isIE()) {
+                getTabsheet().focus();
+            }
+            getTabsheet().onTabSelected(index);
+        }
+
+        public VTabsheet getTabsheet() {
+            return tabsheet;
+        }
+
+        public Tab getTab(int index) {
+            if (index < 0 || index >= getTabCount()) {
+                return null;
+            }
+            return (Tab) super.getWidget(index);
+        }
+
+        public void selectTab(int index) {
+            final Tab newSelected = getTab(index);
+            final Tab oldSelected = selected;
+
+            newSelected.setStyleNames(true, isFirstVisibleTab(index));
+            newSelected.setTabulatorIndex(getTabsheet().tabulatorIndex);
+
+            if (oldSelected != null && oldSelected != newSelected) {
+                oldSelected.setStyleNames(false,
+                        isFirstVisibleTab(getWidgetIndex(oldSelected)));
+                oldSelected.setTabulatorIndex(-1);
+            }
+
+            // Update the field holding the currently selected tab
+            selected = newSelected;
+
+            // The selected tab might need more (or less) space
+            newSelected.recalculateCaptionWidth();
+            getTab(tabsheet.activeTabIndex).recalculateCaptionWidth();
+        }
+
+        public void removeTab(int i) {
+            Tab tab = getTab(i);
+            if (tab == null) {
+                return;
+            }
+
+            remove(tab);
+
+            /*
+             * If this widget was selected we need to unmark it as the last
+             * selected
+             */
+            if (tab == selected) {
+                selected = null;
+            }
+
+            // FIXME: Shouldn't something be selected instead?
+        }
+
+        private boolean isFirstVisibleTab(int index) {
+            return getFirstVisibleTab() == index;
+        }
+
+        /**
+         * Returns the index of the first visible tab
+         * 
+         * @return
+         */
+        private int getFirstVisibleTab() {
+            return getNextVisibleTab(-1);
+        }
+
+        /**
+         * Find the next visible tab. Returns -1 if none is found.
+         * 
+         * @param i
+         * @return
+         */
+        private int getNextVisibleTab(int i) {
+            int tabs = getTabCount();
+            do {
+                i++;
+            } while (i < tabs && getTab(i).isHiddenOnServer());
+
+            if (i == tabs) {
+                return -1;
+            } else {
+                return i;
+            }
+        }
+
+        /**
+         * Find the previous visible tab. Returns -1 if none is found.
+         * 
+         * @param i
+         * @return
+         */
+        private int getPreviousVisibleTab(int i) {
+            do {
+                i--;
+            } while (i >= 0 && getTab(i).isHiddenOnServer());
+
+            return i;
+
+        }
+
+        public int scrollLeft(int currentFirstVisible) {
+            int prevVisible = getPreviousVisibleTab(currentFirstVisible);
+            if (prevVisible == -1) {
+                return -1;
+            }
+
+            Tab newFirst = getTab(prevVisible);
+            newFirst.setVisible(true);
+            newFirst.recalculateCaptionWidth();
+
+            return prevVisible;
+        }
+
+        public int scrollRight(int currentFirstVisible) {
+            int nextVisible = getNextVisibleTab(currentFirstVisible);
+            if (nextVisible == -1) {
+                return -1;
+            }
+            Tab currentFirst = getTab(currentFirstVisible);
+            currentFirst.setVisible(false);
+            currentFirst.recalculateCaptionWidth();
+            return nextVisible;
+        }
+    }
+
+    public static final String CLASSNAME = "v-tabsheet";
+
+    public static final String TABS_CLASSNAME = "v-tabsheet-tabcontainer";
+    public static final String SCROLLER_CLASSNAME = "v-tabsheet-scroller";
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public final Element tabs; // tabbar and 'scroller' container
+    Tab focusedTab;
+    /**
+     * The tabindex property (position in the browser's focus cycle.) Named like
+     * this to avoid confusion with activeTabIndex.
+     */
+    int tabulatorIndex = 0;
+
+    private static final FocusImpl focusImpl = FocusImpl.getFocusImplForPanel();
+
+    private final Element scroller; // tab-scroller element
+    private final Element scrollerNext; // tab-scroller next button element
+    private final Element scrollerPrev; // tab-scroller prev button element
+
+    /**
+     * The index of the first visible tab (when scrolled)
+     */
+    private int scrollerIndex = 0;
+
+    final TabBar tb = new TabBar(this);
+    /** For internal use only. May be removed or replaced in the future. */
+    public final VTabsheetPanel tp = new VTabsheetPanel();
+    /** For internal use only. May be removed or replaced in the future. */
+    public final Element contentNode;
+
+    private final Element deco;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean waitingForResponse;
+
+    private String currentStyle;
+
+    /**
+     * @return Whether the tab could be selected or not.
+     */
+    private boolean onTabSelected(final int tabIndex) {
+        Tab tab = tb.getTab(tabIndex);
+        if (client == null || disabled || waitingForResponse) {
+            return false;
+        }
+        if (!tab.isEnabledOnServer() || tab.isHiddenOnServer()) {
+            return false;
+        }
+        if (activeTabIndex != tabIndex) {
+            tb.selectTab(tabIndex);
+
+            // If this TabSheet already has focus, set the new selected tab
+            // as focused.
+            if (focusedTab != null) {
+                focusedTab = tab;
+            }
+
+            addStyleDependentName("loading");
+            // Hide the current contents so a loading indicator can be shown
+            // instead
+            Widget currentlyDisplayedWidget = tp.getWidget(tp
+                    .getVisibleWidget());
+            currentlyDisplayedWidget.getElement().getParentElement().getStyle()
+                    .setVisibility(Visibility.HIDDEN);
+            client.updateVariable(id, "selected", tabKeys.get(tabIndex)
+                    .toString(), true);
+            waitingForResponse = true;
+        }
+        // Note that we return true when tabIndex == activeTabIndex; the active
+        // tab could be selected, it's just a no-op.
+        return true;
+    }
+
+    public ApplicationConnection getApplicationConnection() {
+        return client;
+    }
+
+    public void tabSizeMightHaveChanged(Tab tab) {
+        // icon onloads may change total width of tabsheet
+        if (isDynamicWidth()) {
+            updateDynamicWidth();
+        }
+        updateTabScroller();
+
+    }
+
+    void sendTabClosedEvent(int tabIndex) {
+        client.updateVariable(id, "close", tabKeys.get(tabIndex), true);
+    }
+
+    boolean isDynamicWidth() {
+        ComponentConnector paintable = ConnectorMap.get(client).getConnector(
+                this);
+        return paintable.isUndefinedWidth();
+    }
+
+    boolean isDynamicHeight() {
+        ComponentConnector paintable = ConnectorMap.get(client).getConnector(
+                this);
+        return paintable.isUndefinedHeight();
+    }
+
+    public VTabsheet() {
+        super(CLASSNAME);
+
+        addHandler(this, FocusEvent.getType());
+        addHandler(this, BlurEvent.getType());
+
+        // Tab scrolling
+        DOM.setStyleAttribute(getElement(), "overflow", "hidden");
+        tabs = DOM.createDiv();
+        DOM.setElementProperty(tabs, "className", TABS_CLASSNAME);
+        scroller = DOM.createDiv();
+
+        DOM.setElementProperty(scroller, "className", SCROLLER_CLASSNAME);
+        scrollerPrev = DOM.createButton();
+        DOM.setElementProperty(scrollerPrev, "className", SCROLLER_CLASSNAME
+                + "Prev");
+        DOM.sinkEvents(scrollerPrev, Event.ONCLICK);
+        scrollerNext = DOM.createButton();
+        DOM.setElementProperty(scrollerNext, "className", SCROLLER_CLASSNAME
+                + "Next");
+        DOM.sinkEvents(scrollerNext, Event.ONCLICK);
+        DOM.appendChild(getElement(), tabs);
+
+        // Tabs
+        tp.setStyleName(CLASSNAME + "-tabsheetpanel");
+        contentNode = DOM.createDiv();
+
+        deco = DOM.createDiv();
+
+        addStyleDependentName("loading"); // Indicate initial progress
+        tb.setStyleName(CLASSNAME + "-tabs");
+        DOM.setElementProperty(contentNode, "className", CLASSNAME + "-content");
+        DOM.setElementProperty(deco, "className", CLASSNAME + "-deco");
+
+        add(tb, tabs);
+        DOM.appendChild(scroller, scrollerPrev);
+        DOM.appendChild(scroller, scrollerNext);
+
+        DOM.appendChild(getElement(), contentNode);
+        add(tp, contentNode);
+        DOM.appendChild(getElement(), deco);
+
+        DOM.appendChild(tabs, scroller);
+
+        // TODO Use for Safari only. Fix annoying 1px first cell in TabBar.
+        // DOM.setStyleAttribute(DOM.getFirstChild(DOM.getFirstChild(DOM
+        // .getFirstChild(tb.getElement()))), "display", "none");
+
+    }
+
+    @Override
+    public void onBrowserEvent(Event event) {
+        if (event.getTypeInt() == Event.ONCLICK) {
+            // Tab scrolling
+            if (isScrolledTabs() && DOM.eventGetTarget(event) == scrollerPrev) {
+                int newFirstIndex = tb.scrollLeft(scrollerIndex);
+                if (newFirstIndex != -1) {
+                    scrollerIndex = newFirstIndex;
+                    updateTabScroller();
+                }
+                event.stopPropagation();
+                return;
+            } else if (isClippedTabs()
+                    && DOM.eventGetTarget(event) == scrollerNext) {
+                int newFirstIndex = tb.scrollRight(scrollerIndex);
+
+                if (newFirstIndex != -1) {
+                    scrollerIndex = newFirstIndex;
+                    updateTabScroller();
+                }
+                event.stopPropagation();
+                return;
+            }
+        }
+        super.onBrowserEvent(event);
+    }
+
+    /**
+     * Checks if the tab with the selected index has been scrolled out of the
+     * view (on the left side).
+     * 
+     * @param index
+     * @return
+     */
+    private boolean scrolledOutOfView(int index) {
+        return scrollerIndex > index;
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void handleStyleNames(UIDL uidl, ComponentState state) {
+        // Add proper stylenames for all elements (easier to prevent unwanted
+        // style inheritance)
+        if (ComponentStateUtil.hasStyles(state)) {
+            final List<String> styles = state.styles;
+            if (!currentStyle.equals(styles.toString())) {
+                currentStyle = styles.toString();
+                final String tabsBaseClass = TABS_CLASSNAME;
+                String tabsClass = tabsBaseClass;
+                final String contentBaseClass = CLASSNAME + "-content";
+                String contentClass = contentBaseClass;
+                final String decoBaseClass = CLASSNAME + "-deco";
+                String decoClass = decoBaseClass;
+                for (String style : styles) {
+                    tb.addStyleDependentName(style);
+                    tabsClass += " " + tabsBaseClass + "-" + style;
+                    contentClass += " " + contentBaseClass + "-" + style;
+                    decoClass += " " + decoBaseClass + "-" + style;
+                }
+                DOM.setElementProperty(tabs, "className", tabsClass);
+                DOM.setElementProperty(contentNode, "className", contentClass);
+                DOM.setElementProperty(deco, "className", decoClass);
+                borderW = -1;
+            }
+        } else {
+            tb.setStyleName(CLASSNAME + "-tabs");
+            DOM.setElementProperty(tabs, "className", TABS_CLASSNAME);
+            DOM.setElementProperty(contentNode, "className", CLASSNAME
+                    + "-content");
+            DOM.setElementProperty(deco, "className", CLASSNAME + "-deco");
+        }
+
+        if (uidl.hasAttribute("hidetabs")) {
+            tb.setVisible(false);
+            addStyleName(CLASSNAME + "-hidetabs");
+        } else {
+            tb.setVisible(true);
+            removeStyleName(CLASSNAME + "-hidetabs");
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void updateDynamicWidth() {
+        // Find width consumed by tabs
+        TableCellElement spacerCell = ((TableElement) tb.getElement().cast())
+                .getRows().getItem(0).getCells().getItem(tb.getTabCount());
+
+        int spacerWidth = spacerCell.getOffsetWidth();
+        DivElement div = (DivElement) spacerCell.getFirstChildElement();
+
+        int spacerMinWidth = spacerCell.getOffsetWidth() - div.getOffsetWidth();
+
+        int tabsWidth = tb.getOffsetWidth() - spacerWidth + spacerMinWidth;
+
+        // Find content width
+        Style style = tp.getElement().getStyle();
+        String overflow = style.getProperty("overflow");
+        style.setProperty("overflow", "hidden");
+        style.setPropertyPx("width", tabsWidth);
+
+        boolean hasTabs = tp.getWidgetCount() > 0;
+
+        Style wrapperstyle = null;
+        if (hasTabs) {
+            wrapperstyle = tp.getWidget(tp.getVisibleWidget()).getElement()
+                    .getParentElement().getStyle();
+            wrapperstyle.setPropertyPx("width", tabsWidth);
+        }
+        // Get content width from actual widget
+
+        int contentWidth = 0;
+        if (hasTabs) {
+            contentWidth = tp.getWidget(tp.getVisibleWidget()).getOffsetWidth();
+        }
+        style.setProperty("overflow", overflow);
+
+        // Set widths to max(tabs,content)
+        if (tabsWidth < contentWidth) {
+            tabsWidth = contentWidth;
+        }
+
+        int outerWidth = tabsWidth + getContentAreaBorderWidth();
+
+        tabs.getStyle().setPropertyPx("width", outerWidth);
+        style.setPropertyPx("width", tabsWidth);
+        if (hasTabs) {
+            wrapperstyle.setPropertyPx("width", tabsWidth);
+        }
+
+        contentNode.getStyle().setPropertyPx("width", tabsWidth);
+        super.setWidth(outerWidth + "px");
+        updateOpenTabSize();
+    }
+
+    @Override
+    public void renderTab(final UIDL tabUidl, int index, boolean selected,
+            boolean hidden) {
+        Tab tab = tb.getTab(index);
+        if (tab == null) {
+            tab = tb.addTab();
+        }
+        tab.updateFromUIDL(tabUidl);
+        tab.setEnabledOnServer((!disabledTabKeys.contains(tabKeys.get(index))));
+        tab.setHiddenOnServer(hidden);
+
+        if (scrolledOutOfView(index)) {
+            // Should not set tabs visible if they are scrolled out of view
+            hidden = true;
+        }
+        // Set the current visibility of the tab (in the browser)
+        tab.setVisible(!hidden);
+
+        /*
+         * Force the width of the caption container so the content will not wrap
+         * and tabs won't be too narrow in certain browsers
+         */
+        tab.recalculateCaptionWidth();
+
+        UIDL tabContentUIDL = null;
+        ComponentConnector tabContentPaintable = null;
+        Widget tabContentWidget = null;
+        if (tabUidl.getChildCount() > 0) {
+            tabContentUIDL = tabUidl.getChildUIDL(0);
+            tabContentPaintable = client.getPaintable(tabContentUIDL);
+            tabContentWidget = tabContentPaintable.getWidget();
+        }
+
+        if (tabContentPaintable != null) {
+            /* This is a tab with content information */
+
+            int oldIndex = tp.getWidgetIndex(tabContentWidget);
+            if (oldIndex != -1 && oldIndex != index) {
+                /*
+                 * The tab has previously been rendered in another position so
+                 * we must move the cached content to correct position
+                 */
+                tp.insert(tabContentWidget, index);
+            }
+        } else {
+            /* A tab whose content has not yet been loaded */
+
+            /*
+             * Make sure there is a corresponding empty tab in tp. The same
+             * operation as the moving above but for not-loaded tabs.
+             */
+            if (index < tp.getWidgetCount()) {
+                Widget oldWidget = tp.getWidget(index);
+                if (!(oldWidget instanceof PlaceHolder)) {
+                    tp.insert(new PlaceHolder(), index);
+                }
+            }
+
+        }
+
+        if (selected) {
+            renderContent(tabContentUIDL);
+            tb.selectTab(index);
+        } else {
+            if (tabContentUIDL != null) {
+                // updating a drawn child on hidden tab
+                if (tp.getWidgetIndex(tabContentWidget) < 0) {
+                    tp.insert(tabContentWidget, index);
+                }
+            } else if (tp.getWidgetCount() <= index) {
+                tp.add(new PlaceHolder());
+            }
+        }
+    }
+
+    public class PlaceHolder extends VLabel {
+        public PlaceHolder() {
+            super("");
+        }
+    }
+
+    @Override
+    protected void selectTab(int index, final UIDL contentUidl) {
+        if (index != activeTabIndex) {
+            activeTabIndex = index;
+            tb.selectTab(activeTabIndex);
+        }
+        renderContent(contentUidl);
+    }
+
+    private void renderContent(final UIDL contentUIDL) {
+        final ComponentConnector content = client.getPaintable(contentUIDL);
+        Widget newWidget = content.getWidget();
+        if (tp.getWidgetCount() > activeTabIndex) {
+            Widget old = tp.getWidget(activeTabIndex);
+            if (old != newWidget) {
+                tp.remove(activeTabIndex);
+                ConnectorMap paintableMap = ConnectorMap.get(client);
+                if (paintableMap.isConnector(old)) {
+                    paintableMap.unregisterConnector(paintableMap
+                            .getConnector(old));
+                }
+                tp.insert(content.getWidget(), activeTabIndex);
+            }
+        } else {
+            tp.add(content.getWidget());
+        }
+
+        tp.showWidget(activeTabIndex);
+
+        VTabsheet.this.iLayout();
+        /*
+         * The size of a cached, relative sized component must be updated to
+         * report correct size to updateOpenTabSize().
+         */
+        if (contentUIDL.getBooleanAttribute("cached")) {
+            client.handleComponentRelativeSize(content.getWidget());
+        }
+        updateOpenTabSize();
+        VTabsheet.this.removeStyleDependentName("loading");
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void updateContentNodeHeight() {
+        if (!isDynamicHeight()) {
+            int contentHeight = getOffsetHeight();
+            contentHeight -= DOM.getElementPropertyInt(deco, "offsetHeight");
+            contentHeight -= tb.getOffsetHeight();
+            if (contentHeight < 0) {
+                contentHeight = 0;
+            }
+
+            // Set proper values for content element
+            DOM.setStyleAttribute(contentNode, "height", contentHeight + "px");
+        } else {
+            DOM.setStyleAttribute(contentNode, "height", "");
+        }
+    }
+
+    public void iLayout() {
+        updateTabScroller();
+    }
+
+    /**
+     * Sets the size of the visible tab (component). As the tab is set to
+     * position: absolute (to work around a firefox flickering bug) we must keep
+     * this up-to-date by hand.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public void updateOpenTabSize() {
+        /*
+         * The overflow=auto element must have a height specified, otherwise it
+         * will be just as high as the contents and no scrollbars will appear
+         */
+        int height = -1;
+        int width = -1;
+        int minWidth = 0;
+
+        if (!isDynamicHeight()) {
+            height = contentNode.getOffsetHeight();
+        }
+        if (!isDynamicWidth()) {
+            width = contentNode.getOffsetWidth() - getContentAreaBorderWidth();
+        } else {
+            /*
+             * If the tabbar is wider than the content we need to use the tabbar
+             * width as minimum width so scrollbars get placed correctly (at the
+             * right edge).
+             */
+            minWidth = tb.getOffsetWidth() - getContentAreaBorderWidth();
+        }
+        tp.fixVisibleTabSize(width, height, minWidth);
+
+    }
+
+    /**
+     * Layouts the tab-scroller elements, and applies styles.
+     */
+    private void updateTabScroller() {
+        if (!isDynamicWidth()) {
+            ComponentConnector paintable = ConnectorMap.get(client)
+                    .getConnector(this);
+            DOM.setStyleAttribute(tabs, "width", paintable.getState().width);
+        }
+
+        // Make sure scrollerIndex is valid
+        if (scrollerIndex < 0 || scrollerIndex > tb.getTabCount()) {
+            scrollerIndex = tb.getFirstVisibleTab();
+        } else if (tb.getTabCount() > 0
+                && tb.getTab(scrollerIndex).isHiddenOnServer()) {
+            scrollerIndex = tb.getNextVisibleTab(scrollerIndex);
+        }
+
+        boolean scrolled = isScrolledTabs();
+        boolean clipped = isClippedTabs();
+        if (tb.getTabCount() > 0 && tb.isVisible() && (scrolled || clipped)) {
+            DOM.setStyleAttribute(scroller, "display", "");
+            DOM.setElementProperty(scrollerPrev, "className",
+                    SCROLLER_CLASSNAME + (scrolled ? "Prev" : "Prev-disabled"));
+            DOM.setElementProperty(scrollerNext, "className",
+                    SCROLLER_CLASSNAME + (clipped ? "Next" : "Next-disabled"));
+        } else {
+            DOM.setStyleAttribute(scroller, "display", "none");
+        }
+
+        if (BrowserInfo.get().isSafari()) {
+            // fix tab height for safari, bugs sometimes if tabs contain icons
+            String property = tabs.getStyle().getProperty("height");
+            if (property == null || property.equals("")) {
+                tabs.getStyle().setPropertyPx("height", tb.getOffsetHeight());
+            }
+            /*
+             * another hack for webkits. tabscroller sometimes drops without
+             * "shaking it" reproducable in
+             * com.vaadin.tests.components.tabsheet.TabSheetIcons
+             */
+            final Style style = scroller.getStyle();
+            style.setProperty("whiteSpace", "normal");
+            Scheduler.get().scheduleDeferred(new Command() {
+
+                @Override
+                public void execute() {
+                    style.setProperty("whiteSpace", "");
+                }
+            });
+        }
+
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void showAllTabs() {
+        scrollerIndex = tb.getFirstVisibleTab();
+        for (int i = 0; i < tb.getTabCount(); i++) {
+            Tab t = tb.getTab(i);
+            if (!t.isHiddenOnServer()) {
+                t.setVisible(true);
+            }
+        }
+    }
+
+    private boolean isScrolledTabs() {
+        return scrollerIndex > tb.getFirstVisibleTab();
+    }
+
+    private boolean isClippedTabs() {
+        return (tb.getOffsetWidth() - DOM.getElementPropertyInt((Element) tb
+                .getContainerElement().getLastChild().cast(), "offsetWidth")) > getOffsetWidth()
+                - (isScrolledTabs() ? scroller.getOffsetWidth() : 0);
+    }
+
+    private boolean isClipped(Tab tab) {
+        return tab.getAbsoluteLeft() + tab.getOffsetWidth() > getAbsoluteLeft()
+                + getOffsetWidth() - scroller.getOffsetWidth();
+    }
+
+    @Override
+    protected void clearPaintables() {
+
+        int i = tb.getTabCount();
+        while (i > 0) {
+            tb.removeTab(--i);
+        }
+        tp.clear();
+
+    }
+
+    @Override
+    public Iterator<Widget> getWidgetIterator() {
+        return tp.iterator();
+    }
+
+    private int borderW = -1;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public int getContentAreaBorderWidth() {
+        if (borderW < 0) {
+            borderW = Util.measureHorizontalBorder(contentNode);
+        }
+        return borderW;
+    }
+
+    @Override
+    public int getTabCount() {
+        return tb.getTabCount();
+    }
+
+    @Override
+    public ComponentConnector getTab(int index) {
+        if (tp.getWidgetCount() > index) {
+            Widget widget = tp.getWidget(index);
+            return ConnectorMap.get(client).getConnector(widget);
+        }
+        return null;
+    }
+
+    @Override
+    public void removeTab(int index) {
+        tb.removeTab(index);
+        /*
+         * This must be checked because renderTab automatically removes the
+         * active tab content when it changes
+         */
+        if (tp.getWidgetCount() > index) {
+            tp.remove(index);
+        }
+    }
+
+    @Override
+    public void onBlur(BlurEvent event) {
+        if (focusedTab != null && event.getSource() instanceof Tab) {
+            focusedTab = null;
+            if (client.hasEventListeners(this, EventId.BLUR)) {
+                client.updateVariable(id, EventId.BLUR, "", true);
+            }
+        }
+    }
+
+    @Override
+    public void onFocus(FocusEvent event) {
+        if (focusedTab == null && event.getSource() instanceof Tab) {
+            focusedTab = (Tab) event.getSource();
+            if (client.hasEventListeners(this, EventId.FOCUS)) {
+                client.updateVariable(id, EventId.FOCUS, "", true);
+            }
+        }
+    }
+
+    @Override
+    public void focus() {
+        tb.getTab(activeTabIndex).focus();
+    }
+
+    public void blur() {
+        tb.getTab(activeTabIndex).blur();
+    }
+
+    @Override
+    public void onKeyDown(KeyDownEvent event) {
+        if (event.getSource() instanceof Tab) {
+            int keycode = event.getNativeEvent().getKeyCode();
+
+            if (keycode == getPreviousTabKey()) {
+                selectPreviousTab();
+            } else if (keycode == getNextTabKey()) {
+                selectNextTab();
+            } else if (keycode == getCloseTabKey()) {
+                Tab tab = tb.getTab(activeTabIndex);
+                if (tab.isClosable()) {
+                    tab.onClose();
+                }
+            }
+        }
+    }
+
+    /**
+     * @return The key code of the keyboard shortcut that selects the previous
+     *         tab in a focused tabsheet.
+     */
+    protected int getPreviousTabKey() {
+        return KeyCodes.KEY_LEFT;
+    }
+
+    /**
+     * @return The key code of the keyboard shortcut that selects the next tab
+     *         in a focused tabsheet.
+     */
+    protected int getNextTabKey() {
+        return KeyCodes.KEY_RIGHT;
+    }
+
+    /**
+     * @return The key code of the keyboard shortcut that closes the currently
+     *         selected tab in a focused tabsheet.
+     */
+    protected int getCloseTabKey() {
+        return KeyCodes.KEY_DELETE;
+    }
+
+    private void selectPreviousTab() {
+        int newTabIndex = activeTabIndex;
+        // Find the previous visible and enabled tab if any.
+        do {
+            newTabIndex--;
+        } while (newTabIndex >= 0 && !onTabSelected(newTabIndex));
+
+        if (newTabIndex >= 0) {
+            activeTabIndex = newTabIndex;
+            if (isScrolledTabs()) {
+                // Scroll until the new active tab is visible
+                int newScrollerIndex = scrollerIndex;
+                while (tb.getTab(activeTabIndex).getAbsoluteLeft() < getAbsoluteLeft()
+                        && newScrollerIndex != -1) {
+                    newScrollerIndex = tb.scrollLeft(newScrollerIndex);
+                }
+                scrollerIndex = newScrollerIndex;
+                updateTabScroller();
+            }
+        }
+    }
+
+    private void selectNextTab() {
+        int newTabIndex = activeTabIndex;
+        // Find the next visible and enabled tab if any.
+        do {
+            newTabIndex++;
+        } while (newTabIndex < getTabCount() && !onTabSelected(newTabIndex));
+
+        if (newTabIndex < getTabCount()) {
+            activeTabIndex = newTabIndex;
+            if (isClippedTabs()) {
+                // Scroll until the new active tab is completely visible
+                int newScrollerIndex = scrollerIndex;
+                while (isClipped(tb.getTab(activeTabIndex))
+                        && newScrollerIndex != -1) {
+                    newScrollerIndex = tb.scrollRight(newScrollerIndex);
+                }
+                scrollerIndex = newScrollerIndex;
+                updateTabScroller();
+            }
+        }
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VTabsheetBase.java b/client/src/com/vaadin/client/ui/VTabsheetBase.java
new file mode 100644 (file)
index 0000000..02e29a5
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.UIDL;
+
+public abstract class VTabsheetBase extends ComplexPanel {
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public String id;
+    /** 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 final ArrayList<String> tabKeys = new ArrayList<String>();
+    /** For internal use only. May be removed or replaced in the future. */
+    public Set<String> disabledTabKeys = new HashSet<String>();
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public int activeTabIndex = 0;
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean disabled;
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean readonly;
+
+    public VTabsheetBase(String classname) {
+        setElement(DOM.createDiv());
+        setStyleName(classname);
+    }
+
+    /**
+     * @return a list of currently shown Widgets
+     */
+    public abstract Iterator<Widget> getWidgetIterator();
+
+    /**
+     * Clears current tabs and contents
+     */
+    abstract protected void clearPaintables();
+
+    /**
+     * Implement in extending classes. This method should render needed elements
+     * and set the visibility of the tab according to the 'selected' parameter.
+     */
+    public abstract void renderTab(final UIDL tabUidl, int index,
+            boolean selected, boolean hidden);
+
+    /**
+     * Implement in extending classes. This method should render any previously
+     * non-cached content and set the activeTabIndex property to the specified
+     * index.
+     */
+    protected abstract void selectTab(int index, final UIDL contentUidl);
+
+    /**
+     * Implement in extending classes. This method should return the number of
+     * tabs currently rendered.
+     */
+    public abstract int getTabCount();
+
+    /**
+     * Implement in extending classes. This method should return the Paintable
+     * corresponding to the given index.
+     */
+    public abstract ComponentConnector getTab(int index);
+
+    /**
+     * Implement in extending classes. This method should remove the rendered
+     * tab with the specified index.
+     */
+    public abstract void removeTab(int index);
+}
diff --git a/client/src/com/vaadin/client/ui/VTabsheetPanel.java b/client/src/com/vaadin/client/ui/VTabsheetPanel.java
new file mode 100644 (file)
index 0000000..3a4c4f8
--- /dev/null
@@ -0,0 +1,200 @@
+/* 
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
+
+/**
+ * A panel that displays all of its child widgets in a 'deck', where only one
+ * can be visible at a time. It is used by
+ * {@link com.vaadin.client.ui.VTabsheet}.
+ * 
+ * This class has the same basic functionality as the GWT DeckPanel
+ * {@link com.google.gwt.user.client.ui.DeckPanel}, with the exception that it
+ * doesn't manipulate the child widgets' width and height attributes.
+ */
+public class VTabsheetPanel extends ComplexPanel {
+
+    private Widget visibleWidget;
+
+    private final TouchScrollHandler touchScrollHandler;
+
+    /**
+     * Creates an empty tabsheet panel.
+     */
+    public VTabsheetPanel() {
+        setElement(DOM.createDiv());
+        touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this);
+    }
+
+    /**
+     * Adds the specified widget to the deck.
+     * 
+     * @param w
+     *            the widget to be added
+     */
+    @Override
+    public void add(Widget w) {
+        Element el = createContainerElement();
+        DOM.appendChild(getElement(), el);
+        super.add(w, el);
+    }
+
+    private Element createContainerElement() {
+        Element el = DOM.createDiv();
+        DOM.setStyleAttribute(el, "position", "absolute");
+        hide(el);
+        touchScrollHandler.addElement(el);
+        return el;
+    }
+
+    /**
+     * Gets the index of the currently-visible widget.
+     * 
+     * @return the visible widget's index
+     */
+    public int getVisibleWidget() {
+        return getWidgetIndex(visibleWidget);
+    }
+
+    /**
+     * Inserts a widget before the specified index.
+     * 
+     * @param w
+     *            the widget to be inserted
+     * @param beforeIndex
+     *            the index before which it will be inserted
+     * @throws IndexOutOfBoundsException
+     *             if <code>beforeIndex</code> is out of range
+     */
+    public void insert(Widget w, int beforeIndex) {
+        Element el = createContainerElement();
+        DOM.insertChild(getElement(), el, beforeIndex);
+        super.insert(w, el, beforeIndex, false);
+    }
+
+    @Override
+    public boolean remove(Widget w) {
+        Element child = w.getElement();
+        Element parent = null;
+        if (child != null) {
+            parent = DOM.getParent(child);
+        }
+        final boolean removed = super.remove(w);
+        if (removed) {
+            if (visibleWidget == w) {
+                visibleWidget = null;
+            }
+            if (parent != null) {
+                DOM.removeChild(getElement(), parent);
+            }
+            touchScrollHandler.removeElement(parent);
+        }
+        return removed;
+    }
+
+    /**
+     * Shows the widget at the specified index. This causes the currently-
+     * visible widget to be hidden.
+     * 
+     * @param index
+     *            the index of the widget to be shown
+     */
+    public void showWidget(int index) {
+        checkIndexBoundsForAccess(index);
+        Widget newVisible = getWidget(index);
+        if (visibleWidget != newVisible) {
+            if (visibleWidget != null) {
+                hide(DOM.getParent(visibleWidget.getElement()));
+            }
+            visibleWidget = newVisible;
+            touchScrollHandler.setElements(visibleWidget.getElement()
+                    .getParentElement());
+        }
+        // Always ensure the selected tab is visible. If server prevents a tab
+        // change we might end up here with visibleWidget == newVisible but its
+        // parent is still hidden.
+        unHide(DOM.getParent(visibleWidget.getElement()));
+    }
+
+    private void hide(Element e) {
+        DOM.setStyleAttribute(e, "visibility", "hidden");
+        DOM.setStyleAttribute(e, "top", "-100000px");
+        DOM.setStyleAttribute(e, "left", "-100000px");
+    }
+
+    private void unHide(Element e) {
+        DOM.setStyleAttribute(e, "top", "0px");
+        DOM.setStyleAttribute(e, "left", "0px");
+        DOM.setStyleAttribute(e, "visibility", "");
+    }
+
+    public void fixVisibleTabSize(int width, int height, int minWidth) {
+        if (visibleWidget == null) {
+            return;
+        }
+
+        boolean dynamicHeight = false;
+
+        if (height < 0) {
+            height = visibleWidget.getOffsetHeight();
+            dynamicHeight = true;
+        }
+        if (width < 0) {
+            width = visibleWidget.getOffsetWidth();
+        }
+        if (width < minWidth) {
+            width = minWidth;
+        }
+
+        Element wrapperDiv = (Element) visibleWidget.getElement()
+                .getParentElement();
+
+        // width first
+        getElement().getStyle().setPropertyPx("width", width);
+        wrapperDiv.getStyle().setPropertyPx("width", width);
+
+        if (dynamicHeight) {
+            // height of widget might have changed due wrapping
+            height = visibleWidget.getOffsetHeight();
+        }
+        // v-tabsheet-tabsheetpanel height
+        getElement().getStyle().setPropertyPx("height", height);
+
+        // widget wrapper height
+        if (dynamicHeight) {
+            wrapperDiv.getStyle().clearHeight();
+        } else {
+            // widget wrapper height
+            wrapperDiv.getStyle().setPropertyPx("height", height);
+        }
+    }
+
+    public void replaceComponent(Widget oldComponent, Widget newComponent) {
+        boolean isVisible = (visibleWidget == oldComponent);
+        int widgetIndex = getWidgetIndex(oldComponent);
+        remove(oldComponent);
+        insert(newComponent, widgetIndex);
+        if (isVisible) {
+            showWidget(widgetIndex);
+        }
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VTextArea.java b/client/src/com/vaadin/client/ui/VTextArea.java
new file mode 100644 (file)
index 0000000..03c5163
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.dom.client.TextAreaElement;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.Util;
+
+/**
+ * This class represents a multiline textfield (textarea).
+ * 
+ * TODO consider replacing this with a RichTextArea based implementation. IE
+ * does not support CSS height for textareas in Strict mode :-(
+ * 
+ * @author Vaadin Ltd.
+ * 
+ */
+public class VTextArea extends VTextField {
+    public static final String CLASSNAME = "v-textarea";
+    private boolean wordwrap = true;
+    private MaxLengthHandler maxLengthHandler = new MaxLengthHandler();
+    private boolean browserSupportsMaxLengthAttribute = browserSupportsMaxLengthAttribute();
+
+    public VTextArea() {
+        super(DOM.createTextArea());
+        setStyleName(CLASSNAME);
+        if (!browserSupportsMaxLengthAttribute) {
+            addKeyUpHandler(maxLengthHandler);
+            addChangeHandler(maxLengthHandler);
+            sinkEvents(Event.ONPASTE);
+        }
+    }
+
+    public TextAreaElement getTextAreaElement() {
+        return super.getElement().cast();
+    }
+
+    public void setRows(int rows) {
+        getTextAreaElement().setRows(rows);
+    }
+
+    private class MaxLengthHandler implements KeyUpHandler, ChangeHandler {
+
+        @Override
+        public void onKeyUp(KeyUpEvent event) {
+            enforceMaxLength();
+        }
+
+        public void onPaste(Event event) {
+            enforceMaxLength();
+        }
+
+        @Override
+        public void onChange(ChangeEvent event) {
+            // Opera does not support paste events so this enforces max length
+            // for Opera.
+            enforceMaxLength();
+        }
+
+    }
+
+    protected void enforceMaxLength() {
+        if (getMaxLength() >= 0) {
+            Scheduler.get().scheduleDeferred(new Command() {
+                @Override
+                public void execute() {
+                    if (getText().length() > getMaxLength()) {
+                        setText(getText().substring(0, getMaxLength()));
+                    }
+                }
+            });
+        }
+    }
+
+    protected boolean browserSupportsMaxLengthAttribute() {
+        BrowserInfo info = BrowserInfo.get();
+        if (info.isFirefox() && info.isBrowserVersionNewerOrEqual(4, 0)) {
+            return true;
+        }
+        if (info.isSafari() && info.isBrowserVersionNewerOrEqual(5, 0)) {
+            return true;
+        }
+        if (info.isIE() && info.isBrowserVersionNewerOrEqual(10, 0)) {
+            return true;
+        }
+        if (info.isAndroid() && info.isBrowserVersionNewerOrEqual(2, 3)) {
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    protected void updateMaxLength(int maxLength) {
+        if (browserSupportsMaxLengthAttribute) {
+            super.updateMaxLength(maxLength);
+        } else {
+            // Events handled by MaxLengthHandler. This call enforces max length
+            // when the max length value has changed
+            enforceMaxLength();
+        }
+    }
+
+    @Override
+    public void onBrowserEvent(Event event) {
+        super.onBrowserEvent(event);
+        if (event.getTypeInt() == Event.ONPASTE) {
+            maxLengthHandler.onPaste(event);
+        }
+    }
+
+    @Override
+    public int getCursorPos() {
+        // This is needed so that TextBoxImplIE6 is used to return the correct
+        // position for old Internet Explorer versions where it has to be
+        // detected in a different way.
+        return getImpl().getTextAreaCursorPos(getElement());
+    }
+
+    @Override
+    protected void setMaxLengthToElement(int newMaxLength) {
+        // There is no maxlength property for textarea. The maximum length is
+        // enforced by the KEYUP handler
+
+    }
+
+    public void setWordwrap(boolean wordwrap) {
+        if (wordwrap == this.wordwrap) {
+            return; // No change
+        }
+
+        if (wordwrap) {
+            getElement().removeAttribute("wrap");
+            getElement().getStyle().clearOverflow();
+        } else {
+            getElement().setAttribute("wrap", "off");
+            getElement().getStyle().setOverflow(Overflow.AUTO);
+        }
+        if (BrowserInfo.get().isOpera()) {
+            // Opera fails to dynamically update the wrap attribute so we detach
+            // and reattach the whole TextArea.
+            Util.detachAttach(getElement());
+        }
+        this.wordwrap = wordwrap;
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VTextField.java b/client/src/com/vaadin/client/ui/VTextField.java
new file mode 100644 (file)
index 0000000..f757b17
--- /dev/null
@@ -0,0 +1,452 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.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.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.TextBoxBase;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.Util;
+import com.vaadin.shared.EventId;
+import com.vaadin.shared.ui.textfield.TextFieldConstants;
+
+/**
+ * This class represents a basic text input field with one row.
+ * 
+ * @author Vaadin Ltd.
+ * 
+ */
+public class VTextField extends TextBoxBase implements Field, ChangeHandler,
+        FocusHandler, BlurHandler, KeyDownHandler {
+
+    /**
+     * The input node CSS classname.
+     */
+    public static final String CLASSNAME = "v-textfield";
+    /**
+     * This CSS classname is added to the input node on hover.
+     */
+    public static final String CLASSNAME_FOCUS = "focus";
+
+    /** 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 ApplicationConnection client;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public String valueBeforeEdit = null;
+
+    /**
+     * Set to false if a text change event has been sent since the last value
+     * change event. This means that {@link #valueBeforeEdit} should not be
+     * trusted when determining whether a text change even should be sent.
+     */
+    private boolean valueBeforeEditIsSynced = true;
+
+    private boolean immediate = false;
+    private int maxLength = -1;
+
+    private static final String CLASSNAME_PROMPT = "prompt";
+    private static final String TEXTCHANGE_MODE_TIMEOUT = "TIMEOUT";
+
+    private String inputPrompt = null;
+    private boolean prompting = false;
+    private int lastCursorPos = -1;
+
+    public VTextField() {
+        this(DOM.createInputText());
+    }
+
+    protected VTextField(Element node) {
+        super(node);
+        setStyleName(CLASSNAME);
+        addChangeHandler(this);
+        if (BrowserInfo.get().isIE()) {
+            // IE does not send change events when pressing enter in a text
+            // input so we handle it using a key listener instead
+            addKeyDownHandler(this);
+        }
+        addFocusHandler(this);
+        addBlurHandler(this);
+    }
+
+    /**
+     * For internal use only. May be removed or replaced in the future.
+     * <p>
+     * TODO When GWT adds ONCUT, add it there and remove workaround. See
+     * http://code.google.com/p/google-web-toolkit/issues/detail?id=4030
+     * <p>
+     * Also note that the cut/paste are not totally crossbrowsers compatible.
+     * E.g. in Opera mac works via context menu, but on via File->Paste/Cut.
+     * Opera might need the polling method for 100% working textchanceevents.
+     * Eager polling for a change is bit dum and heavy operation, so I guess we
+     * should first try to survive without.
+     */
+    public static final int TEXTCHANGE_EVENTS = Event.ONPASTE | Event.KEYEVENTS
+            | Event.ONMOUSEUP;
+
+    @Override
+    public void onBrowserEvent(Event event) {
+        super.onBrowserEvent(event);
+
+        if (listenTextChangeEvents
+                && (event.getTypeInt() & TEXTCHANGE_EVENTS) == event
+                        .getTypeInt()) {
+            deferTextChangeEvent();
+        }
+
+    }
+
+    /*
+     * TODO optimize this so that only changes are sent + make the value change
+     * event just a flag that moves the current text to value
+     */
+    private String lastTextChangeString = null;
+
+    private String getLastCommunicatedString() {
+        return lastTextChangeString;
+    }
+
+    private void communicateTextValueToServer() {
+        String text = getText();
+        if (prompting) {
+            // Input prompt visible, text is actually ""
+            text = "";
+        }
+        if (!text.equals(getLastCommunicatedString())) {
+            if (valueBeforeEditIsSynced && text.equals(valueBeforeEdit)) {
+                /*
+                 * Value change for the current text has been enqueued since the
+                 * last text change event was sent, but we can't know that it
+                 * has been sent to the server. Ensure that all pending changes
+                 * are sent now. Sending a value change without a text change
+                 * will simulate a TextChangeEvent on the server.
+                 */
+                client.sendPendingVariableChanges();
+            } else {
+                // Default case - just send an immediate text change message
+                client.updateVariable(paintableId,
+                        TextFieldConstants.VAR_CUR_TEXT, text, true);
+
+                // Shouldn't investigate valueBeforeEdit to avoid duplicate text
+                // change events as the states are not in sync any more
+                valueBeforeEditIsSynced = false;
+            }
+            lastTextChangeString = text;
+        }
+    }
+
+    private Timer textChangeEventTrigger = new Timer() {
+
+        @Override
+        public void run() {
+            if (isAttached()) {
+                updateCursorPosition();
+                communicateTextValueToServer();
+                scheduled = false;
+            }
+        }
+    };
+
+    private boolean scheduled = false;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean listenTextChangeEvents;
+    /** For internal use only. May be removed or replaced in the future. */
+    public String textChangeEventMode;
+    public int textChangeEventTimeout;
+
+    private void deferTextChangeEvent() {
+        if (textChangeEventMode.equals(TEXTCHANGE_MODE_TIMEOUT) && scheduled) {
+            return;
+        } else {
+            textChangeEventTrigger.cancel();
+        }
+        textChangeEventTrigger.schedule(getTextChangeEventTimeout());
+        scheduled = true;
+    }
+
+    private int getTextChangeEventTimeout() {
+        return textChangeEventTimeout;
+    }
+
+    @Override
+    public void setReadOnly(boolean readOnly) {
+        boolean wasReadOnly = isReadOnly();
+
+        if (readOnly) {
+            setTabIndex(-1);
+        } else if (wasReadOnly && !readOnly && getTabIndex() == -1) {
+            /*
+             * Need to manually set tab index to 0 since server will not send
+             * the tab index if it is 0.
+             */
+            setTabIndex(0);
+        }
+
+        super.setReadOnly(readOnly);
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void updateFieldContent(final String text) {
+        setPrompting(inputPrompt != null && focusedTextField != this
+                && (text.equals("")));
+
+        String fieldValue;
+        if (prompting) {
+            fieldValue = isReadOnly() ? "" : inputPrompt;
+            addStyleDependentName(CLASSNAME_PROMPT);
+        } else {
+            fieldValue = text;
+            removeStyleDependentName(CLASSNAME_PROMPT);
+        }
+        setText(fieldValue);
+
+        lastTextChangeString = valueBeforeEdit = text;
+        valueBeforeEditIsSynced = true;
+    }
+
+    protected void onCut() {
+        if (listenTextChangeEvents) {
+            deferTextChangeEvent();
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public native void attachCutEventListener(Element el)
+    /*-{
+        var me = this;
+        el.oncut = $entry(function() {
+            me.@com.vaadin.client.ui.textfield.VTextField::onCut()();
+        });
+    }-*/;
+
+    protected native void detachCutEventListener(Element el)
+    /*-{
+        el.oncut = null;
+    }-*/;
+
+    @Override
+    protected void onDetach() {
+        super.onDetach();
+        detachCutEventListener(getElement());
+        if (focusedTextField == this) {
+            focusedTextField = null;
+        }
+    }
+
+    @Override
+    protected void onAttach() {
+        super.onAttach();
+        if (listenTextChangeEvents) {
+            detachCutEventListener(getElement());
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void setMaxLength(int newMaxLength) {
+        if (newMaxLength >= 0 && newMaxLength != maxLength) {
+            maxLength = newMaxLength;
+            updateMaxLength(maxLength);
+        } else if (maxLength != -1) {
+            maxLength = -1;
+            updateMaxLength(maxLength);
+        }
+
+    }
+
+    /**
+     * This method is reponsible for updating the DOM or otherwise ensuring that
+     * the given max length is enforced. Called when the max length for the
+     * field has changed.
+     * 
+     * @param maxLength
+     *            The new max length
+     */
+    protected void updateMaxLength(int maxLength) {
+        if (maxLength >= 0) {
+            getElement().setPropertyInt("maxLength", maxLength);
+        } else {
+            getElement().removeAttribute("maxLength");
+
+        }
+        setMaxLengthToElement(maxLength);
+    }
+
+    protected void setMaxLengthToElement(int newMaxLength) {
+        if (newMaxLength >= 0) {
+            getElement().setPropertyInt("maxLength", newMaxLength);
+        } else {
+            getElement().removeAttribute("maxLength");
+        }
+    }
+
+    public int getMaxLength() {
+        return maxLength;
+    }
+
+    @Override
+    public void onChange(ChangeEvent event) {
+        valueChange(false);
+    }
+
+    /**
+     * Called when the field value might have changed and/or the field was
+     * blurred. These are combined so the blur event is sent in the same batch
+     * as a possible value change event (these are often connected).
+     * 
+     * @param blurred
+     *            true if the field was blurred
+     */
+    public void valueChange(boolean blurred) {
+        if (client != null && paintableId != null) {
+            boolean sendBlurEvent = false;
+            boolean sendValueChange = false;
+
+            if (blurred && client.hasEventListeners(this, EventId.BLUR)) {
+                sendBlurEvent = true;
+                client.updateVariable(paintableId, EventId.BLUR, "", false);
+            }
+
+            String newText = getText();
+            if (!prompting && newText != null
+                    && !newText.equals(valueBeforeEdit)) {
+                sendValueChange = immediate;
+                client.updateVariable(paintableId, "text", newText, false);
+                valueBeforeEdit = newText;
+                valueBeforeEditIsSynced = true;
+            }
+
+            /*
+             * also send cursor position, no public api yet but for easier
+             * extension
+             */
+            updateCursorPosition();
+
+            if (sendBlurEvent || sendValueChange) {
+                /*
+                 * Avoid sending text change event as we will simulate it on the
+                 * server side before value change events.
+                 */
+                textChangeEventTrigger.cancel();
+                scheduled = false;
+                client.sendPendingVariableChanges();
+            }
+        }
+    }
+
+    /**
+     * Updates the cursor position variable if it has changed since the last
+     * update.
+     * 
+     * @return true iff the value was updated
+     */
+    protected boolean updateCursorPosition() {
+        if (Util.isAttachedAndDisplayed(this)) {
+            int cursorPos = getCursorPos();
+            if (lastCursorPos != cursorPos) {
+                client.updateVariable(paintableId,
+                        TextFieldConstants.VAR_CURSOR, cursorPos, false);
+                lastCursorPos = cursorPos;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static VTextField focusedTextField;
+
+    public static void flushChangesFromFocusedTextField() {
+        if (focusedTextField != null) {
+            focusedTextField.onChange(null);
+        }
+    }
+
+    @Override
+    public void onFocus(FocusEvent event) {
+        addStyleDependentName(CLASSNAME_FOCUS);
+        if (prompting) {
+            setText("");
+            removeStyleDependentName(CLASSNAME_PROMPT);
+            setPrompting(false);
+        }
+        focusedTextField = this;
+        if (client.hasEventListeners(this, EventId.FOCUS)) {
+            client.updateVariable(paintableId, EventId.FOCUS, "", true);
+        }
+    }
+
+    @Override
+    public void onBlur(BlurEvent event) {
+        // this is called twice on Chrome when e.g. changing tab while prompting
+        // field focused - do not change settings on the second time
+        if (focusedTextField != this) {
+            return;
+        }
+        removeStyleDependentName(CLASSNAME_FOCUS);
+        focusedTextField = null;
+        String text = getText();
+        setPrompting(inputPrompt != null && (text == null || "".equals(text)));
+        if (prompting) {
+            setText(isReadOnly() ? "" : inputPrompt);
+            addStyleDependentName(CLASSNAME_PROMPT);
+        }
+
+        valueChange(true);
+    }
+
+    private void setPrompting(boolean prompting) {
+        this.prompting = prompting;
+    }
+
+    public void setColumns(int columns) {
+        if (columns <= 0) {
+            return;
+        }
+
+        setWidth(columns + "em");
+    }
+
+    @Override
+    public void onKeyDown(KeyDownEvent event) {
+        if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
+            valueChange(false);
+        }
+    }
+
+    public void setImmediate(boolean immediate) {
+        this.immediate = immediate;
+    }
+
+    public void setInputPrompt(String inputPrompt) {
+        this.inputPrompt = inputPrompt;
+    }
+
+}
diff --git a/client/src/com/vaadin/client/ui/VTextualDate.java b/client/src/com/vaadin/client/ui/VTextualDate.java
new file mode 100644 (file)
index 0000000..9396a83
--- /dev/null
@@ -0,0 +1,352 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.Date;
+
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.ChangeEvent;
+import com.google.gwt.event.dom.client.ChangeHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.TextBox;
+import com.vaadin.client.Focusable;
+import com.vaadin.client.LocaleNotLoadedException;
+import com.vaadin.client.LocaleService;
+import com.vaadin.client.VConsole;
+import com.vaadin.shared.EventId;
+import com.vaadin.shared.ui.datefield.Resolution;
+
+public class VTextualDate extends VDateField implements Field, ChangeHandler,
+        Focusable, SubPartAware {
+
+    private static final String PARSE_ERROR_CLASSNAME = "-parseerror";
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public final TextBox text;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public String formatStr;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean lenient;
+
+    private static final String CLASSNAME_PROMPT = "prompt";
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public static final String ATTR_INPUTPROMPT = "prompt";
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public String inputPrompt = "";
+
+    private boolean prompting = false;
+
+    public VTextualDate() {
+        super();
+        text = new TextBox();
+        text.addChangeHandler(this);
+        text.addFocusHandler(new FocusHandler() {
+            @Override
+            public void onFocus(FocusEvent event) {
+                text.addStyleName(VTextField.CLASSNAME + "-"
+                        + VTextField.CLASSNAME_FOCUS);
+                if (prompting) {
+                    text.setText("");
+                    setPrompting(false);
+                }
+                if (getClient() != null
+                        && getClient().hasEventListeners(VTextualDate.this,
+                                EventId.FOCUS)) {
+                    getClient()
+                            .updateVariable(getId(), EventId.FOCUS, "", true);
+                }
+            }
+        });
+        text.addBlurHandler(new BlurHandler() {
+            @Override
+            public void onBlur(BlurEvent event) {
+                text.removeStyleName(VTextField.CLASSNAME + "-"
+                        + VTextField.CLASSNAME_FOCUS);
+                String value = getText();
+                setPrompting(inputPrompt != null
+                        && (value == null || "".equals(value)));
+                if (prompting) {
+                    text.setText(readonly ? "" : inputPrompt);
+                }
+                if (getClient() != null
+                        && getClient().hasEventListeners(VTextualDate.this,
+                                EventId.BLUR)) {
+                    getClient().updateVariable(getId(), EventId.BLUR, "", true);
+                }
+            }
+        });
+        add(text);
+    }
+
+    protected void updateStyleNames() {
+        if (text != null) {
+            text.setStyleName(VTextField.CLASSNAME);
+            text.addStyleName(getStylePrimaryName() + "-textfield");
+        }
+    }
+
+    protected String getFormatString() {
+        if (formatStr == null) {
+            if (currentResolution == Resolution.YEAR) {
+                formatStr = "yyyy"; // force full year
+            } else {
+
+                try {
+                    String frmString = LocaleService
+                            .getDateFormat(currentLocale);
+                    frmString = cleanFormat(frmString);
+                    // String delim = LocaleService
+                    // .getClockDelimiter(currentLocale);
+                    if (currentResolution.getCalendarField() >= Resolution.HOUR
+                            .getCalendarField()) {
+                        if (dts.isTwelveHourClock()) {
+                            frmString += " hh";
+                        } else {
+                            frmString += " HH";
+                        }
+                        if (currentResolution.getCalendarField() >= Resolution.MINUTE
+                                .getCalendarField()) {
+                            frmString += ":mm";
+                            if (currentResolution.getCalendarField() >= Resolution.SECOND
+                                    .getCalendarField()) {
+                                frmString += ":ss";
+                            }
+                        }
+                        if (dts.isTwelveHourClock()) {
+                            frmString += " aaa";
+                        }
+
+                    }
+
+                    formatStr = frmString;
+                } catch (LocaleNotLoadedException e) {
+                    // TODO should die instead? Can the component survive
+                    // without format string?
+                    VConsole.error(e);
+                }
+            }
+        }
+        return formatStr;
+    }
+
+    /**
+     * Updates the text field according to the current date (provided by
+     * {@link #getDate()}). Takes care of updating text, enabling and disabling
+     * the field, setting/removing readonly status and updating readonly styles.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     * <p>
+     * TODO: Split part of this into a method that only updates the text as this
+     * is what usually is needed except for updateFromUIDL.
+     */
+    public void buildDate() {
+        removeStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
+        // Create the initial text for the textfield
+        String dateText;
+        Date currentDate = getDate();
+        if (currentDate != null) {
+            dateText = getDateTimeService().formatDate(currentDate,
+                    getFormatString());
+        } else {
+            dateText = "";
+        }
+
+        setText(dateText);
+        text.setEnabled(enabled);
+        text.setReadOnly(readonly);
+
+        if (readonly) {
+            text.addStyleName("v-readonly");
+        } else {
+            text.removeStyleName("v-readonly");
+        }
+
+    }
+
+    protected void setPrompting(boolean prompting) {
+        this.prompting = prompting;
+        if (prompting) {
+            addStyleDependentName(CLASSNAME_PROMPT);
+        } else {
+            removeStyleDependentName(CLASSNAME_PROMPT);
+        }
+    }
+
+    @Override
+    @SuppressWarnings("deprecation")
+    public void onChange(ChangeEvent event) {
+        if (!text.getText().equals("")) {
+            try {
+                String enteredDate = text.getText();
+
+                setDate(getDateTimeService().parseDate(enteredDate,
+                        getFormatString(), lenient));
+
+                if (lenient) {
+                    // If date value was leniently parsed, normalize text
+                    // presentation.
+                    // FIXME: Add a description/example here of when this is
+                    // needed
+                    text.setValue(
+                            getDateTimeService().formatDate(getDate(),
+                                    getFormatString()), false);
+                }
+
+                // remove possibly added invalid value indication
+                removeStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
+            } catch (final Exception e) {
+                VConsole.log(e);
+
+                addStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
+                // this is a hack that may eventually be removed
+                getClient().updateVariable(getId(), "lastInvalidDateString",
+                        text.getText(), false);
+                setDate(null);
+            }
+        } else {
+            setDate(null);
+            // remove possibly added invalid value indication
+            removeStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
+        }
+        // always send the date string
+        getClient()
+                .updateVariable(getId(), "dateString", text.getText(), false);
+
+        // Update variables
+        // (only the smallest defining resolution needs to be
+        // immediate)
+        Date currentDate = getDate();
+        getClient().updateVariable(getId(), "year",
+                currentDate != null ? currentDate.getYear() + 1900 : -1,
+                currentResolution == Resolution.YEAR && immediate);
+        if (currentResolution.getCalendarField() >= Resolution.MONTH
+                .getCalendarField()) {
+            getClient().updateVariable(getId(), "month",
+                    currentDate != null ? currentDate.getMonth() + 1 : -1,
+                    currentResolution == Resolution.MONTH && immediate);
+        }
+        if (currentResolution.getCalendarField() >= Resolution.DAY
+                .getCalendarField()) {
+            getClient().updateVariable(getId(), "day",
+                    currentDate != null ? currentDate.getDate() : -1,
+                    currentResolution == Resolution.DAY && immediate);
+        }
+        if (currentResolution.getCalendarField() >= Resolution.HOUR
+                .getCalendarField()) {
+            getClient().updateVariable(getId(), "hour",
+                    currentDate != null ? currentDate.getHours() : -1,
+                    currentResolution == Resolution.HOUR && immediate);
+        }
+        if (currentResolution.getCalendarField() >= Resolution.MINUTE
+                .getCalendarField()) {
+            getClient().updateVariable(getId(), "min",
+                    currentDate != null ? currentDate.getMinutes() : -1,
+                    currentResolution == Resolution.MINUTE && immediate);
+        }
+        if (currentResolution.getCalendarField() >= Resolution.SECOND
+                .getCalendarField()) {
+            getClient().updateVariable(getId(), "sec",
+                    currentDate != null ? currentDate.getSeconds() : -1,
+                    currentResolution == Resolution.SECOND && immediate);
+        }
+
+    }
+
+    private String cleanFormat(String format) {
+        // Remove unnecessary d & M if resolution is too low
+        if (currentResolution.getCalendarField() < Resolution.DAY
+                .getCalendarField()) {
+            format = format.replaceAll("d", "");
+        }
+        if (currentResolution.getCalendarField() < Resolution.MONTH
+                .getCalendarField()) {
+            format = format.replaceAll("M", "");
+        }
+
+        // Remove unsupported patterns
+        // TODO support for 'G', era designator (used at least in Japan)
+        format = format.replaceAll("[GzZwWkK]", "");
+
+        // Remove extra delimiters ('/' and '.')
+        while (format.startsWith("/") || format.startsWith(".")
+                || format.startsWith("-")) {
+            format = format.substring(1);
+        }
+        while (format.endsWith("/") || format.endsWith(".")
+                || format.endsWith("-")) {
+            format = format.substring(0, format.length() - 1);
+        }
+
+        // Remove duplicate delimiters
+        format = format.replaceAll("//", "/");
+        format = format.replaceAll("\\.\\.", ".");
+        format = format.replaceAll("--", "-");
+
+        return format.trim();
+    }
+
+    @Override
+    public void focus() {
+        text.setFocus(true);
+    }
+
+    protected String getText() {
+        if (prompting) {
+            return "";
+        }
+        return text.getText();
+    }
+
+    protected void setText(String text) {
+        if (inputPrompt != null && (text == null || "".equals(text))) {
+            text = readonly ? "" : inputPrompt;
+            setPrompting(true);
+        } else {
+            setPrompting(false);
+        }
+
+        this.text.setText(text);
+    }
+
+    private final String TEXTFIELD_ID = "field";
+
+    @Override
+    public Element getSubPartElement(String subPart) {
+        if (subPart.equals(TEXTFIELD_ID)) {
+            return text.getElement();
+        }
+
+        return null;
+    }
+
+    @Override
+    public String getSubPartName(Element subElement) {
+        if (text.getElement().isOrHasChild(subElement)) {
+            return TEXTFIELD_ID;
+        }
+
+        return null;
+    }
+
+}
diff --git a/client/src/com/vaadin/client/ui/VTree.java b/client/src/com/vaadin/client/ui/VTree.java
new file mode 100644 (file)
index 0000000..d8985f0
--- /dev/null
@@ -0,0 +1,2164 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+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.NativeEvent;
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.ContextMenuEvent;
+import com.google.gwt.event.dom.client.ContextMenuHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.UIObject;
+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.ConnectorMap;
+import com.vaadin.client.MouseEventDetailsBuilder;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.Util;
+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.VTransferable;
+import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.MouseEventDetails.MouseButton;
+import com.vaadin.shared.ui.MultiSelectMode;
+import com.vaadin.shared.ui.dd.VerticalDropLocation;
+import com.vaadin.shared.ui.tree.TreeConstants;
+
+/**
+ * 
+ */
+public class VTree extends FocusElementPanel implements VHasDropHandler,
+        FocusHandler, BlurHandler, KeyPressHandler, KeyDownHandler,
+        SubPartAware, ActionOwner {
+
+    public static final String CLASSNAME = "v-tree";
+
+    /**
+     * @deprecated from 7.0, use {@link MultiSelectMode#DEFAULT} instead.
+     */
+    @Deprecated
+    public static final MultiSelectMode MULTISELECT_MODE_DEFAULT = MultiSelectMode.DEFAULT;
+
+    /**
+     * @deprecated from 7.0, use {@link MultiSelectMode#SIMPLE} instead.
+     */
+    @Deprecated
+    public static final MultiSelectMode MULTISELECT_MODE_SIMPLE = MultiSelectMode.SIMPLE;
+
+    private static final int CHARCODE_SPACE = 32;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public final FlowPanel body = new FlowPanel();
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public Set<String> selectedIds = new HashSet<String>();
+
+    /** 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 boolean selectable;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean isMultiselect;
+
+    private String currentMouseOverKey;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public TreeNode lastSelection;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public TreeNode focusedNode;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public MultiSelectMode multiSelectMode = MultiSelectMode.DEFAULT;
+
+    private final HashMap<String, TreeNode> keyToNode = new HashMap<String, TreeNode>();
+
+    /**
+     * This map contains captions and icon urls for actions like: * "33_c" ->
+     * "Edit" * "33_i" -> "http://dom.com/edit.png"
+     */
+    private final HashMap<String, String> actionMap = new HashMap<String, String>();
+
+    /** 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 boolean isNullSelectionAllowed = true;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean disabled = false;
+
+    /** 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 boolean rendering;
+
+    private VAbstractDropHandler dropHandler;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public int dragMode;
+
+    private boolean selectionHasChanged = false;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public String[] bodyActionKeys;
+
+    public VLazyExecutor iconLoaded = new VLazyExecutor(50,
+            new ScheduledCommand() {
+
+                @Override
+                public void execute() {
+                    Util.notifyParentOfSizeChange(VTree.this, true);
+                }
+
+            });
+
+    public VTree() {
+        super();
+        setStyleName(CLASSNAME);
+        add(body);
+
+        addFocusHandler(this);
+        addBlurHandler(this);
+
+        /*
+         * Listen to context menu events on the empty space in the tree
+         */
+        sinkEvents(Event.ONCONTEXTMENU);
+        addDomHandler(new ContextMenuHandler() {
+            @Override
+            public void onContextMenu(ContextMenuEvent event) {
+                handleBodyContextMenu(event);
+            }
+        }, ContextMenuEvent.getType());
+
+        /*
+         * Firefox auto-repeat works correctly only if we use a key press
+         * handler, other browsers handle it correctly when using a key down
+         * handler
+         */
+        if (BrowserInfo.get().isGecko() || BrowserInfo.get().isOpera()) {
+            addKeyPressHandler(this);
+        } else {
+            addKeyDownHandler(this);
+        }
+
+        /*
+         * We need to use the sinkEvents method to catch the keyUp events so we
+         * can cache a single shift. KeyUpHandler cannot do this. At the same
+         * time we catch the mouse down and up events so we can apply the text
+         * selection patch in IE
+         */
+        sinkEvents(Event.ONMOUSEDOWN | Event.ONMOUSEUP | Event.ONKEYUP);
+
+        /*
+         * Re-set the tab index to make sure that the FocusElementPanel's
+         * (super) focus element gets the tab index and not the element
+         * containing the tree.
+         */
+        setTabIndex(0);
+    }
+
+    /*
+     * (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);
+        if (event.getTypeInt() == Event.ONMOUSEDOWN) {
+            // Prevent default text selection in IE
+            if (BrowserInfo.get().isIE()) {
+                ((Element) event.getEventTarget().cast()).setPropertyJSO(
+                        "onselectstart", applyDisableTextSelectionIEHack());
+            }
+        } else if (event.getTypeInt() == Event.ONMOUSEUP) {
+            // Remove IE text selection hack
+            if (BrowserInfo.get().isIE()) {
+                ((Element) event.getEventTarget().cast()).setPropertyJSO(
+                        "onselectstart", null);
+            }
+        } else if (event.getTypeInt() == Event.ONKEYUP) {
+            if (selectionHasChanged) {
+                if (event.getKeyCode() == getNavigationDownKey()
+                        && !event.getShiftKey()) {
+                    sendSelectionToServer();
+                    event.preventDefault();
+                } else if (event.getKeyCode() == getNavigationUpKey()
+                        && !event.getShiftKey()) {
+                    sendSelectionToServer();
+                    event.preventDefault();
+                } else if (event.getKeyCode() == KeyCodes.KEY_SHIFT) {
+                    sendSelectionToServer();
+                    event.preventDefault();
+                } else if (event.getKeyCode() == getNavigationSelectKey()) {
+                    sendSelectionToServer();
+                    event.preventDefault();
+                }
+            }
+        }
+    }
+
+    public String getActionCaption(String actionKey) {
+        return actionMap.get(actionKey + "_c");
+    }
+
+    public String getActionIcon(String actionKey) {
+        return actionMap.get(actionKey + "_i");
+    }
+
+    /**
+     * Returns the first root node of the tree or null if there are no root
+     * nodes.
+     * 
+     * @return The first root {@link TreeNode}
+     */
+    protected TreeNode getFirstRootNode() {
+        if (body.getWidgetCount() == 0) {
+            return null;
+        }
+        return (TreeNode) body.getWidget(0);
+    }
+
+    /**
+     * Returns the last root node of the tree or null if there are no root
+     * nodes.
+     * 
+     * @return The last root {@link TreeNode}
+     */
+    protected TreeNode getLastRootNode() {
+        if (body.getWidgetCount() == 0) {
+            return null;
+        }
+        return (TreeNode) body.getWidget(body.getWidgetCount() - 1);
+    }
+
+    /**
+     * Returns a list of all root nodes in the Tree in the order they appear in
+     * the tree.
+     * 
+     * @return A list of all root {@link TreeNode}s.
+     */
+    protected List<TreeNode> getRootNodes() {
+        ArrayList<TreeNode> rootNodes = new ArrayList<TreeNode>();
+        for (int i = 0; i < body.getWidgetCount(); i++) {
+            rootNodes.add((TreeNode) body.getWidget(i));
+        }
+        return rootNodes;
+    }
+
+    private void updateTreeRelatedDragData(VDragEvent drag) {
+
+        currentMouseOverKey = findCurrentMouseOverKey(drag.getElementOver());
+
+        drag.getDropDetails().put("itemIdOver", currentMouseOverKey);
+        if (currentMouseOverKey != null) {
+            TreeNode treeNode = getNodeByKey(currentMouseOverKey);
+            VerticalDropLocation detail = treeNode.getDropDetail(drag
+                    .getCurrentGwtEvent());
+            Boolean overTreeNode = null;
+            if (treeNode != null && !treeNode.isLeaf()
+                    && detail == VerticalDropLocation.MIDDLE) {
+                overTreeNode = true;
+            }
+            drag.getDropDetails().put("itemIdOverIsNode", overTreeNode);
+            drag.getDropDetails().put("detail", detail);
+        } else {
+            drag.getDropDetails().put("itemIdOverIsNode", null);
+            drag.getDropDetails().put("detail", null);
+        }
+
+    }
+
+    private String findCurrentMouseOverKey(Element elementOver) {
+        TreeNode treeNode = Util.findWidget(elementOver, TreeNode.class);
+        return treeNode == null ? null : treeNode.key;
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void updateDropHandler(UIDL childUidl) {
+        if (dropHandler == null) {
+            dropHandler = new VAbstractDropHandler() {
+
+                @Override
+                public void dragEnter(VDragEvent drag) {
+                }
+
+                @Override
+                protected void dragAccepted(final VDragEvent drag) {
+
+                }
+
+                @Override
+                public void dragOver(final VDragEvent currentDrag) {
+                    final Object oldIdOver = currentDrag.getDropDetails().get(
+                            "itemIdOver");
+                    final VerticalDropLocation oldDetail = (VerticalDropLocation) currentDrag
+                            .getDropDetails().get("detail");
+
+                    updateTreeRelatedDragData(currentDrag);
+                    final VerticalDropLocation detail = (VerticalDropLocation) currentDrag
+                            .getDropDetails().get("detail");
+                    boolean nodeHasChanged = (currentMouseOverKey != null && currentMouseOverKey != oldIdOver)
+                            || (currentMouseOverKey == null && oldIdOver != null);
+                    boolean detailHasChanded = (detail != null && detail != oldDetail)
+                            || (detail == null && oldDetail != null);
+
+                    if (nodeHasChanged || detailHasChanded) {
+                        final String newKey = currentMouseOverKey;
+                        TreeNode treeNode = keyToNode.get(oldIdOver);
+                        if (treeNode != null) {
+                            // clear old styles
+                            treeNode.emphasis(null);
+                        }
+                        if (newKey != null) {
+                            validate(new VAcceptCallback() {
+                                @Override
+                                public void accepted(VDragEvent event) {
+                                    VerticalDropLocation curDetail = (VerticalDropLocation) event
+                                            .getDropDetails().get("detail");
+                                    if (curDetail == detail
+                                            && newKey.equals(currentMouseOverKey)) {
+                                        getNodeByKey(newKey).emphasis(detail);
+                                    }
+                                    /*
+                                     * Else drag is already on a different
+                                     * node-detail pair, new criteria check is
+                                     * going on
+                                     */
+                                }
+                            }, currentDrag);
+
+                        }
+                    }
+
+                }
+
+                @Override
+                public void dragLeave(VDragEvent drag) {
+                    cleanUp();
+                }
+
+                private void cleanUp() {
+                    if (currentMouseOverKey != null) {
+                        getNodeByKey(currentMouseOverKey).emphasis(null);
+                        currentMouseOverKey = null;
+                    }
+                }
+
+                @Override
+                public boolean drop(VDragEvent drag) {
+                    cleanUp();
+                    return super.drop(drag);
+                }
+
+                @Override
+                public ComponentConnector getConnector() {
+                    return ConnectorMap.get(client).getConnector(VTree.this);
+                }
+
+                @Override
+                public ApplicationConnection getApplicationConnection() {
+                    return client;
+                }
+
+            };
+        }
+        dropHandler.updateAcceptRules(childUidl);
+    }
+
+    public void setSelected(TreeNode treeNode, boolean selected) {
+        if (selected) {
+            if (!isMultiselect) {
+                while (selectedIds.size() > 0) {
+                    final String id = selectedIds.iterator().next();
+                    final TreeNode oldSelection = getNodeByKey(id);
+                    if (oldSelection != null) {
+                        // can be null if the node is not visible (parent
+                        // collapsed)
+                        oldSelection.setSelected(false);
+                    }
+                    selectedIds.remove(id);
+                }
+            }
+            treeNode.setSelected(true);
+            selectedIds.add(treeNode.key);
+        } else {
+            if (!isNullSelectionAllowed) {
+                if (!isMultiselect || selectedIds.size() == 1) {
+                    return;
+                }
+            }
+            selectedIds.remove(treeNode.key);
+            treeNode.setSelected(false);
+        }
+
+        sendSelectionToServer();
+    }
+
+    /**
+     * Sends the selection to the server
+     */
+    private void sendSelectionToServer() {
+        Command command = new Command() {
+            @Override
+            public void execute() {
+                client.updateVariable(paintableId, "selected",
+                        selectedIds.toArray(new String[selectedIds.size()]),
+                        immediate);
+                selectionHasChanged = false;
+            }
+        };
+
+        /*
+         * Delaying the sending of the selection in webkit to ensure the
+         * selection is always sent when the tree has focus and after click
+         * events have been processed. This is due to the focusing
+         * implementation in FocusImplSafari which uses timeouts when focusing
+         * and blurring.
+         */
+        if (BrowserInfo.get().isWebkit()) {
+            Scheduler.get().scheduleDeferred(command);
+        } else {
+            command.execute();
+        }
+    }
+
+    /**
+     * Is a node selected in the tree
+     * 
+     * @param treeNode
+     *            The node to check
+     * @return
+     */
+    public boolean isSelected(TreeNode treeNode) {
+        return selectedIds.contains(treeNode.key);
+    }
+
+    public class TreeNode extends SimplePanel implements ActionOwner {
+
+        public static final String CLASSNAME = "v-tree-node";
+        public static final String CLASSNAME_FOCUSED = CLASSNAME + "-focused";
+
+        public String key;
+
+        /** For internal use only. May be removed or replaced in the future. */
+        public String[] actionKeys = null;
+
+        /** For internal use only. May be removed or replaced in the future. */
+        public boolean childrenLoaded;
+
+        Element nodeCaptionDiv;
+
+        protected Element nodeCaptionSpan;
+
+        /** For internal use only. May be removed or replaced in the future. */
+        public FlowPanel childNodeContainer;
+
+        private boolean open;
+
+        private Icon icon;
+
+        private Event mouseDownEvent;
+
+        private int cachedHeight = -1;
+
+        private boolean focused = false;
+
+        public TreeNode() {
+            constructDom();
+            sinkEvents(Event.ONCLICK | Event.ONDBLCLICK | Event.MOUSEEVENTS
+                    | Event.TOUCHEVENTS | Event.ONCONTEXTMENU);
+        }
+
+        public VerticalDropLocation getDropDetail(NativeEvent currentGwtEvent) {
+            if (cachedHeight < 0) {
+                /*
+                 * Height is cached to avoid flickering (drop hints may change
+                 * the reported offsetheight -> would change the drop detail)
+                 */
+                cachedHeight = nodeCaptionDiv.getOffsetHeight();
+            }
+            VerticalDropLocation verticalDropLocation = DDUtil
+                    .getVerticalDropLocation(nodeCaptionDiv, cachedHeight,
+                            currentGwtEvent, 0.15);
+            return verticalDropLocation;
+        }
+
+        protected void emphasis(VerticalDropLocation detail) {
+            String base = "v-tree-node-drag-";
+            UIObject.setStyleName(getElement(), base + "top",
+                    VerticalDropLocation.TOP == detail);
+            UIObject.setStyleName(getElement(), base + "bottom",
+                    VerticalDropLocation.BOTTOM == detail);
+            UIObject.setStyleName(getElement(), base + "center",
+                    VerticalDropLocation.MIDDLE == detail);
+            base = "v-tree-node-caption-drag-";
+            UIObject.setStyleName(nodeCaptionDiv, base + "top",
+                    VerticalDropLocation.TOP == detail);
+            UIObject.setStyleName(nodeCaptionDiv, base + "bottom",
+                    VerticalDropLocation.BOTTOM == detail);
+            UIObject.setStyleName(nodeCaptionDiv, base + "center",
+                    VerticalDropLocation.MIDDLE == detail);
+
+            // also add classname to "folder node" into which the drag is
+            // targeted
+
+            TreeNode folder = null;
+            /* Possible parent of this TreeNode will be stored here */
+            TreeNode parentFolder = getParentNode();
+
+            // TODO fix my bugs
+            if (isLeaf()) {
+                folder = parentFolder;
+                // note, parent folder may be null if this is root node => no
+                // folder target exists
+            } else {
+                if (detail == VerticalDropLocation.TOP) {
+                    folder = parentFolder;
+                } else {
+                    folder = this;
+                }
+                // ensure we remove the dragfolder classname from the previous
+                // folder node
+                setDragFolderStyleName(this, false);
+                setDragFolderStyleName(parentFolder, false);
+            }
+            if (folder != null) {
+                setDragFolderStyleName(folder, detail != null);
+            }
+
+        }
+
+        private TreeNode getParentNode() {
+            Widget parent2 = getParent().getParent();
+            if (parent2 instanceof TreeNode) {
+                return (TreeNode) parent2;
+            }
+            return null;
+        }
+
+        private void setDragFolderStyleName(TreeNode folder, boolean add) {
+            if (folder != null) {
+                UIObject.setStyleName(folder.getElement(),
+                        "v-tree-node-dragfolder", add);
+                UIObject.setStyleName(folder.nodeCaptionDiv,
+                        "v-tree-node-caption-dragfolder", add);
+            }
+        }
+
+        /**
+         * Handles mouse selection
+         * 
+         * @param ctrl
+         *            Was the ctrl-key pressed
+         * @param shift
+         *            Was the shift-key pressed
+         * @return Returns true if event was handled, else false
+         */
+        private boolean handleClickSelection(final boolean ctrl,
+                final boolean shift) {
+
+            // always when clicking an item, focus it
+            setFocusedNode(this, false);
+
+            if (!BrowserInfo.get().isOpera()) {
+                /*
+                 * Ensure that the tree's focus element also gains focus
+                 * (TreeNodes focus is faked using FocusElementPanel in browsers
+                 * other than Opera).
+                 */
+                focus();
+            }
+
+            executeEventCommand(new ScheduledCommand() {
+
+                @Override
+                public void execute() {
+
+                    if (multiSelectMode == MultiSelectMode.SIMPLE
+                            || !isMultiselect) {
+                        toggleSelection();
+                        lastSelection = TreeNode.this;
+                    } else if (multiSelectMode == MultiSelectMode.DEFAULT) {
+                        // Handle ctrl+click
+                        if (isMultiselect && ctrl && !shift) {
+                            toggleSelection();
+                            lastSelection = TreeNode.this;
+
+                            // Handle shift+click
+                        } else if (isMultiselect && !ctrl && shift) {
+                            deselectAll();
+                            selectNodeRange(lastSelection.key, key);
+                            sendSelectionToServer();
+
+                            // Handle ctrl+shift click
+                        } else if (isMultiselect && ctrl && shift) {
+                            selectNodeRange(lastSelection.key, key);
+
+                            // Handle click
+                        } else {
+                            // TODO should happen only if this alone not yet
+                            // selected,
+                            // now sending excess server calls
+                            deselectAll();
+                            toggleSelection();
+                            lastSelection = TreeNode.this;
+                        }
+                    }
+                }
+            });
+
+            return true;
+        }
+
+        /*
+         * (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);
+            final int type = DOM.eventGetType(event);
+            final Element target = DOM.eventGetTarget(event);
+
+            if (type == Event.ONLOAD && target == icon.getElement()) {
+                iconLoaded.trigger();
+            }
+
+            if (disabled) {
+                return;
+            }
+
+            final boolean inCaption = isCaptionElement(target);
+            if (inCaption
+                    && client.hasEventListeners(VTree.this,
+                            TreeConstants.ITEM_CLICK_EVENT_ID)
+
+                    && (type == Event.ONDBLCLICK || type == Event.ONMOUSEUP)) {
+                fireClick(event);
+            }
+            if (type == Event.ONCLICK) {
+                if (getElement() == target) {
+                    // state change
+                    toggleState();
+                } else if (!readonly && inCaption) {
+                    if (selectable) {
+                        // caption click = selection change && possible click
+                        // event
+                        if (handleClickSelection(
+                                event.getCtrlKey() || event.getMetaKey(),
+                                event.getShiftKey())) {
+                            event.preventDefault();
+                        }
+                    } else {
+                        // Not selectable, only focus the node.
+                        setFocusedNode(this);
+                    }
+                }
+                event.stopPropagation();
+            } else if (type == Event.ONCONTEXTMENU) {
+                showContextMenu(event);
+            }
+
+            if (dragMode != 0 || dropHandler != null) {
+                if (type == Event.ONMOUSEDOWN || type == Event.ONTOUCHSTART) {
+                    if (nodeCaptionDiv.isOrHasChild((Node) event
+                            .getEventTarget().cast())) {
+                        if (dragMode > 0
+                                && (type == Event.ONTOUCHSTART || event
+                                        .getButton() == NativeEvent.BUTTON_LEFT)) {
+                            mouseDownEvent = event; // save event for possible
+                            // dd operation
+                            if (type == Event.ONMOUSEDOWN) {
+                                event.preventDefault(); // prevent text
+                                // selection
+                            } else {
+                                /*
+                                 * FIXME We prevent touch start event to be used
+                                 * as a scroll start event. Note that we cannot
+                                 * easily distinguish whether the user wants to
+                                 * drag or scroll. The same issue is in table
+                                 * that has scrollable area and has drag and
+                                 * drop enable. Some kind of timer might be used
+                                 * to resolve the issue.
+                                 */
+                                event.stopPropagation();
+                            }
+                        }
+                    }
+                } else if (type == Event.ONMOUSEMOVE
+                        || type == Event.ONMOUSEOUT
+                        || type == Event.ONTOUCHMOVE) {
+
+                    if (mouseDownEvent != null) {
+                        // start actual drag on slight move when mouse is down
+                        VTransferable t = new VTransferable();
+                        t.setDragSource(ConnectorMap.get(client).getConnector(
+                                VTree.this));
+                        t.setData("itemId", key);
+                        VDragEvent drag = VDragAndDropManager.get().startDrag(
+                                t, mouseDownEvent, true);
+
+                        drag.createDragImage(nodeCaptionDiv, true);
+                        event.stopPropagation();
+
+                        mouseDownEvent = null;
+                    }
+                } else if (type == Event.ONMOUSEUP) {
+                    mouseDownEvent = null;
+                }
+                if (type == Event.ONMOUSEOVER) {
+                    mouseDownEvent = null;
+                    currentMouseOverKey = key;
+                    event.stopPropagation();
+                }
+
+            } else if (type == Event.ONMOUSEDOWN
+                    && event.getButton() == NativeEvent.BUTTON_LEFT) {
+                event.preventDefault(); // text selection
+            }
+        }
+
+        /**
+         * Checks if the given element is the caption or the icon.
+         * 
+         * @param target
+         *            The element to check
+         * @return true if the element is the caption or the icon
+         */
+        public boolean isCaptionElement(com.google.gwt.dom.client.Element target) {
+            return (target == nodeCaptionSpan || (icon != null && target == icon
+                    .getElement()));
+        }
+
+        private void fireClick(final Event evt) {
+            /*
+             * Ensure we have focus in tree before sending variables. Otherwise
+             * previously modified field may contain dirty variables.
+             */
+            if (!treeHasFocus) {
+                if (BrowserInfo.get().isOpera()) {
+                    if (focusedNode == null) {
+                        getNodeByKey(key).setFocused(true);
+                    } else {
+                        focusedNode.setFocused(true);
+                    }
+                } else {
+                    focus();
+                }
+            }
+
+            final MouseEventDetails details = MouseEventDetailsBuilder
+                    .buildMouseEventDetails(evt);
+
+            executeEventCommand(new ScheduledCommand() {
+
+                @Override
+                public void execute() {
+                    // Determine if we should send the event immediately to the
+                    // server. We do not want to send the event if there is a
+                    // selection event happening after this. In all other cases
+                    // we want to send it immediately.
+                    boolean sendClickEventNow = true;
+
+                    if (details.getButton() == MouseButton.LEFT && immediate
+                            && selectable) {
+                        // Probably a selection that will cause a value change
+                        // event to be sent
+                        sendClickEventNow = false;
+
+                        // The exception is that user clicked on the
+                        // currently selected row and null selection is not
+                        // allowed == no selection event
+                        if (isSelected() && selectedIds.size() == 1
+                                && !isNullSelectionAllowed) {
+                            sendClickEventNow = true;
+                        }
+                    }
+
+                    client.updateVariable(paintableId, "clickedKey", key, false);
+                    client.updateVariable(paintableId, "clickEvent",
+                            details.toString(), sendClickEventNow);
+                }
+            });
+        }
+
+        /*
+         * Must wait for Safari to focus before sending click and value change
+         * events (see #6373, #6374)
+         */
+        private void executeEventCommand(ScheduledCommand command) {
+            if (BrowserInfo.get().isWebkit() && !treeHasFocus) {
+                Scheduler.get().scheduleDeferred(command);
+            } else {
+                command.execute();
+            }
+        }
+
+        private void toggleSelection() {
+            if (selectable) {
+                VTree.this.setSelected(this, !isSelected());
+            }
+        }
+
+        private void toggleState() {
+            setState(!getState(), true);
+        }
+
+        protected void constructDom() {
+            addStyleName(CLASSNAME);
+
+            nodeCaptionDiv = DOM.createDiv();
+            DOM.setElementProperty(nodeCaptionDiv, "className", CLASSNAME
+                    + "-caption");
+            Element wrapper = DOM.createDiv();
+            nodeCaptionSpan = DOM.createSpan();
+            DOM.appendChild(getElement(), nodeCaptionDiv);
+            DOM.appendChild(nodeCaptionDiv, wrapper);
+            DOM.appendChild(wrapper, nodeCaptionSpan);
+
+            if (BrowserInfo.get().isOpera()) {
+                /*
+                 * Focus the caption div of the node to get keyboard navigation
+                 * to work without scrolling up or down when focusing a node.
+                 */
+                nodeCaptionDiv.setTabIndex(-1);
+            }
+
+            childNodeContainer = new FlowPanel();
+            childNodeContainer.setStyleName(CLASSNAME + "-children");
+            setWidget(childNodeContainer);
+        }
+
+        public boolean isLeaf() {
+            String[] styleNames = getStyleName().split(" ");
+            for (String styleName : styleNames) {
+                if (styleName.equals(CLASSNAME + "-leaf")) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        /** For internal use only. May be removed or replaced in the future. */
+        public void setState(boolean state, boolean notifyServer) {
+            if (open == state) {
+                return;
+            }
+            if (state) {
+                if (!childrenLoaded && notifyServer) {
+                    client.updateVariable(paintableId, "requestChildTree",
+                            true, false);
+                }
+                if (notifyServer) {
+                    client.updateVariable(paintableId, "expand",
+                            new String[] { key }, true);
+                }
+                addStyleName(CLASSNAME + "-expanded");
+                childNodeContainer.setVisible(true);
+
+            } else {
+                removeStyleName(CLASSNAME + "-expanded");
+                childNodeContainer.setVisible(false);
+                if (notifyServer) {
+                    client.updateVariable(paintableId, "collapse",
+                            new String[] { key }, true);
+                }
+            }
+            open = state;
+
+            if (!rendering) {
+                Util.notifyParentOfSizeChange(VTree.this, false);
+            }
+        }
+
+        /** For internal use only. May be removed or replaced in the future. */
+        public boolean getState() {
+            return open;
+        }
+
+        /** For internal use only. May be removed or replaced in the future. */
+        public void setText(String text) {
+            DOM.setInnerText(nodeCaptionSpan, text);
+        }
+
+        public boolean isChildrenLoaded() {
+            return childrenLoaded;
+        }
+
+        /**
+         * Returns the children of the node
+         * 
+         * @return A set of tree nodes
+         */
+        public List<TreeNode> getChildren() {
+            List<TreeNode> nodes = new LinkedList<TreeNode>();
+
+            if (!isLeaf() && isChildrenLoaded()) {
+                Iterator<Widget> iter = childNodeContainer.iterator();
+                while (iter.hasNext()) {
+                    TreeNode node = (TreeNode) iter.next();
+                    nodes.add(node);
+                }
+            }
+            return nodes;
+        }
+
+        @Override
+        public Action[] getActions() {
+            if (actionKeys == null) {
+                return new Action[] {};
+            }
+            final Action[] actions = new Action[actionKeys.length];
+            for (int i = 0; i < actions.length; i++) {
+                final String actionKey = actionKeys[i];
+                final TreeAction a = new TreeAction(this, String.valueOf(key),
+                        actionKey);
+                a.setCaption(getActionCaption(actionKey));
+                a.setIconUrl(getActionIcon(actionKey));
+                actions[i] = a;
+            }
+            return actions;
+        }
+
+        @Override
+        public ApplicationConnection getClient() {
+            return client;
+        }
+
+        @Override
+        public String getPaintableId() {
+            return paintableId;
+        }
+
+        /**
+         * Adds/removes Vaadin specific style name.
+         * <p>
+         * For internal use only. May be removed or replaced in the future.
+         * 
+         * @param selected
+         */
+        public void setSelected(boolean selected) {
+            // add style name to caption dom structure only, not to subtree
+            setStyleName(nodeCaptionDiv, "v-tree-node-selected", selected);
+        }
+
+        protected boolean isSelected() {
+            return VTree.this.isSelected(this);
+        }
+
+        /**
+         * Travels up the hierarchy looking for this node
+         * 
+         * @param child
+         *            The child which grandparent this is or is not
+         * @return True if this is a grandparent of the child node
+         */
+        public boolean isGrandParentOf(TreeNode child) {
+            TreeNode currentNode = child;
+            boolean isGrandParent = false;
+            while (currentNode != null) {
+                currentNode = currentNode.getParentNode();
+                if (currentNode == this) {
+                    isGrandParent = true;
+                    break;
+                }
+            }
+            return isGrandParent;
+        }
+
+        public boolean isSibling(TreeNode node) {
+            return node.getParentNode() == getParentNode();
+        }
+
+        public void showContextMenu(Event event) {
+            if (!readonly && !disabled) {
+                if (actionKeys != null) {
+                    int left = event.getClientX();
+                    int top = event.getClientY();
+                    top += Window.getScrollTop();
+                    left += Window.getScrollLeft();
+                    client.getContextMenu().showAt(this, left, top);
+                }
+                event.stopPropagation();
+                event.preventDefault();
+            }
+        }
+
+        /*
+         * (non-Javadoc)
+         * 
+         * @see com.google.gwt.user.client.ui.Widget#onDetach()
+         */
+        @Override
+        protected void onDetach() {
+            super.onDetach();
+            client.getContextMenu().ensureHidden(this);
+        }
+
+        /*
+         * (non-Javadoc)
+         * 
+         * @see com.google.gwt.user.client.ui.UIObject#toString()
+         */
+        @Override
+        public String toString() {
+            return nodeCaptionSpan.getInnerText();
+        }
+
+        /**
+         * Is the node focused?
+         * 
+         * @param focused
+         *            True if focused, false if not
+         */
+        public void setFocused(boolean focused) {
+            if (!this.focused && focused) {
+                nodeCaptionDiv.addClassName(CLASSNAME_FOCUSED);
+
+                this.focused = focused;
+                if (BrowserInfo.get().isOpera()) {
+                    nodeCaptionDiv.focus();
+                }
+                treeHasFocus = true;
+            } else if (this.focused && !focused) {
+                nodeCaptionDiv.removeClassName(CLASSNAME_FOCUSED);
+                this.focused = focused;
+                treeHasFocus = false;
+            }
+        }
+
+        /**
+         * Scrolls the caption into view
+         */
+        public void scrollIntoView() {
+            Util.scrollIntoViewVertically(nodeCaptionDiv);
+        }
+
+        public void setIcon(String iconUrl) {
+            if (iconUrl != null) {
+                // Add icon if not present
+                if (icon == null) {
+                    icon = new Icon(client);
+                    DOM.insertBefore(DOM.getFirstChild(nodeCaptionDiv),
+                            icon.getElement(), nodeCaptionSpan);
+                }
+                icon.setUri(iconUrl);
+            } else {
+                // Remove icon if present
+                if (icon != null) {
+                    DOM.removeChild(DOM.getFirstChild(nodeCaptionDiv),
+                            icon.getElement());
+                    icon = null;
+                }
+            }
+        }
+
+        public void setNodeStyleName(String styleName) {
+            addStyleName(TreeNode.CLASSNAME + "-" + styleName);
+            setStyleName(nodeCaptionDiv, TreeNode.CLASSNAME + "-caption-"
+                    + styleName, true);
+            childNodeContainer.addStyleName(TreeNode.CLASSNAME + "-children-"
+                    + styleName);
+
+        }
+
+    }
+
+    @Override
+    public VDropHandler getDropHandler() {
+        return dropHandler;
+    }
+
+    public TreeNode getNodeByKey(String key) {
+        return keyToNode.get(key);
+    }
+
+    /**
+     * Deselects all items in the tree
+     */
+    public void deselectAll() {
+        for (String key : selectedIds) {
+            TreeNode node = keyToNode.get(key);
+            if (node != null) {
+                node.setSelected(false);
+            }
+        }
+        selectedIds.clear();
+        selectionHasChanged = true;
+    }
+
+    /**
+     * Selects a range of nodes
+     * 
+     * @param startNodeKey
+     *            The start node key
+     * @param endNodeKey
+     *            The end node key
+     */
+    private void selectNodeRange(String startNodeKey, String endNodeKey) {
+
+        TreeNode startNode = keyToNode.get(startNodeKey);
+        TreeNode endNode = keyToNode.get(endNodeKey);
+
+        // The nodes have the same parent
+        if (startNode.getParent() == endNode.getParent()) {
+            doSiblingSelection(startNode, endNode);
+
+            // The start node is a grandparent of the end node
+        } else if (startNode.isGrandParentOf(endNode)) {
+            doRelationSelection(startNode, endNode);
+
+            // The end node is a grandparent of the start node
+        } else if (endNode.isGrandParentOf(startNode)) {
+            doRelationSelection(endNode, startNode);
+
+        } else {
+            doNoRelationSelection(startNode, endNode);
+        }
+    }
+
+    /**
+     * Selects a node and deselect all other nodes
+     * 
+     * @param node
+     *            The node to select
+     */
+    private void selectNode(TreeNode node, boolean deselectPrevious) {
+        if (deselectPrevious) {
+            deselectAll();
+        }
+
+        if (node != null) {
+            node.setSelected(true);
+            selectedIds.add(node.key);
+            lastSelection = node;
+        }
+        selectionHasChanged = true;
+    }
+
+    /**
+     * Deselects a node
+     * 
+     * @param node
+     *            The node to deselect
+     */
+    private void deselectNode(TreeNode node) {
+        node.setSelected(false);
+        selectedIds.remove(node.key);
+        selectionHasChanged = true;
+    }
+
+    /**
+     * Selects all the open children to a node
+     * 
+     * @param node
+     *            The parent node
+     */
+    private void selectAllChildren(TreeNode node, boolean includeRootNode) {
+        if (includeRootNode) {
+            node.setSelected(true);
+            selectedIds.add(node.key);
+        }
+
+        for (TreeNode child : node.getChildren()) {
+            if (!child.isLeaf() && child.getState()) {
+                selectAllChildren(child, true);
+            } else {
+                child.setSelected(true);
+                selectedIds.add(child.key);
+            }
+        }
+        selectionHasChanged = true;
+    }
+
+    /**
+     * Selects all children until a stop child is reached
+     * 
+     * @param root
+     *            The root not to start from
+     * @param stopNode
+     *            The node to finish with
+     * @param includeRootNode
+     *            Should the root node be selected
+     * @param includeStopNode
+     *            Should the stop node be selected
+     * 
+     * @return Returns false if the stop child was found, else true if all
+     *         children was selected
+     */
+    private boolean selectAllChildrenUntil(TreeNode root, TreeNode stopNode,
+            boolean includeRootNode, boolean includeStopNode) {
+        if (includeRootNode) {
+            root.setSelected(true);
+            selectedIds.add(root.key);
+        }
+        if (root.getState() && root != stopNode) {
+            for (TreeNode child : root.getChildren()) {
+                if (!child.isLeaf() && child.getState() && child != stopNode) {
+                    if (!selectAllChildrenUntil(child, stopNode, true,
+                            includeStopNode)) {
+                        return false;
+                    }
+                } else if (child == stopNode) {
+                    if (includeStopNode) {
+                        child.setSelected(true);
+                        selectedIds.add(child.key);
+                    }
+                    return false;
+                } else {
+                    child.setSelected(true);
+                    selectedIds.add(child.key);
+                }
+            }
+        }
+        selectionHasChanged = true;
+
+        return true;
+    }
+
+    /**
+     * Select a range between two nodes which have no relation to each other
+     * 
+     * @param startNode
+     *            The start node to start the selection from
+     * @param endNode
+     *            The end node to end the selection to
+     */
+    private void doNoRelationSelection(TreeNode startNode, TreeNode endNode) {
+
+        TreeNode commonParent = getCommonGrandParent(startNode, endNode);
+        TreeNode startBranch = null, endBranch = null;
+
+        // Find the children of the common parent
+        List<TreeNode> children;
+        if (commonParent != null) {
+            children = commonParent.getChildren();
+        } else {
+            children = getRootNodes();
+        }
+
+        // Find the start and end branches
+        for (TreeNode node : children) {
+            if (nodeIsInBranch(startNode, node)) {
+                startBranch = node;
+            }
+            if (nodeIsInBranch(endNode, node)) {
+                endBranch = node;
+            }
+        }
+
+        // Swap nodes if necessary
+        if (children.indexOf(startBranch) > children.indexOf(endBranch)) {
+            TreeNode temp = startBranch;
+            startBranch = endBranch;
+            endBranch = temp;
+
+            temp = startNode;
+            startNode = endNode;
+            endNode = temp;
+        }
+
+        // Select all children under the start node
+        selectAllChildren(startNode, true);
+        TreeNode startParent = startNode.getParentNode();
+        TreeNode currentNode = startNode;
+        while (startParent != null && startParent != commonParent) {
+            List<TreeNode> startChildren = startParent.getChildren();
+            for (int i = startChildren.indexOf(currentNode) + 1; i < startChildren
+                    .size(); i++) {
+                selectAllChildren(startChildren.get(i), true);
+            }
+
+            currentNode = startParent;
+            startParent = startParent.getParentNode();
+        }
+
+        // Select nodes until the end node is reached
+        for (int i = children.indexOf(startBranch) + 1; i <= children
+                .indexOf(endBranch); i++) {
+            selectAllChildrenUntil(children.get(i), endNode, true, true);
+        }
+
+        // Ensure end node was selected
+        endNode.setSelected(true);
+        selectedIds.add(endNode.key);
+        selectionHasChanged = true;
+    }
+
+    /**
+     * Examines the children of the branch node and returns true if a node is in
+     * that branch
+     * 
+     * @param node
+     *            The node to search for
+     * @param branch
+     *            The branch to search in
+     * @return True if found, false if not found
+     */
+    private boolean nodeIsInBranch(TreeNode node, TreeNode branch) {
+        if (node == branch) {
+            return true;
+        }
+        for (TreeNode child : branch.getChildren()) {
+            if (child == node) {
+                return true;
+            }
+            if (!child.isLeaf() && child.getState()) {
+                if (nodeIsInBranch(node, child)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Selects a range of items which are in direct relation with each other.<br/>
+     * NOTE: The start node <b>MUST</b> be before the end node!
+     * 
+     * @param startNode
+     * 
+     * @param endNode
+     */
+    private void doRelationSelection(TreeNode startNode, TreeNode endNode) {
+        TreeNode currentNode = endNode;
+        while (currentNode != startNode) {
+            currentNode.setSelected(true);
+            selectedIds.add(currentNode.key);
+
+            // Traverse children above the selection
+            List<TreeNode> subChildren = currentNode.getParentNode()
+                    .getChildren();
+            if (subChildren.size() > 1) {
+                selectNodeRange(subChildren.iterator().next().key,
+                        currentNode.key);
+            } else if (subChildren.size() == 1) {
+                TreeNode n = subChildren.get(0);
+                n.setSelected(true);
+                selectedIds.add(n.key);
+            }
+
+            currentNode = currentNode.getParentNode();
+        }
+        startNode.setSelected(true);
+        selectedIds.add(startNode.key);
+        selectionHasChanged = true;
+    }
+
+    /**
+     * Selects a range of items which have the same parent.
+     * 
+     * @param startNode
+     *            The start node
+     * @param endNode
+     *            The end node
+     */
+    private void doSiblingSelection(TreeNode startNode, TreeNode endNode) {
+        TreeNode parent = startNode.getParentNode();
+
+        List<TreeNode> children;
+        if (parent == null) {
+            // Topmost parent
+            children = getRootNodes();
+        } else {
+            children = parent.getChildren();
+        }
+
+        // Swap start and end point if needed
+        if (children.indexOf(startNode) > children.indexOf(endNode)) {
+            TreeNode temp = startNode;
+            startNode = endNode;
+            endNode = temp;
+        }
+
+        Iterator<TreeNode> childIter = children.iterator();
+        boolean startFound = false;
+        while (childIter.hasNext()) {
+            TreeNode node = childIter.next();
+            if (node == startNode) {
+                startFound = true;
+            }
+
+            if (startFound && node != endNode && node.getState()) {
+                selectAllChildren(node, true);
+            } else if (startFound && node != endNode) {
+                node.setSelected(true);
+                selectedIds.add(node.key);
+            }
+
+            if (node == endNode) {
+                node.setSelected(true);
+                selectedIds.add(node.key);
+                break;
+            }
+        }
+        selectionHasChanged = true;
+    }
+
+    /**
+     * Returns the first common parent of two nodes
+     * 
+     * @param node1
+     *            The first node
+     * @param node2
+     *            The second node
+     * @return The common parent or null
+     */
+    public TreeNode getCommonGrandParent(TreeNode node1, TreeNode node2) {
+        // If either one does not have a grand parent then return null
+        if (node1.getParentNode() == null || node2.getParentNode() == null) {
+            return null;
+        }
+
+        // If the nodes are parents of each other then return null
+        if (node1.isGrandParentOf(node2) || node2.isGrandParentOf(node1)) {
+            return null;
+        }
+
+        // Get parents of node1
+        List<TreeNode> parents1 = new ArrayList<TreeNode>();
+        TreeNode parent1 = node1.getParentNode();
+        while (parent1 != null) {
+            parents1.add(parent1);
+            parent1 = parent1.getParentNode();
+        }
+
+        // Get parents of node2
+        List<TreeNode> parents2 = new ArrayList<TreeNode>();
+        TreeNode parent2 = node2.getParentNode();
+        while (parent2 != null) {
+            parents2.add(parent2);
+            parent2 = parent2.getParentNode();
+        }
+
+        // Search the parents for the first common parent
+        for (int i = 0; i < parents1.size(); i++) {
+            parent1 = parents1.get(i);
+            for (int j = 0; j < parents2.size(); j++) {
+                parent2 = parents2.get(j);
+                if (parent1 == parent2) {
+                    return parent1;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Sets the node currently in focus
+     * 
+     * @param node
+     *            The node to focus or null to remove the focus completely
+     * @param scrollIntoView
+     *            Scroll the node into view
+     */
+    public void setFocusedNode(TreeNode node, boolean scrollIntoView) {
+        // Unfocus previously focused node
+        if (focusedNode != null) {
+            focusedNode.setFocused(false);
+        }
+
+        if (node != null) {
+            node.setFocused(true);
+        }
+
+        focusedNode = node;
+
+        if (node != null && scrollIntoView) {
+            /*
+             * Delay scrolling the focused node into view if we are still
+             * rendering. #5396
+             */
+            if (!rendering) {
+                node.scrollIntoView();
+            } else {
+                Scheduler.get().scheduleDeferred(new Command() {
+                    @Override
+                    public void execute() {
+                        focusedNode.scrollIntoView();
+                    }
+                });
+            }
+        }
+    }
+
+    /**
+     * Focuses a node and scrolls it into view
+     * 
+     * @param node
+     *            The node to focus
+     */
+    public void setFocusedNode(TreeNode node) {
+        setFocusedNode(node, true);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
+     * .dom.client.FocusEvent)
+     */
+    @Override
+    public void onFocus(FocusEvent event) {
+        treeHasFocus = true;
+        // If no node has focus, focus the first item in the tree
+        if (focusedNode == null && lastSelection == null && selectable) {
+            setFocusedNode(getFirstRootNode(), false);
+        } else if (focusedNode != null && selectable) {
+            setFocusedNode(focusedNode, false);
+        } else if (lastSelection != null && selectable) {
+            setFocusedNode(lastSelection, 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) {
+        treeHasFocus = false;
+        if (focusedNode != null) {
+            focusedNode.setFocused(false);
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google
+     * .gwt.event.dom.client.KeyPressEvent)
+     */
+    @Override
+    public void onKeyPress(KeyPressEvent event) {
+        NativeEvent nativeEvent = event.getNativeEvent();
+        int keyCode = nativeEvent.getKeyCode();
+        if (keyCode == 0 && nativeEvent.getCharCode() == ' ') {
+            // Provide a keyCode for space to be compatible with FireFox
+            // keypress event
+            keyCode = CHARCODE_SPACE;
+        }
+        if (handleKeyNavigation(keyCode,
+                event.isControlKeyDown() || event.isMetaKeyDown(),
+                event.isShiftKeyDown())) {
+            event.preventDefault();
+            event.stopPropagation();
+        }
+    }
+
+    /*
+     * (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 (handleKeyNavigation(event.getNativeEvent().getKeyCode(),
+                event.isControlKeyDown() || event.isMetaKeyDown(),
+                event.isShiftKeyDown())) {
+            event.preventDefault();
+            event.stopPropagation();
+        }
+    }
+
+    /**
+     * Handles the keyboard navigation
+     * 
+     * @param keycode
+     *            The keycode of the pressed key
+     * @param ctrl
+     *            Was ctrl pressed
+     * @param shift
+     *            Was shift pressed
+     * @return Returns true if the key was handled, else false
+     */
+    protected boolean handleKeyNavigation(int keycode, boolean ctrl,
+            boolean shift) {
+        // Navigate down
+        if (keycode == getNavigationDownKey()) {
+            TreeNode node = null;
+            // If node is open and has children then move in to the children
+            if (!focusedNode.isLeaf() && focusedNode.getState()
+                    && focusedNode.getChildren().size() > 0) {
+                node = focusedNode.getChildren().get(0);
+            }
+
+            // Else move down to the next sibling
+            else {
+                node = getNextSibling(focusedNode);
+                if (node == null) {
+                    // Else jump to the parent and try to select the next
+                    // sibling there
+                    TreeNode current = focusedNode;
+                    while (node == null && current.getParentNode() != null) {
+                        node = getNextSibling(current.getParentNode());
+                        current = current.getParentNode();
+                    }
+                }
+            }
+
+            if (node != null) {
+                setFocusedNode(node);
+                if (selectable) {
+                    if (!ctrl && !shift) {
+                        selectNode(node, true);
+                    } else if (shift && isMultiselect) {
+                        deselectAll();
+                        selectNodeRange(lastSelection.key, node.key);
+                    } else if (shift) {
+                        selectNode(node, true);
+                    }
+                }
+            }
+            return true;
+        }
+
+        // Navigate up
+        if (keycode == getNavigationUpKey()) {
+            TreeNode prev = getPreviousSibling(focusedNode);
+            TreeNode node = null;
+            if (prev != null) {
+                node = getLastVisibleChildInTree(prev);
+            } else if (focusedNode.getParentNode() != null) {
+                node = focusedNode.getParentNode();
+            }
+            if (node != null) {
+                setFocusedNode(node);
+                if (selectable) {
+                    if (!ctrl && !shift) {
+                        selectNode(node, true);
+                    } else if (shift && isMultiselect) {
+                        deselectAll();
+                        selectNodeRange(lastSelection.key, node.key);
+                    } else if (shift) {
+                        selectNode(node, true);
+                    }
+                }
+            }
+            return true;
+        }
+
+        // Navigate left (close branch)
+        if (keycode == getNavigationLeftKey()) {
+            if (!focusedNode.isLeaf() && focusedNode.getState()) {
+                focusedNode.setState(false, true);
+            } else if (focusedNode.getParentNode() != null
+                    && (focusedNode.isLeaf() || !focusedNode.getState())) {
+
+                if (ctrl || !selectable) {
+                    setFocusedNode(focusedNode.getParentNode());
+                } else if (shift) {
+                    doRelationSelection(focusedNode.getParentNode(),
+                            focusedNode);
+                    setFocusedNode(focusedNode.getParentNode());
+                } else {
+                    focusAndSelectNode(focusedNode.getParentNode());
+                }
+            }
+            return true;
+        }
+
+        // Navigate right (open branch)
+        if (keycode == getNavigationRightKey()) {
+            if (!focusedNode.isLeaf() && !focusedNode.getState()) {
+                focusedNode.setState(true, true);
+            } else if (!focusedNode.isLeaf()) {
+                if (ctrl || !selectable) {
+                    setFocusedNode(focusedNode.getChildren().get(0));
+                } else if (shift) {
+                    setSelected(focusedNode, true);
+                    setFocusedNode(focusedNode.getChildren().get(0));
+                    setSelected(focusedNode, true);
+                } else {
+                    focusAndSelectNode(focusedNode.getChildren().get(0));
+                }
+            }
+            return true;
+        }
+
+        // Selection
+        if (keycode == getNavigationSelectKey()) {
+            if (!focusedNode.isSelected()) {
+                selectNode(
+                        focusedNode,
+                        (!isMultiselect || multiSelectMode == MULTISELECT_MODE_SIMPLE)
+                                && selectable);
+            } else {
+                deselectNode(focusedNode);
+            }
+            return true;
+        }
+
+        // Home selection
+        if (keycode == getNavigationStartKey()) {
+            TreeNode node = getFirstRootNode();
+            if (ctrl || !selectable) {
+                setFocusedNode(node);
+            } else if (shift) {
+                deselectAll();
+                selectNodeRange(focusedNode.key, node.key);
+            } else {
+                selectNode(node, true);
+            }
+            sendSelectionToServer();
+            return true;
+        }
+
+        // End selection
+        if (keycode == getNavigationEndKey()) {
+            TreeNode lastNode = getLastRootNode();
+            TreeNode node = getLastVisibleChildInTree(lastNode);
+            if (ctrl || !selectable) {
+                setFocusedNode(node);
+            } else if (shift) {
+                deselectAll();
+                selectNodeRange(focusedNode.key, node.key);
+            } else {
+                selectNode(node, true);
+            }
+            sendSelectionToServer();
+            return true;
+        }
+
+        return false;
+    }
+
+    private void focusAndSelectNode(TreeNode node) {
+        /*
+         * Keyboard navigation doesn't work reliably if the tree is in
+         * multiselect mode as well as isNullSelectionAllowed = false. It first
+         * tries to deselect the old focused node, which fails since there must
+         * be at least one selection. After this the newly focused node is
+         * selected and we've ended up with two selected nodes even though we
+         * only navigated with the arrow keys.
+         * 
+         * Because of this, we first select the next node and later de-select
+         * the old one.
+         */
+        TreeNode oldFocusedNode = focusedNode;
+        setFocusedNode(node);
+        setSelected(focusedNode, true);
+        setSelected(oldFocusedNode, false);
+    }
+
+    /**
+     * Traverses the tree to the bottom most child
+     * 
+     * @param root
+     *            The root of the tree
+     * @return The bottom most child
+     */
+    private TreeNode getLastVisibleChildInTree(TreeNode root) {
+        if (root.isLeaf() || !root.getState() || root.getChildren().size() == 0) {
+            return root;
+        }
+        List<TreeNode> children = root.getChildren();
+        return getLastVisibleChildInTree(children.get(children.size() - 1));
+    }
+
+    /**
+     * Gets the next sibling in the tree
+     * 
+     * @param node
+     *            The node to get the sibling for
+     * @return The sibling node or null if the node is the last sibling
+     */
+    private TreeNode getNextSibling(TreeNode node) {
+        TreeNode parent = node.getParentNode();
+        List<TreeNode> children;
+        if (parent == null) {
+            children = getRootNodes();
+        } else {
+            children = parent.getChildren();
+        }
+
+        int idx = children.indexOf(node);
+        if (idx < children.size() - 1) {
+            return children.get(idx + 1);
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns the previous sibling in the tree
+     * 
+     * @param node
+     *            The node to get the sibling for
+     * @return The sibling node or null if the node is the first sibling
+     */
+    private TreeNode getPreviousSibling(TreeNode node) {
+        TreeNode parent = node.getParentNode();
+        List<TreeNode> children;
+        if (parent == null) {
+            children = getRootNodes();
+        } else {
+            children = parent.getChildren();
+        }
+
+        int idx = children.indexOf(node);
+        if (idx > 0) {
+            return children.get(idx - 1);
+        }
+
+        return null;
+    }
+
+    /**
+     * Add this to the element mouse down event by using element.setPropertyJSO
+     * ("onselectstart",applyDisableTextSelectionIEHack()); Remove it then again
+     * when the mouse is depressed in the mouse up event.
+     * 
+     * @return Returns the JSO preventing text selection
+     */
+    private native JavaScriptObject applyDisableTextSelectionIEHack()
+    /*-{
+            return function(){ return false; };
+    }-*/;
+
+    /**
+     * Get the key that moves the selection head upwards. By default it is the
+     * up arrow key but by overriding this you can change the key to whatever
+     * you want.
+     * 
+     * @return The keycode of the key
+     */
+    protected int getNavigationUpKey() {
+        return KeyCodes.KEY_UP;
+    }
+
+    /**
+     * Get the key that moves the selection head downwards. By default it is the
+     * down arrow key but by overriding this you can change the key to whatever
+     * you want.
+     * 
+     * @return The keycode of the key
+     */
+    protected int getNavigationDownKey() {
+        return KeyCodes.KEY_DOWN;
+    }
+
+    /**
+     * Get the key that scrolls to the left in the table. By default it is the
+     * left arrow key but by overriding this you can change the key to whatever
+     * you want.
+     * 
+     * @return The keycode of the key
+     */
+    protected int getNavigationLeftKey() {
+        return KeyCodes.KEY_LEFT;
+    }
+
+    /**
+     * Get the key that scroll to the right on the table. By default it is the
+     * right arrow key but by overriding this you can change the key to whatever
+     * you want.
+     * 
+     * @return The keycode of the key
+     */
+    protected int getNavigationRightKey() {
+        return KeyCodes.KEY_RIGHT;
+    }
+
+    /**
+     * Get the key that selects an item in the table. By default it is the space
+     * bar key but by overriding this you can change the key to whatever you
+     * want.
+     * 
+     * @return
+     */
+    protected int getNavigationSelectKey() {
+        return CHARCODE_SPACE;
+    }
+
+    /**
+     * Get the key the moves the selection one page up in the table. By default
+     * this is the Page Up key but by overriding this you can change the key to
+     * whatever you want.
+     * 
+     * @return
+     */
+    protected int getNavigationPageUpKey() {
+        return KeyCodes.KEY_PAGEUP;
+    }
+
+    /**
+     * Get the key the moves the selection one page down in the table. By
+     * default this is the Page Down key but by overriding this you can change
+     * the key to whatever you want.
+     * 
+     * @return
+     */
+    protected int getNavigationPageDownKey() {
+        return KeyCodes.KEY_PAGEDOWN;
+    }
+
+    /**
+     * Get the key the moves the selection to the beginning of the table. By
+     * default this is the Home key but by overriding this you can change the
+     * key to whatever you want.
+     * 
+     * @return
+     */
+    protected int getNavigationStartKey() {
+        return KeyCodes.KEY_HOME;
+    }
+
+    /**
+     * Get the key the moves the selection to the end of the table. By default
+     * this is the End key but by overriding this you can change the key to
+     * whatever you want.
+     * 
+     * @return
+     */
+    protected int getNavigationEndKey() {
+        return KeyCodes.KEY_END;
+    }
+
+    private final String SUBPART_NODE_PREFIX = "n";
+    private final String EXPAND_IDENTIFIER = "expand";
+
+    /*
+     * In webkit, focus may have been requested for this component but not yet
+     * gained. Use this to trac if tree has gained the focus on webkit. See
+     * FocusImplSafari and #6373
+     */
+    private boolean treeHasFocus;
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see com.vaadin.client.ui.SubPartAware#getSubPartElement(java
+     * .lang.String)
+     */
+    @Override
+    public Element getSubPartElement(String subPart) {
+        if ("fe".equals(subPart)) {
+            if (BrowserInfo.get().isOpera() && focusedNode != null) {
+                return focusedNode.getElement();
+            }
+            return getFocusElement();
+        }
+
+        if (subPart.startsWith(SUBPART_NODE_PREFIX + "[")) {
+            boolean expandCollapse = false;
+
+            // Node
+            String[] nodes = subPart.split("/");
+            TreeNode treeNode = null;
+            try {
+                for (String node : nodes) {
+                    if (node.startsWith(SUBPART_NODE_PREFIX)) {
+
+                        // skip SUBPART_NODE_PREFIX"["
+                        node = node.substring(SUBPART_NODE_PREFIX.length() + 1);
+                        // skip "]"
+                        node = node.substring(0, node.length() - 1);
+                        int position = Integer.parseInt(node);
+                        if (treeNode == null) {
+                            treeNode = getRootNodes().get(position);
+                        } else {
+                            treeNode = treeNode.getChildren().get(position);
+                        }
+                    } else if (node.startsWith(EXPAND_IDENTIFIER)) {
+                        expandCollapse = true;
+                    }
+                }
+
+                if (expandCollapse) {
+                    return treeNode.getElement();
+                } else {
+                    return treeNode.nodeCaptionSpan;
+                }
+            } catch (Exception e) {
+                // Invalid locator string or node could not be found
+                return null;
+            }
+        }
+        return null;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see com.vaadin.client.ui.SubPartAware#getSubPartName(com.google
+     * .gwt.user.client.Element)
+     */
+    @Override
+    public String getSubPartName(Element subElement) {
+        // Supported identifiers:
+        //
+        // n[index]/n[index]/n[index]{/expand}
+        //
+        // Ends with "/expand" if the target is expand/collapse indicator,
+        // otherwise ends with the node
+
+        boolean isExpandCollapse = false;
+
+        if (!getElement().isOrHasChild(subElement)) {
+            return null;
+        }
+
+        if (subElement == getFocusElement()) {
+            return "fe";
+        }
+
+        TreeNode treeNode = Util.findWidget(subElement, TreeNode.class);
+        if (treeNode == null) {
+            // Did not click on a node, let somebody else take care of the
+            // locator string
+            return null;
+        }
+
+        if (subElement == treeNode.getElement()) {
+            // Targets expand/collapse arrow
+            isExpandCollapse = true;
+        }
+
+        ArrayList<Integer> positions = new ArrayList<Integer>();
+        while (treeNode.getParentNode() != null) {
+            positions.add(0,
+                    treeNode.getParentNode().getChildren().indexOf(treeNode));
+            treeNode = treeNode.getParentNode();
+        }
+        positions.add(0, getRootNodes().indexOf(treeNode));
+
+        String locator = "";
+        for (Integer i : positions) {
+            locator += SUBPART_NODE_PREFIX + "[" + i + "]/";
+        }
+
+        locator = locator.substring(0, locator.length() - 1);
+        if (isExpandCollapse) {
+            locator += "/" + EXPAND_IDENTIFIER;
+        }
+        return locator;
+    }
+
+    @Override
+    public Action[] getActions() {
+        if (bodyActionKeys == null) {
+            return new Action[] {};
+        }
+        final Action[] actions = new Action[bodyActionKeys.length];
+        for (int i = 0; i < actions.length; i++) {
+            final String actionKey = bodyActionKeys[i];
+            final TreeAction a = new TreeAction(this, null, actionKey);
+            a.setCaption(getActionCaption(actionKey));
+            a.setIconUrl(getActionIcon(actionKey));
+            actions[i] = a;
+        }
+        return actions;
+    }
+
+    @Override
+    public ApplicationConnection getClient() {
+        return client;
+    }
+
+    @Override
+    public String getPaintableId() {
+        return paintableId;
+    }
+
+    private void handleBodyContextMenu(ContextMenuEvent event) {
+        if (!readonly && !disabled) {
+            if (bodyActionKeys != null) {
+                int left = event.getNativeEvent().getClientX();
+                int top = event.getNativeEvent().getClientY();
+                top += Window.getScrollTop();
+                left += Window.getScrollLeft();
+                client.getContextMenu().showAt(this, left, top);
+            }
+            event.stopPropagation();
+            event.preventDefault();
+        }
+    }
+
+    public void registerAction(String key, String caption, String iconUrl) {
+        actionMap.put(key + "_c", caption);
+        if (iconUrl != null) {
+            actionMap.put(key + "_i", iconUrl);
+        } else {
+            actionMap.remove(key + "_i");
+        }
+
+    }
+
+    public void registerNode(TreeNode treeNode) {
+        keyToNode.put(treeNode.key, treeNode);
+    }
+
+    public void clearNodeToKeyMap() {
+        keyToNode.clear();
+    }
+
+}
diff --git a/client/src/com/vaadin/client/ui/VTreeTable.java b/client/src/com/vaadin/client/ui/VTreeTable.java
new file mode 100644 (file)
index 0000000..f4e3974
--- /dev/null
@@ -0,0 +1,880 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.google.gwt.animation.client.Animation;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.ImageElement;
+import com.google.gwt.dom.client.SpanElement;
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.dom.client.Style.Visibility;
+import com.google.gwt.dom.client.TableCellElement;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ComputedStyle;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.Util;
+import com.vaadin.client.ui.VTreeTable.VTreeTableScrollBody.VTreeTableRow;
+
+public class VTreeTable extends VScrollTable {
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public static class PendingNavigationEvent {
+        public final int keycode;
+        public final boolean ctrl;
+        public final boolean shift;
+
+        public PendingNavigationEvent(int keycode, boolean ctrl, boolean shift) {
+            this.keycode = keycode;
+            this.ctrl = ctrl;
+            this.shift = shift;
+        }
+
+        @Override
+        public String toString() {
+            String string = "Keyboard event: " + keycode;
+            if (ctrl) {
+                string += " + ctrl";
+            }
+            if (shift) {
+                string += " + shift";
+            }
+            return string;
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean collapseRequest;
+
+    private boolean selectionPending;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public int colIndexOfHierarchy;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public String collapsedRowKey;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public VTreeTableScrollBody scrollBody;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean animationsEnabled;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public LinkedList<PendingNavigationEvent> pendingNavigationEvents = new LinkedList<VTreeTable.PendingNavigationEvent>();
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean focusParentResponsePending;
+
+    @Override
+    protected VScrollTableBody createScrollBody() {
+        scrollBody = new VTreeTableScrollBody();
+        return scrollBody;
+    }
+
+    /*
+     * Overridden to allow animation of expands and collapses of nodes.
+     */
+    @Override
+    public void addAndRemoveRows(UIDL partialRowAdditions) {
+        if (partialRowAdditions == null) {
+            return;
+        }
+
+        if (animationsEnabled) {
+            if (partialRowAdditions.hasAttribute("hide")) {
+                scrollBody.unlinkRowsAnimatedAndUpdateCacheWhenFinished(
+                        partialRowAdditions.getIntAttribute("firstprowix"),
+                        partialRowAdditions.getIntAttribute("numprows"));
+            } else {
+                scrollBody.insertRowsAnimated(partialRowAdditions,
+                        partialRowAdditions.getIntAttribute("firstprowix"),
+                        partialRowAdditions.getIntAttribute("numprows"));
+                discardRowsOutsideCacheWindow();
+            }
+        } else {
+            super.addAndRemoveRows(partialRowAdditions);
+        }
+    }
+
+    class VTreeTableScrollBody extends VScrollTable.VScrollTableBody {
+        private int indentWidth = -1;
+
+        VTreeTableScrollBody() {
+            super();
+        }
+
+        @Override
+        protected VScrollTableRow createRow(UIDL uidl, char[] aligns2) {
+            if (uidl.hasAttribute("gen_html")) {
+                // This is a generated row.
+                return new VTreeTableGeneratedRow(uidl, aligns2);
+            }
+            return new VTreeTableRow(uidl, aligns2);
+        }
+
+        class VTreeTableRow extends
+                VScrollTable.VScrollTableBody.VScrollTableRow {
+
+            private boolean isTreeCellAdded = false;
+            private SpanElement treeSpacer;
+            private boolean open;
+            private int depth;
+            private boolean canHaveChildren;
+            protected Widget widgetInHierarchyColumn;
+
+            public VTreeTableRow(UIDL uidl, char[] aligns2) {
+                super(uidl, aligns2);
+            }
+
+            @Override
+            public void addCell(UIDL rowUidl, String text, char align,
+                    String style, boolean textIsHTML, boolean isSorted,
+                    String description) {
+                super.addCell(rowUidl, text, align, style, textIsHTML,
+                        isSorted, description);
+
+                addTreeSpacer(rowUidl);
+            }
+
+            protected boolean addTreeSpacer(UIDL rowUidl) {
+                if (cellShowsTreeHierarchy(getElement().getChildCount() - 1)) {
+                    Element container = (Element) getElement().getLastChild()
+                            .getFirstChild();
+
+                    if (rowUidl.hasAttribute("icon")) {
+                        // icons are in first content cell in TreeTable
+                        ImageElement icon = Document.get().createImageElement();
+                        icon.setClassName("v-icon");
+                        icon.setAlt("icon");
+                        icon.setSrc(client.translateVaadinUri(rowUidl
+                                .getStringAttribute("icon")));
+                        container.insertFirst(icon);
+                    }
+
+                    String classname = "v-treetable-treespacer";
+                    if (rowUidl.getBooleanAttribute("ca")) {
+                        canHaveChildren = true;
+                        open = rowUidl.getBooleanAttribute("open");
+                        classname += open ? " v-treetable-node-open"
+                                : " v-treetable-node-closed";
+                    }
+
+                    treeSpacer = Document.get().createSpanElement();
+
+                    treeSpacer.setClassName(classname);
+                    container.insertFirst(treeSpacer);
+                    depth = rowUidl.hasAttribute("depth") ? rowUidl
+                            .getIntAttribute("depth") : 0;
+                    setIndent();
+                    isTreeCellAdded = true;
+                    return true;
+                }
+                return false;
+            }
+
+            private boolean cellShowsTreeHierarchy(int curColIndex) {
+                if (isTreeCellAdded) {
+                    return false;
+                }
+                return curColIndex == getHierarchyColumnIndex();
+            }
+
+            @Override
+            public void onBrowserEvent(Event event) {
+                if (event.getEventTarget().cast() == treeSpacer
+                        && treeSpacer.getClassName().contains("node")) {
+                    if (event.getTypeInt() == Event.ONMOUSEUP) {
+                        sendToggleCollapsedUpdate(getKey());
+                    }
+                    return;
+                }
+                super.onBrowserEvent(event);
+            }
+
+            @Override
+            public void addCell(UIDL rowUidl, Widget w, char align,
+                    String style, boolean isSorted) {
+                super.addCell(rowUidl, w, align, style, isSorted);
+                if (addTreeSpacer(rowUidl)) {
+                    widgetInHierarchyColumn = w;
+                }
+
+            }
+
+            private void setIndent() {
+                if (getIndentWidth() > 0) {
+                    treeSpacer.getParentElement().getStyle()
+                            .setPaddingLeft(getIndent(), Unit.PX);
+                    treeSpacer.getStyle().setWidth(getIndent(), Unit.PX);
+                }
+            }
+
+            @Override
+            protected void onAttach() {
+                super.onAttach();
+                if (getIndentWidth() < 0) {
+                    detectIndent(this);
+                    // If we detect indent here then the size of the hierarchy
+                    // column is still wrong as it has been set when the indent
+                    // was not known.
+                    int w = getCellWidthFromDom(getHierarchyColumnIndex());
+                    if (w >= 0) {
+                        setColWidth(getHierarchyColumnIndex(), w);
+                    }
+                }
+            }
+
+            private int getCellWidthFromDom(int cellIndex) {
+                final Element cell = DOM.getChild(getElement(), cellIndex);
+                String w = cell.getStyle().getProperty("width");
+                if (w == null || "".equals(w) || !w.endsWith("px")) {
+                    return -1;
+                } else {
+                    return Integer.parseInt(w.substring(0, w.length() - 2));
+                }
+            }
+
+            private int getHierarchyAndIconWidth() {
+                int consumedSpace = treeSpacer.getOffsetWidth();
+                if (treeSpacer.getParentElement().getChildCount() > 2) {
+                    // icon next to tree spacer
+                    consumedSpace += ((com.google.gwt.dom.client.Element) treeSpacer
+                            .getNextSibling()).getOffsetWidth();
+                }
+                return consumedSpace;
+            }
+
+            @Override
+            protected void setCellWidth(int cellIx, int width) {
+                if (cellIx == getHierarchyColumnIndex()) {
+                    // take indentation padding into account if this is the
+                    // hierarchy column
+                    int indent = getIndent();
+                    if (indent != -1) {
+                        width = Math.max(width - getIndent(), 0);
+                    }
+                }
+                super.setCellWidth(cellIx, width);
+            }
+
+            private int getHierarchyColumnIndex() {
+                return colIndexOfHierarchy + (showRowHeaders ? 1 : 0);
+            }
+
+            private int getIndent() {
+                return (depth + 1) * getIndentWidth();
+            }
+        }
+
+        protected class VTreeTableGeneratedRow extends VTreeTableRow {
+            private boolean spanColumns;
+            private boolean htmlContentAllowed;
+
+            public VTreeTableGeneratedRow(UIDL uidl, char[] aligns) {
+                super(uidl, aligns);
+                addStyleName("v-table-generated-row");
+            }
+
+            public boolean isSpanColumns() {
+                return spanColumns;
+            }
+
+            @Override
+            protected void initCellWidths() {
+                if (spanColumns) {
+                    setSpannedColumnWidthAfterDOMFullyInited();
+                } else {
+                    super.initCellWidths();
+                }
+            }
+
+            private void setSpannedColumnWidthAfterDOMFullyInited() {
+                // Defer setting width on spanned columns to make sure that
+                // they are added to the DOM before trying to calculate
+                // widths.
+                Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+
+                    @Override
+                    public void execute() {
+                        if (showRowHeaders) {
+                            setCellWidth(0, tHead.getHeaderCell(0).getWidth());
+                            calcAndSetSpanWidthOnCell(1);
+                        } else {
+                            calcAndSetSpanWidthOnCell(0);
+                        }
+                    }
+                });
+            }
+
+            @Override
+            protected boolean isRenderHtmlInCells() {
+                return htmlContentAllowed;
+            }
+
+            @Override
+            protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col,
+                    int visibleColumnIndex) {
+                htmlContentAllowed = uidl.getBooleanAttribute("gen_html");
+                spanColumns = uidl.getBooleanAttribute("gen_span");
+
+                final Iterator<?> cells = uidl.getChildIterator();
+                if (spanColumns) {
+                    int colCount = uidl.getChildCount();
+                    if (cells.hasNext()) {
+                        final Object cell = cells.next();
+                        if (cell instanceof String) {
+                            addSpannedCell(uidl, cell.toString(), aligns[0],
+                                    "", htmlContentAllowed, false, null,
+                                    colCount);
+                        } else {
+                            addSpannedCell(uidl, (Widget) cell, aligns[0], "",
+                                    false, colCount);
+                        }
+                    }
+                } else {
+                    super.addCellsFromUIDL(uidl, aligns, col,
+                            visibleColumnIndex);
+                }
+            }
+
+            private void addSpannedCell(UIDL rowUidl, Widget w, char align,
+                    String style, boolean sorted, int colCount) {
+                TableCellElement td = DOM.createTD().cast();
+                td.setColSpan(colCount);
+                initCellWithWidget(w, align, style, sorted, td);
+                td.getStyle().setHeight(getRowHeight(), Unit.PX);
+                if (addTreeSpacer(rowUidl)) {
+                    widgetInHierarchyColumn = w;
+                }
+            }
+
+            private void addSpannedCell(UIDL rowUidl, String text, char align,
+                    String style, boolean textIsHTML, boolean sorted,
+                    String description, int colCount) {
+                // String only content is optimized by not using Label widget
+                final TableCellElement td = DOM.createTD().cast();
+                td.setColSpan(colCount);
+                initCellWithText(text, align, style, textIsHTML, sorted,
+                        description, td);
+                td.getStyle().setHeight(getRowHeight(), Unit.PX);
+                addTreeSpacer(rowUidl);
+            }
+
+            @Override
+            protected void setCellWidth(int cellIx, int width) {
+                if (isSpanColumns()) {
+                    if (showRowHeaders) {
+                        if (cellIx == 0) {
+                            super.setCellWidth(0, width);
+                        } else {
+                            // We need to recalculate the spanning TDs width for
+                            // every cellIx in order to support column resizing.
+                            calcAndSetSpanWidthOnCell(1);
+                        }
+                    } else {
+                        // Same as above.
+                        calcAndSetSpanWidthOnCell(0);
+                    }
+                } else {
+                    super.setCellWidth(cellIx, width);
+                }
+            }
+
+            private void calcAndSetSpanWidthOnCell(final int cellIx) {
+                int spanWidth = 0;
+                for (int ix = (showRowHeaders ? 1 : 0); ix < tHead
+                        .getVisibleCellCount(); ix++) {
+                    spanWidth += tHead.getHeaderCell(ix).getOffsetWidth();
+                }
+                Util.setWidthExcludingPaddingAndBorder((Element) getElement()
+                        .getChild(cellIx), spanWidth, 13, false);
+            }
+        }
+
+        private int getIndentWidth() {
+            return indentWidth;
+        }
+
+        private void detectIndent(VTreeTableRow vTreeTableRow) {
+            indentWidth = vTreeTableRow.treeSpacer.getOffsetWidth();
+            if (indentWidth == 0) {
+                indentWidth = -1;
+                return;
+            }
+            Iterator<Widget> iterator = iterator();
+            while (iterator.hasNext()) {
+                VTreeTableRow next = (VTreeTableRow) iterator.next();
+                next.setIndent();
+            }
+        }
+
+        protected void unlinkRowsAnimatedAndUpdateCacheWhenFinished(
+                final int firstIndex, final int rows) {
+            List<VScrollTableRow> rowsToDelete = new ArrayList<VScrollTableRow>();
+            for (int ix = firstIndex; ix < firstIndex + rows; ix++) {
+                VScrollTableRow row = getRowByRowIndex(ix);
+                if (row != null) {
+                    rowsToDelete.add(row);
+                }
+            }
+            if (!rowsToDelete.isEmpty()) {
+                // #8810 Only animate if there's something to animate
+                RowCollapseAnimation anim = new RowCollapseAnimation(
+                        rowsToDelete) {
+                    @Override
+                    protected void onComplete() {
+                        super.onComplete();
+                        // Actually unlink the rows and update the cache after
+                        // the
+                        // animation is done.
+                        unlinkAndReindexRows(firstIndex, rows);
+                        discardRowsOutsideCacheWindow();
+                        ensureCacheFilled();
+                    }
+                };
+                anim.run(150);
+            }
+        }
+
+        protected List<VScrollTableRow> insertRowsAnimated(UIDL rowData,
+                int firstIndex, int rows) {
+            List<VScrollTableRow> insertedRows = insertAndReindexRows(rowData,
+                    firstIndex, rows);
+            if (!insertedRows.isEmpty()) {
+                // Only animate if there's something to animate (#8810)
+                RowExpandAnimation anim = new RowExpandAnimation(insertedRows);
+                anim.run(150);
+            }
+            return insertedRows;
+        }
+
+        /**
+         * Prepares the table for animation by copying the background colors of
+         * all TR elements to their respective TD elements if the TD element is
+         * transparent. This is needed, since if TDs have transparent
+         * backgrounds, the rows sliding behind them are visible.
+         */
+        private class AnimationPreparator {
+            private final int lastItemIx;
+
+            public AnimationPreparator(int lastItemIx) {
+                this.lastItemIx = lastItemIx;
+            }
+
+            public void prepareTableForAnimation() {
+                int ix = lastItemIx;
+                VScrollTableRow row = null;
+                while ((row = getRowByRowIndex(ix)) != null) {
+                    copyTRBackgroundsToTDs(row);
+                    --ix;
+                }
+            }
+
+            private void copyTRBackgroundsToTDs(VScrollTableRow row) {
+                Element tr = row.getElement();
+                ComputedStyle cs = new ComputedStyle(tr);
+                String backgroundAttachment = cs
+                        .getProperty("backgroundAttachment");
+                String backgroundClip = cs.getProperty("backgroundClip");
+                String backgroundColor = cs.getProperty("backgroundColor");
+                String backgroundImage = cs.getProperty("backgroundImage");
+                String backgroundOrigin = cs.getProperty("backgroundOrigin");
+                for (int ix = 0; ix < tr.getChildCount(); ix++) {
+                    Element td = tr.getChild(ix).cast();
+                    if (!elementHasBackground(td)) {
+                        td.getStyle().setProperty("backgroundAttachment",
+                                backgroundAttachment);
+                        td.getStyle().setProperty("backgroundClip",
+                                backgroundClip);
+                        td.getStyle().setProperty("backgroundColor",
+                                backgroundColor);
+                        td.getStyle().setProperty("backgroundImage",
+                                backgroundImage);
+                        td.getStyle().setProperty("backgroundOrigin",
+                                backgroundOrigin);
+                    }
+                }
+            }
+
+            private boolean elementHasBackground(Element element) {
+                ComputedStyle cs = new ComputedStyle(element);
+                String clr = cs.getProperty("backgroundColor");
+                String img = cs.getProperty("backgroundImage");
+                return !("rgba(0, 0, 0, 0)".equals(clr.trim())
+                        || "transparent".equals(clr.trim()) || img == null);
+            }
+
+            public void restoreTableAfterAnimation() {
+                int ix = lastItemIx;
+                VScrollTableRow row = null;
+                while ((row = getRowByRowIndex(ix)) != null) {
+                    restoreStyleForTDsInRow(row);
+
+                    --ix;
+                }
+            }
+
+            private void restoreStyleForTDsInRow(VScrollTableRow row) {
+                Element tr = row.getElement();
+                for (int ix = 0; ix < tr.getChildCount(); ix++) {
+                    Element td = tr.getChild(ix).cast();
+                    td.getStyle().clearProperty("backgroundAttachment");
+                    td.getStyle().clearProperty("backgroundClip");
+                    td.getStyle().clearProperty("backgroundColor");
+                    td.getStyle().clearProperty("backgroundImage");
+                    td.getStyle().clearProperty("backgroundOrigin");
+                }
+            }
+        }
+
+        /**
+         * Animates row expansion using the GWT animation framework.
+         * 
+         * The idea is as follows:
+         * 
+         * 1. Insert all rows normally
+         * 
+         * 2. Insert a newly created DIV containing a new TABLE element below
+         * the DIV containing the actual scroll table body.
+         * 
+         * 3. Clone the rows that were inserted in step 1 and attach the clones
+         * to the new TABLE element created in step 2.
+         * 
+         * 4. The new DIV from step 2 is absolutely positioned so that the last
+         * inserted row is just behind the row that was expanded.
+         * 
+         * 5. Hide the contents of the originally inserted rows by setting the
+         * DIV.v-table-cell-wrapper to display:none;.
+         * 
+         * 6. Set the height of the originally inserted rows to 0.
+         * 
+         * 7. The animation loop slides the DIV from step 2 downwards, while at
+         * the same pace growing the height of each of the inserted rows from 0
+         * to full height. The first inserted row grows from 0 to full and after
+         * this the second row grows from 0 to full, etc until all rows are full
+         * height.
+         * 
+         * 8. Remove the DIV from step 2
+         * 
+         * 9. Restore display:block; to the DIV.v-table-cell-wrapper elements.
+         * 
+         * 10. DONE
+         */
+        private class RowExpandAnimation extends Animation {
+
+            private final List<VScrollTableRow> rows;
+            private Element cloneDiv;
+            private Element cloneTable;
+            private AnimationPreparator preparator;
+
+            /**
+             * @param rows
+             *            List of rows to animate. Must not be empty.
+             */
+            public RowExpandAnimation(List<VScrollTableRow> rows) {
+                this.rows = rows;
+                buildAndInsertAnimatingDiv();
+                preparator = new AnimationPreparator(rows.get(0).getIndex() - 1);
+                preparator.prepareTableForAnimation();
+                for (VScrollTableRow row : rows) {
+                    cloneAndAppendRow(row);
+                    row.addStyleName("v-table-row-animating");
+                    setCellWrapperDivsToDisplayNone(row);
+                    row.setHeight(getInitialHeight());
+                }
+            }
+
+            protected String getInitialHeight() {
+                return "0px";
+            }
+
+            private void cloneAndAppendRow(VScrollTableRow row) {
+                Element clonedTR = null;
+                clonedTR = row.getElement().cloneNode(true).cast();
+                clonedTR.getStyle().setVisibility(Visibility.VISIBLE);
+                cloneTable.appendChild(clonedTR);
+            }
+
+            protected double getBaseOffset() {
+                return rows.get(0).getAbsoluteTop()
+                        - rows.get(0).getParent().getAbsoluteTop()
+                        - rows.size() * getRowHeight();
+            }
+
+            private void buildAndInsertAnimatingDiv() {
+                cloneDiv = DOM.createDiv();
+                cloneDiv.addClassName("v-treetable-animation-clone-wrapper");
+                cloneTable = DOM.createTable();
+                cloneTable.addClassName("v-treetable-animation-clone");
+                cloneDiv.appendChild(cloneTable);
+                insertAnimatingDiv();
+            }
+
+            private void insertAnimatingDiv() {
+                Element tableBody = getElement().cast();
+                Element tableBodyParent = tableBody.getParentElement().cast();
+                tableBodyParent.insertAfter(cloneDiv, tableBody);
+            }
+
+            @Override
+            protected void onUpdate(double progress) {
+                animateDiv(progress);
+                animateRowHeights(progress);
+            }
+
+            private void animateDiv(double progress) {
+                double offset = calculateDivOffset(progress, getRowHeight());
+
+                cloneDiv.getStyle().setTop(getBaseOffset() + offset, Unit.PX);
+            }
+
+            private void animateRowHeights(double progress) {
+                double rh = getRowHeight();
+                double vlh = calculateHeightOfAllVisibleLines(progress, rh);
+                int ix = 0;
+
+                while (ix < rows.size()) {
+                    double height = vlh < rh ? vlh : rh;
+                    rows.get(ix).setHeight(height + "px");
+                    vlh -= height;
+                    ix++;
+                }
+            }
+
+            protected double calculateHeightOfAllVisibleLines(double progress,
+                    double rh) {
+                return rows.size() * rh * progress;
+            }
+
+            protected double calculateDivOffset(double progress, double rh) {
+                return progress * rows.size() * rh;
+            }
+
+            @Override
+            protected void onComplete() {
+                preparator.restoreTableAfterAnimation();
+                for (VScrollTableRow row : rows) {
+                    resetCellWrapperDivsDisplayProperty(row);
+                    row.removeStyleName("v-table-row-animating");
+                }
+                Element tableBodyParent = (Element) getElement()
+                        .getParentElement();
+                tableBodyParent.removeChild(cloneDiv);
+            }
+
+            private void setCellWrapperDivsToDisplayNone(VScrollTableRow row) {
+                Element tr = row.getElement();
+                for (int ix = 0; ix < tr.getChildCount(); ix++) {
+                    getWrapperDiv(tr, ix).getStyle().setDisplay(Display.NONE);
+                }
+            }
+
+            private Element getWrapperDiv(Element tr, int tdIx) {
+                Element td = tr.getChild(tdIx).cast();
+                return td.getChild(0).cast();
+            }
+
+            private void resetCellWrapperDivsDisplayProperty(VScrollTableRow row) {
+                Element tr = row.getElement();
+                for (int ix = 0; ix < tr.getChildCount(); ix++) {
+                    getWrapperDiv(tr, ix).getStyle().clearProperty("display");
+                }
+            }
+
+        }
+
+        /**
+         * This is the inverse of the RowExpandAnimation and is implemented by
+         * extending it and overriding the calculation of offsets and heights.
+         */
+        private class RowCollapseAnimation extends RowExpandAnimation {
+
+            private final List<VScrollTableRow> rows;
+
+            /**
+             * @param rows
+             *            List of rows to animate. Must not be empty.
+             */
+            public RowCollapseAnimation(List<VScrollTableRow> rows) {
+                super(rows);
+                this.rows = rows;
+            }
+
+            @Override
+            protected String getInitialHeight() {
+                return getRowHeight() + "px";
+            }
+
+            @Override
+            protected double getBaseOffset() {
+                return getRowHeight();
+            }
+
+            @Override
+            protected double calculateHeightOfAllVisibleLines(double progress,
+                    double rh) {
+                return rows.size() * rh * (1 - progress);
+            }
+
+            @Override
+            protected double calculateDivOffset(double progress, double rh) {
+                return -super.calculateDivOffset(progress, rh);
+            }
+        }
+    }
+
+    /**
+     * Icons rendered into first actual column in TreeTable, not to row header
+     * cell
+     */
+    @Override
+    protected String buildCaptionHtmlSnippet(UIDL uidl) {
+        if (uidl.getTag().equals("column")) {
+            return super.buildCaptionHtmlSnippet(uidl);
+        } else {
+            String s = uidl.getStringAttribute("caption");
+            return s;
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    @Override
+    public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
+        if (collapseRequest || focusParentResponsePending) {
+            // Enqueue the event if there might be pending content changes from
+            // the server
+            if (pendingNavigationEvents.size() < 10) {
+                // Only keep 10 keyboard events in the queue
+                PendingNavigationEvent pendingNavigationEvent = new PendingNavigationEvent(
+                        keycode, ctrl, shift);
+                pendingNavigationEvents.add(pendingNavigationEvent);
+            }
+            return true;
+        }
+
+        VTreeTableRow focusedRow = (VTreeTableRow) getFocusedRow();
+        if (focusedRow != null) {
+            if (focusedRow.canHaveChildren
+                    && ((keycode == KeyCodes.KEY_RIGHT && !focusedRow.open) || (keycode == KeyCodes.KEY_LEFT && focusedRow.open))) {
+                if (!ctrl) {
+                    client.updateVariable(paintableId, "selectCollapsed", true,
+                            false);
+                }
+                sendSelectedRows(false);
+                sendToggleCollapsedUpdate(focusedRow.getKey());
+                return true;
+            } else if (keycode == KeyCodes.KEY_RIGHT && focusedRow.open) {
+                // already expanded, move selection down if next is on a deeper
+                // level (is-a-child)
+                VTreeTableScrollBody body = (VTreeTableScrollBody) focusedRow
+                        .getParent();
+                Iterator<Widget> iterator = body.iterator();
+                VTreeTableRow next = null;
+                while (iterator.hasNext()) {
+                    next = (VTreeTableRow) iterator.next();
+                    if (next == focusedRow) {
+                        next = (VTreeTableRow) iterator.next();
+                        break;
+                    }
+                }
+                if (next != null) {
+                    if (next.depth > focusedRow.depth) {
+                        selectionPending = true;
+                        return super.handleNavigation(getNavigationDownKey(),
+                                ctrl, shift);
+                    }
+                } else {
+                    // Note, a minor change here for a bit false behavior if
+                    // cache rows is disabled + last visible row + no childs for
+                    // the node
+                    selectionPending = true;
+                    return super.handleNavigation(getNavigationDownKey(), ctrl,
+                            shift);
+                }
+            } else if (keycode == KeyCodes.KEY_LEFT) {
+                // already collapsed move selection up to parent node
+                // do on the server side as the parent is not necessary
+                // rendered on the client, could check if parent is visible if
+                // a performance issue arises
+
+                client.updateVariable(paintableId, "focusParent",
+                        focusedRow.getKey(), true);
+
+                // Set flag that we should enqueue navigation events until we
+                // get a response to this request
+                focusParentResponsePending = true;
+
+                return true;
+            }
+        }
+        return super.handleNavigation(keycode, ctrl, shift);
+    }
+
+    private void sendToggleCollapsedUpdate(String rowKey) {
+        collapsedRowKey = rowKey;
+        collapseRequest = true;
+        client.updateVariable(paintableId, "toggleCollapsed", rowKey, true);
+    }
+
+    @Override
+    public void onBrowserEvent(Event event) {
+        super.onBrowserEvent(event);
+        if (event.getTypeInt() == Event.ONKEYUP && selectionPending) {
+            sendSelectedRows();
+        }
+    }
+
+    @Override
+    protected void sendSelectedRows(boolean immediately) {
+        super.sendSelectedRows(immediately);
+        selectionPending = false;
+    }
+
+    @Override
+    protected void reOrderColumn(String columnKey, int newIndex) {
+        super.reOrderColumn(columnKey, newIndex);
+        // current impl not intelligent enough to survive without visiting the
+        // server to redraw content
+        client.sendPendingVariableChanges();
+    }
+
+    @Override
+    public void setStyleName(String style) {
+        super.setStyleName(style + " v-treetable");
+    }
+
+    @Override
+    public void updateTotalRows(UIDL uidl) {
+        // Make sure that initializedAndAttached & al are not reset when the
+        // totalrows are updated on expand/collapse requests.
+        int newTotalRows = uidl.getIntAttribute("totalrows");
+        setTotalRows(newTotalRows);
+    }
+
+}
diff --git a/client/src/com/vaadin/client/ui/VTwinColSelect.java b/client/src/com/vaadin/client/ui/VTwinColSelect.java
new file mode 100644 (file)
index 0000000..22aa745
--- /dev/null
@@ -0,0 +1,625 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.DoubleClickEvent;
+import com.google.gwt.event.dom.client.DoubleClickHandler;
+import com.google.gwt.event.dom.client.HasDoubleClickHandlers;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.MouseDownEvent;
+import com.google.gwt.event.dom.client.MouseDownHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.ListBox;
+import com.google.gwt.user.client.ui.Panel;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.Util;
+import com.vaadin.shared.ui.twincolselect.TwinColSelectConstants;
+
+public class VTwinColSelect extends VOptionGroupBase implements KeyDownHandler,
+        MouseDownHandler, DoubleClickHandler, SubPartAware {
+
+    public static final String CLASSNAME = "v-select-twincol";
+
+    private static final int VISIBLE_COUNT = 10;
+
+    private static final int DEFAULT_COLUMN_COUNT = 10;
+
+    private final DoubleClickListBox options;
+
+    private final DoubleClickListBox selections;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public FlowPanel captionWrapper;
+
+    private HTML optionsCaption = null;
+
+    private HTML selectionsCaption = null;
+
+    private final VButton add;
+
+    private final VButton remove;
+
+    private final FlowPanel buttons;
+
+    private final Panel panel;
+
+    /**
+     * A ListBox which catches double clicks
+     * 
+     */
+    public class DoubleClickListBox extends ListBox implements
+            HasDoubleClickHandlers {
+        public DoubleClickListBox(boolean isMultipleSelect) {
+            super(isMultipleSelect);
+        }
+
+        public DoubleClickListBox() {
+            super();
+        }
+
+        @Override
+        public HandlerRegistration addDoubleClickHandler(
+                DoubleClickHandler handler) {
+            return addDomHandler(handler, DoubleClickEvent.getType());
+        }
+    }
+
+    public VTwinColSelect() {
+        super(CLASSNAME);
+
+        captionWrapper = new FlowPanel();
+
+        options = new DoubleClickListBox();
+        options.addClickHandler(this);
+        options.addDoubleClickHandler(this);
+        options.setVisibleItemCount(VISIBLE_COUNT);
+        options.setStyleName(CLASSNAME + "-options");
+
+        selections = new DoubleClickListBox();
+        selections.addClickHandler(this);
+        selections.addDoubleClickHandler(this);
+        selections.setVisibleItemCount(VISIBLE_COUNT);
+        selections.setStyleName(CLASSNAME + "-selections");
+
+        buttons = new FlowPanel();
+        buttons.setStyleName(CLASSNAME + "-buttons");
+        add = new VButton();
+        add.setText(">>");
+        add.addClickHandler(this);
+        remove = new VButton();
+        remove.setText("<<");
+        remove.addClickHandler(this);
+
+        panel = ((Panel) optionsContainer);
+
+        panel.add(captionWrapper);
+        captionWrapper.getElement().getStyle().setOverflow(Overflow.HIDDEN);
+        // Hide until there actually is a caption to prevent IE from rendering
+        // extra empty space
+        captionWrapper.setVisible(false);
+
+        panel.add(options);
+        buttons.add(add);
+        final HTML br = new HTML("<span/>");
+        br.setStyleName(CLASSNAME + "-deco");
+        buttons.add(br);
+        buttons.add(remove);
+        panel.add(buttons);
+        panel.add(selections);
+
+        options.addKeyDownHandler(this);
+        options.addMouseDownHandler(this);
+
+        selections.addMouseDownHandler(this);
+        selections.addKeyDownHandler(this);
+    }
+
+    public HTML getOptionsCaption() {
+        if (optionsCaption == null) {
+            optionsCaption = new HTML();
+            optionsCaption.setStyleName(CLASSNAME + "-caption-left");
+            optionsCaption.getElement().getStyle()
+                    .setFloat(com.google.gwt.dom.client.Style.Float.LEFT);
+            captionWrapper.add(optionsCaption);
+        }
+
+        return optionsCaption;
+    }
+
+    public HTML getSelectionsCaption() {
+        if (selectionsCaption == null) {
+            selectionsCaption = new HTML();
+            selectionsCaption.setStyleName(CLASSNAME + "-caption-right");
+            selectionsCaption.getElement().getStyle()
+                    .setFloat(com.google.gwt.dom.client.Style.Float.RIGHT);
+            captionWrapper.add(selectionsCaption);
+        }
+
+        return selectionsCaption;
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void updateCaptions(UIDL uidl) {
+        String leftCaption = (uidl
+                .hasAttribute(TwinColSelectConstants.ATTRIBUTE_LEFT_CAPTION) ? uidl
+                .getStringAttribute(TwinColSelectConstants.ATTRIBUTE_LEFT_CAPTION)
+                : null);
+        String rightCaption = (uidl
+                .hasAttribute(TwinColSelectConstants.ATTRIBUTE_RIGHT_CAPTION) ? uidl
+                .getStringAttribute(TwinColSelectConstants.ATTRIBUTE_RIGHT_CAPTION)
+                : null);
+
+        boolean hasCaptions = (leftCaption != null || rightCaption != null);
+
+        if (leftCaption == null) {
+            removeOptionsCaption();
+        } else {
+            getOptionsCaption().setText(leftCaption);
+
+        }
+
+        if (rightCaption == null) {
+            removeSelectionsCaption();
+        } else {
+            getSelectionsCaption().setText(rightCaption);
+        }
+
+        captionWrapper.setVisible(hasCaptions);
+    }
+
+    private void removeOptionsCaption() {
+        if (optionsCaption == null) {
+            return;
+        }
+
+        if (optionsCaption.getParent() != null) {
+            captionWrapper.remove(optionsCaption);
+        }
+
+        optionsCaption = null;
+    }
+
+    private void removeSelectionsCaption() {
+        if (selectionsCaption == null) {
+            return;
+        }
+
+        if (selectionsCaption.getParent() != null) {
+            captionWrapper.remove(selectionsCaption);
+        }
+
+        selectionsCaption = null;
+    }
+
+    @Override
+    public void buildOptions(UIDL uidl) {
+        final boolean enabled = !isDisabled() && !isReadonly();
+        options.setMultipleSelect(isMultiselect());
+        selections.setMultipleSelect(isMultiselect());
+        options.setEnabled(enabled);
+        selections.setEnabled(enabled);
+        add.setEnabled(enabled && !readonly);
+        remove.setEnabled(enabled && !readonly);
+        options.clear();
+        selections.clear();
+        for (final Iterator<?> i = uidl.getChildIterator(); i.hasNext();) {
+            final UIDL optionUidl = (UIDL) i.next();
+            if (optionUidl.hasAttribute("selected")) {
+                selections.addItem(optionUidl.getStringAttribute("caption"),
+                        optionUidl.getStringAttribute("key"));
+            } else {
+                options.addItem(optionUidl.getStringAttribute("caption"),
+                        optionUidl.getStringAttribute("key"));
+            }
+        }
+
+        if (getRows() > 0) {
+            options.setVisibleItemCount(getRows());
+            selections.setVisibleItemCount(getRows());
+
+        }
+
+        add.setStyleName("v-disabled", readonly);
+        remove.setStyleName("v-disabled", readonly);
+    }
+
+    @Override
+    protected String[] getSelectedItems() {
+        final ArrayList<String> selectedItemKeys = new ArrayList<String>();
+        for (int i = 0; i < selections.getItemCount(); i++) {
+            selectedItemKeys.add(selections.getValue(i));
+        }
+        return selectedItemKeys.toArray(new String[selectedItemKeys.size()]);
+    }
+
+    private boolean[] getSelectionBitmap(ListBox listBox) {
+        final boolean[] selectedIndexes = new boolean[listBox.getItemCount()];
+        for (int i = 0; i < listBox.getItemCount(); i++) {
+            if (listBox.isItemSelected(i)) {
+                selectedIndexes[i] = true;
+            } else {
+                selectedIndexes[i] = false;
+            }
+        }
+        return selectedIndexes;
+    }
+
+    private void addItem() {
+        Set<String> movedItems = moveSelectedItems(options, selections);
+        selectedKeys.addAll(movedItems);
+
+        client.updateVariable(paintableId, "selected",
+                selectedKeys.toArray(new String[selectedKeys.size()]),
+                isImmediate());
+    }
+
+    private void removeItem() {
+        Set<String> movedItems = moveSelectedItems(selections, options);
+        selectedKeys.removeAll(movedItems);
+
+        client.updateVariable(paintableId, "selected",
+                selectedKeys.toArray(new String[selectedKeys.size()]),
+                isImmediate());
+    }
+
+    private Set<String> moveSelectedItems(ListBox source, ListBox target) {
+        final boolean[] sel = getSelectionBitmap(source);
+        final Set<String> movedItems = new HashSet<String>();
+        int lastSelected = 0;
+        for (int i = 0; i < sel.length; i++) {
+            if (sel[i]) {
+                final int optionIndex = i
+                        - (sel.length - source.getItemCount());
+                movedItems.add(source.getValue(optionIndex));
+
+                // Move selection to another column
+                final String text = source.getItemText(optionIndex);
+                final String value = source.getValue(optionIndex);
+                target.addItem(text, value);
+                target.setItemSelected(target.getItemCount() - 1, true);
+                source.removeItem(optionIndex);
+
+                if (source.getItemCount() > 0) {
+                    lastSelected = optionIndex > 0 ? optionIndex - 1 : 0;
+                }
+            }
+        }
+
+        if (source.getItemCount() > 0) {
+            source.setSelectedIndex(lastSelected);
+        }
+
+        // If no items are left move the focus to the selections
+        if (source.getItemCount() == 0) {
+            target.setFocus(true);
+        } else {
+            source.setFocus(true);
+        }
+
+        return movedItems;
+    }
+
+    @Override
+    public void onClick(ClickEvent event) {
+        super.onClick(event);
+        if (event.getSource() == add) {
+            addItem();
+
+        } else if (event.getSource() == remove) {
+            removeItem();
+
+        } else if (event.getSource() == options) {
+            // unselect all in other list, to avoid mistakes (i.e wrong button)
+            final int c = selections.getItemCount();
+            for (int i = 0; i < c; i++) {
+                selections.setItemSelected(i, false);
+            }
+        } else if (event.getSource() == selections) {
+            // unselect all in other list, to avoid mistakes (i.e wrong button)
+            final int c = options.getItemCount();
+            for (int i = 0; i < c; i++) {
+                options.setItemSelected(i, false);
+            }
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void clearInternalHeights() {
+        selections.setHeight("");
+        options.setHeight("");
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void setInternalHeights() {
+        int captionHeight = Util.getRequiredHeight(captionWrapper);
+        int totalHeight = getOffsetHeight();
+
+        String selectHeight = (totalHeight - captionHeight) + "px";
+
+        selections.setHeight(selectHeight);
+        options.setHeight(selectHeight);
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void clearInternalWidths() {
+        int cols = -1;
+        if (getColumns() > 0) {
+            cols = getColumns();
+        } else {
+            cols = DEFAULT_COLUMN_COUNT;
+        }
+
+        if (cols >= 0) {
+            String colWidth = cols + "em";
+            String containerWidth = (2 * cols + 4) + "em";
+            // Caption wrapper width == optionsSelect + buttons +
+            // selectionsSelect
+            String captionWrapperWidth = (2 * cols + 4 - 0.5) + "em";
+
+            options.setWidth(colWidth);
+            if (optionsCaption != null) {
+                optionsCaption.setWidth(colWidth);
+            }
+            selections.setWidth(colWidth);
+            if (selectionsCaption != null) {
+                selectionsCaption.setWidth(colWidth);
+            }
+            buttons.setWidth("3.5em");
+            optionsContainer.setWidth(containerWidth);
+            captionWrapper.setWidth(captionWrapperWidth);
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void setInternalWidths() {
+        DOM.setStyleAttribute(getElement(), "position", "relative");
+        int bordersAndPaddings = Util.measureHorizontalPaddingAndBorder(
+                buttons.getElement(), 0);
+
+        int buttonWidth = Util.getRequiredWidth(buttons);
+        int totalWidth = getOffsetWidth();
+
+        int spaceForSelect = (totalWidth - buttonWidth - bordersAndPaddings) / 2;
+
+        options.setWidth(spaceForSelect + "px");
+        if (optionsCaption != null) {
+            optionsCaption.setWidth(spaceForSelect + "px");
+        }
+
+        selections.setWidth(spaceForSelect + "px");
+        if (selectionsCaption != null) {
+            selectionsCaption.setWidth(spaceForSelect + "px");
+        }
+        captionWrapper.setWidth("100%");
+    }
+
+    @Override
+    public void setTabIndex(int tabIndex) {
+        options.setTabIndex(tabIndex);
+        selections.setTabIndex(tabIndex);
+        add.setTabIndex(tabIndex);
+        remove.setTabIndex(tabIndex);
+    }
+
+    @Override
+    public void focus() {
+        options.setFocus(true);
+    }
+
+    /**
+     * Get the key that selects an item in the table. By default it is the Enter
+     * key but by overriding this you can change the key to whatever you want.
+     * 
+     * @return
+     */
+    protected int getNavigationSelectKey() {
+        return 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) {
+        int keycode = event.getNativeKeyCode();
+
+        // Catch tab and move between select:s
+        if (keycode == KeyCodes.KEY_TAB && event.getSource() == options) {
+            // Prevent default behavior
+            event.preventDefault();
+
+            // Remove current selections
+            for (int i = 0; i < options.getItemCount(); i++) {
+                options.setItemSelected(i, false);
+            }
+
+            // Focus selections
+            selections.setFocus(true);
+        }
+
+        if (keycode == KeyCodes.KEY_TAB && event.isShiftKeyDown()
+                && event.getSource() == selections) {
+            // Prevent default behavior
+            event.preventDefault();
+
+            // Remove current selections
+            for (int i = 0; i < selections.getItemCount(); i++) {
+                selections.setItemSelected(i, false);
+            }
+
+            // Focus options
+            options.setFocus(true);
+        }
+
+        if (keycode == getNavigationSelectKey()) {
+            // Prevent default behavior
+            event.preventDefault();
+
+            // Decide which select the selection was made in
+            if (event.getSource() == options) {
+                // Prevents the selection to become a single selection when
+                // using Enter key
+                // as the selection key (default)
+                options.setFocus(false);
+
+                addItem();
+
+            } else if (event.getSource() == selections) {
+                // Prevents the selection to become a single selection when
+                // using Enter key
+                // as the selection key (default)
+                selections.setFocus(false);
+
+                removeItem();
+            }
+        }
+
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.MouseDownHandler#onMouseDown(com.google
+     * .gwt.event.dom.client.MouseDownEvent)
+     */
+    @Override
+    public void onMouseDown(MouseDownEvent event) {
+        // Ensure that items are deselected when selecting
+        // from a different source. See #3699 for details.
+        if (event.getSource() == options) {
+            for (int i = 0; i < selections.getItemCount(); i++) {
+                selections.setItemSelected(i, false);
+            }
+        } else if (event.getSource() == selections) {
+            for (int i = 0; i < options.getItemCount(); i++) {
+                options.setItemSelected(i, false);
+            }
+        }
+
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.dom.client.DoubleClickHandler#onDoubleClick(com.
+     * google.gwt.event.dom.client.DoubleClickEvent)
+     */
+    @Override
+    public void onDoubleClick(DoubleClickEvent event) {
+        if (event.getSource() == options) {
+            addItem();
+            options.setSelectedIndex(-1);
+            options.setFocus(false);
+        } else if (event.getSource() == selections) {
+            removeItem();
+            selections.setSelectedIndex(-1);
+            selections.setFocus(false);
+        }
+
+    }
+
+    private static final String SUBPART_OPTION_SELECT = "leftSelect";
+    private static final String SUBPART_OPTION_SELECT_ITEM = SUBPART_OPTION_SELECT
+            + "-item";
+    private static final String SUBPART_SELECTION_SELECT = "rightSelect";
+    private static final String SUBPART_SELECTION_SELECT_ITEM = SUBPART_SELECTION_SELECT
+            + "-item";
+    private static final String SUBPART_LEFT_CAPTION = "leftCaption";
+    private static final String SUBPART_RIGHT_CAPTION = "rightCaption";
+    private static final String SUBPART_ADD_BUTTON = "add";
+    private static final String SUBPART_REMOVE_BUTTON = "remove";
+
+    @Override
+    public Element getSubPartElement(String subPart) {
+        if (SUBPART_OPTION_SELECT.equals(subPart)) {
+            return options.getElement();
+        } else if (subPart.startsWith(SUBPART_OPTION_SELECT_ITEM)) {
+            String idx = subPart.substring(SUBPART_OPTION_SELECT_ITEM.length());
+            return (Element) options.getElement().getChild(
+                    Integer.parseInt(idx));
+        } else if (SUBPART_SELECTION_SELECT.equals(subPart)) {
+            return selections.getElement();
+        } else if (subPart.startsWith(SUBPART_SELECTION_SELECT_ITEM)) {
+            String idx = subPart.substring(SUBPART_SELECTION_SELECT_ITEM
+                    .length());
+            return (Element) selections.getElement().getChild(
+                    Integer.parseInt(idx));
+        } else if (optionsCaption != null
+                && SUBPART_LEFT_CAPTION.equals(subPart)) {
+            return optionsCaption.getElement();
+        } else if (selectionsCaption != null
+                && SUBPART_RIGHT_CAPTION.equals(subPart)) {
+            return selectionsCaption.getElement();
+        } else if (SUBPART_ADD_BUTTON.equals(subPart)) {
+            return add.getElement();
+        } else if (SUBPART_REMOVE_BUTTON.equals(subPart)) {
+            return remove.getElement();
+        }
+
+        return null;
+    }
+
+    @Override
+    public String getSubPartName(Element subElement) {
+        if (optionsCaption != null
+                && optionsCaption.getElement().isOrHasChild(subElement)) {
+            return SUBPART_LEFT_CAPTION;
+        } else if (selectionsCaption != null
+                && selectionsCaption.getElement().isOrHasChild(subElement)) {
+            return SUBPART_RIGHT_CAPTION;
+        } else if (options.getElement().isOrHasChild(subElement)) {
+            if (options.getElement() == subElement) {
+                return SUBPART_OPTION_SELECT;
+            } else {
+                int idx = Util.getChildElementIndex(subElement);
+                return SUBPART_OPTION_SELECT_ITEM + idx;
+            }
+        } else if (selections.getElement().isOrHasChild(subElement)) {
+            if (selections.getElement() == subElement) {
+                return SUBPART_SELECTION_SELECT;
+            } else {
+                int idx = Util.getChildElementIndex(subElement);
+                return SUBPART_SELECTION_SELECT_ITEM + idx;
+            }
+        } else if (add.getElement().isOrHasChild(subElement)) {
+            return SUBPART_ADD_BUTTON;
+        } else if (remove.getElement().isOrHasChild(subElement)) {
+            return SUBPART_REMOVE_BUTTON;
+        }
+
+        return null;
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VUI.java b/client/src/com/vaadin/client/ui/VUI.java
new file mode 100644 (file)
index 0000000..7f067c6
--- /dev/null
@@ -0,0 +1,479 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.logical.shared.HasResizeHandlers;
+import com.google.gwt.event.logical.shared.ResizeEvent;
+import com.google.gwt.event.logical.shared.ResizeHandler;
+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.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.History;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ConnectorMap;
+import com.vaadin.client.Focusable;
+import com.vaadin.client.LayoutManager;
+import com.vaadin.client.VConsole;
+import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
+import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
+import com.vaadin.shared.ApplicationConstants;
+import com.vaadin.shared.ui.ui.UIConstants;
+
+/**
+ *
+ */
+public class VUI extends SimplePanel implements ResizeHandler,
+        Window.ClosingHandler, ShortcutActionHandlerOwner, Focusable,
+        HasResizeHandlers {
+
+    private static final String CLASSNAME = "v-view";
+
+    private static int MONITOR_PARENT_TIMER_INTERVAL = 1000;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public String theme;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public String id;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public ShortcutActionHandler actionHandler;
+
+    /*
+     * Last known window size used to detect whether VView should be layouted
+     * again. Detection must check window size, because the VView size might be
+     * fixed and thus not automatically adapt to changed window sizes.
+     */
+    private int windowWidth;
+    private int windowHeight;
+
+    /*
+     * Last know view size used to detect whether new dimensions should be sent
+     * to the server.
+     */
+    private int viewWidth;
+    private int viewHeight;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public ApplicationConnection connection;
+
+    /**
+     * Keep track of possible parent size changes when an embedded application.
+     * 
+     * Uses {@link #parentWidth} and {@link #parentHeight} as an optimization to
+     * keep track of when there is a real change.
+     */
+    private Timer resizeTimer;
+
+    /** stored width of parent for embedded application auto-resize */
+    private int parentWidth;
+
+    /** stored height of parent for embedded application auto-resize */
+    private int parentHeight;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public int scrollTop;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public int scrollLeft;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean rendering;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean scrollable;
+
+    /** 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 boolean resizeLazy = false;
+
+    private HandlerRegistration historyHandlerRegistration;
+
+    private TouchScrollHandler touchScrollHandler;
+
+    /**
+     * The current URI fragment, used to avoid sending updates if nothing has
+     * changed.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public String currentFragment;
+
+    /**
+     * Listener for URI fragment changes. Notifies the server of the new value
+     * whenever the value changes.
+     */
+    private final ValueChangeHandler<String> historyChangeHandler = new ValueChangeHandler<String>() {
+
+        @Override
+        public void onValueChange(ValueChangeEvent<String> event) {
+            String newFragment = event.getValue();
+
+            // Send the location to the server if the fragment has changed
+            if (!newFragment.equals(currentFragment) && connection != null) {
+                currentFragment = newFragment;
+                connection.updateVariable(id, UIConstants.LOCATION_VARIABLE,
+                        Window.Location.getHref(), true);
+            }
+        }
+    };
+
+    private VLazyExecutor delayedResizeExecutor = new VLazyExecutor(200,
+            new ScheduledCommand() {
+
+                @Override
+                public void execute() {
+                    performSizeCheck();
+                }
+
+            });
+
+    public VUI() {
+        super();
+        setStyleName(CLASSNAME);
+
+        // Allow focusing the view by using the focus() method, the view
+        // should not be in the document focus flow
+        getElement().setTabIndex(-1);
+        makeScrollable();
+    }
+
+    /**
+     * Start to periodically monitor for parent element resizes if embedded
+     * application (e.g. portlet).
+     */
+    @Override
+    protected void onLoad() {
+        super.onLoad();
+        if (isMonitoringParentSize()) {
+            resizeTimer = new Timer() {
+
+                @Override
+                public void run() {
+                    // trigger check to see if parent size has changed,
+                    // recalculate layouts
+                    performSizeCheck();
+                    resizeTimer.schedule(MONITOR_PARENT_TIMER_INTERVAL);
+                }
+            };
+            resizeTimer.schedule(MONITOR_PARENT_TIMER_INTERVAL);
+        }
+    }
+
+    @Override
+    protected void onAttach() {
+        super.onAttach();
+        historyHandlerRegistration = History
+                .addValueChangeHandler(historyChangeHandler);
+        currentFragment = History.getToken();
+    }
+
+    @Override
+    protected void onDetach() {
+        super.onDetach();
+        historyHandlerRegistration.removeHandler();
+        historyHandlerRegistration = null;
+    }
+
+    /**
+     * Stop monitoring for parent element resizes.
+     */
+
+    @Override
+    protected void onUnload() {
+        if (resizeTimer != null) {
+            resizeTimer.cancel();
+            resizeTimer = null;
+        }
+        super.onUnload();
+    }
+
+    /**
+     * Called when the window or parent div might have been resized.
+     * 
+     * This immediately checks the sizes of the window and the parent div (if
+     * monitoring it) and triggers layout recalculation if they have changed.
+     */
+    protected void performSizeCheck() {
+        windowSizeMaybeChanged(Window.getClientWidth(),
+                Window.getClientHeight());
+    }
+
+    /**
+     * Called when the window or parent div might have been resized.
+     * 
+     * This immediately checks the sizes of the window and the parent div (if
+     * monitoring it) and triggers layout recalculation if they have changed.
+     * 
+     * @param newWindowWidth
+     *            The new width of the window
+     * @param newWindowHeight
+     *            The new height of the window
+     * 
+     * @deprecated use {@link #performSizeCheck()}
+     */
+    @Deprecated
+    protected void windowSizeMaybeChanged(int newWindowWidth,
+            int newWindowHeight) {
+        boolean changed = false;
+        ComponentConnector connector = ConnectorMap.get(connection)
+                .getConnector(this);
+        if (windowWidth != newWindowWidth) {
+            windowWidth = newWindowWidth;
+            changed = true;
+            connector.getLayoutManager().reportOuterWidth(connector,
+                    newWindowWidth);
+            VConsole.log("New window width: " + windowWidth);
+        }
+        if (windowHeight != newWindowHeight) {
+            windowHeight = newWindowHeight;
+            changed = true;
+            connector.getLayoutManager().reportOuterHeight(connector,
+                    newWindowHeight);
+            VConsole.log("New window height: " + windowHeight);
+        }
+        Element parentElement = getElement().getParentElement();
+        if (isMonitoringParentSize() && parentElement != null) {
+            // check also for parent size changes
+            int newParentWidth = parentElement.getClientWidth();
+            int newParentHeight = parentElement.getClientHeight();
+            if (parentWidth != newParentWidth) {
+                parentWidth = newParentWidth;
+                changed = true;
+                VConsole.log("New parent width: " + parentWidth);
+            }
+            if (parentHeight != newParentHeight) {
+                parentHeight = newParentHeight;
+                changed = true;
+                VConsole.log("New parent height: " + parentHeight);
+            }
+        }
+        if (changed) {
+            /*
+             * If the window size has changed, layout the VView again and send
+             * new size to the server if the size changed. (Just checking VView
+             * size would cause us to ignore cases when a relatively sized VView
+             * should shrink as the content's size is fixed and would thus not
+             * automatically shrink.)
+             */
+            VConsole.log("Running layout functions due to window or parent resize");
+
+            // update size to avoid (most) redundant re-layout passes
+            // there can still be an extra layout recalculation if webkit
+            // overflow fix updates the size in a deferred block
+            if (isMonitoringParentSize() && parentElement != null) {
+                parentWidth = parentElement.getClientWidth();
+                parentHeight = parentElement.getClientHeight();
+            }
+
+            sendClientResized();
+
+            LayoutManager layoutManager = connector.getLayoutManager();
+            if (layoutManager.isLayoutRunning()) {
+                layoutManager.layoutLater();
+            } else {
+                layoutManager.layoutNow();
+            }
+        }
+    }
+
+    public String getTheme() {
+        return theme;
+    }
+
+    /**
+     * Used to reload host page on theme changes.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public static native void reloadHostPage()
+    /*-{
+         $wnd.location.reload();
+     }-*/;
+
+    /**
+     * Returns true if the body is NOT generated, i.e if someone else has made
+     * the page that we're running in. Otherwise we're in charge of the whole
+     * page.
+     * 
+     * @return true if we're running embedded
+     */
+    public boolean isEmbedded() {
+        return !getElement().getOwnerDocument().getBody().getClassName()
+                .contains(ApplicationConstants.GENERATED_BODY_CLASSNAME);
+    }
+
+    /**
+     * Returns true if the size of the parent should be checked periodically and
+     * the application should react to its changes.
+     * 
+     * @return true if size of parent should be tracked
+     */
+    protected boolean isMonitoringParentSize() {
+        // could also perform a more specific check (Liferay portlet)
+        return isEmbedded();
+    }
+
+    @Override
+    public void onBrowserEvent(Event event) {
+        super.onBrowserEvent(event);
+        int type = DOM.eventGetType(event);
+        if (type == Event.ONKEYDOWN && actionHandler != null) {
+            actionHandler.handleKeyboardEvent(event);
+            return;
+        } else if (scrollable && type == Event.ONSCROLL) {
+            updateScrollPosition();
+        }
+    }
+
+    /**
+     * Updates scroll position from DOM and saves variables to server.
+     */
+    private void updateScrollPosition() {
+        int oldTop = scrollTop;
+        int oldLeft = scrollLeft;
+        scrollTop = DOM.getElementPropertyInt(getElement(), "scrollTop");
+        scrollLeft = DOM.getElementPropertyInt(getElement(), "scrollLeft");
+        if (connection != null && !rendering) {
+            if (oldTop != scrollTop) {
+                connection.updateVariable(id, "scrollTop", scrollTop, false);
+            }
+            if (oldLeft != scrollLeft) {
+                connection.updateVariable(id, "scrollLeft", scrollLeft, false);
+            }
+        }
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see
+     * com.google.gwt.event.logical.shared.ResizeHandler#onResize(com.google
+     * .gwt.event.logical.shared.ResizeEvent)
+     */
+
+    @Override
+    public void onResize(ResizeEvent event) {
+        triggerSizeChangeCheck();
+    }
+
+    /**
+     * Called when a resize event is received.
+     * 
+     * This may trigger a lazy refresh or perform the size check immediately
+     * depending on the browser used and whether the server side requests
+     * resizes to be lazy.
+     */
+    private void triggerSizeChangeCheck() {
+        /*
+         * IE (pre IE9 at least) will give us some false resize events due to
+         * problems with scrollbars. Firefox 3 might also produce some extra
+         * events. We postpone both the re-layouting and the server side event
+         * for a while to deal with these issues.
+         * 
+         * We may also postpone these events to avoid slowness when resizing the
+         * browser window. Constantly recalculating the layout causes the resize
+         * operation to be really slow with complex layouts.
+         */
+        boolean lazy = resizeLazy || BrowserInfo.get().isIE8();
+
+        if (lazy) {
+            delayedResizeExecutor.trigger();
+        } else {
+            performSizeCheck();
+        }
+    }
+
+    /**
+     * Send new dimensions to the server.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public void sendClientResized() {
+        Element parentElement = getElement().getParentElement();
+        int viewHeight = parentElement.getClientHeight();
+        int viewWidth = parentElement.getClientWidth();
+
+        ResizeEvent.fire(this, viewWidth, viewHeight);
+    }
+
+    public native static void goTo(String url)
+    /*-{
+       $wnd.location = url;
+     }-*/;
+
+    @Override
+    public void onWindowClosing(Window.ClosingEvent event) {
+        // Change focus on this window in order to ensure that all state is
+        // collected from textfields
+        // TODO this is a naive hack, that only works with text fields and may
+        // cause some odd issues. Should be replaced with a decent solution, see
+        // also related BeforeShortcutActionListener interface. Same interface
+        // might be usable here.
+        VTextField.flushChangesFromFocusedTextField();
+    }
+
+    private native static void loadAppIdListFromDOM(ArrayList<String> list)
+    /*-{
+         var j;
+         for(j in $wnd.vaadin.vaadinConfigurations) {
+            // $entry not needed as function is not exported
+            list.@java.util.Collection::add(Ljava/lang/Object;)(j);
+         }
+     }-*/;
+
+    @Override
+    public ShortcutActionHandler getShortcutActionHandler() {
+        return actionHandler;
+    }
+
+    @Override
+    public void focus() {
+        getElement().focus();
+    }
+
+    /**
+     * Ensures the root is scrollable eg. after style name changes.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public void makeScrollable() {
+        if (touchScrollHandler == null) {
+            touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this);
+        }
+        touchScrollHandler.addElement(getElement());
+    }
+
+    @Override
+    public HandlerRegistration addResizeHandler(ResizeHandler resizeHandler) {
+        return addHandler(resizeHandler, ResizeEvent.getType());
+    }
+
+}
diff --git a/client/src/com/vaadin/client/ui/VUpload.java b/client/src/com/vaadin/client/ui/VUpload.java
new file mode 100644 (file)
index 0000000..bad22e3
--- /dev/null
@@ -0,0 +1,334 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.FormElement;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.FileUpload;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.FormPanel;
+import com.google.gwt.user.client.ui.Hidden;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.VConsole;
+import com.vaadin.client.ui.upload.UploadIFrameOnloadStrategy;
+
+/**
+ * 
+ * Note, we are not using GWT FormPanel as we want to listen submitcomplete
+ * events even though the upload component is already detached.
+ * 
+ */
+public class VUpload extends SimplePanel {
+
+    private final class MyFileUpload extends FileUpload {
+        @Override
+        public void onBrowserEvent(Event event) {
+            super.onBrowserEvent(event);
+            if (event.getTypeInt() == Event.ONCHANGE) {
+                if (immediate && fu.getFilename() != null
+                        && !"".equals(fu.getFilename())) {
+                    submit();
+                }
+            } else if (BrowserInfo.get().isIE()
+                    && event.getTypeInt() == Event.ONFOCUS) {
+                // IE and user has clicked on hidden textarea part of upload
+                // field. Manually open file selector, other browsers do it by
+                // default.
+                fireNativeClick(fu.getElement());
+                // also remove focus to enable hack if user presses cancel
+                // button
+                fireNativeBlur(fu.getElement());
+            }
+        }
+    }
+
+    public static final String CLASSNAME = "v-upload";
+
+    /**
+     * FileUpload component that opens native OS dialog to select file.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public FileUpload fu = new MyFileUpload();
+
+    Panel panel = new FlowPanel();
+
+    UploadIFrameOnloadStrategy onloadstrategy = GWT
+            .create(UploadIFrameOnloadStrategy.class);
+
+    /** 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;
+
+    /**
+     * Button that initiates uploading.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public final VButton submitButton;
+
+    /**
+     * When expecting big files, programmer may initiate some UI changes when
+     * uploading the file starts. Bit after submitting file we'll visit the
+     * server to check possible changes.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public Timer t;
+
+    /**
+     * some browsers tries to send form twice if submit is called in button
+     * click handler, some don't submit at all without it, so we need to track
+     * if form is already being submitted
+     */
+    private boolean submitted = false;
+
+    private boolean enabled = true;
+
+    private boolean immediate;
+
+    private Hidden maxfilesize = new Hidden();
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public FormElement element;
+
+    private com.google.gwt.dom.client.Element synthesizedFrame;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public int nextUploadId;
+
+    public VUpload() {
+        super(com.google.gwt.dom.client.Document.get().createFormElement());
+
+        element = getElement().cast();
+        setEncoding(getElement(), FormPanel.ENCODING_MULTIPART);
+        element.setMethod(FormPanel.METHOD_POST);
+
+        setWidget(panel);
+        panel.add(maxfilesize);
+        panel.add(fu);
+        submitButton = new VButton();
+        submitButton.addClickHandler(new ClickHandler() {
+            @Override
+            public void onClick(ClickEvent event) {
+                if (immediate) {
+                    // fire click on upload (eg. focused button and hit space)
+                    fireNativeClick(fu.getElement());
+                } else {
+                    submit();
+                }
+            }
+        });
+        panel.add(submitButton);
+
+        setStyleName(CLASSNAME);
+    }
+
+    private static native void setEncoding(Element form, String encoding)
+    /*-{
+      form.enctype = encoding;
+    }-*/;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void setImmediate(boolean booleanAttribute) {
+        if (immediate != booleanAttribute) {
+            immediate = booleanAttribute;
+            if (immediate) {
+                fu.sinkEvents(Event.ONCHANGE);
+                fu.sinkEvents(Event.ONFOCUS);
+            }
+        }
+        setStyleName(getElement(), CLASSNAME + "-immediate", immediate);
+    }
+
+    private static native void fireNativeClick(Element element)
+    /*-{
+        element.click();
+    }-*/;
+
+    private static native void fireNativeBlur(Element element)
+    /*-{
+        element.blur();
+    }-*/;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void disableUpload() {
+        submitButton.setEnabled(false);
+        if (!submitted) {
+            // Cannot disable the fileupload while submitting or the file won't
+            // be submitted at all
+            fu.getElement().setPropertyBoolean("disabled", true);
+        }
+        enabled = false;
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void enableUpload() {
+        submitButton.setEnabled(true);
+        fu.getElement().setPropertyBoolean("disabled", false);
+        enabled = true;
+        if (submitted) {
+            /*
+             * An old request is still in progress (most likely cancelled),
+             * ditching that target frame to make it possible to send a new
+             * file. A new target frame is created later."
+             */
+            cleanTargetFrame();
+            submitted = false;
+        }
+    }
+
+    /**
+     * Re-creates file input field and populates panel. This is needed as we
+     * want to clear existing values from our current file input field.
+     */
+    private void rebuildPanel() {
+        panel.remove(submitButton);
+        panel.remove(fu);
+        fu = new MyFileUpload();
+        fu.setName(paintableId + "_file");
+        fu.getElement().setPropertyBoolean("disabled", !enabled);
+        panel.add(fu);
+        panel.add(submitButton);
+        if (immediate) {
+            fu.sinkEvents(Event.ONCHANGE);
+        }
+    }
+
+    /**
+     * Called by JSNI (hooked via {@link #onloadstrategy})
+     */
+    private void onSubmitComplete() {
+        /* Needs to be run dereferred to avoid various browser issues. */
+        Scheduler.get().scheduleDeferred(new Command() {
+            @Override
+            public void execute() {
+                if (submitted) {
+                    if (client != null) {
+                        if (t != null) {
+                            t.cancel();
+                        }
+                        VConsole.log("VUpload:Submit complete");
+                        client.sendPendingVariableChanges();
+                    }
+
+                    rebuildPanel();
+
+                    submitted = false;
+                    enableUpload();
+                    if (!isAttached()) {
+                        /*
+                         * Upload is complete when upload is already abandoned.
+                         */
+                        cleanTargetFrame();
+                    }
+                }
+            }
+        });
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void submit() {
+        if (fu.getFilename().length() == 0 || submitted || !enabled) {
+            VConsole.log("Submit cancelled (disabled, no file or already submitted)");
+            return;
+        }
+        // flush possibly pending variable changes, so they will be handled
+        // before upload
+        client.sendPendingVariableChanges();
+
+        element.submit();
+        submitted = true;
+        VConsole.log("Submitted form");
+
+        disableUpload();
+
+        /*
+         * Visit server a moment after upload has started to see possible
+         * changes from UploadStarted event. Will be cleared on complete.
+         */
+        t = new Timer() {
+            @Override
+            public void run() {
+                VConsole.log("Visiting server to see if upload started event changed UI.");
+                client.updateVariable(paintableId, "pollForStart",
+                        nextUploadId, true);
+            }
+        };
+        t.schedule(800);
+    }
+
+    @Override
+    protected void onAttach() {
+        super.onAttach();
+        if (client != null) {
+            ensureTargetFrame();
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void ensureTargetFrame() {
+        if (synthesizedFrame == null) {
+            // Attach a hidden IFrame to the form. This is the target iframe to
+            // which the form will be submitted. We have to create the iframe
+            // using innerHTML, because setting an iframe's 'name' property
+            // dynamically doesn't work on most browsers.
+            DivElement dummy = Document.get().createDivElement();
+            dummy.setInnerHTML("<iframe src=\"javascript:''\" name='"
+                    + getFrameName()
+                    + "' style='position:absolute;width:0;height:0;border:0'>");
+            synthesizedFrame = dummy.getFirstChildElement();
+            Document.get().getBody().appendChild(synthesizedFrame);
+            element.setTarget(getFrameName());
+            onloadstrategy.hookEvents(synthesizedFrame, this);
+        }
+    }
+
+    private String getFrameName() {
+        return paintableId + "_TGT_FRAME";
+    }
+
+    @Override
+    protected void onDetach() {
+        super.onDetach();
+        if (!submitted) {
+            cleanTargetFrame();
+        }
+    }
+
+    private void cleanTargetFrame() {
+        if (synthesizedFrame != null) {
+            Document.get().getBody().removeChild(synthesizedFrame);
+            onloadstrategy.unHookEvents(synthesizedFrame);
+            synthesizedFrame = null;
+        }
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VVerticalLayout.java b/client/src/com/vaadin/client/ui/VVerticalLayout.java
new file mode 100644 (file)
index 0000000..1a3ce33
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui;
+
+import com.vaadin.client.StyleConstants;
+
+/**
+ * Represents a layout where the children is ordered vertically
+ */
+public class VVerticalLayout extends VOrderedLayout {
+
+    public static final String CLASSNAME = "v-verticallayout";
+
+    /**
+     * Default constructor
+     */
+    public VVerticalLayout() {
+        super(true);
+        setStyleName(CLASSNAME);
+    }
+
+    @Override
+    public void setStyleName(String style) {
+        super.setStyleName(style);
+        addStyleName(StyleConstants.UI_LAYOUT);
+        addStyleName("v-vertical");
+    }
+}
diff --git a/client/src/com/vaadin/client/ui/VVideo.java b/client/src/com/vaadin/client/ui/VVideo.java
new file mode 100644 (file)
index 0000000..754af86
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.dom.client.VideoElement;
+import com.google.gwt.user.client.Element;
+import com.vaadin.client.Util;
+
+public class VVideo extends VMediaBase {
+
+    public static String CLASSNAME = "v-video";
+
+    private VideoElement video;
+
+    public VVideo() {
+        video = Document.get().createVideoElement();
+        setMediaElement(video);
+        setStyleName(CLASSNAME);
+
+        updateDimensionsWhenMetadataLoaded(getElement());
+    }
+
+    /**
+     * Registers a listener that updates the dimensions of the widget when the
+     * video metadata has been loaded.
+     * 
+     * @param el
+     */
+    private native void updateDimensionsWhenMetadataLoaded(Element el)
+    /*-{
+              var self = this;
+              el.addEventListener('loadedmetadata', $entry(function(e) {
+                  self.@com.vaadin.client.ui.video.VVideo::updateElementDynamicSize(II)(el.videoWidth, el.videoHeight);
+              }), false);
+
+    }-*/;
+
+    /**
+     * Updates the dimensions of the widget.
+     * 
+     * @param w
+     * @param h
+     */
+    private void updateElementDynamicSize(int w, int h) {
+        video.getStyle().setWidth(w, Unit.PX);
+        video.getStyle().setHeight(h, Unit.PX);
+        Util.notifyParentOfSizeChange(this, true);
+    }
+
+    public void setPoster(String poster) {
+        video.setPoster(poster);
+    }
+
+}
diff --git a/client/src/com/vaadin/client/ui/VWindow.java b/client/src/com/vaadin/client/ui/VWindow.java
new file mode 100644 (file)
index 0000000..0885077
--- /dev/null
@@ -0,0 +1,955 @@
+/*
+ * Copyright 2011 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.ScrollEvent;
+import com.google.gwt.event.dom.client.ScrollHandler;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.HasWidgets;
+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.ConnectorMap;
+import com.vaadin.client.Console;
+import com.vaadin.client.Focusable;
+import com.vaadin.client.LayoutManager;
+import com.vaadin.client.Util;
+import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
+import com.vaadin.shared.EventId;
+
+/**
+ * "Sub window" component.
+ * 
+ * @author Vaadin Ltd
+ */
+public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
+        ScrollHandler, KeyDownHandler, FocusHandler, BlurHandler, Focusable {
+
+    /**
+     * Minimum allowed height of a window. This refers to the content area, not
+     * the outer borders.
+     */
+    private static final int MIN_CONTENT_AREA_HEIGHT = 100;
+
+    /**
+     * Minimum allowed width of a window. This refers to the content area, not
+     * the outer borders.
+     */
+    private static final int MIN_CONTENT_AREA_WIDTH = 150;
+
+    private static ArrayList<VWindow> windowOrder = new ArrayList<VWindow>();
+
+    private static boolean orderingDefered;
+
+    public static final String CLASSNAME = "v-window";
+
+    private static final int STACKING_OFFSET_PIXELS = 15;
+
+    public static final int Z_INDEX = 10000;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public ComponentConnector layout;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public Element contents;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public Element header;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public Element footer;
+
+    private Element resizeBox;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public final FocusableScrollPanel contentPanel = new FocusableScrollPanel();
+
+    private boolean dragging;
+
+    private int startX;
+
+    private int startY;
+
+    private int origX;
+
+    private int origY;
+
+    private boolean resizing;
+
+    private int origW;
+
+    private int origH;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public Element closeBox;
+
+    /** 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 id;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public ShortcutActionHandler shortcutHandler;
+
+    /** Last known positionx read from UIDL or updated to application connection */
+    private int uidlPositionX = -1;
+
+    /** Last known positiony read from UIDL or updated to application connection */
+    private int uidlPositionY = -1;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean vaadinModality = false;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean resizable = true;
+
+    private boolean draggable = true;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean resizeLazy = false;
+
+    private Element modalityCurtain;
+    private Element draggingCurtain;
+    private Element resizingCurtain;
+
+    private Element headerText;
+
+    private boolean closable = true;
+
+    /**
+     * If centered (via UIDL), the window should stay in the centered -mode
+     * until a position is received from the server, or the user moves or
+     * resizes the window.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public boolean centered = false;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean immediate;
+
+    private Element wrapper;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public boolean visibilityChangesDisabled;
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public int bringToFrontSequence = -1;
+
+    private VLazyExecutor delayedContentsSizeUpdater = new VLazyExecutor(200,
+            new ScheduledCommand() {
+
+                @Override
+                public void execute() {
+                    updateContentsSize();
+                }
+            });
+
+    public VWindow() {
+        super(false, false, true); // no autohide, not modal, shadow
+        // Different style of shadow for windows
+        setShadowStyle("window");
+
+        constructDOM();
+        contentPanel.addScrollHandler(this);
+        contentPanel.addKeyDownHandler(this);
+        contentPanel.addFocusHandler(this);
+        contentPanel.addBlurHandler(this);
+    }
+
+    public void bringToFront() {
+        int curIndex = windowOrder.indexOf(this);
+        if (curIndex + 1 < windowOrder.size()) {
+            windowOrder.remove(this);
+            windowOrder.add(this);
+            for (; curIndex < windowOrder.size(); curIndex++) {
+                windowOrder.get(curIndex).setWindowOrder(curIndex);
+            }
+        }
+    }
+
+    /**
+     * Returns true if this window is the topmost VWindow
+     * 
+     * @return
+     */
+    private boolean isActive() {
+        return equals(getTopmostWindow());
+    }
+
+    private static VWindow getTopmostWindow() {
+        return windowOrder.get(windowOrder.size() - 1);
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void setWindowOrderAndPosition() {
+        // This cannot be done in the constructor as the widgets are created in
+        // a different order than on they should appear on screen
+        if (windowOrder.contains(this)) {
+            // Already set
+            return;
+        }
+        final int order = windowOrder.size();
+        setWindowOrder(order);
+        windowOrder.add(this);
+        setPopupPosition(order * STACKING_OFFSET_PIXELS, order
+                * STACKING_OFFSET_PIXELS);
+
+    }
+
+    private void setWindowOrder(int order) {
+        setZIndex(order + Z_INDEX);
+    }
+
+    @Override
+    protected void setZIndex(int zIndex) {
+        super.setZIndex(zIndex);
+        if (vaadinModality) {
+            DOM.setStyleAttribute(getModalityCurtain(), "zIndex", "" + zIndex);
+        }
+    }
+
+    protected Element getModalityCurtain() {
+        if (modalityCurtain == null) {
+            modalityCurtain = DOM.createDiv();
+            modalityCurtain.setClassName(CLASSNAME + "-modalitycurtain");
+        }
+        return modalityCurtain;
+    }
+
+    protected void constructDOM() {
+        setStyleName(CLASSNAME);
+
+        header = DOM.createDiv();
+        DOM.setElementProperty(header, "className", CLASSNAME + "-outerheader");
+        headerText = DOM.createDiv();
+        DOM.setElementProperty(headerText, "className", CLASSNAME + "-header");
+        contents = DOM.createDiv();
+        DOM.setElementProperty(contents, "className", CLASSNAME + "-contents");
+        footer = DOM.createDiv();
+        DOM.setElementProperty(footer, "className", CLASSNAME + "-footer");
+        resizeBox = DOM.createDiv();
+        DOM.setElementProperty(resizeBox, "className", CLASSNAME + "-resizebox");
+        closeBox = DOM.createDiv();
+        DOM.setElementProperty(closeBox, "className", CLASSNAME + "-closebox");
+        DOM.appendChild(footer, resizeBox);
+
+        wrapper = DOM.createDiv();
+        DOM.setElementProperty(wrapper, "className", CLASSNAME + "-wrap");
+
+        DOM.appendChild(wrapper, header);
+        DOM.appendChild(wrapper, closeBox);
+        DOM.appendChild(header, headerText);
+        DOM.appendChild(wrapper, contents);
+        DOM.appendChild(wrapper, footer);
+        DOM.appendChild(super.getContainerElement(), wrapper);
+
+        sinkEvents(Event.MOUSEEVENTS | Event.TOUCHEVENTS | Event.ONCLICK
+                | Event.ONLOSECAPTURE);
+
+        setWidget(contentPanel);
+
+    }
+
+    /**
+     * Calling this method will defer ordering algorithm, to order windows based
+     * on servers bringToFront and modality instructions. Non changed windows
+     * will be left intact.
+     * <p>
+     * For internal use only. May be removed or replaced in the future.
+     */
+    public static void deferOrdering() {
+        if (!orderingDefered) {
+            orderingDefered = true;
+            Scheduler.get().scheduleFinally(new Command() {
+
+                @Override
+                public void execute() {
+                    doServerSideOrdering();
+                    VNotification.bringNotificationsToFront();
+                }
+            });
+        }
+    }
+
+    private static void doServerSideOrdering() {
+        orderingDefered = false;
+        VWindow[] array = windowOrder.toArray(new VWindow[windowOrder.size()]);
+        Arrays.sort(array, new Comparator<VWindow>() {
+
+            @Override
+            public int compare(VWindow o1, VWindow o2) {
+                /*
+                 * Order by modality, then by bringtofront sequence.
+                 */
+
+                if (o1.vaadinModality && !o2.vaadinModality) {
+                    return 1;
+                } else if (!o1.vaadinModality && o2.vaadinModality) {
+                    return -1;
+                } else if (o1.bringToFrontSequence > o2.bringToFrontSequence) {
+                    return 1;
+                } else if (o1.bringToFrontSequence < o2.bringToFrontSequence) {
+                    return -1;
+                } else {
+                    return 0;
+                }
+            }
+        });
+        for (int i = 0; i < array.length; i++) {
+            VWindow w = array[i];
+            if (w.bringToFrontSequence != -1 || w.vaadinModality) {
+                w.bringToFront();
+                w.bringToFrontSequence = -1;
+            }
+        }
+    }
+
+    @Override
+    public void setVisible(boolean visible) {
+        /*
+         * Visibility with VWindow works differently than with other Paintables
+         * in Vaadin. Invisible VWindows are not attached to DOM at all. Flag is
+         * used to avoid visibility call from
+         * ApplicationConnection.updateComponent();
+         */
+        if (!visibilityChangesDisabled) {
+            super.setVisible(visible);
+        }
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void setDraggable(boolean draggable) {
+        if (this.draggable == draggable) {
+            return;
+        }
+
+        this.draggable = draggable;
+
+        setCursorProperties();
+    }
+
+    private void setCursorProperties() {
+        if (!draggable) {
+            header.getStyle().setProperty("cursor", "default");
+            footer.getStyle().setProperty("cursor", "default");
+        } else {
+            header.getStyle().setProperty("cursor", "");
+            footer.getStyle().setProperty("cursor", "");
+        }
+    }
+
+    /**
+     * Sets the closable state of the window. Additionally hides/shows the close
+     * button according to the new state.
+     * 
+     * @param closable
+     *            true if the window can be closed by the user
+     */
+    public void setClosable(boolean closable) {
+        if (this.closable == closable) {
+            return;
+        }
+
+        this.closable = closable;
+        if (closable) {
+            DOM.setStyleAttribute(closeBox, "display", "");
+        } else {
+            DOM.setStyleAttribute(closeBox, "display", "none");
+        }
+
+    }
+
+    /**
+     * Returns the closable state of the sub window. If the sub window is
+     * closable a decoration (typically an X) is shown to the user. By clicking
+     * on the X the user can close the window.
+     * 
+     * @return true if the sub window is closable
+     */
+    protected boolean isClosable() {
+        return closable;
+    }
+
+    @Override
+    public void show() {
+        if (!windowOrder.contains(this)) {
+            // This is needed if the window is hidden and then shown again.
+            // Otherwise this VWindow is added to windowOrder in the
+            // constructor.
+            windowOrder.add(this);
+        }
+
+        if (vaadinModality) {
+            showModalityCurtain();
+        }
+        super.show();
+    }
+
+    @Override
+    public void hide() {
+        if (vaadinModality) {
+            hideModalityCurtain();
+        }
+        super.hide();
+
+        // Remove window from windowOrder to avoid references being left
+        // hanging.
+        windowOrder.remove(this);
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void setVaadinModality(boolean modality) {
+        vaadinModality = modality;
+        if (vaadinModality) {
+            if (isAttached()) {
+                showModalityCurtain();
+            }
+            deferOrdering();
+        } else {
+            if (modalityCurtain != null) {
+                if (isAttached()) {
+                    hideModalityCurtain();
+                }
+                modalityCurtain = null;
+            }
+        }
+    }
+
+    private void showModalityCurtain() {
+        DOM.setStyleAttribute(getModalityCurtain(), "zIndex",
+                "" + (windowOrder.indexOf(this) + Z_INDEX));
+
+        if (isShowing()) {
+            getOverlayContainer().insertBefore(getModalityCurtain(),
+                    getElement());
+        } else {
+            getOverlayContainer().appendChild(getModalityCurtain());
+        }
+
+    }
+
+    private void hideModalityCurtain() {
+        modalityCurtain.removeFromParent();
+    }
+
+    /*
+     * Shows an empty div on top of all other content; used when moving, so that
+     * iframes (etc) do not steal event.
+     */
+    private void showDraggingCurtain() {
+        getElement().getParentElement().insertBefore(getDraggingCurtain(),
+                getElement());
+    }
+
+    private void hideDraggingCurtain() {
+        if (draggingCurtain != null) {
+            draggingCurtain.removeFromParent();
+        }
+    }
+
+    /*
+     * Shows an empty div on top of all other content; used when resizing, so
+     * that iframes (etc) do not steal event.
+     */
+    private void showResizingCurtain() {
+        getElement().getParentElement().insertBefore(getResizingCurtain(),
+                getElement());
+    }
+
+    private void hideResizingCurtain() {
+        if (resizingCurtain != null) {
+            resizingCurtain.removeFromParent();
+        }
+    }
+
+    private Element getDraggingCurtain() {
+        if (draggingCurtain == null) {
+            draggingCurtain = createCurtain();
+            draggingCurtain.setClassName(CLASSNAME + "-draggingCurtain");
+        }
+
+        return draggingCurtain;
+    }
+
+    private Element getResizingCurtain() {
+        if (resizingCurtain == null) {
+            resizingCurtain = createCurtain();
+            resizingCurtain.setClassName(CLASSNAME + "-resizingCurtain");
+        }
+
+        return resizingCurtain;
+    }
+
+    private Element createCurtain() {
+        Element curtain = DOM.createDiv();
+
+        DOM.setStyleAttribute(curtain, "position", "absolute");
+        DOM.setStyleAttribute(curtain, "top", "0px");
+        DOM.setStyleAttribute(curtain, "left", "0px");
+        DOM.setStyleAttribute(curtain, "width", "100%");
+        DOM.setStyleAttribute(curtain, "height", "100%");
+        DOM.setStyleAttribute(curtain, "zIndex", "" + VOverlay.Z_INDEX);
+
+        return curtain;
+    }
+
+    /** For internal use only. May be removed or replaced in the future. */
+    public void setResizable(boolean resizability) {
+        resizable = resizability;
+        if (resizability) {
+            DOM.setElementProperty(footer, "className", CLASSNAME + "-footer");
+            DOM.setElementProperty(resizeBox, "className", CLASSNAME
+                    + "-resizebox");
+        } else {
+            DOM.setElementProperty(footer, "className", CLASSNAME + "-footer "
+                    + CLASSNAME + "-footer-noresize");
+            DOM.setElementProperty(resizeBox, "className", CLASSNAME
+                    + "-resizebox " + CLASSNAME + "-resizebox-disabled");
+        }
+    }
+
+    @Override
+    public void setPopupPosition(int left, int top) {
+        if (top < 0) {
+            // ensure window is not moved out of browser window from top of the
+            // screen
+            top = 0;
+        }
+        super.setPopupPosition(left, top);
+        if (left != uidlPositionX && client != null) {
+            client.updateVariable(id, "positionx", left, false);
+            uidlPositionX = left;
+        }
+        if (top != uidlPositionY && client != null) {
+            client.updateVariable(id, "positiony", top, false);
+            uidlPositionY = top;
+        }
+    }
+
+    public void setCaption(String c) {
+        setCaption(c, null);
+    }
+
+    public void setCaption(String c, String icon) {
+        String html = Util.escapeHTML(c);
+        if (icon != null) {
+            icon = client.translateVaadinUri(icon);
+            html = "<img src=\"" + Util.escapeAttribute(icon)
+                    + "\" class=\"v-icon\" />" + html;
+        }
+        DOM.setInnerHTML(headerText, html);
+    }
+
+    @Override
+    protected Element getContainerElement() {
+        // in GWT 1.5 this method is used in PopupPanel constructor
+        if (contents == null) {
+            return super.getContainerElement();
+        }
+        return contents;
+    }
+
+    @Override
+    public void onBrowserEvent(final Event event) {
+        boolean bubble = true;
+
+        final int type = event.getTypeInt();
+
+        final Element target = DOM.eventGetTarget(event);
+
+        if (resizing || resizeBox == target) {
+            onResizeEvent(event);
+            bubble = false;
+        } else if (isClosable() && target == closeBox) {
+            if (type == Event.ONCLICK) {
+                onCloseClick();
+            }
+            bubble = false;
+        } else if (dragging || !contents.isOrHasChild(target)) {
+            onDragEvent(event);
+            bubble = false;
+        } else if (type == Event.ONCLICK) {
+            // clicked inside window, ensure to be on top
+            if (!isActive()) {
+                bringToFront();
+            }
+        }
+
+        /*
+         * If clicking on other than the content, move focus to the window.
+         * After that this windows e.g. gets all keyboard shortcuts.
+         */
+        if (type == Event.ONMOUSEDOWN
+                && !contentPanel.getElement().isOrHasChild(target)
+                && target != closeBox) {
+            contentPanel.focus();
+        }
+
+        if (!bubble) {
+            event.stopPropagation();
+        } else {
+            // Super.onBrowserEvent takes care of Handlers added by the
+            // ClickEventHandler
+            super.onBrowserEvent(event);
+        }
+    }
+
+    private void onCloseClick() {
+        client.updateVariable(id, "close", true, true);
+    }
+
+    private void onResizeEvent(Event event) {
+        if (resizable && Util.isTouchEventOrLeftMouseButton(event)) {
+            switch (event.getTypeInt()) {
+            case Event.ONMOUSEDOWN:
+            case Event.ONTOUCHSTART:
+                if (!isActive()) {
+                    bringToFront();
+                }
+                showResizingCurtain();
+                if (BrowserInfo.get().isIE()) {
+                    DOM.setStyleAttribute(resizeBox, "visibility", "hidden");
+                }
+                resizing = true;
+                startX = Util.getTouchOrMouseClientX(event);
+                startY = Util.getTouchOrMouseClientY(event);
+                origW = getElement().getOffsetWidth();
+                origH = getElement().getOffsetHeight();
+                DOM.setCapture(getElement());
+                event.preventDefault();
+                break;
+            case Event.ONMOUSEUP:
+            case Event.ONTOUCHEND:
+                setSize(event, true);
+            case Event.ONTOUCHCANCEL:
+                DOM.releaseCapture(getElement());
+            case Event.ONLOSECAPTURE:
+                hideResizingCurtain();
+                if (BrowserInfo.get().isIE()) {
+                    DOM.setStyleAttribute(resizeBox, "visibility", "");
+                }
+                resizing = false;
+                break;
+            case Event.ONMOUSEMOVE:
+            case Event.ONTOUCHMOVE:
+                if (resizing) {
+                    centered = false;
+                    setSize(event, false);
+                    event.preventDefault();
+                }
+                break;
+            default:
+                event.preventDefault();
+                break;
+            }
+        }
+    }
+
+    /**
+     * TODO check if we need to support this with touch based devices.
+     * 
+     * Checks if the cursor was inside the browser content area when the event
+     * happened.
+     * 
+     * @param event
+     *            The event to be checked
+     * @return true, if the cursor is inside the browser content area
+     * 
+     *         false, otherwise
+     */
+    private boolean cursorInsideBrowserContentArea(Event event) {
+        if (event.getClientX() < 0 || event.getClientY() < 0) {
+            // Outside to the left or above
+            return false;
+        }
+
+        if (event.getClientX() > Window.getClientWidth()
+                || event.getClientY() > Window.getClientHeight()) {
+            // Outside to the right or below
+            return false;
+        }
+
+        return true;
+    }
+
+    private void setSize(Event event, boolean updateVariables) {
+        if (!cursorInsideBrowserContentArea(event)) {
+            // Only drag while cursor is inside the browser client area
+            return;
+        }
+
+        int w = Util.getTouchOrMouseClientX(event) - startX + origW;
+        int minWidth = getMinWidth();
+        if (w < minWidth) {
+            w = minWidth;
+        }
+
+        int h = Util.getTouchOrMouseClientY(event) - startY + origH;
+        int minHeight = getMinHeight();
+        if (h < minHeight) {
+            h = minHeight;
+        }
+
+        setWidth(w + "px");
+        setHeight(h + "px");
+
+        if (updateVariables) {
+            // sending width back always as pixels, no need for unit
+            client.updateVariable(id, "width", w, false);
+            client.updateVariable(id, "height", h, immediate);
+        }
+
+        if (updateVariables || !resizeLazy) {
+            // Resize has finished or is not lazy
+            updateContentsSize();
+        } else {
+            // Lazy resize - wait for a while before re-rendering contents
+            delayedContentsSizeUpdater.trigger();
+        }
+    }
+
+    private void updateContentsSize() {
+        // Update child widget dimensions
+        if (client != null) {
+            client.handleComponentRelativeSize(layout.getWidget());
+            client.runDescendentsLayout((HasWidgets) layout.getWidget());
+        }
+
+        LayoutManager layoutManager = LayoutManager.get(client);
+        layoutManager.setNeedsMeasure(ConnectorMap.get(client).getConnector(
+                this));
+        layoutManager.layoutNow();
+    }
+
+    @Override
+    public void setWidth(String width) {
+        // Override PopupPanel which sets the width to the contents
+        getElement().getStyle().setProperty("width", width);
+        // Update v-has-width in case undefined window is resized
+        setStyleName("v-has-width", width != null && width.length() > 0);
+    }
+
+    @Override
+    public void setHeight(String height) {
+        // Override PopupPanel which sets the height to the contents
+        getElement().getStyle().setProperty("height", height);
+        // Update v-has-height in case undefined window is resized
+        setStyleName("v-has-height", height != null && height.length() > 0);
+    }
+
+    private void onDragEvent(Event event) {
+        if (!Util.isTouchEventOrLeftMouseButton(event)) {
+            return;
+        }
+
+        switch (DOM.eventGetType(event)) {
+        case Event.ONTOUCHSTART:
+            if (event.getTouches().length() > 1) {
+                return;
+            }
+        case Event.ONMOUSEDOWN:
+            if (!isActive()) {
+                bringToFront();
+            }
+            beginMovingWindow(event);
+            break;
+        case Event.ONMOUSEUP:
+        case Event.ONTOUCHEND:
+        case Event.ONTOUCHCANCEL:
+        case Event.ONLOSECAPTURE:
+            stopMovingWindow();
+            break;
+        case Event.ONMOUSEMOVE:
+        case Event.ONTOUCHMOVE:
+            moveWindow(event);
+            break;
+        default:
+            break;
+        }
+    }
+
+    private void moveWindow(Event event) {
+        if (dragging) {
+            centered = false;
+            if (cursorInsideBrowserContentArea(event)) {
+                // Only drag while cursor is inside the browser client area
+                final int x = Util.getTouchOrMouseClientX(event) - startX
+                        + origX;
+                final int y = Util.getTouchOrMouseClientY(event) - startY
+                        + origY;
+                setPopupPosition(x, y);
+            }
+            DOM.eventPreventDefault(event);
+        }
+    }
+
+    private void beginMovingWindow(Event event) {
+        if (draggable) {
+            showDraggingCurtain();
+            dragging = true;
+            startX = Util.getTouchOrMouseClientX(event);
+            startY = Util.getTouchOrMouseClientY(event);
+            origX = DOM.getAbsoluteLeft(getElement());
+            origY = DOM.getAbsoluteTop(getElement());
+            DOM.setCapture(getElement());
+            DOM.eventPreventDefault(event);
+        }
+    }
+
+    private void stopMovingWindow() {
+        dragging = false;
+        hideDraggingCurtain();
+        DOM.releaseCapture(getElement());
+    }
+
+    @Override
+    public boolean onEventPreview(Event event) {
+        if (dragging) {
+            onDragEvent(event);
+            return false;
+        } else if (resizing) {
+            onResizeEvent(event);
+            return false;
+        }
+
+        // TODO This is probably completely unnecessary as the modality curtain
+        // prevents events from reaching other windows and any security check
+        // must be done on the server side and not here.
+        // The code here is also run many times as each VWindow has an event
+        // preview but we cannot check only the current VWindow here (e.g.
+        // if(isTopMost) {...}) because PopupPanel will cause all events that
+        // are not cancelled here and target this window to be consume():d
+        // meaning the event won't be sent to the rest of the preview handlers.
+
+        if (getTopmostWindow().vaadinModality) {
+            // Topmost window is modal. Cancel the event if it targets something
+            // outside that window (except debug console...)
+            if (DOM.getCaptureElement() != null) {
+                // Allow events when capture is set
+                return true;
+            }
+
+            final Element target = event.getEventTarget().cast();
+            if (!DOM.isOrHasChild(getTopmostWindow().getElement(), target)) {
+                // not within the modal window, but let's see if it's in the
+                // debug window
+                Widget w = Util.findWidget(target, null);
+                while (w != null) {
+                    if (w instanceof Console) {
+                        return true; // allow debug-window clicks
+                    } else if (ConnectorMap.get(client).isConnector(w)) {
+                        return false;
+                    }
+                    w = w.getParent();
+                }
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void addStyleDependentName(String styleSuffix) {
+        // VWindow's getStyleElement() does not return the same element as
+        // getElement(), so we need to override this.
+        setStyleName(getElement(), getStylePrimaryName() + "-" + styleSuffix,
+                true);
+    }
+
+    @Override
+    public ShortcutActionHandler getShortcutActionHandler() {
+        return shortcutHandler;
+    }
+
+    @Override
+    public void onScroll(ScrollEvent event) {
+        client.updateVariable(id, "scrollTop",
+                contentPanel.getScrollPosition(), false);
+        client.updateVariable(id, "scrollLeft",
+                contentPanel.getHorizontalScrollPosition(), false);
+
+    }
+
+    @Override
+    public void onKeyDown(KeyDownEvent event) {
+        if (shortcutHandler != null) {
+            shortcutHandler
+                    .handleKeyboardEvent(Event.as(event.getNativeEvent()));
+            return;
+        }
+    }
+
+    @Override
+    public void onBlur(BlurEvent event) {
+        if (client.hasEventListeners(this, EventId.BLUR)) {
+            client.updateVariable(id, EventId.BLUR, "", true);
+        }
+    }
+
+    @Override
+    public void onFocus(FocusEvent event) {
+        if (client.hasEventListeners(this, EventId.FOCUS)) {
+            client.updateVariable(id, EventId.FOCUS, "", true);
+        }
+    }
+
+    @Override
+    public void focus() {
+        contentPanel.focus();
+    }
+
+    public int getMinHeight() {
+        return MIN_CONTENT_AREA_HEIGHT + getDecorationHeight();
+    }
+
+    private int getDecorationHeight() {
+        LayoutManager lm = layout.getLayoutManager();
+        int headerHeight = lm.getOuterHeight(header);
+        int footerHeight = lm.getOuterHeight(footer);
+        return headerHeight + footerHeight;
+    }
+
+    public int getMinWidth() {
+        return MIN_CONTENT_AREA_WIDTH + getDecorationWidth();
+    }
+
+    private int getDecorationWidth() {
+        LayoutManager layoutManager = layout.getLayoutManager();
+        return layoutManager.getOuterWidth(getElement())
+                - contentPanel.getElement().getOffsetWidth();
+    }
+
+}
index 40830531b6a359b060bf5f229045e916f526abd4..d13b0793887179d773156039a15423b60bd8c3fd 100644 (file)
@@ -27,6 +27,7 @@ import com.vaadin.client.communication.StateChangeEvent;
 import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler;
 import com.vaadin.client.ui.AbstractComponentContainerConnector;
 import com.vaadin.client.ui.LayoutClickEventHandler;
+import com.vaadin.client.ui.VAbsoluteLayout;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.LayoutClickRpc;
 import com.vaadin.shared.ui.absolutelayout.AbsoluteLayoutServerRpc;
diff --git a/client/src/com/vaadin/client/ui/absolutelayout/VAbsoluteLayout.java b/client/src/com/vaadin/client/ui/absolutelayout/VAbsoluteLayout.java
deleted file mode 100644 (file)
index 0cfd4fb..0000000
+++ /dev/null
@@ -1,570 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.vaadin.client.ui.absolutelayout;
-
-import com.google.gwt.dom.client.DivElement;
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.dom.client.Style;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.ui.ComplexPanel;
-import com.google.gwt.user.client.ui.SimplePanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.StyleConstants;
-import com.vaadin.client.VCaption;
-
-public class VAbsoluteLayout extends ComplexPanel {
-
-    /** Tag name for widget creation */
-    public static final String TAGNAME = "absolutelayout";
-
-    /** Class name, prefix in styling */
-    public static final String CLASSNAME = "v-absolutelayout";
-
-    private DivElement marginElement;
-
-    protected final Element canvas = DOM.createDiv();
-
-    /**
-     * Default constructor
-     */
-    public VAbsoluteLayout() {
-        setElement(Document.get().createDivElement());
-        marginElement = Document.get().createDivElement();
-        canvas.getStyle().setProperty("position", "relative");
-        canvas.getStyle().setProperty("overflow", "hidden");
-        marginElement.appendChild(canvas);
-        getElement().appendChild(marginElement);
-        setStyleName(CLASSNAME);
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see
-     * com.google.gwt.user.client.ui.Panel#add(com.google.gwt.user.client.ui
-     * .Widget)
-     */
-    @Override
-    public void add(Widget child) {
-        AbsoluteWrapper wrapper = new AbsoluteWrapper(child);
-        wrapper.updateStyleNames();
-        super.add(wrapper, canvas);
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see
-     * com.google.gwt.user.client.ui.ComplexPanel#remove(com.google.gwt.user
-     * .client.ui.Widget)
-     */
-    @Override
-    public boolean remove(Widget w) {
-        AbsoluteWrapper wrapper = getChildWrapper(w);
-        if (wrapper != null) {
-            wrapper.destroy();
-            return super.remove(wrapper);
-        }
-        return super.remove(w);
-    }
-
-    /**
-     * Does this layout contain a widget
-     * 
-     * @param widget
-     *            The widget to check
-     * @return Returns true if the widget is in this layout, false if not
-     */
-    public boolean contains(Widget widget) {
-        return getChildWrapper(widget) != null;
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see com.google.gwt.user.client.ui.ComplexPanel#getWidget(int)
-     */
-    @Override
-    public Widget getWidget(int index) {
-        for (int i = 0, j = 0; i < super.getWidgetCount(); i++) {
-            Widget w = getWidget(i);
-            if (w instanceof AbsoluteWrapper) {
-                if (j == index) {
-                    return w;
-                } else {
-                    j++;
-                }
-            }
-        }
-        return null;
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see com.google.gwt.user.client.ui.ComplexPanel#getWidgetCount()
-     */
-    @Override
-    public int getWidgetCount() {
-        int counter = 0;
-        for (int i = 0; i < super.getWidgetCount(); i++) {
-            if (getWidget(i) instanceof AbsoluteWrapper) {
-                counter++;
-            }
-        }
-        return counter;
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see
-     * com.google.gwt.user.client.ui.ComplexPanel#getWidgetIndex(com.google.
-     * gwt.user.client.ui.Widget)
-     */
-    @Override
-    public int getWidgetIndex(Widget child) {
-        for (int i = 0, j = 0; i < super.getWidgetCount(); i++) {
-            Widget w = getWidget(i);
-            if (w instanceof AbsoluteWrapper) {
-                if (child == w) {
-                    return j;
-                } else {
-                    j++;
-                }
-            }
-        }
-        return -1;
-    }
-
-    /**
-     * Sets a caption for a contained widget
-     * 
-     * @param child
-     *            The child widget to set the caption for
-     * @param caption
-     *            The caption of the widget
-     */
-    public void setWidgetCaption(Widget child, VCaption caption) {
-        AbsoluteWrapper wrapper = getChildWrapper(child);
-        if (wrapper != null) {
-            if (caption != null) {
-                if (!getChildren().contains(caption)) {
-                    super.add(caption, canvas);
-                }
-                wrapper.setCaption(caption);
-                caption.updateCaption();
-                wrapper.updateCaptionPosition();
-            } else if (wrapper.getCaption() != null) {
-                wrapper.setCaption(null);
-            }
-        }
-    }
-
-    /**
-     * Set the position of the widget in the layout. The position is a CSS
-     * property string using properties such as top,left,right,top
-     * 
-     * @param child
-     *            The child widget to set the position for
-     * @param position
-     *            The position string
-     */
-    public void setWidgetPosition(Widget child, String position) {
-        AbsoluteWrapper wrapper = getChildWrapper(child);
-        if (wrapper != null) {
-            wrapper.setPosition(position);
-        }
-    }
-
-    /**
-     * Get the caption for a widget
-     * 
-     * @param child
-     *            The child widget to get the caption of
-     */
-    public VCaption getWidgetCaption(Widget child) {
-        AbsoluteWrapper wrapper = getChildWrapper(child);
-        if (wrapper != null) {
-            return wrapper.getCaption();
-        }
-        return null;
-    }
-
-    /**
-     * Get the pixel width of an slot in the layout
-     * 
-     * @param child
-     *            The widget in the layout.
-     * @return Returns the size in pixels, or 0 if child is not in the layout
-     */
-    public int getWidgetSlotWidth(Widget child) {
-        AbsoluteWrapper wrapper = getChildWrapper(child);
-        if (wrapper != null) {
-            return wrapper.getOffsetWidth();
-        }
-        return 0;
-    }
-
-    /**
-     * Get the pixel height of an slot in the layout
-     * 
-     * @param child
-     *            The widget in the layout
-     * @return Returns the size in pixels, or 0 if the child is not in the
-     *         layout
-     */
-    public int getWidgetSlotHeight(Widget child) {
-        AbsoluteWrapper wrapper = getChildWrapper(child);
-        if (wrapper != null) {
-            return wrapper.getOffsetHeight();
-        }
-        return 0;
-    }
-
-    /**
-     * Get the wrapper for a widget
-     * 
-     * @param child
-     *            The child to get the wrapper for
-     * @return
-     */
-    protected AbsoluteWrapper getChildWrapper(Widget child) {
-        for (Widget w : getChildren()) {
-            if (w instanceof AbsoluteWrapper) {
-                AbsoluteWrapper wrapper = (AbsoluteWrapper) w;
-                if (wrapper.getWidget() == child) {
-                    return wrapper;
-                }
-            }
-        }
-        return null;
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see
-     * com.google.gwt.user.client.ui.UIObject#setStylePrimaryName(java.lang.
-     * String)
-     */
-    @Override
-    public void setStylePrimaryName(String style) {
-        updateStylenames(style);
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see
-     * com.google.gwt.user.client.ui.UIObject#setStyleName(java.lang.String)
-     */
-    @Override
-    public void setStyleName(String style) {
-        super.setStyleName(style);
-        updateStylenames(style);
-        addStyleName(StyleConstants.UI_LAYOUT);
-    }
-
-    /**
-     * Updates all style names contained in the layout
-     * 
-     * @param primaryStyleName
-     *            The style name to use as primary
-     */
-    protected void updateStylenames(String primaryStyleName) {
-        super.setStylePrimaryName(primaryStyleName);
-        canvas.setClassName(getStylePrimaryName() + "-canvas");
-        canvas.setClassName(getStylePrimaryName() + "-margin");
-        for (Widget w : getChildren()) {
-            if (w instanceof AbsoluteWrapper) {
-                AbsoluteWrapper wrapper = (AbsoluteWrapper) w;
-                wrapper.updateStyleNames();
-            }
-        }
-    }
-
-    /**
-     * Performs a vertical layout of the layout. Should be called when a widget
-     * is added or removed
-     */
-    public void layoutVertically() {
-        for (Widget widget : getChildren()) {
-            if (widget instanceof AbsoluteWrapper) {
-                AbsoluteWrapper wrapper = (AbsoluteWrapper) widget;
-
-                /*
-                 * Cleanup old wrappers which have been left empty by other
-                 * inner layouts moving the widget from the wrapper into their
-                 * own hierarchy. This usually happens when a call to
-                 * setWidget(widget) is done in an inner layout which
-                 * automatically detaches the widget from the parent, in this
-                 * case the wrapper, and re-attaches it somewhere else. This has
-                 * to be done in the layout phase since the order of the
-                 * hierarchy events are not defined.
-                 */
-                if (wrapper.getWidget() == null) {
-                    wrapper.destroy();
-                    super.remove(wrapper);
-                    continue;
-                }
-
-                Style wrapperStyle = wrapper.getElement().getStyle();
-                Style widgetStyle = wrapper.getWidget().getElement().getStyle();
-                if (widgetStyle.getHeight() != null
-                        && widgetStyle.getHeight().endsWith("%")) {
-                    int h;
-                    if (wrapper.top != null && wrapper.bottom != null) {
-                        h = wrapper.getOffsetHeight();
-                    } else if (wrapper.bottom != null) {
-                        // top not defined, available space 0... bottom of
-                        // wrapper
-                        h = wrapper.getElement().getOffsetTop()
-                                + wrapper.getOffsetHeight();
-                    } else {
-                        // top defined or both undefined, available space ==
-                        // canvas - top
-                        h = canvas.getOffsetHeight()
-                                - wrapper.getElement().getOffsetTop();
-                    }
-                    wrapperStyle.setHeight(h, Unit.PX);
-                } else {
-                    wrapperStyle.clearHeight();
-                }
-
-                wrapper.updateCaptionPosition();
-            }
-        }
-    }
-
-    /**
-     * Performs an horizontal layout. Should be called when a widget is add or
-     * removed
-     */
-    public void layoutHorizontally() {
-        for (Widget widget : getChildren()) {
-            if (widget instanceof AbsoluteWrapper) {
-                AbsoluteWrapper wrapper = (AbsoluteWrapper) widget;
-
-                /*
-                 * Cleanup old wrappers which have been left empty by other
-                 * inner layouts moving the widget from the wrapper into their
-                 * own hierarchy. This usually happens when a call to
-                 * setWidget(widget) is done in an inner layout which
-                 * automatically detaches the widget from the parent, in this
-                 * case the wrapper, and re-attaches it somewhere else. This has
-                 * to be done in the layout phase since the order of the
-                 * hierarchy events are not defined.
-                 */
-                if (wrapper.getWidget() == null) {
-                    wrapper.destroy();
-                    super.remove(wrapper);
-                    continue;
-                }
-
-                Style wrapperStyle = wrapper.getElement().getStyle();
-                Style widgetStyle = wrapper.getWidget().getElement().getStyle();
-
-                if (widgetStyle.getWidth() != null
-                        && widgetStyle.getWidth().endsWith("%")) {
-                    int w;
-                    if (wrapper.left != null && wrapper.right != null) {
-                        w = wrapper.getOffsetWidth();
-                    } else if (wrapper.right != null) {
-                        // left == null
-                        // available width == right edge == offsetleft + width
-                        w = wrapper.getOffsetWidth()
-                                + wrapper.getElement().getOffsetLeft();
-                    } else {
-                        // left != null && right == null || left == null &&
-                        // right == null
-                        // available width == canvas width - offset left
-                        w = canvas.getOffsetWidth()
-                                - wrapper.getElement().getOffsetLeft();
-                    }
-                    wrapperStyle.setWidth(w, Unit.PX);
-                } else {
-                    wrapperStyle.clearWidth();
-                }
-
-                wrapper.updateCaptionPosition();
-            }
-        }
-    }
-
-    /**
-     * Sets style names for the wrapper wrapping the widget in the layout. The
-     * style names will be prefixed with v-absolutelayout-wrapper.
-     * 
-     * @param widget
-     *            The widget which wrapper we want to add the stylenames to
-     * @param stylenames
-     *            The style names that should be added to the wrapper
-     */
-    public void setWidgetWrapperStyleNames(Widget widget, String... stylenames) {
-        AbsoluteWrapper wrapper = getChildWrapper(widget);
-        if (wrapper == null) {
-            throw new IllegalArgumentException(
-                    "No wrapper for widget found, has the widget been added to the layout?");
-        }
-        wrapper.setWrapperStyleNames(stylenames);
-    }
-
-    /**
-     * Internal wrapper for wrapping widgets in the Absolute layout
-     */
-    protected class AbsoluteWrapper extends SimplePanel {
-        private String css;
-        private String left;
-        private String top;
-        private String right;
-        private String bottom;
-        private String zIndex;
-
-        private VCaption caption;
-        private String[] extraStyleNames;
-
-        /**
-         * Constructor
-         * 
-         * @param child
-         *            The child to wrap
-         */
-        public AbsoluteWrapper(Widget child) {
-            setWidget(child);
-        }
-
-        /**
-         * Get the caption of the wrapper
-         */
-        public VCaption getCaption() {
-            return caption;
-        }
-
-        /**
-         * Set the caption for the wrapper
-         * 
-         * @param caption
-         *            The caption for the wrapper
-         */
-        public void setCaption(VCaption caption) {
-            if (caption != null) {
-                this.caption = caption;
-            } else if (this.caption != null) {
-                this.caption.removeFromParent();
-                this.caption = caption;
-            }
-        }
-
-        /**
-         * Removes the wrapper caption and itself from the layout
-         */
-        public void destroy() {
-            if (caption != null) {
-                caption.removeFromParent();
-            }
-            removeFromParent();
-        }
-
-        /**
-         * Set the position for the wrapper in the layout
-         * 
-         * @param position
-         *            The position string
-         */
-        public void setPosition(String position) {
-            if (css == null || !css.equals(position)) {
-                css = position;
-                top = right = bottom = left = zIndex = null;
-                if (!css.equals("")) {
-                    String[] properties = css.split(";");
-                    for (int i = 0; i < properties.length; i++) {
-                        String[] keyValue = properties[i].split(":");
-                        if (keyValue[0].equals("left")) {
-                            left = keyValue[1];
-                        } else if (keyValue[0].equals("top")) {
-                            top = keyValue[1];
-                        } else if (keyValue[0].equals("right")) {
-                            right = keyValue[1];
-                        } else if (keyValue[0].equals("bottom")) {
-                            bottom = keyValue[1];
-                        } else if (keyValue[0].equals("z-index")) {
-                            zIndex = keyValue[1];
-                        }
-                    }
-                }
-                // ensure ne values
-                Style style = getElement().getStyle();
-                /*
-                 * IE8 dies when nulling zIndex, even in IE7 mode. All other css
-                 * properties (and even in older IE's) accept null values just
-                 * fine. Assign empty string instead of null.
-                 */
-                if (zIndex != null) {
-                    style.setProperty("zIndex", zIndex);
-                } else {
-                    style.setProperty("zIndex", "");
-                }
-                style.setProperty("top", top);
-                style.setProperty("left", left);
-                style.setProperty("right", right);
-                style.setProperty("bottom", bottom);
-
-            }
-            updateCaptionPosition();
-        }
-
-        /**
-         * Updates the caption position by using element offset left and top
-         */
-        private void updateCaptionPosition() {
-            if (caption != null) {
-                Style style = caption.getElement().getStyle();
-                style.setProperty("position", "absolute");
-                style.setPropertyPx("left", getElement().getOffsetLeft());
-                style.setPropertyPx("top", getElement().getOffsetTop()
-                        - caption.getHeight());
-            }
-        }
-
-        /**
-         * Sets the style names of the wrapper. Will be prefixed with the
-         * v-absolutelayout-wrapper prefix
-         * 
-         * @param stylenames
-         *            The wrapper style names
-         */
-        public void setWrapperStyleNames(String... stylenames) {
-            extraStyleNames = stylenames;
-            updateStyleNames();
-        }
-
-        /**
-         * Updates the style names using the primary style name as prefix
-         */
-        protected void updateStyleNames() {
-            setStyleName(VAbsoluteLayout.this.getStylePrimaryName()
-                    + "-wrapper");
-            if (extraStyleNames != null) {
-                for (String stylename : extraStyleNames) {
-                    addStyleDependentName(stylename);
-                }
-            }
-        }
-    }
-}
index ec33ed72fdc3ce79ab39386201238dc79693fbbc..d5ff4f16b154c92be73ce63be3d8f5d9c31c5856 100644 (file)
@@ -22,7 +22,8 @@ import com.vaadin.client.ComponentConnector;
 import com.vaadin.client.ConnectorHierarchyChangeEvent;
 import com.vaadin.client.UIDL;
 import com.vaadin.client.ui.SimpleManagedLayout;
-import com.vaadin.client.ui.accordion.VAccordion.StackItem;
+import com.vaadin.client.ui.VAccordion;
+import com.vaadin.client.ui.VAccordion.StackItem;
 import com.vaadin.client.ui.layout.MayScrollChildren;
 import com.vaadin.client.ui.tabsheet.TabsheetBaseConnector;
 import com.vaadin.shared.ui.Connect;
diff --git a/client/src/com/vaadin/client/ui/accordion/VAccordion.java b/client/src/com/vaadin/client/ui/accordion/VAccordion.java
deleted file mode 100644 (file)
index 70398c2..0000000
+++ /dev/null
@@ -1,562 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.vaadin.client.ui.accordion;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.ComplexPanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ComponentConnector;
-import com.vaadin.client.ConnectorMap;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.Util;
-import com.vaadin.client.VCaption;
-import com.vaadin.client.ui.TouchScrollDelegate;
-import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
-import com.vaadin.client.ui.tabsheet.VTabsheetBase;
-import com.vaadin.shared.ui.tabsheet.TabsheetBaseConstants;
-
-public class VAccordion extends VTabsheetBase {
-
-    public static final String CLASSNAME = "v-accordion";
-
-    private Set<Widget> widgets = new HashSet<Widget>();
-
-    HashMap<StackItem, UIDL> lazyUpdateMap = new HashMap<StackItem, UIDL>();
-
-    StackItem openTab = null;
-
-    int selectedUIDLItemIndex = -1;
-
-    private final TouchScrollHandler touchScrollHandler;
-
-    public VAccordion() {
-        super(CLASSNAME);
-        touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this);
-    }
-
-    @Override
-    protected void renderTab(UIDL tabUidl, int index, boolean selected,
-            boolean hidden) {
-        StackItem item;
-        int itemIndex;
-        if (getWidgetCount() <= index) {
-            // Create stackItem and render caption
-            item = new StackItem(tabUidl);
-            if (getWidgetCount() == 0) {
-                item.addStyleDependentName("first");
-            }
-            itemIndex = getWidgetCount();
-            add(item, getElement());
-        } else {
-            item = getStackItem(index);
-            item = moveStackItemIfNeeded(item, index, tabUidl);
-            itemIndex = index;
-        }
-        item.updateCaption(tabUidl);
-
-        item.setVisible(!hidden);
-
-        if (selected) {
-            selectedUIDLItemIndex = itemIndex;
-        }
-
-        if (tabUidl.getChildCount() > 0) {
-            lazyUpdateMap.put(item, tabUidl.getChildUIDL(0));
-        }
-    }
-
-    @Override
-    public void setStylePrimaryName(String style) {
-        super.setStylePrimaryName(style);
-        updateStyleNames(style);
-    }
-
-    @Override
-    public void setStyleName(String style) {
-        super.setStyleName(style);
-        updateStyleNames(style);
-    }
-
-    protected void updateStyleNames(String primaryStyleName) {
-        for (Widget w : getChildren()) {
-            if (w instanceof StackItem) {
-                StackItem item = (StackItem) w;
-                item.updateStyleNames(primaryStyleName);
-            }
-        }
-    }
-
-    /**
-     * This method tries to find out if a tab has been rendered with a different
-     * index previously. If this is the case it re-orders the children so the
-     * same StackItem is used for rendering this time. E.g. if the first tab has
-     * been removed all tabs which contain cached content must be moved 1 step
-     * up to preserve the cached content.
-     * 
-     * @param item
-     * @param newIndex
-     * @param tabUidl
-     * @return
-     */
-    private StackItem moveStackItemIfNeeded(StackItem item, int newIndex,
-            UIDL tabUidl) {
-        UIDL tabContentUIDL = null;
-        ComponentConnector tabContent = null;
-        if (tabUidl.getChildCount() > 0) {
-            tabContentUIDL = tabUidl.getChildUIDL(0);
-            tabContent = client.getPaintable(tabContentUIDL);
-        }
-
-        Widget itemWidget = item.getComponent();
-        if (tabContent != null) {
-            if (tabContent.getWidget() != itemWidget) {
-                /*
-                 * This is not the same widget as before, find out if it has
-                 * been moved
-                 */
-                int oldIndex = -1;
-                StackItem oldItem = null;
-                for (int i = 0; i < getWidgetCount(); i++) {
-                    Widget w = getWidget(i);
-                    oldItem = (StackItem) w;
-                    if (tabContent == oldItem.getComponent()) {
-                        oldIndex = i;
-                        break;
-                    }
-                }
-
-                if (oldIndex != -1 && oldIndex > newIndex) {
-                    /*
-                     * The tab has previously been rendered in another position
-                     * so we must move the cached content to correct position.
-                     * We move only items with oldIndex > newIndex to prevent
-                     * moving items already rendered in this update. If for
-                     * instance tabs 1,2,3 are removed and added as 3,2,1 we
-                     * cannot re-use "1" when we get to the third tab.
-                     */
-                    insert(oldItem, getElement(), newIndex, true);
-                    return oldItem;
-                }
-            }
-        } else {
-            // Tab which has never been loaded. Must assure we use an empty
-            // StackItem
-            Widget oldWidget = item.getComponent();
-            if (oldWidget != null) {
-                oldWidget.removeFromParent();
-            }
-        }
-        return item;
-    }
-
-    void open(int itemIndex) {
-        StackItem item = (StackItem) getWidget(itemIndex);
-        boolean alreadyOpen = false;
-        if (openTab != null) {
-            if (openTab.isOpen()) {
-                if (openTab == item) {
-                    alreadyOpen = true;
-                } else {
-                    openTab.close();
-                }
-            }
-        }
-        if (!alreadyOpen) {
-            item.open();
-            activeTabIndex = itemIndex;
-            openTab = item;
-        }
-
-        // Update the size for the open tab
-        updateOpenTabSize();
-    }
-
-    void close(StackItem item) {
-        if (!item.isOpen()) {
-            return;
-        }
-
-        item.close();
-        activeTabIndex = -1;
-        openTab = null;
-
-    }
-
-    @Override
-    protected void selectTab(final int index, final UIDL contentUidl) {
-        StackItem item = getStackItem(index);
-        if (index != activeTabIndex) {
-            open(index);
-            iLayout();
-            // TODO Check if this is needed
-            client.runDescendentsLayout(this);
-
-        }
-        item.setContent(contentUidl);
-    }
-
-    public void onSelectTab(StackItem item) {
-        final int index = getWidgetIndex(item);
-        if (index != activeTabIndex && !disabled && !readonly
-                && !disabledTabKeys.contains(tabKeys.get(index))) {
-            addStyleDependentName("loading");
-            client.updateVariable(id, "selected", "" + tabKeys.get(index), true);
-        }
-    }
-
-    /**
-     * Sets the size of the open tab
-     */
-    void updateOpenTabSize() {
-        if (openTab == null) {
-            return;
-        }
-
-        // WIDTH
-        if (!isDynamicWidth()) {
-            openTab.setWidth("100%");
-        } else {
-            openTab.setWidth(null);
-        }
-
-        // HEIGHT
-        if (!isDynamicHeight()) {
-            int usedPixels = 0;
-            for (Widget w : getChildren()) {
-                StackItem item = (StackItem) w;
-                if (item == openTab) {
-                    usedPixels += item.getCaptionHeight();
-                } else {
-                    // This includes the captionNode borders
-                    usedPixels += item.getHeight();
-                }
-            }
-
-            int offsetHeight = getOffsetHeight();
-
-            int spaceForOpenItem = offsetHeight - usedPixels;
-
-            if (spaceForOpenItem < 0) {
-                spaceForOpenItem = 0;
-            }
-
-            openTab.setHeight(spaceForOpenItem);
-        } else {
-            openTab.setHeightFromWidget();
-
-        }
-
-    }
-
-    public void iLayout() {
-        if (openTab == null) {
-            return;
-        }
-
-        if (isDynamicWidth()) {
-            int maxWidth = 40;
-            for (Widget w : getChildren()) {
-                StackItem si = (StackItem) w;
-                int captionWidth = si.getCaptionWidth();
-                if (captionWidth > maxWidth) {
-                    maxWidth = captionWidth;
-                }
-            }
-            int widgetWidth = openTab.getWidgetWidth();
-            if (widgetWidth > maxWidth) {
-                maxWidth = widgetWidth;
-            }
-            super.setWidth(maxWidth + "px");
-            openTab.setWidth(maxWidth);
-        }
-    }
-
-    /**
-     * A StackItem has always two children, Child 0 is a VCaption, Child 1 is
-     * the actual child widget.
-     */
-    protected class StackItem extends ComplexPanel implements ClickHandler {
-
-        public void setHeight(int height) {
-            if (height == -1) {
-                super.setHeight("");
-                DOM.setStyleAttribute(content, "height", "0px");
-            } else {
-                super.setHeight((height + getCaptionHeight()) + "px");
-                DOM.setStyleAttribute(content, "height", height + "px");
-                DOM.setStyleAttribute(content, "top", getCaptionHeight() + "px");
-
-            }
-        }
-
-        public Widget getComponent() {
-            if (getWidgetCount() < 2) {
-                return null;
-            }
-            return getWidget(1);
-        }
-
-        @Override
-        public void setVisible(boolean visible) {
-            super.setVisible(visible);
-        }
-
-        public void setHeightFromWidget() {
-            Widget widget = getChildWidget();
-            if (widget == null) {
-                return;
-            }
-
-            int paintableHeight = widget.getElement().getOffsetHeight();
-            setHeight(paintableHeight);
-
-        }
-
-        /**
-         * Returns caption width including padding
-         * 
-         * @return
-         */
-        public int getCaptionWidth() {
-            if (caption == null) {
-                return 0;
-            }
-
-            int captionWidth = caption.getRequiredWidth();
-            int padding = Util.measureHorizontalPaddingAndBorder(
-                    caption.getElement(), 18);
-            return captionWidth + padding;
-        }
-
-        public void setWidth(int width) {
-            if (width == -1) {
-                super.setWidth("");
-            } else {
-                super.setWidth(width + "px");
-            }
-        }
-
-        public int getHeight() {
-            return getOffsetHeight();
-        }
-
-        public int getCaptionHeight() {
-            return captionNode.getOffsetHeight();
-        }
-
-        private VCaption caption;
-        private boolean open = false;
-        private Element content = DOM.createDiv();
-        private Element captionNode = DOM.createDiv();
-
-        public StackItem(UIDL tabUidl) {
-            setElement(DOM.createDiv());
-            caption = new VCaption(client);
-            caption.addClickHandler(this);
-            super.add(caption, captionNode);
-            DOM.appendChild(captionNode, caption.getElement());
-            DOM.appendChild(getElement(), captionNode);
-            DOM.appendChild(getElement(), content);
-
-            updateStyleNames(VAccordion.this.getStylePrimaryName());
-
-            touchScrollHandler.addElement(getContainerElement());
-
-            close();
-        }
-
-        private void updateStyleNames(String primaryStyleName) {
-            content.removeClassName(getStylePrimaryName() + "-content");
-            captionNode.removeClassName(getStylePrimaryName() + "-caption");
-
-            setStylePrimaryName(primaryStyleName + "-item");
-
-            captionNode.addClassName(getStylePrimaryName() + "-caption");
-            content.addClassName(getStylePrimaryName() + "-content");
-        }
-
-        @Override
-        public void onBrowserEvent(Event event) {
-            onSelectTab(this);
-        }
-
-        public Element getContainerElement() {
-            return content;
-        }
-
-        public Widget getChildWidget() {
-            if (getWidgetCount() > 1) {
-                return getWidget(1);
-            } else {
-                return null;
-            }
-        }
-
-        public void replaceWidget(Widget newWidget) {
-            if (getWidgetCount() > 1) {
-                Widget oldWidget = getWidget(1);
-                ComponentConnector oldPaintable = ConnectorMap.get(client)
-                        .getConnector(oldWidget);
-                ConnectorMap.get(client).unregisterConnector(oldPaintable);
-                widgets.remove(oldWidget);
-                remove(1);
-            }
-            add(newWidget, content);
-            widgets.add(newWidget);
-        }
-
-        public void open() {
-            open = true;
-            DOM.setStyleAttribute(content, "top", getCaptionHeight() + "px");
-            DOM.setStyleAttribute(content, "left", "0px");
-            DOM.setStyleAttribute(content, "visibility", "");
-            addStyleDependentName("open");
-        }
-
-        public void hide() {
-            DOM.setStyleAttribute(content, "visibility", "hidden");
-        }
-
-        public void close() {
-            DOM.setStyleAttribute(content, "visibility", "hidden");
-            DOM.setStyleAttribute(content, "top", "-100000px");
-            DOM.setStyleAttribute(content, "left", "-100000px");
-            removeStyleDependentName("open");
-            setHeight(-1);
-            setWidth("");
-            open = false;
-        }
-
-        public boolean isOpen() {
-            return open;
-        }
-
-        public void setContent(UIDL contentUidl) {
-            final ComponentConnector newPntbl = client
-                    .getPaintable(contentUidl);
-            Widget newWidget = newPntbl.getWidget();
-            if (getChildWidget() == null) {
-                add(newWidget, content);
-                widgets.add(newWidget);
-            } else if (getChildWidget() != newWidget) {
-                replaceWidget(newWidget);
-            }
-            if (contentUidl.getBooleanAttribute("cached")) {
-                /*
-                 * The size of a cached, relative sized component must be
-                 * updated to report correct size.
-                 */
-                client.handleComponentRelativeSize(newPntbl.getWidget());
-            }
-            if (isOpen() && isDynamicHeight()) {
-                setHeightFromWidget();
-            }
-        }
-
-        @Override
-        public void onClick(ClickEvent event) {
-            onSelectTab(this);
-        }
-
-        public void updateCaption(UIDL uidl) {
-            // TODO need to call this because the caption does not have an owner
-            caption.updateCaptionWithoutOwner(
-                    uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_CAPTION),
-                    uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DISABLED),
-                    uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION),
-                    uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ERROR_MESSAGE),
-                    uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ICON));
-        }
-
-        public int getWidgetWidth() {
-            return DOM.getFirstChild(content).getOffsetWidth();
-        }
-
-        public boolean contains(ComponentConnector p) {
-            return (getChildWidget() == p.getWidget());
-        }
-
-        public boolean isCaptionVisible() {
-            return caption.isVisible();
-        }
-
-    }
-
-    @Override
-    protected void clearPaintables() {
-        clear();
-    }
-
-    boolean isDynamicWidth() {
-        ComponentConnector paintable = ConnectorMap.get(client).getConnector(
-                this);
-        return paintable.isUndefinedWidth();
-    }
-
-    boolean isDynamicHeight() {
-        ComponentConnector paintable = ConnectorMap.get(client).getConnector(
-                this);
-        return paintable.isUndefinedHeight();
-    }
-
-    @Override
-    @SuppressWarnings("unchecked")
-    protected Iterator<Widget> getWidgetIterator() {
-        return widgets.iterator();
-    }
-
-    @Override
-    protected int getTabCount() {
-        return getWidgetCount();
-    }
-
-    @Override
-    protected void removeTab(int index) {
-        StackItem item = getStackItem(index);
-        remove(item);
-        touchScrollHandler.removeElement(item.getContainerElement());
-    }
-
-    @Override
-    protected ComponentConnector getTab(int index) {
-        if (index < getWidgetCount()) {
-            StackItem stackItem = getStackItem(index);
-            if (stackItem == null) {
-                return null;
-            }
-            Widget w = stackItem.getChildWidget();
-            if (w != null) {
-                return ConnectorMap.get(client).getConnector(w);
-            }
-        }
-
-        return null;
-    }
-
-    StackItem getStackItem(int index) {
-        return (StackItem) getWidget(index);
-    }
-
-}
index bfa282ca09ba875db6ea7d5376f79196a7c28c48..55b154b2c20b1eb3ef4b6e1af06061c1f0f2d3de 100644 (file)
@@ -22,6 +22,7 @@ import com.google.gwt.user.client.ui.Widget;
 import com.vaadin.client.BrowserInfo;
 import com.vaadin.client.communication.StateChangeEvent;
 import com.vaadin.client.ui.MediaBaseConnector;
+import com.vaadin.client.ui.VAudio;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.ui.Audio;
 
diff --git a/client/src/com/vaadin/client/ui/audio/VAudio.java b/client/src/com/vaadin/client/ui/audio/VAudio.java
deleted file mode 100644 (file)
index 8b2a915..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.audio;
-
-import com.google.gwt.dom.client.AudioElement;
-import com.google.gwt.dom.client.Document;
-import com.vaadin.client.ui.VMediaBase;
-
-public class VAudio extends VMediaBase {
-    private static String CLASSNAME = "v-audio";
-
-    private AudioElement audio;
-
-    public VAudio() {
-        audio = Document.get().createAudioElement();
-        setMediaElement(audio);
-        setStyleName(CLASSNAME);
-    }
-
-}
index c22d92235ba33339b3f190f3833d7dd65030c596..09d4fc337d5f345f6c587b1b5cad3f06d19e887d 100644 (file)
@@ -2,6 +2,7 @@ package com.vaadin.client.ui.browserframe;
 
 import com.vaadin.client.communication.StateChangeEvent;
 import com.vaadin.client.ui.AbstractComponentConnector;
+import com.vaadin.client.ui.VBrowserFrame;
 import com.vaadin.shared.ui.AbstractEmbeddedState;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.browserframe.BrowserFrameState;
diff --git a/client/src/com/vaadin/client/ui/browserframe/VBrowserFrame.java b/client/src/com/vaadin/client/ui/browserframe/VBrowserFrame.java
deleted file mode 100644 (file)
index aac8151..0000000
+++ /dev/null
@@ -1,122 +0,0 @@
-package com.vaadin.client.ui.browserframe;
-
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.dom.client.IFrameElement;
-import com.google.gwt.user.client.ui.Widget;
-
-public class VBrowserFrame extends Widget {
-
-    protected IFrameElement iframe;
-    protected Element altElement;
-    protected String altText;
-
-    public static final String CLASSNAME = "v-browserframe";
-
-    public VBrowserFrame() {
-        Element root = Document.get().createDivElement();
-        setElement(root);
-
-        setStyleName(CLASSNAME);
-
-        createAltTextElement();
-    }
-
-    /**
-     * Always creates new iframe inside widget. Will replace previous iframe.
-     * 
-     * @return
-     */
-    protected IFrameElement createIFrameElement(String src) {
-        String name = null;
-
-        // Remove alt text
-        if (altElement != null) {
-            getElement().removeChild(altElement);
-            altElement = null;
-        }
-
-        // Remove old iframe
-        if (iframe != null) {
-            name = iframe.getAttribute("name");
-            getElement().removeChild(iframe);
-            iframe = null;
-        }
-
-        iframe = Document.get().createIFrameElement();
-        iframe.setSrc(src);
-        iframe.setFrameBorder(0);
-        iframe.setAttribute("width", "100%");
-        iframe.setAttribute("height", "100%");
-        iframe.setAttribute("allowTransparency", "true");
-
-        getElement().appendChild(iframe);
-
-        // Reset old attributes (except src)
-        if (name != null) {
-            iframe.setName(name);
-        }
-
-        return iframe;
-    }
-
-    protected void createAltTextElement() {
-        if (iframe != null) {
-            return;
-        }
-
-        if (altElement == null) {
-            altElement = Document.get().createSpanElement();
-            getElement().appendChild(altElement);
-        }
-
-        if (altText != null) {
-            altElement.setInnerText(altText);
-        } else {
-            altElement.setInnerText("");
-        }
-    }
-
-    public void setAlternateText(String altText) {
-        if (this.altText != altText) {
-            this.altText = altText;
-            if (altElement != null) {
-                if (altText != null) {
-                    altElement.setInnerText(altText);
-                } else {
-                    altElement.setInnerText("");
-                }
-            }
-        }
-    }
-
-    /**
-     * Set the source (the "src" attribute) of iframe. Will replace old iframe
-     * with new.
-     * 
-     * @param source
-     *            Source of iframe.
-     */
-    public void setSource(String source) {
-
-        if (source == null) {
-            if (iframe != null) {
-                getElement().removeChild(iframe);
-                iframe = null;
-            }
-            createAltTextElement();
-            setAlternateText(altText);
-            return;
-        }
-
-        if (iframe == null || iframe.getSrc() != source) {
-            createIFrameElement(source);
-        }
-    }
-
-    public void setName(String name) {
-        if (iframe != null) {
-            iframe.setName(name);
-        }
-    }
-}
index bc1e7b0e69dc5fbf07016a5e2917bbac2c0080b0..f84659ece156279c2047a908e0427db83eea1f27 100644 (file)
@@ -34,6 +34,7 @@ import com.vaadin.client.ui.AbstractComponentConnector;
 import com.vaadin.client.ui.Icon;
 import com.vaadin.client.ui.ShortcutAction;
 import com.vaadin.client.ui.ShortcutActionTarget;
+import com.vaadin.client.ui.VButton;
 import com.vaadin.shared.MouseEventDetails;
 import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc;
 import com.vaadin.shared.ui.Connect;
diff --git a/client/src/com/vaadin/client/ui/button/VButton.java b/client/src/com/vaadin/client/ui/button/VButton.java
deleted file mode 100644 (file)
index 52987b0..0000000
+++ /dev/null
@@ -1,438 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.button;
-
-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.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.shared.HandlerRegistration;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.Accessibility;
-import com.google.gwt.user.client.ui.FocusWidget;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.Icon;
-
-public class VButton extends FocusWidget implements ClickHandler {
-
-    public static final String CLASSNAME = "v-button";
-    private static final String CLASSNAME_PRESSED = "v-pressed";
-
-    // mouse movement is checked before synthesizing click event on mouseout
-    protected static int MOVE_THRESHOLD = 3;
-    protected int mousedownX = 0;
-    protected int mousedownY = 0;
-
-    protected ApplicationConnection client;
-
-    protected final Element wrapper = DOM.createSpan();
-
-    protected Element errorIndicatorElement;
-
-    protected final Element captionElement = DOM.createSpan();
-
-    protected Icon icon;
-
-    /**
-     * Helper flag to handle special-case where the button is moved from under
-     * mouse while clicking it. In this case mouse leaves the button without
-     * moving.
-     */
-    protected boolean clickPending;
-
-    private boolean enabled = true;
-
-    private int tabIndex = 0;
-
-    /*
-     * BELOW PRIVATE MEMBERS COPY-PASTED FROM GWT CustomButton
-     */
-
-    /**
-     * If <code>true</code>, this widget is capturing with the mouse held down.
-     */
-    private boolean isCapturing;
-
-    /**
-     * If <code>true</code>, this widget has focus with the space bar down. This
-     * means that we will get events when the button is released, but we should
-     * trigger the button only if the button is still focused at that point.
-     */
-    private boolean isFocusing;
-
-    /**
-     * Used to decide whether to allow clicks to propagate up to the superclass
-     * or container elements.
-     */
-    private boolean disallowNextClick = false;
-    private boolean isHovering;
-
-    protected int clickShortcut = 0;
-
-    private HandlerRegistration focusHandlerRegistration;
-    private HandlerRegistration blurHandlerRegistration;
-
-    public VButton() {
-        super(DOM.createDiv());
-        setTabIndex(0);
-        sinkEvents(Event.ONCLICK | Event.MOUSEEVENTS | Event.FOCUSEVENTS
-                | Event.KEYEVENTS);
-
-        // Add a11y role "button"
-        Accessibility.setRole(getElement(), Accessibility.ROLE_BUTTON);
-
-        getElement().appendChild(wrapper);
-        wrapper.appendChild(captionElement);
-
-        setStyleName(CLASSNAME);
-
-        addClickHandler(this);
-    }
-
-    @Override
-    public void setStyleName(String style) {
-        super.setStyleName(style);
-        wrapper.setClassName(getStylePrimaryName() + "-wrap");
-        captionElement.setClassName(getStylePrimaryName() + "-caption");
-    }
-
-    @Override
-    public void setStylePrimaryName(String style) {
-        super.setStylePrimaryName(style);
-        wrapper.setClassName(getStylePrimaryName() + "-wrap");
-        captionElement.setClassName(getStylePrimaryName() + "-caption");
-    }
-
-    public void setText(String text) {
-        captionElement.setInnerText(text);
-    }
-
-    public void setHtml(String html) {
-        captionElement.setInnerHTML(html);
-    }
-
-    @SuppressWarnings("deprecation")
-    @Override
-    /*
-     * Copy-pasted from GWT CustomButton, some minor modifications done:
-     * 
-     * -for IE/Opera added CLASSNAME_PRESSED
-     * 
-     * -event.preventDefault() commented from ONMOUSEDOWN (Firefox won't apply
-     * :active styles if it is present)
-     * 
-     * -Tooltip event handling added
-     * 
-     * -onload event handler added (for icon handling)
-     */
-    public void onBrowserEvent(Event event) {
-        if (DOM.eventGetType(event) == Event.ONLOAD) {
-            Util.notifyParentOfSizeChange(this, true);
-        }
-        // Should not act on button if disabled.
-        if (!isEnabled()) {
-            // This can happen when events are bubbled up from non-disabled
-            // children
-            return;
-        }
-
-        int type = DOM.eventGetType(event);
-        switch (type) {
-        case Event.ONCLICK:
-            // If clicks are currently disallowed, keep it from bubbling or
-            // being passed to the superclass.
-            if (disallowNextClick) {
-                event.stopPropagation();
-                disallowNextClick = false;
-                return;
-            }
-            break;
-        case Event.ONMOUSEDOWN:
-            if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) {
-                // This was moved from mouseover, which iOS sometimes skips.
-                // We're certainly hovering at this point, and we don't actually
-                // need that information before this point.
-                setHovering(true);
-            }
-            if (event.getButton() == Event.BUTTON_LEFT) {
-                // save mouse position to detect movement before synthesizing
-                // event later
-                mousedownX = event.getClientX();
-                mousedownY = event.getClientY();
-
-                disallowNextClick = true;
-                clickPending = true;
-                setFocus(true);
-                DOM.setCapture(getElement());
-                isCapturing = true;
-                // Prevent dragging (on some browsers);
-                // DOM.eventPreventDefault(event);
-                if (BrowserInfo.get().isIE() || BrowserInfo.get().isOpera()) {
-                    addStyleName(CLASSNAME_PRESSED);
-                }
-            }
-            break;
-        case Event.ONMOUSEUP:
-            if (isCapturing) {
-                isCapturing = false;
-                DOM.releaseCapture(getElement());
-                if (isHovering() && event.getButton() == Event.BUTTON_LEFT) {
-                    // Click ok
-                    disallowNextClick = false;
-                }
-                if (BrowserInfo.get().isIE() || BrowserInfo.get().isOpera()) {
-                    removeStyleName(CLASSNAME_PRESSED);
-                }
-                // Explicitly prevent IE 8 from propagating mouseup events
-                // upward (fixes #6753)
-                if (BrowserInfo.get().isIE8()) {
-                    event.stopPropagation();
-                }
-            }
-            break;
-        case Event.ONMOUSEMOVE:
-            clickPending = false;
-            if (isCapturing) {
-                // Prevent dragging (on other browsers);
-                DOM.eventPreventDefault(event);
-            }
-            break;
-        case Event.ONMOUSEOUT:
-            Element to = event.getRelatedTarget();
-            if (getElement().isOrHasChild(DOM.eventGetTarget(event))
-                    && (to == null || !getElement().isOrHasChild(to))) {
-                if (clickPending
-                        && Math.abs(mousedownX - event.getClientX()) < MOVE_THRESHOLD
-                        && Math.abs(mousedownY - event.getClientY()) < MOVE_THRESHOLD) {
-                    onClick();
-                    break;
-                }
-                clickPending = false;
-                if (isCapturing) {
-                }
-                setHovering(false);
-                if (BrowserInfo.get().isIE() || BrowserInfo.get().isOpera()) {
-                    removeStyleName(CLASSNAME_PRESSED);
-                }
-            }
-            break;
-        case Event.ONBLUR:
-            if (isFocusing) {
-                isFocusing = false;
-            }
-            break;
-        case Event.ONLOSECAPTURE:
-            if (isCapturing) {
-                isCapturing = false;
-            }
-            break;
-        }
-
-        super.onBrowserEvent(event);
-
-        // Synthesize clicks based on keyboard events AFTER the normal key
-        // handling.
-        if ((event.getTypeInt() & Event.KEYEVENTS) != 0) {
-            switch (type) {
-            case Event.ONKEYDOWN:
-                // Stop propagation when the user starts pressing a button that
-                // we are handling to prevent actions from getting triggered
-                if (event.getKeyCode() == 32 /* space */) {
-                    isFocusing = true;
-                    event.preventDefault();
-                    event.stopPropagation();
-                } else if (event.getKeyCode() == KeyCodes.KEY_ENTER) {
-                    event.stopPropagation();
-                }
-                break;
-            case Event.ONKEYUP:
-                if (isFocusing && event.getKeyCode() == 32 /* space */) {
-                    isFocusing = false;
-                    onClick();
-                    event.stopPropagation();
-                    event.preventDefault();
-                }
-                break;
-            case Event.ONKEYPRESS:
-                if (event.getKeyCode() == KeyCodes.KEY_ENTER) {
-                    onClick();
-                    event.stopPropagation();
-                    event.preventDefault();
-                }
-                break;
-            }
-        }
-    }
-
-    final void setHovering(boolean hovering) {
-        if (hovering != isHovering()) {
-            isHovering = hovering;
-        }
-    }
-
-    final boolean isHovering() {
-        return isHovering;
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see
-     * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event
-     * .dom.client.ClickEvent)
-     */
-    @Override
-    public void onClick(ClickEvent event) {
-        if (BrowserInfo.get().isSafari()) {
-            VButton.this.setFocus(true);
-        }
-
-        clickPending = false;
-    }
-
-    /*
-     * ALL BELOW COPY-PASTED FROM GWT CustomButton
-     */
-
-    /**
-     * Called internally when the user finishes clicking on this button. The
-     * default behavior is to fire the click event to listeners. Subclasses that
-     * override {@link #onClickStart()} should override this method to restore
-     * the normal widget display.
-     * <p>
-     * To add custom code for a click event, override
-     * {@link #onClick(ClickEvent)} instead of this.
-     */
-    protected void onClick() {
-        // Allow the click we're about to synthesize to pass through to the
-        // superclass and containing elements. Element.dispatchEvent() is
-        // synchronous, so we simply set and clear the flag within this method.
-
-        disallowNextClick = false;
-
-        // Mouse coordinates are not always available (e.g., when the click is
-        // caused by a keyboard event).
-        NativeEvent evt = Document.get().createClickEvent(1, 0, 0, 0, 0, false,
-                false, false, false);
-        getElement().dispatchEvent(evt);
-    }
-
-    /**
-     * Sets whether this button is enabled.
-     * 
-     * @param enabled
-     *            <code>true</code> to enable the button, <code>false</code> to
-     *            disable it
-     */
-
-    @Override
-    public final void setEnabled(boolean enabled) {
-        if (isEnabled() != enabled) {
-            this.enabled = enabled;
-            if (!enabled) {
-                cleanupCaptureState();
-                Accessibility.removeState(getElement(),
-                        Accessibility.STATE_PRESSED);
-                super.setTabIndex(-1);
-            } else {
-                Accessibility.setState(getElement(),
-                        Accessibility.STATE_PRESSED, "false");
-                super.setTabIndex(tabIndex);
-            }
-        }
-    }
-
-    @Override
-    public final boolean isEnabled() {
-        return enabled;
-    }
-
-    @Override
-    public final void setTabIndex(int index) {
-        super.setTabIndex(index);
-        tabIndex = index;
-    }
-
-    /**
-     * Resets internal state if this button can no longer service events. This
-     * can occur when the widget becomes detached or disabled.
-     */
-    private void cleanupCaptureState() {
-        if (isCapturing || isFocusing) {
-            DOM.releaseCapture(getElement());
-            isCapturing = false;
-            isFocusing = false;
-        }
-    }
-
-    private static native int getHorizontalBorderAndPaddingWidth(Element elem)
-    /*-{
-        // THIS METHOD IS ONLY USED FOR INTERNET EXPLORER, IT DOESN'T WORK WITH OTHERS
-       
-       var convertToPixel = function(elem, value) {
-           // From the awesome hack by Dean Edwards
-            // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
-
-            // Remember the original values
-            var left = elem.style.left, rsLeft = elem.runtimeStyle.left;
-
-            // Put in the new values to get a computed value out
-            elem.runtimeStyle.left = elem.currentStyle.left;
-            elem.style.left = value || 0;
-            var ret = elem.style.pixelLeft;
-
-            // Revert the changed values
-            elem.style.left = left;
-            elem.runtimeStyle.left = rsLeft;
-            
-            return ret;
-       }
-       
-       var ret = 0;
-
-        var sides = ["Right","Left"];
-        for(var i=0; i<2; i++) {
-            var side = sides[i];
-            var value;
-            // Border -------------------------------------------------------
-            if(elem.currentStyle["border"+side+"Style"] != "none") {
-                value = elem.currentStyle["border"+side+"Width"];
-                if ( !/^\d+(px)?$/i.test( value ) && /^\d/.test( value ) ) {
-                    ret += convertToPixel(elem, value);
-                } else if(value.length > 2) {
-                    ret += parseInt(value.substr(0, value.length-2));
-                }
-            }
-                    
-            // Padding -------------------------------------------------------
-            value = elem.currentStyle["padding"+side];
-            if ( !/^\d+(px)?$/i.test( value ) && /^\d/.test( value ) ) {
-                ret += convertToPixel(elem, value);
-            } else if(value.length > 2) {
-                ret += parseInt(value.substr(0, value.length-2));
-            }
-        }
-
-       return ret;
-    }-*/;
-
-}
index cf771bed0d923bdae1ddce57d47859dd49c8cec7..46f6dcbb658f568c09e2ad140b228fd9fe48dbde 100644 (file)
@@ -30,6 +30,7 @@ import com.vaadin.client.VTooltip;
 import com.vaadin.client.communication.StateChangeEvent;
 import com.vaadin.client.ui.AbstractFieldConnector;
 import com.vaadin.client.ui.Icon;
+import com.vaadin.client.ui.VCheckBox;
 import com.vaadin.shared.MouseEventDetails;
 import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc;
 import com.vaadin.shared.ui.Connect;
diff --git a/client/src/com/vaadin/client/ui/checkbox/VCheckBox.java b/client/src/com/vaadin/client/ui/checkbox/VCheckBox.java
deleted file mode 100644 (file)
index e78251c..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.checkbox;
-
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.Util;
-import com.vaadin.client.VTooltip;
-import com.vaadin.client.ui.Field;
-import com.vaadin.client.ui.Icon;
-
-public class VCheckBox extends com.google.gwt.user.client.ui.CheckBox implements
-        Field {
-
-    public static final String CLASSNAME = "v-checkbox";
-
-    String id;
-
-    boolean immediate;
-
-    ApplicationConnection client;
-
-    Element errorIndicatorElement;
-
-    Icon icon;
-
-    public VCheckBox() {
-        setStyleName(CLASSNAME);
-
-        Element el = DOM.getFirstChild(getElement());
-        while (el != null) {
-            DOM.sinkEvents(el,
-                    (DOM.getEventsSunk(el) | VTooltip.TOOLTIP_EVENTS));
-            el = DOM.getNextSibling(el);
-        }
-    }
-
-    @Override
-    public void onBrowserEvent(Event event) {
-        if (icon != null && (event.getTypeInt() == Event.ONCLICK)
-                && (DOM.eventGetTarget(event) == icon.getElement())) {
-            // Click on icon should do nothing if widget is disabled
-            if (isEnabled()) {
-                setValue(!getValue());
-            }
-        }
-        super.onBrowserEvent(event);
-        if (event.getTypeInt() == Event.ONLOAD) {
-            Util.notifyParentOfSizeChange(this, true);
-        }
-    }
-
-}
index 697ade43e799256c063780a84381f4e6446ea245..5f9a3ded58651cfcab5f4cbce3da2e34b33fc1b9 100644 (file)
@@ -23,7 +23,8 @@ import com.vaadin.client.UIDL;
 import com.vaadin.client.Util;
 import com.vaadin.client.ui.AbstractFieldConnector;
 import com.vaadin.client.ui.SimpleManagedLayout;
-import com.vaadin.client.ui.combobox.VFilterSelect.FilterSelectSuggestion;
+import com.vaadin.client.ui.VFilterSelect;
+import com.vaadin.client.ui.VFilterSelect.FilterSelectSuggestion;
 import com.vaadin.client.ui.menubar.MenuItem;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.combobox.ComboBoxConstants;
diff --git a/client/src/com/vaadin/client/ui/combobox/VFilterSelect.java b/client/src/com/vaadin/client/ui/combobox/VFilterSelect.java
deleted file mode 100644 (file)
index 31b240a..0000000
+++ /dev/null
@@ -1,1765 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.combobox;
-
-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.core.client.Scheduler;
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
-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.user.client.Command;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-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.Image;
-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.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.ComponentConnector;
-import com.vaadin.client.ConnectorMap;
-import com.vaadin.client.Focusable;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.Util;
-import com.vaadin.client.VConsole;
-import com.vaadin.client.ui.Field;
-import com.vaadin.client.ui.SubPartAware;
-import com.vaadin.client.ui.VLazyExecutor;
-import com.vaadin.client.ui.VOverlay;
-import com.vaadin.client.ui.menubar.MenuBar;
-import com.vaadin.client.ui.menubar.MenuItem;
-import com.vaadin.shared.ComponentState;
-import com.vaadin.shared.EventId;
-import com.vaadin.shared.ui.ComponentStateUtil;
-import com.vaadin.shared.ui.combobox.FilteringMode;
-
-/**
- * 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 {
-
-    /**
-     * Represents a suggestion in the suggestion popup box
-     */
-    public class FilterSelectSuggestion implements Suggestion, Command {
-
-        private final String key;
-        private final String caption;
-        private String iconUri;
-
-        /**
-         * Constructor
-         * 
-         * @param uidl
-         *            The UIDL recieved from the server
-         */
-        public FilterSelectSuggestion(UIDL uidl) {
-            key = uidl.getStringAttribute("key");
-            caption = uidl.getStringAttribute("caption");
-            if (uidl.hasAttribute("icon")) {
-                iconUri = client.translateVaadinUri(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();
-            if (iconUri != null) {
-                sb.append("<img src=\"");
-                sb.append(Util.escapeAttribute(iconUri));
-                sb.append("\" alt=\"\" class=\"v-icon\" />");
-            }
-            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 = Util.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 int getOptionKey() {
-            return Integer.parseInt(key);
-        }
-
-        /**
-         * Get the URI of the icon. Used when constructing the displayed option.
-         * 
-         * @return
-         */
-        public String getIconUri() {
-            return iconUri;
-        }
-
-        /**
-         * Executes a selection of this item.
-         */
-
-        @Override
-        public void execute() {
-            onSuggestionSelected(this);
-        }
-    }
-
-    /**
-     * 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;
-
-        protected 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;
-
-        /**
-         * Default constructor
-         */
-        SuggestionPopup() {
-            super(true, false, true);
-            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);
-        }
-
-        /**
-         * 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) {
-
-            /*
-             * 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
-             */
-            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);
-                }
-            });
-        }
-
-        /**
-         * Should the next page button be visible to the user?
-         * 
-         * @param active
-         */
-        private void setNextButtonActive(boolean 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 (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() {
-            final MenuItem cur = menu.getSelectedItem();
-            final int index = 1 + menu.getItems().indexOf(cur);
-            if (menu.getItems().size() > index) {
-                final MenuItem newSelectedItem = menu.getItems().get(index);
-                menu.selectItem(newSelectedItem);
-                tb.setText(newSelectedItem.getText());
-                tb.setSelectionRange(lastFilter.length(), newSelectedItem
-                        .getText().length() - lastFilter.length());
-
-            } else if (hasNextPage()) {
-                selectPopupItemWhenResponseIsReceived = Select.FIRST;
-                filterOptions(currentPage + 1, lastFilter);
-            }
-        }
-
-        /**
-         * Selects the previous item in the filtered selections
-         */
-        public void selectPrevItem() {
-            final MenuItem cur = menu.getSelectedItem();
-            final int index = -1 + menu.getItems().indexOf(cur);
-            if (index > -1) {
-                final MenuItem newSelectedItem = menu.getItems().get(index);
-                menu.selectItem(newSelectedItem);
-                tb.setText(newSelectedItem.getText());
-                tb.setSelectionRange(lastFilter.length(), newSelectedItem
-                        .getText().length() - lastFilter.length());
-            } else if (index == -1) {
-                if (currentPage > 0) {
-                    selectPopupItemWhenResponseIsReceived = Select.LAST;
-                    filterOptions(currentPage - 1, lastFilter);
-                }
-            } else {
-                final MenuItem newSelectedItem = menu.getItems().get(
-                        menu.getItems().size() - 1);
-                menu.selectItem(newSelectedItem);
-                tb.setText(newSelectedItem.getText());
-                tb.setSelectionRange(lastFilter.length(), newSelectedItem
-                        .getText().length() - lastFilter.length());
-            }
-        }
-
-        /*
-         * 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() {
-                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() {
-                if (currentPage + pagesToScroll > 0) {
-                    pagesToScroll--;
-                    cancel();
-                    schedule(200);
-                }
-            }
-
-            public void scrollDown() {
-                if (totalMatches > (currentPage + pagesToScroll + 1)
-                        * pageLength) {
-                    pagesToScroll++;
-                    cancel();
-                    schedule(200);
-                }
-            }
-        }
-
-        /*
-         * (non-Javadoc)
-         * 
-         * @see
-         * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt
-         * .user.client.Event)
-         */
-
-        @Override
-        public void onBrowserEvent(Event event) {
-            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();
-                }
-            } else if (event.getTypeInt() == Event.ONMOUSEWHEEL) {
-                int velocity = event.getMouseWheelVelocityY();
-                if (velocity > 0) {
-                    lazyPageScroller.scrollDown();
-                } else {
-                    lazyPageScroller.scrollUp();
-                }
-            }
-
-            /*
-             * 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) {
-            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;
-        }
-
-        /*
-         * (non-Javadoc)
-         * 
-         * @see
-         * com.google.gwt.user.client.ui.PopupPanel$PositionCallback#setPosition
-         * (int, int)
-         */
-
-        @Override
-        public void setPosition(int offsetWidth, int offsetHeight) {
-
-            int top = -1;
-            int left = -1;
-
-            // reset menu size and retrieve its "natural" size
-            menu.setHeight("");
-            if (currentPage > 0) {
-                // fix height to avoid height change when getting to last page
-                menu.fixHeightTo(pageLength);
-            }
-            offsetHeight = getOffsetHeight();
-
-            final int desiredWidth = getMainWidth();
-            Element menuFirstChild = menu.getElement().getFirstChildElement()
-                    .cast();
-            int naturalMenuWidth = menuFirstChild.getOffsetWidth();
-
-            if (popupOuterPadding == -1) {
-                popupOuterPadding = Util.measureHorizontalPaddingAndBorder(
-                        getElement(), 2);
-            }
-
-            if (naturalMenuWidth < desiredWidth) {
-                menu.setWidth((desiredWidth - popupOuterPadding) + "px");
-                menuFirstChild.getStyle().setWidth(100, Unit.PCT);
-                naturalMenuWidth = desiredWidth;
-            }
-
-            if (BrowserInfo.get().isIE()) {
-                /*
-                 * IE requires us to specify the width for the container
-                 * element. Otherwise it will be 100% wide
-                 */
-                int rootWidth = naturalMenuWidth - popupOuterPadding;
-                getContainerElement().getStyle().setWidth(rootWidth, Unit.PX);
-            }
-
-            if (offsetHeight + getPopupTop() > Window.getClientHeight()
-                    + Window.getScrollTop()) {
-                // popup on top of input instead
-                top = getPopupTop() - offsetHeight
-                        - VFilterSelect.this.getOffsetHeight();
-                if (top < 0) {
-                    top = 0;
-                }
-            } else {
-                top = getPopupTop();
-                /*
-                 * Take popup top margin into account. getPopupTop() returns the
-                 * top value including the margin but the value we give must not
-                 * include the margin.
-                 */
-                int topMargin = (top - topPosition);
-                top -= topMargin;
-            }
-
-            // fetch real width (mac FF bugs here due GWT popups overflow:auto )
-            offsetWidth = menuFirstChild.getOffsetWidth();
-            if (offsetWidth + getPopupLeft() > Window.getClientWidth()
-                    + Window.getScrollLeft()) {
-                left = VFilterSelect.this.getAbsoluteLeft()
-                        + VFilterSelect.this.getOffsetWidth()
-                        + Window.getScrollLeft() - offsetWidth;
-                if (left < 0) {
-                    left = 0;
-                }
-            } else {
-                left = getPopupLeft();
-            }
-            setPopupPosition(left, top);
-        }
-
-        /**
-         * Was the popup just closed?
-         * 
-         * @return true if popup was just closed
-         */
-        public boolean isJustClosed() {
-            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 (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, ComponentState componentState) {
-            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 {
-
-        /**
-         * Tracks the item that is currently selected using the keyboard. This
-         * is need only because mouseover changes the selection and we do not
-         * want to use that selection when pressing enter to select the item.
-         */
-        private MenuItem keyboardSelectedItem;
-
-        private VLazyExecutor delayedImageLoadExecutioner = new VLazyExecutor(
-                100, new ScheduledCommand() {
-
-                    @Override
-                    public void execute() {
-                        if (suggestionPopup.isVisible()
-                                && suggestionPopup.isAttached()) {
-                            setWidth("");
-                            getElement().getFirstChildElement().getStyle()
-                                    .clearWidth();
-                            suggestionPopup
-                                    .setPopupPositionAndShow(suggestionPopup);
-                        }
-
-                    }
-                });
-
-        /**
-         * Default constructor
-         */
-        SuggestionMenu() {
-            super(true);
-            addDomHandler(this, LoadEvent.getType());
-        }
-
-        /**
-         * 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 pagelenth) {
-            if (currentSuggestions.size() > 0) {
-                final int pixels = pagelenth * (getOffsetHeight() - 2)
-                        / currentSuggestions.size();
-                setHeight((pixels + 2) + "px");
-            }
-        }
-
-        /**
-         * Sets the suggestions rendered in the menu
-         * 
-         * @param suggestions
-         *            The suggestions to be rendered in the menu
-         */
-        public void setSuggestions(
-                Collection<FilterSelectSuggestion> suggestions) {
-            // Reset keyboard selection when contents is updated to avoid
-            // reusing old, invalid data
-            setKeyboardSelectedItem(null);
-
-            clearItems();
-            final Iterator<FilterSelectSuggestion> it = suggestions.iterator();
-            while (it.hasNext()) {
-                final FilterSelectSuggestion s = it.next();
-                final MenuItem mi = new MenuItem(s.getDisplayString(), true, s);
-
-                Util.sinkOnloadForImages(mi.getElement());
-
-                this.addItem(mi);
-                if (s == currentSuggestion) {
-                    selectItem(mi);
-                }
-            }
-        }
-
-        /**
-         * Send the current selection to the server. Triggered when a selection
-         * is made or on a blur event.
-         */
-        public void 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);
-                suggestionPopup.hide();
-                return;
-            }
-
-            updateSelectionWhenReponseIsReceived = waitingForFilteringResponse;
-            if (!waitingForFilteringResponse) {
-                doPostFilterSelectedItemAction();
-            }
-        }
-
-        /**
-         * Triggered after a selection has been made
-         */
-        public void 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);
-                }
-            } 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();
-                    tb.setText(text);
-                    selectedOptionKey = currentSuggestion.key;
-                } else {
-                    // Null selected
-                    tb.setText("");
-                    selectedOptionKey = null;
-                }
-            }
-            suggestionPopup.hide();
-        }
-
-        private static final String SUBPART_PREFIX = "item";
-
-        @Override
-        public 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(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) {
-            // Handle icon onload events to ensure shadow is resized
-            // correctly
-            delayedImageLoadExecutioner.trigger();
-
-        }
-
-        public void selectFirstItem() {
-            MenuItem firstItem = getItems().get(0);
-            selectItem(firstItem);
-        }
-
-        private MenuItem getKeyboardSelectedItem() {
-            return keyboardSelectedItem;
-        }
-
-        protected void setKeyboardSelectedItem(MenuItem firstItem) {
-            keyboardSelectedItem = firstItem;
-        }
-
-        public void selectLastItem() {
-            List<MenuItem> items = getItems();
-            MenuItem lastItem = items.get(items.size() - 1);
-            selectItem(lastItem);
-        }
-    }
-
-    @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";
-
-    protected int pageLength = 10;
-
-    private boolean enableDebug = false;
-
-    private final FlowPanel panel = new FlowPanel();
-
-    /**
-     * The text box where the filter is written
-     */
-    protected final TextBox tb = new TextBox() {
-
-        // Overridden to avoid selecting text when text input is disabled
-        @Override
-        public void setSelectionRange(int pos, int length) {
-            if (textInputEnabled) {
-                super.setSelectionRange(pos, length);
-            } else {
-                super.setSelectionRange(getValue().length(), 0);
-            }
-        };
-    };
-
-    protected final SuggestionPopup suggestionPopup = new 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 final Image selectedItemIcon = new Image();
-
-    protected ApplicationConnection client;
-
-    protected String paintableId;
-
-    protected int currentPage;
-
-    /**
-     * A collection of available suggestions (options) as received from the
-     * server.
-     */
-    protected final List<FilterSelectSuggestion> currentSuggestions = new ArrayList<FilterSelectSuggestion>();
-
-    protected boolean immediate;
-
-    protected String selectedOptionKey;
-
-    protected boolean waitingForFilteringResponse = false;
-    protected boolean updateSelectionWhenReponseIsReceived = false;
-    private boolean tabPressedWhenPopupOpen = false;
-    protected boolean initDone = false;
-
-    protected String lastFilter = "";
-
-    protected enum Select {
-        NONE, FIRST, LAST
-    };
-
-    protected 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.
-     */
-    protected FilterSelectSuggestion currentSuggestion;
-
-    protected int totalMatches;
-    protected boolean allowNewItem;
-    protected boolean nullSelectionAllowed;
-    protected boolean nullSelectItem;
-    protected boolean enabled;
-    protected boolean readonly;
-
-    protected FilteringMode filteringmode = FilteringMode.OFF;
-
-    // shown in unfocused empty field, disappears on focus (e.g "Search here")
-    private static final String CLASSNAME_PROMPT = "prompt";
-    protected String inputPrompt = "";
-    protected 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
-    protected boolean popupOpenerClicked;
-    protected int suggestionPopupMinWidth = 0;
-    private int popupWidth = -1;
-    /*
-     * Stores the last new item string to avoid double submissions. Cleared on
-     * uidl updates
-     */
-    protected String lastNewItemString;
-    protected 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() {
-        selectedItemIcon.setStyleName("v-icon");
-        selectedItemIcon.addLoadHandler(new LoadHandler() {
-
-            @Override
-            public void onLoad(LoadEvent event) {
-                if (BrowserInfo.get().isIE8()) {
-                    // IE8 needs some help to discover it should reposition the
-                    // text field
-                    forceReflow();
-                }
-                updateRootWidth();
-                updateSelectedIconPosition();
-            }
-        });
-
-        popupOpener.sinkEvents(Event.ONMOUSEDOWN);
-        panel.add(tb);
-        panel.add(popupOpener);
-        initWidget(panel);
-        tb.addKeyDownHandler(this);
-        tb.addKeyUpHandler(this);
-
-        tb.addFocusHandler(this);
-        tb.addBlurHandler(this);
-        tb.addClickHandler(this);
-
-        popupOpener.addClickHandler(this);
-
-        setStyleName(CLASSNAME);
-    }
-
-    @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 (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) {
-        if (filter.equals(lastFilter) && currentPage == page) {
-            if (!suggestionPopup.isAttached()) {
-                suggestionPopup.showSuggestions(currentSuggestions,
-                        currentPage, totalMatches);
-            }
-            return;
-        }
-        if (!filter.equals(lastFilter)) {
-            // we are on subsequent page and text has changed -> reset page
-            if ("".equals(filter)) {
-                // let server decide
-                page = -1;
-            } else {
-                page = 0;
-            }
-        }
-
-        waitingForFilteringResponse = true;
-        client.updateVariable(paintableId, "filter", filter, false);
-        client.updateVariable(paintableId, "page", page, immediate);
-        lastFilter = filter;
-        currentPage = page;
-    }
-
-    protected void updateReadOnly() {
-        tb.setReadOnly(readonly || !textInputEnabled);
-    }
-
-    protected void setTextInputEnabled(boolean textInputEnabled) {
-        // Always update styles as they might have been overwritten
-        if (textInputEnabled) {
-            removeStyleDependentName(STYLE_NO_INPUT);
-        } else {
-            addStyleDependentName(STYLE_NO_INPUT);
-        }
-
-        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
-     */
-    protected void setTextboxText(final String text) {
-        tb.setText(text);
-    }
-
-    /**
-     * Turns prompting on. When prompting is turned on a command prompt is shown
-     * in the text box if nothing has been entered.
-     */
-    protected void 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.
-     * 
-     * @param text
-     *            The text the text box should contain.
-     */
-    protected void setPromptingOff(String text) {
-        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) {
-        updateSelectionWhenReponseIsReceived = false;
-
-        currentSuggestion = suggestion;
-        String newKey;
-        if (suggestion.key.equals("")) {
-            // "nullselection"
-            newKey = "";
-        } else {
-            // normal selection
-            newKey = String.valueOf(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);
-            // 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
-     */
-    protected void setSelectedItemIcon(String iconUri) {
-        if (iconUri == null || iconUri.length() == 0) {
-            if (selectedItemIcon.isAttached()) {
-                panel.remove(selectedItemIcon);
-                if (BrowserInfo.get().isIE8()) {
-                    // IE8 needs some help to discover it should reposition the
-                    // text field
-                    forceReflow();
-                }
-                updateRootWidth();
-            }
-        } else {
-            panel.insert(selectedItemIcon, 0);
-            selectedItemIcon.setUrl(iconUri);
-            updateRootWidth();
-            updateSelectedIconPosition();
-        }
-    }
-
-    private void forceReflow() {
-        Util.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 = Util.getRequiredHeight(selectedItemIcon);
-        int marginTop = (availableHeight - iconHeight) / 2;
-        DOM.setStyleAttribute(selectedItemIcon.getElement(), "marginTop",
-                marginTop + "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();
-
-            debug("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.
-                 */
-                debug("Ignoring " + keyCode
-                        + " because we are waiting for a filtering response");
-                DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
-                event.stopPropagation();
-                return;
-            }
-
-            if (suggestionPopup.isAttached()) {
-                debug("Keycode " + keyCode + " target is popup");
-                popupKeyDown(event);
-            } else {
-                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) {
-        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) {
-        // 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();
-            suggestionPopup.menu.setKeyboardSelectedItem(suggestionPopup.menu
-                    .getSelectedItem());
-            DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
-            event.stopPropagation();
-            break;
-        case KeyCodes.KEY_UP:
-            suggestionPopup.selectPrevItem();
-            suggestionPopup.menu.setKeyboardSelectedItem(suggestionPopup.menu
-                    .getSelectedItem());
-            DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
-            event.stopPropagation();
-            break;
-        case KeyCodes.KEY_PAGEDOWN:
-            if (hasNextPage()) {
-                filterOptions(currentPage + 1, lastFilter);
-            }
-            event.stopPropagation();
-            break;
-        case KeyCodes.KEY_PAGEUP:
-            if (currentPage > 0) {
-                filterOptions(currentPage - 1, lastFilter);
-            }
-            event.stopPropagation();
-            break;
-        case KeyCodes.KEY_TAB:
-            tabPressedWhenPopupOpen = true;
-            filterOptions(currentPage);
-            // onBlur() takes care of the rest
-            break;
-        case KeyCodes.KEY_ESCAPE:
-            reset();
-            event.stopPropagation();
-            break;
-        case KeyCodes.KEY_ENTER:
-            if (suggestionPopup.menu.getKeyboardSelectedItem() == null) {
-                /*
-                 * Nothing selected using up/down. Happens e.g. when entering a
-                 * text (causes popup to open) and then pressing enter.
-                 */
-                if (!allowNewItem) {
-                    /*
-                     * New items are not allowed: If there is only one
-                     * suggestion, select that. Otherwise do nothing.
-                     */
-                    if (currentSuggestions.size() == 1) {
-                        onSuggestionSelected(currentSuggestions.get(0));
-                    }
-                } else {
-                    // Handle addition of new items.
-                    suggestionPopup.menu.doSelectedItemAction();
-                }
-            } else {
-                /*
-                 * Get the suggestion that was navigated to using up/down.
-                 */
-                currentSuggestion = ((FilterSelectSuggestion) suggestionPopup.menu
-                        .getKeyboardSelectedItem().getCommand());
-                onSuggestionSelected(currentSuggestion);
-            }
-
-            event.stopPropagation();
-            break;
-        }
-
-    }
-
-    /**
-     * Triggered when a key was depressed
-     * 
-     * @param event
-     *            The KeyUpEvent of the key depressed
-     */
-
-    @Override
-    public void onKeyUp(KeyUpEvent event) {
-        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) {
-                    filterOptions(currentPage);
-                }
-                break;
-            }
-        }
-    }
-
-    /**
-     * Resets the Select to its initial state
-     */
-    private void reset() {
-        if (currentSuggestion != null) {
-            String text = currentSuggestion.getReplacementString();
-            setPromptingOff(text);
-            selectedOptionKey = currentSuggestion.key;
-        } else {
-            if (focused) {
-                setPromptingOff("");
-            } else {
-                setPromptingOn();
-            }
-            selectedOptionKey = null;
-        }
-        lastFilter = "";
-        suggestionPopup.hide();
-    }
-
-    /**
-     * Listener for popupopener
-     */
-
-    @Override
-    public void onClick(ClickEvent event) {
-        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();
-        }
-    }
-
-    /**
-     * Calculate minimum width for FilterSelect textarea
-     */
-    protected 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) {
-
-        /*
-         * 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);
-        }
-    }
-
-    /**
-     * 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) {
-
-        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 = Util.getIEFocusedElement();
-            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) {
-            // much of the TAB handling takes place here
-            if (tabPressedWhenPopupOpen) {
-                tabPressedWhenPopupOpen = false;
-                suggestionPopup.menu.doSelectedItemAction();
-                suggestionPopup.hide();
-            } else if (!suggestionPopup.isAttached()
-                    || suggestionPopup.isJustClosed()) {
-                suggestionPopup.menu.doSelectedItemAction();
-            }
-            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);
-        }
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see com.vaadin.client.Focusable#focus()
-     */
-
-    @Override
-    public void 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.
-     */
-    protected 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 = Util.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);
-                int offset = w - Util.getRequiredWidth(this);
-                style.setProperty("padding", originalPadding);
-                style.setProperty("borderWidth", originalBorder);
-
-                setWidth(suggestionPopupMinWidth + offset + "px");
-            }
-
-            /*
-             * Lock the textbox width to its current value if it's not already
-             * locked
-             */
-            if (!tb.getElement().getStyle().getWidth().endsWith("px")) {
-                tb.setWidth((tb.getOffsetWidth() - selectedItemIcon
-                        .getOffsetWidth()) + "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;
-            }
-        }
-    }
-
-    @Override
-    protected void onDetach() {
-        super.onDetach();
-        suggestionPopup.hide();
-    }
-
-    @Override
-    public Element getSubPartElement(String subPart) {
-        if ("textbox".equals(subPart)) {
-            return tb.getElement();
-        } else if ("button".equals(subPart)) {
-            return popupOpener.getElement();
-        }
-        return null;
-    }
-
-    @Override
-    public String getSubPartName(Element subElement) {
-        if (tb.getElement().isOrHasChild(subElement)) {
-            return "textbox";
-        } else if (popupOpener.getElement().isOrHasChild(subElement)) {
-            return "button";
-        }
-        return null;
-    }
-}
index 1a83ae90b40f49f886a5237c10cfe0b64a1f7f0b..98969d881afbc87da048298072ee5e6e42719cb9 100644 (file)
@@ -29,6 +29,7 @@ import com.vaadin.client.VCaption;
 import com.vaadin.client.communication.StateChangeEvent;
 import com.vaadin.client.ui.AbstractLayoutConnector;
 import com.vaadin.client.ui.LayoutClickEventHandler;
+import com.vaadin.client.ui.VCssLayout;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.LayoutClickRpc;
 import com.vaadin.shared.ui.csslayout.CssLayoutServerRpc;
diff --git a/client/src/com/vaadin/client/ui/csslayout/VCssLayout.java b/client/src/com/vaadin/client/ui/csslayout/VCssLayout.java
deleted file mode 100644 (file)
index 212a630..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.csslayout;
-
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.StyleConstants;
-
-/**
- * VCCSlayout is a layout which supports configuring it's children with CSS
- * selectors
- */
-public class VCssLayout extends FlowPanel {
-
-    public static final String CLASSNAME = "v-csslayout";
-
-    /**
-     * Default constructor
-     */
-    public VCssLayout() {
-        super();
-        setStyleName(CLASSNAME);
-        addStyleName(StyleConstants.UI_LAYOUT);
-    }
-
-    /**
-     * Add or move a child in the
-     * 
-     * @param child
-     * @param index
-     */
-    void addOrMove(Widget child, int index) {
-        if (child.getParent() == this) {
-            int currentIndex = getWidgetIndex(child);
-            if (index == currentIndex) {
-                return;
-            }
-        }
-        insert(child, index);
-    }
-}
index 09c90b3577c425823b7229bda4b7758607c99a72..7a7a7c3456e8aee807c0cc739fe98d2411381685 100644 (file)
@@ -18,6 +18,7 @@ package com.vaadin.client.ui.customcomponent;
 import com.vaadin.client.ComponentConnector;
 import com.vaadin.client.ConnectorHierarchyChangeEvent;
 import com.vaadin.client.ui.AbstractComponentContainerConnector;
+import com.vaadin.client.ui.VCustomComponent;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.Connect.LoadStyle;
 import com.vaadin.ui.CustomComponent;
diff --git a/client/src/com/vaadin/client/ui/customcomponent/VCustomComponent.java b/client/src/com/vaadin/client/ui/customcomponent/VCustomComponent.java
deleted file mode 100644 (file)
index fa2d5c4..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-/* 
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.customcomponent;
-
-import com.google.gwt.user.client.ui.SimplePanel;
-
-public class VCustomComponent extends SimplePanel {
-
-    private static final String CLASSNAME = "v-customcomponent";
-
-    public VCustomComponent() {
-        super();
-        setStyleName(CLASSNAME);
-    }
-
-}
index 199f3cb3cc4e6d4ae89a6fe2297fb804776f257c..4472a068523539f20e50b7bed83c41e2deb4d159 100644 (file)
@@ -25,6 +25,7 @@ import com.vaadin.client.UIDL;
 import com.vaadin.client.communication.StateChangeEvent;
 import com.vaadin.client.ui.AbstractLayoutConnector;
 import com.vaadin.client.ui.SimpleManagedLayout;
+import com.vaadin.client.ui.VCustomLayout;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.customlayout.CustomLayoutState;
 import com.vaadin.ui.CustomLayout;
diff --git a/client/src/com/vaadin/client/ui/customlayout/VCustomLayout.java b/client/src/com/vaadin/client/ui/customlayout/VCustomLayout.java
deleted file mode 100644 (file)
index 803b266..0000000
+++ /dev/null
@@ -1,432 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.customlayout;
-
-import java.util.HashMap;
-import java.util.Iterator;
-
-import com.google.gwt.dom.client.ImageElement;
-import com.google.gwt.dom.client.NodeList;
-import com.google.gwt.dom.client.Style;
-import com.google.gwt.dom.client.Style.BorderStyle;
-import com.google.gwt.dom.client.Style.Position;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.ComplexPanel;
-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.StyleConstants;
-import com.vaadin.client.Util;
-import com.vaadin.client.VCaption;
-import com.vaadin.client.VCaptionWrapper;
-
-/**
- * Custom Layout implements complex layout defined with HTML template.
- * 
- * @author Vaadin Ltd
- * 
- */
-public class VCustomLayout extends ComplexPanel {
-
-    public static final String CLASSNAME = "v-customlayout";
-
-    /** Location-name to containing element in DOM map */
-    private final HashMap<String, Element> locationToElement = new HashMap<String, Element>();
-
-    /** Location-name to contained widget map */
-    final HashMap<String, Widget> locationToWidget = new HashMap<String, Widget>();
-
-    /** Widget to captionwrapper map */
-    private final HashMap<Widget, VCaptionWrapper> childWidgetToCaptionWrapper = new HashMap<Widget, VCaptionWrapper>();
-
-    /** Name of the currently rendered style */
-    String currentTemplateName;
-
-    /** Unexecuted scripts loaded from the template */
-    String scripts = "";
-
-    /** Paintable ID of this paintable */
-    String pid;
-
-    ApplicationConnection client;
-
-    private boolean htmlInitialized = false;
-
-    private Element elementWithNativeResizeFunction;
-
-    private String height = "";
-
-    private String width = "";
-
-    public VCustomLayout() {
-        setElement(DOM.createDiv());
-        // Clear any unwanted styling
-        Style style = getElement().getStyle();
-        style.setBorderStyle(BorderStyle.NONE);
-        style.setMargin(0, Unit.PX);
-        style.setPadding(0, Unit.PX);
-
-        if (BrowserInfo.get().isIE()) {
-            style.setPosition(Position.RELATIVE);
-        }
-
-        setStyleName(CLASSNAME);
-    }
-
-    @Override
-    public void setStyleName(String style) {
-        super.setStyleName(style);
-        addStyleName(StyleConstants.UI_LAYOUT);
-    }
-
-    /**
-     * Sets widget to given location.
-     * 
-     * If location already contains a widget it will be removed.
-     * 
-     * @param widget
-     *            Widget to be set into location.
-     * @param location
-     *            location name where widget will be added
-     * 
-     * @throws IllegalArgumentException
-     *             if no such location is found in the layout.
-     */
-    public void setWidget(Widget widget, String location) {
-
-        if (widget == null) {
-            return;
-        }
-
-        // If no given location is found in the layout, and exception is throws
-        Element elem = locationToElement.get(location);
-        if (elem == null && hasTemplate()) {
-            throw new IllegalArgumentException("No location " + location
-                    + " found");
-        }
-
-        // Get previous widget
-        final Widget previous = locationToWidget.get(location);
-        // NOP if given widget already exists in this location
-        if (previous == widget) {
-            return;
-        }
-
-        if (previous != null) {
-            remove(previous);
-        }
-
-        // if template is missing add element in order
-        if (!hasTemplate()) {
-            elem = getElement();
-        }
-
-        // Add widget to location
-        super.add(widget, elem);
-        locationToWidget.put(location, widget);
-    }
-
-    /** Initialize HTML-layout. */
-    public void initializeHTML(String template, String themeUri) {
-
-        // Connect body of the template to DOM
-        template = extractBodyAndScriptsFromTemplate(template);
-
-        // TODO prefix img src:s here with a regeps, cannot work further with IE
-
-        String relImgPrefix = themeUri + "/layouts/";
-
-        // prefix all relative image elements to point to theme dir with a
-        // regexp search
-        template = template.replaceAll(
-                "<((?:img)|(?:IMG))\\s([^>]*)src=\"((?![a-z]+:)[^/][^\"]+)\"",
-                "<$1 $2src=\"" + relImgPrefix + "$3\"");
-        // also support src attributes without quotes
-        template = template
-                .replaceAll(
-                        "<((?:img)|(?:IMG))\\s([^>]*)src=[^\"]((?![a-z]+:)[^/][^ />]+)[ />]",
-                        "<$1 $2src=\"" + relImgPrefix + "$3\"");
-        // also prefix relative style="...url(...)..."
-        template = template
-                .replaceAll(
-                        "(<[^>]+style=\"[^\"]*url\\()((?![a-z]+:)[^/][^\"]+)(\\)[^>]*>)",
-                        "$1 " + relImgPrefix + "$2 $3");
-
-        getElement().setInnerHTML(template);
-
-        // Remap locations to elements
-        locationToElement.clear();
-        scanForLocations(getElement());
-
-        initImgElements();
-
-        elementWithNativeResizeFunction = DOM.getFirstChild(getElement());
-        if (elementWithNativeResizeFunction == null) {
-            elementWithNativeResizeFunction = getElement();
-        }
-        publishResizedFunction(elementWithNativeResizeFunction);
-
-        htmlInitialized = true;
-    }
-
-    private native boolean uriEndsWithSlash()
-    /*-{
-        var path =  $wnd.location.pathname;
-        if(path.charAt(path.length - 1) == "/")
-            return true;
-        return false;
-    }-*/;
-
-    boolean hasTemplate() {
-        return htmlInitialized;
-    }
-
-    /** Collect locations from template */
-    private void scanForLocations(Element elem) {
-
-        final String location = elem.getAttribute("location");
-        if (!"".equals(location)) {
-            locationToElement.put(location, elem);
-            elem.setInnerHTML("");
-
-        } else {
-            final int len = DOM.getChildCount(elem);
-            for (int i = 0; i < len; i++) {
-                scanForLocations(DOM.getChild(elem, i));
-            }
-        }
-    }
-
-    /** Evaluate given script in browser document */
-    static native void eval(String script)
-    /*-{
-      try {
-        if (script != null)
-      eval("{ var document = $doc; var window = $wnd; "+ script + "}");
-      } catch (e) {
-      }
-    }-*/;
-
-    /**
-     * Img elements needs some special handling in custom layout. Img elements
-     * will get their onload events sunk. This way custom layout can notify
-     * parent about possible size change.
-     */
-    private void initImgElements() {
-        NodeList<com.google.gwt.dom.client.Element> nodeList = getElement()
-                .getElementsByTagName("IMG");
-        for (int i = 0; i < nodeList.getLength(); i++) {
-            com.google.gwt.dom.client.ImageElement img = (ImageElement) nodeList
-                    .getItem(i);
-            DOM.sinkEvents((Element) img.cast(), Event.ONLOAD);
-        }
-    }
-
-    /**
-     * Extract body part and script tags from raw html-template.
-     * 
-     * Saves contents of all script-tags to private property: scripts. Returns
-     * contents of the body part for the html without script-tags. Also replaces
-     * all _UID_ tags with an unique id-string.
-     * 
-     * @param html
-     *            Original HTML-template received from server
-     * @return html that is used to create the HTMLPanel.
-     */
-    private String extractBodyAndScriptsFromTemplate(String html) {
-
-        // Replace UID:s
-        html = html.replaceAll("_UID_", pid + "__");
-
-        // Exctract script-tags
-        scripts = "";
-        int endOfPrevScript = 0;
-        int nextPosToCheck = 0;
-        String lc = html.toLowerCase();
-        String res = "";
-        int scriptStart = lc.indexOf("<script", nextPosToCheck);
-        while (scriptStart > 0) {
-            res += html.substring(endOfPrevScript, scriptStart);
-            scriptStart = lc.indexOf(">", scriptStart);
-            final int j = lc.indexOf("</script>", scriptStart);
-            scripts += html.substring(scriptStart + 1, j) + ";";
-            nextPosToCheck = endOfPrevScript = j + "</script>".length();
-            scriptStart = lc.indexOf("<script", nextPosToCheck);
-        }
-        res += html.substring(endOfPrevScript);
-
-        // Extract body
-        html = res;
-        lc = html.toLowerCase();
-        int startOfBody = lc.indexOf("<body");
-        if (startOfBody < 0) {
-            res = html;
-        } else {
-            res = "";
-            startOfBody = lc.indexOf(">", startOfBody) + 1;
-            final int endOfBody = lc.indexOf("</body>", startOfBody);
-            if (endOfBody > startOfBody) {
-                res = html.substring(startOfBody, endOfBody);
-            } else {
-                res = html.substring(startOfBody);
-            }
-        }
-
-        return res;
-    }
-
-    /** Update caption for given widget */
-    public void updateCaption(ComponentConnector paintable) {
-        Widget widget = paintable.getWidget();
-        VCaptionWrapper wrapper = childWidgetToCaptionWrapper.get(widget);
-        if (VCaption.isNeeded(paintable.getState())) {
-            if (wrapper == null) {
-                // Add a wrapper between the layout and the child widget
-                final String loc = getLocation(widget);
-                super.remove(widget);
-                wrapper = new VCaptionWrapper(paintable, client);
-                super.add(wrapper, locationToElement.get(loc));
-                childWidgetToCaptionWrapper.put(widget, wrapper);
-            }
-            wrapper.updateCaption();
-        } else {
-            if (wrapper != null) {
-                // Remove the wrapper and add the widget directly to the layout
-                final String loc = getLocation(widget);
-                super.remove(wrapper);
-                super.add(widget, locationToElement.get(loc));
-                childWidgetToCaptionWrapper.remove(widget);
-            }
-        }
-    }
-
-    /** Get the location of an widget */
-    public String getLocation(Widget w) {
-        for (final Iterator<String> i = locationToWidget.keySet().iterator(); i
-                .hasNext();) {
-            final String location = i.next();
-            if (locationToWidget.get(location) == w) {
-                return location;
-            }
-        }
-        return null;
-    }
-
-    /** Removes given widget from the layout */
-    @Override
-    public boolean remove(Widget w) {
-        final String location = getLocation(w);
-        if (location != null) {
-            locationToWidget.remove(location);
-        }
-        final VCaptionWrapper cw = childWidgetToCaptionWrapper.get(w);
-        if (cw != null) {
-            childWidgetToCaptionWrapper.remove(w);
-            return super.remove(cw);
-        } else if (w != null) {
-            return super.remove(w);
-        }
-        return false;
-    }
-
-    /** Adding widget without specifying location is not supported */
-    @Override
-    public void add(Widget w) {
-        throw new UnsupportedOperationException();
-    }
-
-    /** Clear all widgets from the layout */
-    @Override
-    public void clear() {
-        super.clear();
-        locationToWidget.clear();
-        childWidgetToCaptionWrapper.clear();
-    }
-
-    /**
-     * This method is published to JS side with the same name into first DOM
-     * node of custom layout. This way if one implements some resizeable
-     * containers in custom layout he/she can notify children after resize.
-     */
-    public void notifyChildrenOfSizeChange() {
-        client.runDescendentsLayout(this);
-    }
-
-    @Override
-    public void onDetach() {
-        super.onDetach();
-        if (elementWithNativeResizeFunction != null) {
-            detachResizedFunction(elementWithNativeResizeFunction);
-        }
-    }
-
-    private native void detachResizedFunction(Element element)
-    /*-{
-       element.notifyChildrenOfSizeChange = null;
-    }-*/;
-
-    private native void publishResizedFunction(Element element)
-    /*-{
-       var self = this;
-       element.notifyChildrenOfSizeChange = $entry(function() {
-               self.@com.vaadin.client.ui.customlayout.VCustomLayout::notifyChildrenOfSizeChange()();
-       });
-    }-*/;
-
-    /**
-     * In custom layout one may want to run layout functions made with
-     * JavaScript. This function tests if one exists (with name "iLayoutJS" in
-     * layouts first DOM node) and runs et. Return value is used to determine if
-     * children needs to be notified of size changes.
-     * 
-     * Note! When implementing a JS layout function you most likely want to call
-     * notifyChildrenOfSizeChange() function on your custom layouts main
-     * element. That method is used to control whether child components layout
-     * functions are to be run.
-     * 
-     * @param el
-     * @return true if layout function exists and was run successfully, else
-     *         false.
-     */
-    native boolean iLayoutJS(Element el)
-    /*-{
-       if(el && el.iLayoutJS) {
-               try {
-                       el.iLayoutJS();
-                       return true;
-               } catch (e) {
-                       return false;
-               }
-       } else {
-               return false;
-       }
-    }-*/;
-
-    @Override
-    public void onBrowserEvent(Event event) {
-        super.onBrowserEvent(event);
-        if (event.getTypeInt() == Event.ONLOAD) {
-            Util.notifyParentOfSizeChange(this, true);
-            event.cancelBubble(true);
-        }
-    }
-
-}
index 2f48d7411c9172fadd3134a1f199bd96ea37fef2..5cefdd2d3899a10fbb2907e72216b87e71c02582 100644 (file)
@@ -23,6 +23,7 @@ import com.vaadin.client.Paintable;
 import com.vaadin.client.UIDL;
 import com.vaadin.client.VConsole;
 import com.vaadin.client.ui.AbstractFieldConnector;
+import com.vaadin.client.ui.VDateField;
 import com.vaadin.shared.ui.datefield.DateFieldConstants;
 import com.vaadin.shared.ui.datefield.Resolution;
 
@@ -40,28 +41,28 @@ public class AbstractDateFieldConnector extends AbstractFieldConnector
         getWidget().paintableId = uidl.getId();
         getWidget().immediate = getState().immediate;
 
-        getWidget().readonly = isReadOnly();
-        getWidget().enabled = isEnabled();
+        getWidget().setReadonly(isReadOnly());
+        getWidget().setEnabled(isEnabled());
 
         if (uidl.hasAttribute("locale")) {
             final String locale = uidl.getStringAttribute("locale");
             try {
                 getWidget().dts.setLocale(locale);
-                getWidget().currentLocale = locale;
+                getWidget().setCurrentLocale(locale);
             } catch (final LocaleNotLoadedException e) {
-                getWidget().currentLocale = getWidget().dts.getLocale();
+                getWidget().setCurrentLocale(getWidget().dts.getLocale());
                 VConsole.error("Tried to use an unloaded locale \"" + locale
                         + "\". Using default locale ("
-                        + getWidget().currentLocale + ").");
+                        + getWidget().getCurrentLocale() + ").");
                 VConsole.error(e);
             }
         }
 
         // We show week numbers only if the week starts with Monday, as ISO 8601
         // specifies
-        getWidget().showISOWeekNumbers = uidl
-                .getBooleanAttribute(DateFieldConstants.ATTR_WEEK_NUMBERS)
-                && getWidget().dts.getFirstDayOfWeek() == 1;
+        getWidget().setShowISOWeekNumbers(
+                uidl.getBooleanAttribute(DateFieldConstants.ATTR_WEEK_NUMBERS)
+                        && getWidget().dts.getFirstDayOfWeek() == 1);
 
         Resolution newResolution;
         if (uidl.hasVariable("sec")) {
@@ -82,30 +83,29 @@ public class AbstractDateFieldConnector extends AbstractFieldConnector
         setWidgetStyleName(
                 getWidget().getStylePrimaryName()
                         + "-"
-                        + VDateField
-                                .resolutionToString(getWidget().currentResolution),
-                false);
+                        + VDateField.resolutionToString(getWidget()
+                                .getCurrentResolution()), false);
 
-        getWidget().currentResolution = newResolution;
+        getWidget().setCurrentResolution(newResolution);
 
         // Add stylename that indicates current resolution
         setWidgetStyleName(
                 getWidget().getStylePrimaryName()
                         + "-"
-                        + VDateField
-                                .resolutionToString(getWidget().currentResolution),
-                true);
+                        + VDateField.resolutionToString(getWidget()
+                                .getCurrentResolution()), true);
 
+        final Resolution resolution = getWidget().getCurrentResolution();
         final int year = uidl.getIntVariable("year");
-        final int month = (getWidget().currentResolution.getCalendarField() >= Resolution.MONTH
+        final int month = (resolution.getCalendarField() >= Resolution.MONTH
                 .getCalendarField()) ? uidl.getIntVariable("month") : -1;
-        final int day = (getWidget().currentResolution.getCalendarField() >= Resolution.DAY
+        final int day = (resolution.getCalendarField() >= Resolution.DAY
                 .getCalendarField()) ? uidl.getIntVariable("day") : -1;
-        final int hour = (getWidget().currentResolution.getCalendarField() >= Resolution.HOUR
+        final int hour = (resolution.getCalendarField() >= Resolution.HOUR
                 .getCalendarField()) ? uidl.getIntVariable("hour") : 0;
-        final int min = (getWidget().currentResolution.getCalendarField() >= Resolution.MINUTE
+        final int min = (resolution.getCalendarField() >= Resolution.MINUTE
                 .getCalendarField()) ? uidl.getIntVariable("min") : 0;
-        final int sec = (getWidget().currentResolution.getCalendarField() >= Resolution.SECOND
+        final int sec = (resolution.getCalendarField() >= Resolution.SECOND
                 .getCalendarField()) ? uidl.getIntVariable("sec") : 0;
 
         // Construct new date for this datefield (only if not null)
index 1800125402f6e4b84b792181d322e289e189d991..ca3488af87bd94b30230161e0fe3f5c776483278 100644 (file)
@@ -20,8 +20,9 @@ import java.util.Date;
 import com.vaadin.client.ApplicationConnection;
 import com.vaadin.client.DateTimeService;
 import com.vaadin.client.UIDL;
-import com.vaadin.client.ui.datefield.VCalendarPanel.FocusChangeListener;
-import com.vaadin.client.ui.datefield.VCalendarPanel.TimeChangeListener;
+import com.vaadin.client.ui.VCalendarPanel.FocusChangeListener;
+import com.vaadin.client.ui.VCalendarPanel.TimeChangeListener;
+import com.vaadin.client.ui.VDateFieldCalendar;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.datefield.InlineDateFieldState;
 import com.vaadin.shared.ui.datefield.Resolution;
@@ -51,7 +52,7 @@ public class InlineDateFieldConnector extends AbstractDateFieldConnector {
             getWidget().calendarPanel.setDate(null);
         }
 
-        if (getWidget().currentResolution.getCalendarField() > Resolution.DAY
+        if (getWidget().getCurrentResolution().getCalendarField() > Resolution.DAY
                 .getCalendarField()) {
             getWidget().calendarPanel
                     .setTimeChangeListener(new TimeChangeListener() {
@@ -77,7 +78,7 @@ public class InlineDateFieldConnector extends AbstractDateFieldConnector {
                     });
         }
 
-        if (getWidget().currentResolution.getCalendarField() <= Resolution.MONTH
+        if (getWidget().getCurrentResolution().getCalendarField() <= Resolution.MONTH
                 .getCalendarField()) {
             getWidget().calendarPanel
                     .setFocusChangeListener(new FocusChangeListener() {
index 6f23d42de9c7143dd39db938a878382b8c0bb777..3859a2204693bed05045eec0549b45749b76cbee 100644 (file)
@@ -21,8 +21,9 @@ import java.util.Date;
 import com.vaadin.client.ApplicationConnection;
 import com.vaadin.client.DateTimeService;
 import com.vaadin.client.UIDL;
-import com.vaadin.client.ui.datefield.VCalendarPanel.FocusChangeListener;
-import com.vaadin.client.ui.datefield.VCalendarPanel.TimeChangeListener;
+import com.vaadin.client.ui.VCalendarPanel.FocusChangeListener;
+import com.vaadin.client.ui.VCalendarPanel.TimeChangeListener;
+import com.vaadin.client.ui.VPopupCalendar;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.datefield.PopupDateFieldState;
 import com.vaadin.shared.ui.datefield.Resolution;
@@ -40,7 +41,7 @@ public class PopupDateFieldConnector extends TextualDateConnector {
     @Override
     @SuppressWarnings("deprecation")
     public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
-        boolean lastReadOnlyState = getWidget().readonly;
+        boolean lastReadOnlyState = getWidget().isReadonly();
         boolean lastEnabledState = getWidget().isEnabled();
 
         getWidget().parsable = uidl.getBooleanAttribute("parsable");
@@ -51,8 +52,10 @@ public class PopupDateFieldConnector extends TextualDateConnector {
                 .getDateTimeService());
         getWidget().calendar.setShowISOWeekNumbers(getWidget()
                 .isShowISOWeekNumbers());
-        if (getWidget().calendar.getResolution() != getWidget().currentResolution) {
-            getWidget().calendar.setResolution(getWidget().currentResolution);
+        if (getWidget().calendar.getResolution() != getWidget()
+                .getCurrentResolution()) {
+            getWidget().calendar.setResolution(getWidget()
+                    .getCurrentResolution());
             if (getWidget().calendar.getDate() != null) {
                 getWidget().calendar.setDate((Date) getWidget()
                         .getCurrentDate().clone());
@@ -60,9 +63,9 @@ public class PopupDateFieldConnector extends TextualDateConnector {
                 getWidget().calendar.renderCalendar();
             }
         }
-        getWidget().calendarToggle.setEnabled(getWidget().enabled);
+        getWidget().calendarToggle.setEnabled(getWidget().isEnabled());
 
-        if (getWidget().currentResolution.getCalendarField() <= Resolution.MONTH
+        if (getWidget().getCurrentResolution().getCalendarField() <= Resolution.MONTH
                 .getCalendarField()) {
             getWidget().calendar
                     .setFocusChangeListener(new FocusChangeListener() {
@@ -79,7 +82,7 @@ public class PopupDateFieldConnector extends TextualDateConnector {
             getWidget().calendar.setFocusChangeListener(null);
         }
 
-        if (getWidget().currentResolution.getCalendarField() > Resolution.DAY
+        if (getWidget().getCurrentResolution().getCalendarField() > Resolution.DAY
                 .getCalendarField()) {
             getWidget().calendar
                     .setTimeChangeListener(new TimeChangeListener() {
@@ -107,7 +110,7 @@ public class PopupDateFieldConnector extends TextualDateConnector {
                     });
         }
 
-        if (getWidget().readonly) {
+        if (getWidget().isReadonly()) {
             getWidget().calendarToggle.addStyleName(VPopupCalendar.CLASSNAME
                     + "-button-readonly");
         } else {
index ececdb677537b7edfcc8e9b578c9cf3c4974e30d..b5297f535af2401bcc1d3d8ed543694c5045bc7a 100644 (file)
@@ -18,6 +18,7 @@ package com.vaadin.client.ui.datefield;
 
 import com.vaadin.client.ApplicationConnection;
 import com.vaadin.client.UIDL;
+import com.vaadin.client.ui.VTextualDate;
 import com.vaadin.shared.ui.datefield.Resolution;
 import com.vaadin.shared.ui.datefield.TextualDateFieldState;
 
@@ -25,11 +26,11 @@ public class TextualDateConnector extends AbstractDateFieldConnector {
 
     @Override
     public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
-        Resolution origRes = getWidget().currentResolution;
-        String oldLocale = getWidget().currentLocale;
+        Resolution origRes = getWidget().getCurrentResolution();
+        String oldLocale = getWidget().getCurrentLocale();
         super.updateFromUIDL(uidl, client);
-        if (origRes != getWidget().currentResolution
-                || oldLocale != getWidget().currentLocale) {
+        if (origRes != getWidget().getCurrentResolution()
+                || oldLocale != getWidget().getCurrentLocale()) {
             // force recreating format string
             getWidget().formatStr = null;
         }
@@ -48,7 +49,7 @@ public class TextualDateConnector extends AbstractDateFieldConnector {
             getWidget().text.setTabIndex(uidl.getIntAttribute("tabindex"));
         }
 
-        if (getWidget().readonly) {
+        if (getWidget().isReadonly()) {
             getWidget().text.addStyleDependentName("readonly");
         } else {
             getWidget().text.removeStyleDependentName("readonly");
diff --git a/client/src/com/vaadin/client/ui/datefield/VCalendarPanel.java b/client/src/com/vaadin/client/ui/datefield/VCalendarPanel.java
deleted file mode 100644 (file)
index 0d77b4d..0000000
+++ /dev/null
@@ -1,1828 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.datefield;
-
-import java.util.Date;
-import java.util.Iterator;
-
-import com.google.gwt.dom.client.Node;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.event.dom.client.ChangeEvent;
-import com.google.gwt.event.dom.client.ChangeHandler;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.DomEvent;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.dom.client.KeyDownEvent;
-import com.google.gwt.event.dom.client.KeyDownHandler;
-import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.event.dom.client.KeyPressHandler;
-import com.google.gwt.event.dom.client.MouseDownEvent;
-import com.google.gwt.event.dom.client.MouseDownHandler;
-import com.google.gwt.event.dom.client.MouseOutEvent;
-import com.google.gwt.event.dom.client.MouseOutHandler;
-import com.google.gwt.event.dom.client.MouseUpEvent;
-import com.google.gwt.event.dom.client.MouseUpHandler;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Timer;
-import com.google.gwt.user.client.ui.Button;
-import com.google.gwt.user.client.ui.FlexTable;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.InlineHTML;
-import com.google.gwt.user.client.ui.ListBox;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.DateTimeService;
-import com.vaadin.client.Util;
-import com.vaadin.client.VConsole;
-import com.vaadin.client.ui.FocusableFlexTable;
-import com.vaadin.client.ui.SubPartAware;
-import com.vaadin.client.ui.label.VLabel;
-import com.vaadin.client.ui.nativeselect.VNativeSelect;
-import com.vaadin.shared.ui.datefield.Resolution;
-
-@SuppressWarnings("deprecation")
-public class VCalendarPanel extends FocusableFlexTable implements
-        KeyDownHandler, KeyPressHandler, MouseOutHandler, MouseDownHandler,
-        MouseUpHandler, BlurHandler, FocusHandler, SubPartAware {
-
-    public interface SubmitListener {
-
-        /**
-         * Called when calendar user triggers a submitting operation in calendar
-         * panel. Eg. clicking on day or hitting enter.
-         */
-        void onSubmit();
-
-        /**
-         * On eg. ESC key.
-         */
-        void onCancel();
-    }
-
-    /**
-     * Blur listener that listens to blur event from the panel
-     */
-    public interface FocusOutListener {
-        /**
-         * @return true if the calendar panel is not used after focus moves out
-         */
-        boolean onFocusOut(DomEvent<?> event);
-    }
-
-    /**
-     * FocusChangeListener is notified when the panel changes its _focused_
-     * value.
-     */
-    public interface FocusChangeListener {
-        void focusChanged(Date focusedDate);
-    }
-
-    /**
-     * Dispatches an event when the panel when time is changed
-     */
-    public interface TimeChangeListener {
-
-        void changed(int hour, int min, int sec, int msec);
-    }
-
-    /**
-     * Represents a Date button in the calendar
-     */
-    private class VEventButton extends Button {
-        public VEventButton() {
-            addMouseDownHandler(VCalendarPanel.this);
-            addMouseOutHandler(VCalendarPanel.this);
-            addMouseUpHandler(VCalendarPanel.this);
-        }
-    }
-
-    private static final String CN_FOCUSED = "focused";
-
-    private static final String CN_TODAY = "today";
-
-    private static final String CN_SELECTED = "selected";
-
-    private static final String CN_OFFMONTH = "offmonth";
-
-    /**
-     * Represents a click handler for when a user selects a value by using the
-     * mouse
-     */
-    private ClickHandler dayClickHandler = new ClickHandler() {
-        /*
-         * (non-Javadoc)
-         * 
-         * @see
-         * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt
-         * .event.dom.client.ClickEvent)
-         */
-        @Override
-        public void onClick(ClickEvent event) {
-            Date newDate = ((Day) event.getSource()).getDate();
-            if (newDate.getMonth() != displayedMonth.getMonth()
-                    || newDate.getYear() != displayedMonth.getYear()) {
-                // If an off-month date was clicked, we must change the
-                // displayed month and re-render the calendar (#8931)
-                displayedMonth.setMonth(newDate.getMonth());
-                displayedMonth.setYear(newDate.getYear());
-                renderCalendar();
-            }
-            focusDay(newDate);
-            selectFocused();
-            onSubmit();
-        }
-    };
-
-    private VEventButton prevYear;
-
-    private VEventButton nextYear;
-
-    private VEventButton prevMonth;
-
-    private VEventButton nextMonth;
-
-    private VTime time;
-
-    private FlexTable days = new FlexTable();
-
-    private Resolution resolution = Resolution.YEAR;
-
-    private int focusedRow;
-
-    private Timer mouseTimer;
-
-    private Date value;
-
-    private boolean enabled = true;
-
-    private boolean readonly = false;
-
-    private DateTimeService dateTimeService;
-
-    private boolean showISOWeekNumbers;
-
-    private Date displayedMonth;
-
-    private Date focusedDate;
-
-    private Day selectedDay;
-
-    private Day focusedDay;
-
-    private FocusOutListener focusOutListener;
-
-    private SubmitListener submitListener;
-
-    private FocusChangeListener focusChangeListener;
-
-    private TimeChangeListener timeChangeListener;
-
-    private boolean hasFocus = false;
-
-    private VDateField parent;
-
-    private boolean initialRenderDone = false;
-
-    public VCalendarPanel() {
-
-        setStyleName(VDateField.CLASSNAME + "-calendarpanel");
-
-        /*
-         * Firefox auto-repeat works correctly only if we use a key press
-         * handler, other browsers handle it correctly when using a key down
-         * handler
-         */
-        if (BrowserInfo.get().isGecko()) {
-            addKeyPressHandler(this);
-        } else {
-            addKeyDownHandler(this);
-        }
-        addFocusHandler(this);
-        addBlurHandler(this);
-    }
-
-    public void setParentField(VDateField parent) {
-        this.parent = parent;
-    }
-
-    /**
-     * Sets the focus to given date in the current view. Used when moving in the
-     * calendar with the keyboard.
-     * 
-     * @param date
-     *            A Date representing the day of month to be focused. Must be
-     *            one of the days currently visible.
-     */
-    private void focusDay(Date date) {
-        // Only used when calender body is present
-        if (resolution.getCalendarField() > Resolution.MONTH.getCalendarField()) {
-            if (focusedDay != null) {
-                focusedDay.removeStyleDependentName(CN_FOCUSED);
-            }
-
-            if (date != null && focusedDate != null) {
-                focusedDate.setTime(date.getTime());
-                int rowCount = days.getRowCount();
-                for (int i = 0; i < rowCount; i++) {
-                    int cellCount = days.getCellCount(i);
-                    for (int j = 0; j < cellCount; j++) {
-                        Widget widget = days.getWidget(i, j);
-                        if (widget != null && widget instanceof Day) {
-                            Day curday = (Day) widget;
-                            if (curday.getDate().equals(date)) {
-                                curday.addStyleDependentName(CN_FOCUSED);
-                                focusedDay = curday;
-                                focusedRow = i;
-                                return;
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Sets the selection highlight to a given day in the current view
-     * 
-     * @param date
-     *            A Date representing the day of month to be selected. Must be
-     *            one of the days currently visible.
-     * 
-     */
-    private void selectDate(Date date) {
-        if (selectedDay != null) {
-            selectedDay.removeStyleDependentName(CN_SELECTED);
-        }
-
-        int rowCount = days.getRowCount();
-        for (int i = 0; i < rowCount; i++) {
-            int cellCount = days.getCellCount(i);
-            for (int j = 0; j < cellCount; j++) {
-                Widget widget = days.getWidget(i, j);
-                if (widget != null && widget instanceof Day) {
-                    Day curday = (Day) widget;
-                    if (curday.getDate().equals(date)) {
-                        curday.addStyleDependentName(CN_SELECTED);
-                        selectedDay = curday;
-                        return;
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Updates year, month, day from focusedDate to value
-     */
-    private void selectFocused() {
-        if (focusedDate != null) {
-            if (value == null) {
-                // No previously selected value (set to null on server side).
-                // Create a new date using current date and time
-                value = new Date();
-            }
-            /*
-             * #5594 set Date (day) to 1 in order to prevent any kind of
-             * wrapping of months when later setting the month. (e.g. 31 ->
-             * month with 30 days -> wraps to the 1st of the following month,
-             * e.g. 31st of May -> 31st of April = 1st of May)
-             */
-            value.setDate(1);
-            if (value.getYear() != focusedDate.getYear()) {
-                value.setYear(focusedDate.getYear());
-            }
-            if (value.getMonth() != focusedDate.getMonth()) {
-                value.setMonth(focusedDate.getMonth());
-            }
-            if (value.getDate() != focusedDate.getDate()) {
-            }
-            // We always need to set the date, even if it hasn't changed, since
-            // it was forced to 1 above.
-            value.setDate(focusedDate.getDate());
-
-            selectDate(focusedDate);
-        } else {
-            VConsole.log("Trying to select a the focused date which is NULL!");
-        }
-    }
-
-    protected boolean onValueChange() {
-        return false;
-    }
-
-    public Resolution getResolution() {
-        return resolution;
-    }
-
-    public void setResolution(Resolution resolution) {
-        this.resolution = resolution;
-        if (time != null) {
-            time.removeFromParent();
-            time = null;
-        }
-    }
-
-    private boolean isReadonly() {
-        return readonly;
-    }
-
-    private boolean isEnabled() {
-        return enabled;
-    }
-
-    @Override
-    public void setStyleName(String style) {
-        super.setStyleName(style);
-        if (initialRenderDone) {
-            // Dynamic updates to the stylename needs to render the calendar to
-            // update the inner element stylenames
-            renderCalendar();
-        }
-    }
-
-    @Override
-    public void setStylePrimaryName(String style) {
-        super.setStylePrimaryName(style);
-        if (initialRenderDone) {
-            // Dynamic updates to the stylename needs to render the calendar to
-            // update the inner element stylenames
-            renderCalendar();
-        }
-    }
-
-    private void clearCalendarBody(boolean remove) {
-        if (!remove) {
-            // Leave the cells in place but clear their contents
-
-            // This has the side effect of ensuring that the calendar always
-            // contain 7 rows.
-            for (int row = 1; row < 7; row++) {
-                for (int col = 0; col < 8; col++) {
-                    days.setHTML(row, col, "&nbsp;");
-                }
-            }
-        } else if (getRowCount() > 1) {
-            removeRow(1);
-            days.clear();
-        }
-    }
-
-    /**
-     * Builds the top buttons and current month and year header.
-     * 
-     * @param needsMonth
-     *            Should the month buttons be visible?
-     */
-    private void buildCalendarHeader(boolean needsMonth) {
-
-        getRowFormatter().addStyleName(0,
-                parent.getStylePrimaryName() + "-calendarpanel-header");
-
-        if (prevMonth == null && needsMonth) {
-            prevMonth = new VEventButton();
-            prevMonth.setHTML("&lsaquo;");
-            prevMonth.setStyleName("v-button-prevmonth");
-            prevMonth.setTabIndex(-1);
-            nextMonth = new VEventButton();
-            nextMonth.setHTML("&rsaquo;");
-            nextMonth.setStyleName("v-button-nextmonth");
-            nextMonth.setTabIndex(-1);
-
-            setWidget(0, 3, nextMonth);
-            setWidget(0, 1, prevMonth);
-        } else if (prevMonth != null && !needsMonth) {
-            // Remove month traverse buttons
-            remove(prevMonth);
-            remove(nextMonth);
-            prevMonth = null;
-            nextMonth = null;
-        }
-
-        if (prevYear == null) {
-            prevYear = new VEventButton();
-            prevYear.setHTML("&laquo;");
-            prevYear.setStyleName("v-button-prevyear");
-            prevYear.setTabIndex(-1);
-            nextYear = new VEventButton();
-            nextYear.setHTML("&raquo;");
-            nextYear.setStyleName("v-button-nextyear");
-            nextYear.setTabIndex(-1);
-            setWidget(0, 0, prevYear);
-            setWidget(0, 4, nextYear);
-        }
-
-        final String monthName = needsMonth ? getDateTimeService().getMonth(
-                displayedMonth.getMonth()) : "";
-        final int year = displayedMonth.getYear() + 1900;
-
-        getFlexCellFormatter().setStyleName(0, 2,
-                parent.getStylePrimaryName() + "-calendarpanel-month");
-        getFlexCellFormatter().setStyleName(0, 0,
-                parent.getStylePrimaryName() + "-calendarpanel-prevyear");
-        getFlexCellFormatter().setStyleName(0, 4,
-                parent.getStylePrimaryName() + "-calendarpanel-nextyear");
-        getFlexCellFormatter().setStyleName(0, 3,
-                parent.getStylePrimaryName() + "-calendarpanel-nextmonth");
-        getFlexCellFormatter().setStyleName(0, 1,
-                parent.getStylePrimaryName() + "-calendarpanel-prevmonth");
-
-        setHTML(0, 2, "<span class=\"" + parent.getStylePrimaryName()
-                + "-calendarpanel-month\">" + monthName + " " + year
-                + "</span>");
-    }
-
-    private DateTimeService getDateTimeService() {
-        return dateTimeService;
-    }
-
-    public void setDateTimeService(DateTimeService dateTimeService) {
-        this.dateTimeService = dateTimeService;
-    }
-
-    /**
-     * Returns whether ISO 8601 week numbers should be shown in the value
-     * selector or not. ISO 8601 defines that a week always starts with a Monday
-     * so the week numbers are only shown if this is the case.
-     * 
-     * @return true if week number should be shown, false otherwise
-     */
-    public boolean isShowISOWeekNumbers() {
-        return showISOWeekNumbers;
-    }
-
-    public void setShowISOWeekNumbers(boolean showISOWeekNumbers) {
-        this.showISOWeekNumbers = showISOWeekNumbers;
-    }
-
-    /**
-     * Builds the day and time selectors of the calendar.
-     */
-    private void buildCalendarBody() {
-
-        final int weekColumn = 0;
-        final int firstWeekdayColumn = 1;
-        final int headerRow = 0;
-
-        setWidget(1, 0, days);
-        setCellPadding(0);
-        setCellSpacing(0);
-        getFlexCellFormatter().setColSpan(1, 0, 5);
-        getFlexCellFormatter().setStyleName(1, 0,
-                parent.getStylePrimaryName() + "-calendarpanel-body");
-
-        days.getFlexCellFormatter().setStyleName(headerRow, weekColumn,
-                "v-week");
-        days.setHTML(headerRow, weekColumn, "<strong></strong>");
-        // Hide the week column if week numbers are not to be displayed.
-        days.getFlexCellFormatter().setVisible(headerRow, weekColumn,
-                isShowISOWeekNumbers());
-
-        days.getRowFormatter().setStyleName(headerRow,
-                parent.getStylePrimaryName() + "-calendarpanel-weekdays");
-
-        if (isShowISOWeekNumbers()) {
-            days.getFlexCellFormatter().setStyleName(headerRow, weekColumn,
-                    "v-first");
-            days.getFlexCellFormatter().setStyleName(headerRow,
-                    firstWeekdayColumn, "");
-            days.getRowFormatter()
-                    .addStyleName(
-                            headerRow,
-                            parent.getStylePrimaryName()
-                                    + "-calendarpanel-weeknumbers");
-        } else {
-            days.getFlexCellFormatter().setStyleName(headerRow, weekColumn, "");
-            days.getFlexCellFormatter().setStyleName(headerRow,
-                    firstWeekdayColumn, "v-first");
-        }
-
-        days.getFlexCellFormatter().setStyleName(headerRow,
-                firstWeekdayColumn + 6, "v-last");
-
-        // Print weekday names
-        final int firstDay = getDateTimeService().getFirstDayOfWeek();
-        for (int i = 0; i < 7; i++) {
-            int day = i + firstDay;
-            if (day > 6) {
-                day = 0;
-            }
-            if (getResolution().getCalendarField() > Resolution.MONTH
-                    .getCalendarField()) {
-                days.setHTML(headerRow, firstWeekdayColumn + i, "<strong>"
-                        + getDateTimeService().getShortDay(day) + "</strong>");
-            } else {
-                days.setHTML(headerRow, firstWeekdayColumn + i, "");
-            }
-        }
-
-        // Zero out hours, minutes, seconds, and milliseconds to compare dates
-        // without time part
-        final Date tmp = new Date();
-        final Date today = new Date(tmp.getYear(), tmp.getMonth(),
-                tmp.getDate());
-
-        final Date selectedDate = value == null ? null : new Date(
-                value.getYear(), value.getMonth(), value.getDate());
-
-        final int startWeekDay = getDateTimeService().getStartWeekDay(
-                displayedMonth);
-        final Date curr = (Date) displayedMonth.clone();
-        // Start from the first day of the week that at least partially belongs
-        // to the current month
-        curr.setDate(1 - startWeekDay);
-
-        // No month has more than 6 weeks so 6 is a safe maximum for rows.
-        for (int weekOfMonth = 1; weekOfMonth < 7; weekOfMonth++) {
-            for (int dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++) {
-
-                // Actually write the day of month
-                Day day = new Day((Date) curr.clone());
-                day.setStyleName(parent.getStylePrimaryName()
-                        + "-calendarpanel-day");
-
-                if (curr.equals(selectedDate)) {
-                    day.addStyleDependentName(CN_SELECTED);
-                    selectedDay = day;
-                }
-                if (curr.equals(today)) {
-                    day.addStyleDependentName(CN_TODAY);
-                }
-                if (curr.equals(focusedDate)) {
-                    focusedDay = day;
-                    focusedRow = weekOfMonth;
-                    if (hasFocus) {
-                        day.addStyleDependentName(CN_FOCUSED);
-                    }
-                }
-                if (curr.getMonth() != displayedMonth.getMonth()) {
-                    day.addStyleDependentName(CN_OFFMONTH);
-                }
-
-                days.setWidget(weekOfMonth, firstWeekdayColumn + dayOfWeek, day);
-
-                // ISO week numbers if requested
-                days.getCellFormatter().setVisible(weekOfMonth, weekColumn,
-                        isShowISOWeekNumbers());
-                if (isShowISOWeekNumbers()) {
-                    final String baseCssClass = parent.getStylePrimaryName()
-                            + "-calendarpanel-weeknumber";
-                    String weekCssClass = baseCssClass;
-
-                    int weekNumber = DateTimeService.getISOWeekNumber(curr);
-
-                    days.setHTML(weekOfMonth, 0, "<span class=\""
-                            + weekCssClass + "\"" + ">" + weekNumber
-                            + "</span>");
-                }
-                curr.setDate(curr.getDate() + 1);
-            }
-        }
-    }
-
-    /**
-     * Do we need the time selector
-     * 
-     * @return True if it is required
-     */
-    private boolean isTimeSelectorNeeded() {
-        return getResolution().getCalendarField() > Resolution.DAY
-                .getCalendarField();
-    }
-
-    /**
-     * Updates the calendar and text field with the selected dates.
-     */
-    public void renderCalendar() {
-
-        super.setStylePrimaryName(parent.getStylePrimaryName()
-                + "-calendarpanel");
-
-        if (focusedDate == null) {
-            Date now = new Date();
-            // focusedDate must have zero hours, mins, secs, millisecs
-            focusedDate = new Date(now.getYear(), now.getMonth(), now.getDate());
-            displayedMonth = new Date(now.getYear(), now.getMonth(), 1);
-        }
-
-        if (getResolution().getCalendarField() <= Resolution.MONTH
-                .getCalendarField() && focusChangeListener != null) {
-            focusChangeListener.focusChanged(new Date(focusedDate.getTime()));
-        }
-
-        final boolean needsMonth = getResolution().getCalendarField() > Resolution.YEAR
-                .getCalendarField();
-        boolean needsBody = getResolution().getCalendarField() >= Resolution.DAY
-                .getCalendarField();
-        buildCalendarHeader(needsMonth);
-        clearCalendarBody(!needsBody);
-        if (needsBody) {
-            buildCalendarBody();
-        }
-
-        if (isTimeSelectorNeeded() && time == null) {
-            time = new VTime();
-            setWidget(2, 0, time);
-            getFlexCellFormatter().setColSpan(2, 0, 5);
-            getFlexCellFormatter().setStyleName(2, 0,
-                    parent.getStylePrimaryName() + "-calendarpanel-time");
-        } else if (isTimeSelectorNeeded()) {
-            time.updateTimes();
-        } else if (time != null) {
-            remove(time);
-        }
-
-        initialRenderDone = true;
-    }
-
-    /**
-     * Moves the focus forward the given number of days.
-     */
-    private void focusNextDay(int days) {
-        int oldMonth = focusedDate.getMonth();
-        int oldYear = focusedDate.getYear();
-        focusedDate.setDate(focusedDate.getDate() + days);
-
-        if (focusedDate.getMonth() == oldMonth
-                && focusedDate.getYear() == oldYear) {
-            // Month did not change, only move the selection
-            focusDay(focusedDate);
-        } else {
-            // If the month changed we need to re-render the calendar
-            displayedMonth.setMonth(focusedDate.getMonth());
-            displayedMonth.setYear(focusedDate.getYear());
-            renderCalendar();
-        }
-    }
-
-    /**
-     * Moves the focus backward the given number of days.
-     */
-    private void focusPreviousDay(int days) {
-        focusNextDay(-days);
-    }
-
-    /**
-     * Selects the next month
-     */
-    private void focusNextMonth() {
-
-        int currentMonth = focusedDate.getMonth();
-        focusedDate.setMonth(currentMonth + 1);
-        int requestedMonth = (currentMonth + 1) % 12;
-
-        /*
-         * If the selected value was e.g. 31.3 the new value would be 31.4 but
-         * this value is invalid so the new value will be 1.5. This is taken
-         * care of by decreasing the value until we have the correct month.
-         */
-        while (focusedDate.getMonth() != requestedMonth) {
-            focusedDate.setDate(focusedDate.getDate() - 1);
-        }
-        displayedMonth.setMonth(displayedMonth.getMonth() + 1);
-
-        renderCalendar();
-    }
-
-    /**
-     * Selects the previous month
-     */
-    private void focusPreviousMonth() {
-        int currentMonth = focusedDate.getMonth();
-        focusedDate.setMonth(currentMonth - 1);
-
-        /*
-         * If the selected value was e.g. 31.12 the new value would be 31.11 but
-         * this value is invalid so the new value will be 1.12. This is taken
-         * care of by decreasing the value until we have the correct month.
-         */
-        while (focusedDate.getMonth() == currentMonth) {
-            focusedDate.setDate(focusedDate.getDate() - 1);
-        }
-        displayedMonth.setMonth(displayedMonth.getMonth() - 1);
-
-        renderCalendar();
-    }
-
-    /**
-     * Selects the previous year
-     */
-    private void focusPreviousYear(int years) {
-        int currentMonth = focusedDate.getMonth();
-        focusedDate.setYear(focusedDate.getYear() - years);
-        displayedMonth.setYear(displayedMonth.getYear() - years);
-        /*
-         * If the focused date was a leap day (Feb 29), the new date becomes Mar
-         * 1 if the new year is not also a leap year. Set it to Feb 28 instead.
-         */
-        if (focusedDate.getMonth() != currentMonth) {
-            focusedDate.setDate(0);
-        }
-        renderCalendar();
-    }
-
-    /**
-     * Selects the next year
-     */
-    private void focusNextYear(int years) {
-        int currentMonth = focusedDate.getMonth();
-        focusedDate.setYear(focusedDate.getYear() + years);
-        displayedMonth.setYear(displayedMonth.getYear() + years);
-        /*
-         * If the focused date was a leap day (Feb 29), the new date becomes Mar
-         * 1 if the new year is not also a leap year. Set it to Feb 28 instead.
-         */
-        if (focusedDate.getMonth() != currentMonth) {
-            focusedDate.setDate(0);
-        }
-        renderCalendar();
-    }
-
-    /**
-     * Handles a user click on the component
-     * 
-     * @param sender
-     *            The component that was clicked
-     * @param updateVariable
-     *            Should the value field be updated
-     * 
-     */
-    private void processClickEvent(Widget sender) {
-        if (!isEnabled() || isReadonly()) {
-            return;
-        }
-        if (sender == prevYear) {
-            focusPreviousYear(1);
-        } else if (sender == nextYear) {
-            focusNextYear(1);
-        } else if (sender == prevMonth) {
-            focusPreviousMonth();
-        } else if (sender == nextMonth) {
-            focusNextMonth();
-        }
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see
-     * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
-     * .event.dom.client.KeyDownEvent)
-     */
-    @Override
-    public void onKeyDown(KeyDownEvent event) {
-        handleKeyPress(event);
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see
-     * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google
-     * .gwt.event.dom.client.KeyPressEvent)
-     */
-    @Override
-    public void onKeyPress(KeyPressEvent event) {
-        handleKeyPress(event);
-    }
-
-    /**
-     * Handles the keypress from both the onKeyPress event and the onKeyDown
-     * event
-     * 
-     * @param event
-     *            The keydown/keypress event
-     */
-    private void handleKeyPress(DomEvent<?> event) {
-        if (time != null
-                && time.getElement().isOrHasChild(
-                        (Node) event.getNativeEvent().getEventTarget().cast())) {
-            int nativeKeyCode = event.getNativeEvent().getKeyCode();
-            if (nativeKeyCode == getSelectKey()) {
-                onSubmit(); // submit happens if enter key hit down on listboxes
-                event.preventDefault();
-                event.stopPropagation();
-            }
-            return;
-        }
-
-        // Check tabs
-        int keycode = event.getNativeEvent().getKeyCode();
-        if (keycode == KeyCodes.KEY_TAB && event.getNativeEvent().getShiftKey()) {
-            if (onTabOut(event)) {
-                return;
-            }
-        }
-
-        // Handle the navigation
-        if (handleNavigation(keycode, event.getNativeEvent().getCtrlKey()
-                || event.getNativeEvent().getMetaKey(), event.getNativeEvent()
-                .getShiftKey())) {
-            event.preventDefault();
-        }
-
-    }
-
-    /**
-     * Notifies submit-listeners of a submit event
-     */
-    private void onSubmit() {
-        if (getSubmitListener() != null) {
-            getSubmitListener().onSubmit();
-        }
-    }
-
-    /**
-     * Notifies submit-listeners of a cancel event
-     */
-    private void onCancel() {
-        if (getSubmitListener() != null) {
-            getSubmitListener().onCancel();
-        }
-    }
-
-    /**
-     * Handles the keyboard navigation when the resolution is set to years.
-     * 
-     * @param keycode
-     *            The keycode to process
-     * @param ctrl
-     *            Is ctrl pressed?
-     * @param shift
-     *            is shift pressed
-     * @return Returns true if the keycode was processed, else false
-     */
-    protected boolean handleNavigationYearMode(int keycode, boolean ctrl,
-            boolean shift) {
-
-        // Ctrl and Shift selection not supported
-        if (ctrl || shift) {
-            return false;
-        }
-
-        else if (keycode == getPreviousKey()) {
-            focusNextYear(10); // Add 10 years
-            return true;
-        }
-
-        else if (keycode == getForwardKey()) {
-            focusNextYear(1); // Add 1 year
-            return true;
-        }
-
-        else if (keycode == getNextKey()) {
-            focusPreviousYear(10); // Subtract 10 years
-            return true;
-        }
-
-        else if (keycode == getBackwardKey()) {
-            focusPreviousYear(1); // Subtract 1 year
-            return true;
-
-        } else if (keycode == getSelectKey()) {
-            value = (Date) focusedDate.clone();
-            onSubmit();
-            return true;
-
-        } else if (keycode == getResetKey()) {
-            // Restore showing value the selected value
-            focusedDate.setTime(value.getTime());
-            renderCalendar();
-            return true;
-
-        } else if (keycode == getCloseKey()) {
-            // TODO fire listener, on users responsibility??
-
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Handle the keyboard navigation when the resolution is set to MONTH
-     * 
-     * @param keycode
-     *            The keycode to handle
-     * @param ctrl
-     *            Was the ctrl key pressed?
-     * @param shift
-     *            Was the shift key pressed?
-     * @return
-     */
-    protected boolean handleNavigationMonthMode(int keycode, boolean ctrl,
-            boolean shift) {
-
-        // Ctrl selection not supported
-        if (ctrl) {
-            return false;
-
-        } else if (keycode == getPreviousKey()) {
-            focusNextYear(1); // Add 1 year
-            return true;
-
-        } else if (keycode == getForwardKey()) {
-            focusNextMonth(); // Add 1 month
-            return true;
-
-        } else if (keycode == getNextKey()) {
-            focusPreviousYear(1); // Subtract 1 year
-            return true;
-
-        } else if (keycode == getBackwardKey()) {
-            focusPreviousMonth(); // Subtract 1 month
-            return true;
-
-        } else if (keycode == getSelectKey()) {
-            value = (Date) focusedDate.clone();
-            onSubmit();
-            return true;
-
-        } else if (keycode == getResetKey()) {
-            // Restore showing value the selected value
-            focusedDate.setTime(value.getTime());
-            renderCalendar();
-            return true;
-
-        } else if (keycode == getCloseKey() || keycode == KeyCodes.KEY_TAB) {
-
-            // TODO fire close event
-
-            return true;
-        }
-
-        return false;
-    }
-
-    /**
-     * Handle keyboard navigation what the resolution is set to DAY
-     * 
-     * @param keycode
-     *            The keycode to handle
-     * @param ctrl
-     *            Was the ctrl key pressed?
-     * @param shift
-     *            Was the shift key pressed?
-     * @return Return true if the key press was handled by the method, else
-     *         return false.
-     */
-    protected boolean handleNavigationDayMode(int keycode, boolean ctrl,
-            boolean shift) {
-
-        // Ctrl key is not in use
-        if (ctrl) {
-            return false;
-        }
-
-        /*
-         * Jumps to the next day.
-         */
-        if (keycode == getForwardKey() && !shift) {
-            focusNextDay(1);
-            return true;
-
-            /*
-             * Jumps to the previous day
-             */
-        } else if (keycode == getBackwardKey() && !shift) {
-            focusPreviousDay(1);
-            return true;
-
-            /*
-             * Jumps one week forward in the calendar
-             */
-        } else if (keycode == getNextKey() && !shift) {
-            focusNextDay(7);
-            return true;
-
-            /*
-             * Jumps one week back in the calendar
-             */
-        } else if (keycode == getPreviousKey() && !shift) {
-            focusPreviousDay(7);
-            return true;
-
-            /*
-             * Selects the value that is chosen
-             */
-        } else if (keycode == getSelectKey() && !shift) {
-            selectFocused();
-            onSubmit(); // submit
-            return true;
-
-        } else if (keycode == getCloseKey()) {
-            onCancel();
-            // TODO close event
-
-            return true;
-
-            /*
-             * Jumps to the next month
-             */
-        } else if (shift && keycode == getForwardKey()) {
-            focusNextMonth();
-            return true;
-
-            /*
-             * Jumps to the previous month
-             */
-        } else if (shift && keycode == getBackwardKey()) {
-            focusPreviousMonth();
-            return true;
-
-            /*
-             * Jumps to the next year
-             */
-        } else if (shift && keycode == getPreviousKey()) {
-            focusNextYear(1);
-            return true;
-
-            /*
-             * Jumps to the previous year
-             */
-        } else if (shift && keycode == getNextKey()) {
-            focusPreviousYear(1);
-            return true;
-
-            /*
-             * Resets the selection
-             */
-        } else if (keycode == getResetKey() && !shift) {
-            // Restore showing value the selected value
-            focusedDate = new Date(value.getYear(), value.getMonth(),
-                    value.getDate());
-            displayedMonth = new Date(value.getYear(), value.getMonth(), 1);
-            renderCalendar();
-            return true;
-        }
-
-        return false;
-    }
-
-    /**
-     * Handles the keyboard navigation
-     * 
-     * @param keycode
-     *            The key code that was pressed
-     * @param ctrl
-     *            Was the ctrl key pressed
-     * @param shift
-     *            Was the shift key pressed
-     * @return Return true if key press was handled by the component, else
-     *         return false
-     */
-    protected boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
-        if (!isEnabled() || isReadonly()) {
-            return false;
-        }
-
-        else if (resolution == Resolution.YEAR) {
-            return handleNavigationYearMode(keycode, ctrl, shift);
-        }
-
-        else if (resolution == Resolution.MONTH) {
-            return handleNavigationMonthMode(keycode, ctrl, shift);
-        }
-
-        else if (resolution == Resolution.DAY) {
-            return handleNavigationDayMode(keycode, ctrl, shift);
-        }
-
-        else {
-            return handleNavigationDayMode(keycode, ctrl, shift);
-        }
-
-    }
-
-    /**
-     * Returns the reset key which will reset the calendar to the previous
-     * selection. By default this is backspace but it can be overriden to change
-     * the key to whatever you want.
-     * 
-     * @return
-     */
-    protected int getResetKey() {
-        return KeyCodes.KEY_BACKSPACE;
-    }
-
-    /**
-     * Returns the select key which selects the value. By default this is the
-     * enter key but it can be changed to whatever you like by overriding this
-     * method.
-     * 
-     * @return
-     */
-    protected int getSelectKey() {
-        return KeyCodes.KEY_ENTER;
-    }
-
-    /**
-     * Returns the key that closes the popup window if this is a VPopopCalendar.
-     * Else this does nothing. By default this is the Escape key but you can
-     * change the key to whatever you want by overriding this method.
-     * 
-     * @return
-     */
-    protected int getCloseKey() {
-        return KeyCodes.KEY_ESCAPE;
-    }
-
-    /**
-     * The key that selects the next day in the calendar. By default this is the
-     * right arrow key but by overriding this method it can be changed to
-     * whatever you like.
-     * 
-     * @return
-     */
-    protected int getForwardKey() {
-        return KeyCodes.KEY_RIGHT;
-    }
-
-    /**
-     * The key that selects the previous day in the calendar. By default this is
-     * the left arrow key but by overriding this method it can be changed to
-     * whatever you like.
-     * 
-     * @return
-     */
-    protected int getBackwardKey() {
-        return KeyCodes.KEY_LEFT;
-    }
-
-    /**
-     * The key that selects the next week in the calendar. By default this is
-     * the down arrow key but by overriding this method it can be changed to
-     * whatever you like.
-     * 
-     * @return
-     */
-    protected int getNextKey() {
-        return KeyCodes.KEY_DOWN;
-    }
-
-    /**
-     * The key that selects the previous week in the calendar. By default this
-     * is the up arrow key but by overriding this method it can be changed to
-     * whatever you like.
-     * 
-     * @return
-     */
-    protected int getPreviousKey() {
-        return KeyCodes.KEY_UP;
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see
-     * com.google.gwt.event.dom.client.MouseOutHandler#onMouseOut(com.google
-     * .gwt.event.dom.client.MouseOutEvent)
-     */
-    @Override
-    public void onMouseOut(MouseOutEvent event) {
-        if (mouseTimer != null) {
-            mouseTimer.cancel();
-        }
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see
-     * com.google.gwt.event.dom.client.MouseDownHandler#onMouseDown(com.google
-     * .gwt.event.dom.client.MouseDownEvent)
-     */
-    @Override
-    public void onMouseDown(MouseDownEvent event) {
-        // Allow user to click-n-hold for fast-forward or fast-rewind.
-        // Timer is first used for a 500ms delay after mousedown. After that has
-        // elapsed, another timer is triggered to go off every 150ms. Both
-        // timers are cancelled on mouseup or mouseout.
-        if (event.getSource() instanceof VEventButton) {
-            final VEventButton sender = (VEventButton) event.getSource();
-            processClickEvent(sender);
-            mouseTimer = new Timer() {
-                @Override
-                public void run() {
-                    mouseTimer = new Timer() {
-                        @Override
-                        public void run() {
-                            processClickEvent(sender);
-                        }
-                    };
-                    mouseTimer.scheduleRepeating(150);
-                }
-            };
-            mouseTimer.schedule(500);
-        }
-
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see
-     * com.google.gwt.event.dom.client.MouseUpHandler#onMouseUp(com.google.gwt
-     * .event.dom.client.MouseUpEvent)
-     */
-    @Override
-    public void onMouseUp(MouseUpEvent event) {
-        if (mouseTimer != null) {
-            mouseTimer.cancel();
-        }
-    }
-
-    /**
-     * Sets the data of the Panel.
-     * 
-     * @param currentDate
-     *            The date to set
-     */
-    public void setDate(Date currentDate) {
-
-        // Check that we are not re-rendering an already active date
-        if (currentDate == value && currentDate != null) {
-            return;
-        }
-
-        Date oldDisplayedMonth = displayedMonth;
-        value = currentDate;
-
-        if (value == null) {
-            focusedDate = displayedMonth = null;
-        } else {
-            focusedDate = new Date(value.getYear(), value.getMonth(),
-                    value.getDate());
-            displayedMonth = new Date(value.getYear(), value.getMonth(), 1);
-        }
-
-        // Re-render calendar if the displayed month is changed,
-        // or if a time selector is needed but does not exist.
-        if ((isTimeSelectorNeeded() && time == null)
-                || oldDisplayedMonth == null || value == null
-                || oldDisplayedMonth.getYear() != value.getYear()
-                || oldDisplayedMonth.getMonth() != value.getMonth()) {
-            renderCalendar();
-        } else {
-            focusDay(focusedDate);
-            selectFocused();
-            if (isTimeSelectorNeeded()) {
-                time.updateTimes();
-            }
-        }
-
-        if (!hasFocus) {
-            focusDay(null);
-        }
-    }
-
-    /**
-     * TimeSelector is a widget consisting of list boxes that modifie the Date
-     * object that is given for.
-     * 
-     */
-    public class VTime extends FlowPanel implements ChangeHandler {
-
-        private ListBox hours;
-
-        private ListBox mins;
-
-        private ListBox sec;
-
-        private ListBox ampm;
-
-        /**
-         * Constructor
-         */
-        public VTime() {
-            super();
-            setStyleName(VDateField.CLASSNAME + "-time");
-            buildTime();
-        }
-
-        private ListBox createListBox() {
-            ListBox lb = new ListBox();
-            lb.setStyleName(VNativeSelect.CLASSNAME);
-            lb.addChangeHandler(this);
-            lb.addBlurHandler(VCalendarPanel.this);
-            lb.addFocusHandler(VCalendarPanel.this);
-            return lb;
-        }
-
-        /**
-         * Constructs the ListBoxes and updates their value
-         * 
-         * @param redraw
-         *            Should new instances of the listboxes be created
-         */
-        private void buildTime() {
-            clear();
-
-            hours = createListBox();
-            if (getDateTimeService().isTwelveHourClock()) {
-                hours.addItem("12");
-                for (int i = 1; i < 12; i++) {
-                    hours.addItem((i < 10) ? "0" + i : "" + i);
-                }
-            } else {
-                for (int i = 0; i < 24; i++) {
-                    hours.addItem((i < 10) ? "0" + i : "" + i);
-                }
-            }
-
-            hours.addChangeHandler(this);
-            if (getDateTimeService().isTwelveHourClock()) {
-                ampm = createListBox();
-                final String[] ampmText = getDateTimeService().getAmPmStrings();
-                ampm.addItem(ampmText[0]);
-                ampm.addItem(ampmText[1]);
-                ampm.addChangeHandler(this);
-            }
-
-            if (getResolution().getCalendarField() >= Resolution.MINUTE
-                    .getCalendarField()) {
-                mins = createListBox();
-                for (int i = 0; i < 60; i++) {
-                    mins.addItem((i < 10) ? "0" + i : "" + i);
-                }
-                mins.addChangeHandler(this);
-            }
-            if (getResolution().getCalendarField() >= Resolution.SECOND
-                    .getCalendarField()) {
-                sec = createListBox();
-                for (int i = 0; i < 60; i++) {
-                    sec.addItem((i < 10) ? "0" + i : "" + i);
-                }
-                sec.addChangeHandler(this);
-            }
-
-            final String delimiter = getDateTimeService().getClockDelimeter();
-            if (isReadonly()) {
-                int h = 0;
-                if (value != null) {
-                    h = value.getHours();
-                }
-                if (getDateTimeService().isTwelveHourClock()) {
-                    h -= h < 12 ? 0 : 12;
-                }
-                add(new VLabel(h < 10 ? "0" + h : "" + h));
-            } else {
-                add(hours);
-            }
-
-            if (getResolution().getCalendarField() >= Resolution.MINUTE
-                    .getCalendarField()) {
-                add(new VLabel(delimiter));
-                if (isReadonly()) {
-                    final int m = mins.getSelectedIndex();
-                    add(new VLabel(m < 10 ? "0" + m : "" + m));
-                } else {
-                    add(mins);
-                }
-            }
-            if (getResolution().getCalendarField() >= Resolution.SECOND
-                    .getCalendarField()) {
-                add(new VLabel(delimiter));
-                if (isReadonly()) {
-                    final int s = sec.getSelectedIndex();
-                    add(new VLabel(s < 10 ? "0" + s : "" + s));
-                } else {
-                    add(sec);
-                }
-            }
-            if (getResolution() == Resolution.HOUR) {
-                add(new VLabel(delimiter + "00")); // o'clock
-            }
-            if (getDateTimeService().isTwelveHourClock()) {
-                add(new VLabel("&nbsp;"));
-                if (isReadonly()) {
-                    int i = 0;
-                    if (value != null) {
-                        i = (value.getHours() < 12) ? 0 : 1;
-                    }
-                    add(new VLabel(ampm.getItemText(i)));
-                } else {
-                    add(ampm);
-                }
-            }
-
-            if (isReadonly()) {
-                return;
-            }
-
-            // Update times
-            updateTimes();
-
-            ListBox lastDropDown = getLastDropDown();
-            lastDropDown.addKeyDownHandler(new KeyDownHandler() {
-                @Override
-                public void onKeyDown(KeyDownEvent event) {
-                    boolean shiftKey = event.getNativeEvent().getShiftKey();
-                    if (shiftKey) {
-                        return;
-                    } else {
-                        int nativeKeyCode = event.getNativeKeyCode();
-                        if (nativeKeyCode == KeyCodes.KEY_TAB) {
-                            onTabOut(event);
-                        }
-                    }
-                }
-            });
-
-        }
-
-        private ListBox getLastDropDown() {
-            int i = getWidgetCount() - 1;
-            while (i >= 0) {
-                Widget widget = getWidget(i);
-                if (widget instanceof ListBox) {
-                    return (ListBox) widget;
-                }
-                i--;
-            }
-            return null;
-        }
-
-        /**
-         * Updates the valus to correspond to the values in value
-         */
-        public void updateTimes() {
-            boolean selected = true;
-            if (value == null) {
-                value = new Date();
-                selected = false;
-            }
-            if (getDateTimeService().isTwelveHourClock()) {
-                int h = value.getHours();
-                ampm.setSelectedIndex(h < 12 ? 0 : 1);
-                h -= ampm.getSelectedIndex() * 12;
-                hours.setSelectedIndex(h);
-            } else {
-                hours.setSelectedIndex(value.getHours());
-            }
-            if (getResolution().getCalendarField() >= Resolution.MINUTE
-                    .getCalendarField()) {
-                mins.setSelectedIndex(value.getMinutes());
-            }
-            if (getResolution().getCalendarField() >= Resolution.SECOND
-                    .getCalendarField()) {
-                sec.setSelectedIndex(value.getSeconds());
-            }
-            if (getDateTimeService().isTwelveHourClock()) {
-                ampm.setSelectedIndex(value.getHours() < 12 ? 0 : 1);
-            }
-
-            hours.setEnabled(isEnabled());
-            if (mins != null) {
-                mins.setEnabled(isEnabled());
-            }
-            if (sec != null) {
-                sec.setEnabled(isEnabled());
-            }
-            if (ampm != null) {
-                ampm.setEnabled(isEnabled());
-            }
-
-        }
-
-        private int getMilliseconds() {
-            return DateTimeService.getMilliseconds(value);
-        }
-
-        private DateTimeService getDateTimeService() {
-            if (dateTimeService == null) {
-                dateTimeService = new DateTimeService();
-            }
-            return dateTimeService;
-        }
-
-        /*
-         * (non-Javadoc) VT
-         * 
-         * @see
-         * com.google.gwt.event.dom.client.ChangeHandler#onChange(com.google.gwt
-         * .event.dom.client.ChangeEvent)
-         */
-        @Override
-        public void onChange(ChangeEvent event) {
-            /*
-             * Value from dropdowns gets always set for the value. Like year and
-             * month when resolution is month or year.
-             */
-            if (event.getSource() == hours) {
-                int h = hours.getSelectedIndex();
-                if (getDateTimeService().isTwelveHourClock()) {
-                    h = h + ampm.getSelectedIndex() * 12;
-                }
-                value.setHours(h);
-                if (timeChangeListener != null) {
-                    timeChangeListener.changed(h, value.getMinutes(),
-                            value.getSeconds(),
-                            DateTimeService.getMilliseconds(value));
-                }
-                event.preventDefault();
-                event.stopPropagation();
-            } else if (event.getSource() == mins) {
-                final int m = mins.getSelectedIndex();
-                value.setMinutes(m);
-                if (timeChangeListener != null) {
-                    timeChangeListener.changed(value.getHours(), m,
-                            value.getSeconds(),
-                            DateTimeService.getMilliseconds(value));
-                }
-                event.preventDefault();
-                event.stopPropagation();
-            } else if (event.getSource() == sec) {
-                final int s = sec.getSelectedIndex();
-                value.setSeconds(s);
-                if (timeChangeListener != null) {
-                    timeChangeListener.changed(value.getHours(),
-                            value.getMinutes(), s,
-                            DateTimeService.getMilliseconds(value));
-                }
-                event.preventDefault();
-                event.stopPropagation();
-            } else if (event.getSource() == ampm) {
-                final int h = hours.getSelectedIndex()
-                        + (ampm.getSelectedIndex() * 12);
-                value.setHours(h);
-                if (timeChangeListener != null) {
-                    timeChangeListener.changed(h, value.getMinutes(),
-                            value.getSeconds(),
-                            DateTimeService.getMilliseconds(value));
-                }
-                event.preventDefault();
-                event.stopPropagation();
-            }
-        }
-
-    }
-
-    /**
-     * A widget representing a single day in the calendar panel.
-     */
-    private class Day extends InlineHTML {
-        private final Date date;
-
-        Day(Date date) {
-            super("" + date.getDate());
-            this.date = date;
-            addClickHandler(dayClickHandler);
-        }
-
-        public Date getDate() {
-            return date;
-        }
-    }
-
-    public Date getDate() {
-        return value;
-    }
-
-    /**
-     * If true should be returned if the panel will not be used after this
-     * event.
-     * 
-     * @param event
-     * @return
-     */
-    protected boolean onTabOut(DomEvent<?> event) {
-        if (focusOutListener != null) {
-            return focusOutListener.onFocusOut(event);
-        }
-        return false;
-    }
-
-    /**
-     * A focus out listener is triggered when the panel loosed focus. This can
-     * happen either after a user clicks outside the panel or tabs out.
-     * 
-     * @param listener
-     *            The listener to trigger
-     */
-    public void setFocusOutListener(FocusOutListener listener) {
-        focusOutListener = listener;
-    }
-
-    /**
-     * The submit listener is called when the user selects a value from the
-     * calender either by clicking the day or selects it by keyboard.
-     * 
-     * @param submitListener
-     *            The listener to trigger
-     */
-    public void setSubmitListener(SubmitListener submitListener) {
-        this.submitListener = submitListener;
-    }
-
-    /**
-     * The given FocusChangeListener is notified when the focused date changes
-     * by user either clicking on a new date or by using the keyboard.
-     * 
-     * @param listener
-     *            The FocusChangeListener to be notified
-     */
-    public void setFocusChangeListener(FocusChangeListener listener) {
-        focusChangeListener = listener;
-    }
-
-    /**
-     * The time change listener is triggered when the user changes the time.
-     * 
-     * @param listener
-     */
-    public void setTimeChangeListener(TimeChangeListener listener) {
-        timeChangeListener = listener;
-    }
-
-    /**
-     * Returns the submit listener that listens to selection made from the panel
-     * 
-     * @return The listener or NULL if no listener has been set
-     */
-    public SubmitListener getSubmitListener() {
-        return submitListener;
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see
-     * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event
-     * .dom.client.BlurEvent)
-     */
-    @Override
-    public void onBlur(final BlurEvent event) {
-        if (event.getSource() instanceof VCalendarPanel) {
-            hasFocus = false;
-            focusDay(null);
-        }
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see
-     * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
-     * .dom.client.FocusEvent)
-     */
-    @Override
-    public void onFocus(FocusEvent event) {
-        if (event.getSource() instanceof VCalendarPanel) {
-            hasFocus = true;
-
-            // Focuses the current day if the calendar shows the days
-            if (focusedDay != null) {
-                focusDay(focusedDate);
-            }
-        }
-    }
-
-    private static final String SUBPART_NEXT_MONTH = "nextmon";
-    private static final String SUBPART_PREV_MONTH = "prevmon";
-
-    private static final String SUBPART_NEXT_YEAR = "nexty";
-    private static final String SUBPART_PREV_YEAR = "prevy";
-    private static final String SUBPART_HOUR_SELECT = "h";
-    private static final String SUBPART_MINUTE_SELECT = "m";
-    private static final String SUBPART_SECS_SELECT = "s";
-    private static final String SUBPART_MSECS_SELECT = "ms";
-    private static final String SUBPART_AMPM_SELECT = "ampm";
-    private static final String SUBPART_DAY = "day";
-    private static final String SUBPART_MONTH_YEAR_HEADER = "header";
-
-    @Override
-    public String getSubPartName(Element subElement) {
-        if (contains(nextMonth, subElement)) {
-            return SUBPART_NEXT_MONTH;
-        } else if (contains(prevMonth, subElement)) {
-            return SUBPART_PREV_MONTH;
-        } else if (contains(nextYear, subElement)) {
-            return SUBPART_NEXT_YEAR;
-        } else if (contains(prevYear, subElement)) {
-            return SUBPART_PREV_YEAR;
-        } else if (contains(days, subElement)) {
-            // Day, find out which dayOfMonth and use that as the identifier
-            Day day = Util.findWidget(subElement, Day.class);
-            if (day != null) {
-                Date date = day.getDate();
-                int id = date.getDate();
-                // Zero or negative ids map to days of the preceding month,
-                // past-the-end-of-month ids to days of the following month
-                if (date.getMonth() < displayedMonth.getMonth()) {
-                    id -= DateTimeService.getNumberOfDaysInMonth(date);
-                } else if (date.getMonth() > displayedMonth.getMonth()) {
-                    id += DateTimeService
-                            .getNumberOfDaysInMonth(displayedMonth);
-                }
-                return SUBPART_DAY + id;
-            }
-        } else if (time != null) {
-            if (contains(time.hours, subElement)) {
-                return SUBPART_HOUR_SELECT;
-            } else if (contains(time.mins, subElement)) {
-                return SUBPART_MINUTE_SELECT;
-            } else if (contains(time.sec, subElement)) {
-                return SUBPART_SECS_SELECT;
-            } else if (contains(time.ampm, subElement)) {
-                return SUBPART_AMPM_SELECT;
-
-            }
-        } else if (getCellFormatter().getElement(0, 2).isOrHasChild(subElement)) {
-            return SUBPART_MONTH_YEAR_HEADER;
-        }
-
-        return null;
-    }
-
-    /**
-     * Checks if subElement is inside the widget DOM hierarchy.
-     * 
-     * @param w
-     * @param subElement
-     * @return true if {@code w} is a parent of subElement, false otherwise.
-     */
-    private boolean contains(Widget w, Element subElement) {
-        if (w == null || w.getElement() == null) {
-            return false;
-        }
-
-        return w.getElement().isOrHasChild(subElement);
-    }
-
-    @Override
-    public Element getSubPartElement(String subPart) {
-        if (SUBPART_NEXT_MONTH.equals(subPart)) {
-            return nextMonth.getElement();
-        }
-        if (SUBPART_PREV_MONTH.equals(subPart)) {
-            return prevMonth.getElement();
-        }
-        if (SUBPART_NEXT_YEAR.equals(subPart)) {
-            return nextYear.getElement();
-        }
-        if (SUBPART_PREV_YEAR.equals(subPart)) {
-            return prevYear.getElement();
-        }
-        if (SUBPART_HOUR_SELECT.equals(subPart)) {
-            return time.hours.getElement();
-        }
-        if (SUBPART_MINUTE_SELECT.equals(subPart)) {
-            return time.mins.getElement();
-        }
-        if (SUBPART_SECS_SELECT.equals(subPart)) {
-            return time.sec.getElement();
-        }
-        if (SUBPART_AMPM_SELECT.equals(subPart)) {
-            return time.ampm.getElement();
-        }
-        if (subPart.startsWith(SUBPART_DAY)) {
-            // Zero or negative ids map to days in the preceding month,
-            // past-the-end-of-month ids to days in the following month
-            int dayOfMonth = Integer.parseInt(subPart.substring(SUBPART_DAY
-                    .length()));
-            Date date = new Date(displayedMonth.getYear(),
-                    displayedMonth.getMonth(), dayOfMonth);
-            Iterator<Widget> iter = days.iterator();
-            while (iter.hasNext()) {
-                Widget w = iter.next();
-                if (w instanceof Day) {
-                    Day day = (Day) w;
-                    if (day.getDate().equals(date)) {
-                        return day.getElement();
-                    }
-                }
-            }
-        }
-
-        if (SUBPART_MONTH_YEAR_HEADER.equals(subPart)) {
-            return (Element) getCellFormatter().getElement(0, 2).getChild(0);
-        }
-        return null;
-    }
-
-    @Override
-    protected void onDetach() {
-        super.onDetach();
-        if (mouseTimer != null) {
-            mouseTimer.cancel();
-        }
-    }
-}
diff --git a/client/src/com/vaadin/client/ui/datefield/VDateField.java b/client/src/com/vaadin/client/ui/datefield/VDateField.java
deleted file mode 100644 (file)
index 84fe061..0000000
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.datefield;
-
-import java.util.Date;
-
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.DateTimeService;
-import com.vaadin.client.ui.Field;
-import com.vaadin.shared.ui.datefield.Resolution;
-
-public class VDateField extends FlowPanel implements Field {
-
-    public static final String CLASSNAME = "v-datefield";
-
-    protected String paintableId;
-
-    protected ApplicationConnection client;
-
-    protected boolean immediate;
-
-    @Deprecated
-    public static final Resolution RESOLUTION_YEAR = Resolution.YEAR;
-    @Deprecated
-    public static final Resolution RESOLUTION_MONTH = Resolution.MONTH;
-    @Deprecated
-    public static final Resolution RESOLUTION_DAY = Resolution.DAY;
-    @Deprecated
-    public static final Resolution RESOLUTION_HOUR = Resolution.HOUR;
-    @Deprecated
-    public static final Resolution RESOLUTION_MIN = Resolution.MINUTE;
-    @Deprecated
-    public static final Resolution RESOLUTION_SEC = Resolution.SECOND;
-
-    static String resolutionToString(Resolution res) {
-        if (res.getCalendarField() > Resolution.DAY.getCalendarField()) {
-            return "full";
-        }
-        if (res == Resolution.DAY) {
-            return "day";
-        }
-        if (res == Resolution.MONTH) {
-            return "month";
-        }
-        return "year";
-    }
-
-    protected Resolution currentResolution = Resolution.YEAR;
-
-    protected String currentLocale;
-
-    protected boolean readonly;
-
-    protected boolean enabled;
-
-    /**
-     * The date that is selected in the date field. Null if an invalid date is
-     * specified.
-     */
-    private Date date = null;
-
-    protected DateTimeService dts;
-
-    protected boolean showISOWeekNumbers = false;
-
-    public VDateField() {
-        setStyleName(CLASSNAME);
-        dts = new DateTimeService();
-    }
-
-    /*
-     * We need this redundant native function because Java's Date object doesn't
-     * have a setMilliseconds method.
-     */
-    protected static native double getTime(int y, int m, int d, int h, int mi,
-            int s, int ms)
-    /*-{
-       try {
-               var date = new Date(2000,1,1,1); // don't use current date here
-               if(y && y >= 0) date.setFullYear(y);
-               if(m && m >= 1) date.setMonth(m-1);
-               if(d && d >= 0) date.setDate(d);
-               if(h >= 0) date.setHours(h);
-               if(mi >= 0) date.setMinutes(mi);
-               if(s >= 0) date.setSeconds(s);
-               if(ms >= 0) date.setMilliseconds(ms);
-               return date.getTime();
-       } catch (e) {
-               // TODO print some error message on the console
-               //console.log(e);
-               return (new Date()).getTime();
-       }
-    }-*/;
-
-    public int getMilliseconds() {
-        return DateTimeService.getMilliseconds(date);
-    }
-
-    public void setMilliseconds(int ms) {
-        DateTimeService.setMilliseconds(date, ms);
-    }
-
-    public Resolution getCurrentResolution() {
-        return currentResolution;
-    }
-
-    public void setCurrentResolution(Resolution currentResolution) {
-        this.currentResolution = currentResolution;
-    }
-
-    public String getCurrentLocale() {
-        return currentLocale;
-    }
-
-    public void setCurrentLocale(String currentLocale) {
-        this.currentLocale = currentLocale;
-    }
-
-    public Date getCurrentDate() {
-        return date;
-    }
-
-    public void setCurrentDate(Date date) {
-        this.date = date;
-    }
-
-    public boolean isImmediate() {
-        return immediate;
-    }
-
-    public boolean isReadonly() {
-        return readonly;
-    }
-
-    public boolean isEnabled() {
-        return enabled;
-    }
-
-    public DateTimeService getDateTimeService() {
-        return dts;
-    }
-
-    public String getId() {
-        return paintableId;
-    }
-
-    public ApplicationConnection getClient() {
-        return client;
-    }
-
-    /**
-     * Returns whether ISO 8601 week numbers should be shown in the date
-     * selector or not. ISO 8601 defines that a week always starts with a Monday
-     * so the week numbers are only shown if this is the case.
-     * 
-     * @return true if week number should be shown, false otherwise
-     */
-    public boolean isShowISOWeekNumbers() {
-        return showISOWeekNumbers;
-    }
-
-    /**
-     * Returns a copy of the current date. Modifying the returned date will not
-     * modify the value of this VDateField. Use {@link #setDate(Date)} to change
-     * the current date.
-     * 
-     * @return A copy of the current date
-     */
-    protected Date getDate() {
-        Date current = getCurrentDate();
-        if (current == null) {
-            return null;
-        } else {
-            return (Date) getCurrentDate().clone();
-        }
-    }
-
-    /**
-     * Sets the current date for this VDateField.
-     * 
-     * @param date
-     *            The new date to use
-     */
-    protected void setDate(Date date) {
-        this.date = date;
-    }
-}
diff --git a/client/src/com/vaadin/client/ui/datefield/VDateFieldCalendar.java b/client/src/com/vaadin/client/ui/datefield/VDateFieldCalendar.java
deleted file mode 100644 (file)
index 39972e7..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.datefield;
-
-import java.util.Date;
-
-import com.google.gwt.event.dom.client.DomEvent;
-import com.vaadin.client.DateTimeService;
-import com.vaadin.client.ui.datefield.VCalendarPanel.FocusOutListener;
-import com.vaadin.client.ui.datefield.VCalendarPanel.SubmitListener;
-import com.vaadin.shared.ui.datefield.Resolution;
-
-/**
- * A client side implementation for InlineDateField
- */
-public class VDateFieldCalendar extends VDateField {
-
-    protected final VCalendarPanel calendarPanel;
-
-    public VDateFieldCalendar() {
-        super();
-        calendarPanel = new VCalendarPanel();
-        calendarPanel.setParentField(this);
-        add(calendarPanel);
-        calendarPanel.setSubmitListener(new SubmitListener() {
-            @Override
-            public void onSubmit() {
-                updateValueFromPanel();
-            }
-
-            @Override
-            public void onCancel() {
-                // TODO Auto-generated method stub
-
-            }
-        });
-        calendarPanel.setFocusOutListener(new FocusOutListener() {
-            @Override
-            public boolean onFocusOut(DomEvent<?> event) {
-                updateValueFromPanel();
-                return false;
-            }
-        });
-    }
-
-    /**
-     * TODO refactor: almost same method as in VPopupCalendar.updateValue
-     */
-    @SuppressWarnings("deprecation")
-    protected void updateValueFromPanel() {
-
-        // If field is invisible at the beginning, client can still be null when
-        // this function is called.
-        if (getClient() == null) {
-            return;
-        }
-
-        Date date2 = calendarPanel.getDate();
-        Date currentDate = getCurrentDate();
-        if (currentDate == null || date2.getTime() != currentDate.getTime()) {
-            setCurrentDate((Date) date2.clone());
-            getClient().updateVariable(getId(), "year", date2.getYear() + 1900,
-                    false);
-            if (getCurrentResolution().getCalendarField() > Resolution.YEAR
-                    .getCalendarField()) {
-                getClient().updateVariable(getId(), "month",
-                        date2.getMonth() + 1, false);
-                if (getCurrentResolution().getCalendarField() > Resolution.MONTH
-                        .getCalendarField()) {
-                    getClient().updateVariable(getId(), "day", date2.getDate(),
-                            false);
-                    if (getCurrentResolution().getCalendarField() > Resolution.DAY
-                            .getCalendarField()) {
-                        getClient().updateVariable(getId(), "hour",
-                                date2.getHours(), false);
-                        if (getCurrentResolution().getCalendarField() > Resolution.HOUR
-                                .getCalendarField()) {
-                            getClient().updateVariable(getId(), "min",
-                                    date2.getMinutes(), false);
-                            if (getCurrentResolution().getCalendarField() > Resolution.MINUTE
-                                    .getCalendarField()) {
-                                getClient().updateVariable(getId(), "sec",
-                                        date2.getSeconds(), false);
-                                if (getCurrentResolution().getCalendarField() > Resolution.SECOND
-                                        .getCalendarField()) {
-                                    getClient().updateVariable(
-                                            getId(),
-                                            "msec",
-                                            DateTimeService
-                                                    .getMilliseconds(date2),
-                                            false);
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-            if (isImmediate()) {
-                getClient().sendPendingVariableChanges();
-            }
-        }
-    }
-}
diff --git a/client/src/com/vaadin/client/ui/datefield/VPopupCalendar.java b/client/src/com/vaadin/client/ui/datefield/VPopupCalendar.java
deleted file mode 100644 (file)
index 05f1d00..0000000
+++ /dev/null
@@ -1,406 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.datefield;
-
-import java.util.Date;
-
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.DomEvent;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.logical.shared.CloseEvent;
-import com.google.gwt.event.logical.shared.CloseHandler;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-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.Button;
-import com.google.gwt.user.client.ui.PopupPanel;
-import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.VConsole;
-import com.vaadin.client.ui.Field;
-import com.vaadin.client.ui.SubPartAware;
-import com.vaadin.client.ui.VOverlay;
-import com.vaadin.client.ui.datefield.VCalendarPanel.FocusOutListener;
-import com.vaadin.client.ui.datefield.VCalendarPanel.SubmitListener;
-import com.vaadin.shared.ui.datefield.Resolution;
-
-/**
- * Represents a date selection component with a text field and a popup date
- * selector.
- * 
- * <b>Note:</b> To change the keyboard assignments used in the popup dialog you
- * should extend <code>com.vaadin.client.ui.VCalendarPanel</code> and then pass
- * set it by calling the <code>setCalendarPanel(VCalendarPanel panel)</code>
- * method.
- * 
- */
-public class VPopupCalendar extends VTextualDate implements Field,
-        ClickHandler, CloseHandler<PopupPanel>, SubPartAware {
-
-    protected final Button calendarToggle = new Button();
-
-    protected VCalendarPanel calendar;
-
-    protected final VOverlay popup;
-    private boolean open = false;
-    protected boolean parsable = true;
-
-    public VPopupCalendar() {
-        super();
-
-        calendarToggle.setText("");
-        calendarToggle.addClickHandler(this);
-        // -2 instead of -1 to avoid FocusWidget.onAttach to reset it
-        calendarToggle.getElement().setTabIndex(-2);
-        add(calendarToggle);
-
-        calendar = GWT.create(VCalendarPanel.class);
-        calendar.setParentField(this);
-        calendar.setFocusOutListener(new FocusOutListener() {
-            @Override
-            public boolean onFocusOut(DomEvent<?> event) {
-                event.preventDefault();
-                closeCalendarPanel();
-                return true;
-            }
-        });
-
-        calendar.setSubmitListener(new SubmitListener() {
-            @Override
-            public void onSubmit() {
-                // Update internal value and send valuechange event if immediate
-                updateValue(calendar.getDate());
-
-                // Update text field (a must when not immediate).
-                buildDate(true);
-
-                closeCalendarPanel();
-            }
-
-            @Override
-            public void onCancel() {
-                closeCalendarPanel();
-            }
-        });
-
-        popup = new VOverlay(true, true, true);
-        popup.setOwner(this);
-
-        popup.setWidget(calendar);
-        popup.addCloseHandler(this);
-
-        DOM.setElementProperty(calendar.getElement(), "id",
-                "PID_VAADIN_POPUPCAL");
-
-        sinkEvents(Event.ONKEYDOWN);
-
-        updateStyleNames();
-    }
-
-    @SuppressWarnings("deprecation")
-    protected void updateValue(Date newDate) {
-        Date currentDate = getCurrentDate();
-        if (currentDate == null || newDate.getTime() != currentDate.getTime()) {
-            setCurrentDate((Date) newDate.clone());
-            getClient().updateVariable(getId(), "year",
-                    newDate.getYear() + 1900, false);
-            if (getCurrentResolution().getCalendarField() > Resolution.YEAR
-                    .getCalendarField()) {
-                getClient().updateVariable(getId(), "month",
-                        newDate.getMonth() + 1, false);
-                if (getCurrentResolution().getCalendarField() > Resolution.MONTH
-                        .getCalendarField()) {
-                    getClient().updateVariable(getId(), "day",
-                            newDate.getDate(), false);
-                    if (getCurrentResolution().getCalendarField() > Resolution.DAY
-                            .getCalendarField()) {
-                        getClient().updateVariable(getId(), "hour",
-                                newDate.getHours(), false);
-                        if (getCurrentResolution().getCalendarField() > Resolution.HOUR
-                                .getCalendarField()) {
-                            getClient().updateVariable(getId(), "min",
-                                    newDate.getMinutes(), false);
-                            if (getCurrentResolution().getCalendarField() > Resolution.MINUTE
-                                    .getCalendarField()) {
-                                getClient().updateVariable(getId(), "sec",
-                                        newDate.getSeconds(), false);
-                            }
-                        }
-                    }
-                }
-            }
-            if (isImmediate()) {
-                getClient().sendPendingVariableChanges();
-            }
-        }
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see
-     * com.google.gwt.user.client.ui.UIObject#setStyleName(java.lang.String)
-     */
-    @Override
-    public void setStyleName(String style) {
-        super.setStyleName(style);
-        updateStyleNames();
-    }
-
-    @Override
-    public void setStylePrimaryName(String style) {
-        removeStyleName(getStylePrimaryName() + "-popupcalendar");
-        super.setStylePrimaryName(style);
-        updateStyleNames();
-    }
-
-    @Override
-    protected void updateStyleNames() {
-        super.updateStyleNames();
-        if (getStylePrimaryName() != null && calendarToggle != null) {
-            addStyleName(getStylePrimaryName() + "-popupcalendar");
-            calendarToggle.setStyleName(getStylePrimaryName() + "-button");
-            popup.setStyleName(getStylePrimaryName() + "-popup");
-            calendar.setStyleName(getStylePrimaryName() + "-calendarpanel");
-        }
-    }
-
-    /**
-     * Opens the calendar panel popup
-     */
-    public void openCalendarPanel() {
-
-        if (!open && !readonly) {
-            open = true;
-
-            if (getCurrentDate() != null) {
-                calendar.setDate((Date) getCurrentDate().clone());
-            } else {
-                calendar.setDate(new Date());
-            }
-
-            // clear previous values
-            popup.setWidth("");
-            popup.setHeight("");
-            popup.setPopupPositionAndShow(new PositionCallback() {
-                @Override
-                public void setPosition(int offsetWidth, int offsetHeight) {
-                    final int w = offsetWidth;
-                    final int h = offsetHeight;
-                    final int browserWindowWidth = Window.getClientWidth()
-                            + Window.getScrollLeft();
-                    final int browserWindowHeight = Window.getClientHeight()
-                            + Window.getScrollTop();
-                    int t = calendarToggle.getAbsoluteTop();
-                    int l = calendarToggle.getAbsoluteLeft();
-
-                    // Add a little extra space to the right to avoid
-                    // problems with IE7 scrollbars and to make it look
-                    // nicer.
-                    int extraSpace = 30;
-
-                    boolean overflowRight = false;
-                    if (l + +w + extraSpace > browserWindowWidth) {
-                        overflowRight = true;
-                        // Part of the popup is outside the browser window
-                        // (to the right)
-                        l = browserWindowWidth - w - extraSpace;
-                    }
-
-                    if (t + h + calendarToggle.getOffsetHeight() + 30 > browserWindowHeight) {
-                        // Part of the popup is outside the browser window
-                        // (below)
-                        t = browserWindowHeight - h
-                                - calendarToggle.getOffsetHeight() - 30;
-                        if (!overflowRight) {
-                            // Show to the right of the popup button unless we
-                            // are in the lower right corner of the screen
-                            l += calendarToggle.getOffsetWidth();
-                        }
-                    }
-
-                    // fix size
-                    popup.setWidth(w + "px");
-                    popup.setHeight(h + "px");
-
-                    popup.setPopupPosition(l,
-                            t + calendarToggle.getOffsetHeight() + 2);
-
-                    /*
-                     * We have to wait a while before focusing since the popup
-                     * needs to be opened before we can focus
-                     */
-                    Timer focusTimer = new Timer() {
-                        @Override
-                        public void run() {
-                            setFocus(true);
-                        }
-                    };
-
-                    focusTimer.schedule(100);
-                }
-            });
-        } else {
-            VConsole.error("Cannot reopen popup, it is already open!");
-        }
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see
-     * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event
-     * .dom.client.ClickEvent)
-     */
-    @Override
-    public void onClick(ClickEvent event) {
-        if (event.getSource() == calendarToggle && isEnabled()) {
-            openCalendarPanel();
-        }
-    }
-
-    /*
-     * (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 (event.getSource() == popup) {
-            buildDate();
-            if (!BrowserInfo.get().isTouchDevice()) {
-                /*
-                 * Move focus to textbox, unless on touch device (avoids opening
-                 * virtual keyboard).
-                 */
-                focus();
-            }
-
-            // TODO resolve what the "Sigh." is all about and document it here
-            // Sigh.
-            Timer t = new Timer() {
-                @Override
-                public void run() {
-                    open = false;
-                }
-            };
-            t.schedule(100);
-        }
-    }
-
-    /**
-     * Sets focus to Calendar panel.
-     * 
-     * @param focus
-     */
-    public void setFocus(boolean focus) {
-        calendar.setFocus(focus);
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see com.vaadin.client.ui.VTextualDate#buildDate()
-     */
-    @Override
-    protected void buildDate() {
-        // Save previous value
-        String previousValue = getText();
-        super.buildDate();
-
-        // Restore previous value if the input could not be parsed
-        if (!parsable) {
-            setText(previousValue);
-        }
-    }
-
-    /**
-     * Update the text field contents from the date. See {@link #buildDate()}.
-     * 
-     * @param forceValid
-     *            true to force the text field to be updated, false to only
-     *            update if the parsable flag is true.
-     */
-    protected void buildDate(boolean forceValid) {
-        if (forceValid) {
-            parsable = true;
-        }
-        buildDate();
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see com.vaadin.client.ui.VDateField#onBrowserEvent(com.google
-     * .gwt.user.client.Event)
-     */
-    @Override
-    public void onBrowserEvent(com.google.gwt.user.client.Event event) {
-        super.onBrowserEvent(event);
-        if (DOM.eventGetType(event) == Event.ONKEYDOWN
-                && event.getKeyCode() == getOpenCalenderPanelKey()) {
-            openCalendarPanel();
-            event.preventDefault();
-        }
-    }
-
-    /**
-     * Get the key code that opens the calendar panel. By default it is the down
-     * key but you can override this to be whatever you like
-     * 
-     * @return
-     */
-    protected int getOpenCalenderPanelKey() {
-        return KeyCodes.KEY_DOWN;
-    }
-
-    /**
-     * Closes the open popup panel
-     */
-    public void closeCalendarPanel() {
-        if (open) {
-            popup.hide(true);
-        }
-    }
-
-    private final String CALENDAR_TOGGLE_ID = "popupButton";
-
-    @Override
-    public Element getSubPartElement(String subPart) {
-        if (subPart.equals(CALENDAR_TOGGLE_ID)) {
-            return calendarToggle.getElement();
-        }
-
-        return super.getSubPartElement(subPart);
-    }
-
-    @Override
-    public String getSubPartName(Element subElement) {
-        if (calendarToggle.getElement().isOrHasChild(subElement)) {
-            return CALENDAR_TOGGLE_ID;
-        }
-
-        return super.getSubPartName(subElement);
-    }
-
-}
diff --git a/client/src/com/vaadin/client/ui/datefield/VTextualDate.java b/client/src/com/vaadin/client/ui/datefield/VTextualDate.java
deleted file mode 100644 (file)
index 9bacfde..0000000
+++ /dev/null
@@ -1,345 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.datefield;
-
-import java.util.Date;
-
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.event.dom.client.ChangeEvent;
-import com.google.gwt.event.dom.client.ChangeHandler;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.ui.TextBox;
-import com.vaadin.client.Focusable;
-import com.vaadin.client.LocaleNotLoadedException;
-import com.vaadin.client.LocaleService;
-import com.vaadin.client.VConsole;
-import com.vaadin.client.ui.Field;
-import com.vaadin.client.ui.SubPartAware;
-import com.vaadin.client.ui.textfield.VTextField;
-import com.vaadin.shared.EventId;
-import com.vaadin.shared.ui.datefield.Resolution;
-
-public class VTextualDate extends VDateField implements Field, ChangeHandler,
-        Focusable, SubPartAware {
-
-    private static final String PARSE_ERROR_CLASSNAME = "-parseerror";
-
-    protected final TextBox text;
-
-    protected String formatStr;
-
-    protected boolean lenient;
-
-    private static final String CLASSNAME_PROMPT = "prompt";
-    protected static final String ATTR_INPUTPROMPT = "prompt";
-    protected String inputPrompt = "";
-    private boolean prompting = false;
-
-    public VTextualDate() {
-        super();
-        text = new TextBox();
-        text.addChangeHandler(this);
-        text.addFocusHandler(new FocusHandler() {
-            @Override
-            public void onFocus(FocusEvent event) {
-                text.addStyleName(VTextField.CLASSNAME + "-"
-                        + VTextField.CLASSNAME_FOCUS);
-                if (prompting) {
-                    text.setText("");
-                    setPrompting(false);
-                }
-                if (getClient() != null
-                        && getClient().hasEventListeners(VTextualDate.this,
-                                EventId.FOCUS)) {
-                    getClient()
-                            .updateVariable(getId(), EventId.FOCUS, "", true);
-                }
-            }
-        });
-        text.addBlurHandler(new BlurHandler() {
-            @Override
-            public void onBlur(BlurEvent event) {
-                text.removeStyleName(VTextField.CLASSNAME + "-"
-                        + VTextField.CLASSNAME_FOCUS);
-                String value = getText();
-                setPrompting(inputPrompt != null
-                        && (value == null || "".equals(value)));
-                if (prompting) {
-                    text.setText(readonly ? "" : inputPrompt);
-                }
-                if (getClient() != null
-                        && getClient().hasEventListeners(VTextualDate.this,
-                                EventId.BLUR)) {
-                    getClient().updateVariable(getId(), EventId.BLUR, "", true);
-                }
-            }
-        });
-        add(text);
-    }
-
-    protected void updateStyleNames() {
-        if (text != null) {
-            text.setStyleName(VTextField.CLASSNAME);
-            text.addStyleName(getStylePrimaryName() + "-textfield");
-        }
-    }
-
-    protected String getFormatString() {
-        if (formatStr == null) {
-            if (currentResolution == Resolution.YEAR) {
-                formatStr = "yyyy"; // force full year
-            } else {
-
-                try {
-                    String frmString = LocaleService
-                            .getDateFormat(currentLocale);
-                    frmString = cleanFormat(frmString);
-                    // String delim = LocaleService
-                    // .getClockDelimiter(currentLocale);
-                    if (currentResolution.getCalendarField() >= Resolution.HOUR
-                            .getCalendarField()) {
-                        if (dts.isTwelveHourClock()) {
-                            frmString += " hh";
-                        } else {
-                            frmString += " HH";
-                        }
-                        if (currentResolution.getCalendarField() >= Resolution.MINUTE
-                                .getCalendarField()) {
-                            frmString += ":mm";
-                            if (currentResolution.getCalendarField() >= Resolution.SECOND
-                                    .getCalendarField()) {
-                                frmString += ":ss";
-                            }
-                        }
-                        if (dts.isTwelveHourClock()) {
-                            frmString += " aaa";
-                        }
-
-                    }
-
-                    formatStr = frmString;
-                } catch (LocaleNotLoadedException e) {
-                    // TODO should die instead? Can the component survive
-                    // without format string?
-                    VConsole.error(e);
-                }
-            }
-        }
-        return formatStr;
-    }
-
-    /**
-     * Updates the text field according to the current date (provided by
-     * {@link #getDate()}). Takes care of updating text, enabling and disabling
-     * the field, setting/removing readonly status and updating readonly styles.
-     * 
-     * TODO: Split part of this into a method that only updates the text as this
-     * is what usually is needed except for updateFromUIDL.
-     */
-    protected void buildDate() {
-        removeStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
-        // Create the initial text for the textfield
-        String dateText;
-        Date currentDate = getDate();
-        if (currentDate != null) {
-            dateText = getDateTimeService().formatDate(currentDate,
-                    getFormatString());
-        } else {
-            dateText = "";
-        }
-
-        setText(dateText);
-        text.setEnabled(enabled);
-        text.setReadOnly(readonly);
-
-        if (readonly) {
-            text.addStyleName("v-readonly");
-        } else {
-            text.removeStyleName("v-readonly");
-        }
-
-    }
-
-    protected void setPrompting(boolean prompting) {
-        this.prompting = prompting;
-        if (prompting) {
-            addStyleDependentName(CLASSNAME_PROMPT);
-        } else {
-            removeStyleDependentName(CLASSNAME_PROMPT);
-        }
-    }
-
-    @Override
-    @SuppressWarnings("deprecation")
-    public void onChange(ChangeEvent event) {
-        if (!text.getText().equals("")) {
-            try {
-                String enteredDate = text.getText();
-
-                setDate(getDateTimeService().parseDate(enteredDate,
-                        getFormatString(), lenient));
-
-                if (lenient) {
-                    // If date value was leniently parsed, normalize text
-                    // presentation.
-                    // FIXME: Add a description/example here of when this is
-                    // needed
-                    text.setValue(
-                            getDateTimeService().formatDate(getDate(),
-                                    getFormatString()), false);
-                }
-
-                // remove possibly added invalid value indication
-                removeStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
-            } catch (final Exception e) {
-                VConsole.log(e);
-
-                addStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
-                // this is a hack that may eventually be removed
-                getClient().updateVariable(getId(), "lastInvalidDateString",
-                        text.getText(), false);
-                setDate(null);
-            }
-        } else {
-            setDate(null);
-            // remove possibly added invalid value indication
-            removeStyleName(getStylePrimaryName() + PARSE_ERROR_CLASSNAME);
-        }
-        // always send the date string
-        getClient()
-                .updateVariable(getId(), "dateString", text.getText(), false);
-
-        // Update variables
-        // (only the smallest defining resolution needs to be
-        // immediate)
-        Date currentDate = getDate();
-        getClient().updateVariable(getId(), "year",
-                currentDate != null ? currentDate.getYear() + 1900 : -1,
-                currentResolution == Resolution.YEAR && immediate);
-        if (currentResolution.getCalendarField() >= Resolution.MONTH
-                .getCalendarField()) {
-            getClient().updateVariable(getId(), "month",
-                    currentDate != null ? currentDate.getMonth() + 1 : -1,
-                    currentResolution == Resolution.MONTH && immediate);
-        }
-        if (currentResolution.getCalendarField() >= Resolution.DAY
-                .getCalendarField()) {
-            getClient().updateVariable(getId(), "day",
-                    currentDate != null ? currentDate.getDate() : -1,
-                    currentResolution == Resolution.DAY && immediate);
-        }
-        if (currentResolution.getCalendarField() >= Resolution.HOUR
-                .getCalendarField()) {
-            getClient().updateVariable(getId(), "hour",
-                    currentDate != null ? currentDate.getHours() : -1,
-                    currentResolution == Resolution.HOUR && immediate);
-        }
-        if (currentResolution.getCalendarField() >= Resolution.MINUTE
-                .getCalendarField()) {
-            getClient().updateVariable(getId(), "min",
-                    currentDate != null ? currentDate.getMinutes() : -1,
-                    currentResolution == Resolution.MINUTE && immediate);
-        }
-        if (currentResolution.getCalendarField() >= Resolution.SECOND
-                .getCalendarField()) {
-            getClient().updateVariable(getId(), "sec",
-                    currentDate != null ? currentDate.getSeconds() : -1,
-                    currentResolution == Resolution.SECOND && immediate);
-        }
-
-    }
-
-    private String cleanFormat(String format) {
-        // Remove unnecessary d & M if resolution is too low
-        if (currentResolution.getCalendarField() < Resolution.DAY
-                .getCalendarField()) {
-            format = format.replaceAll("d", "");
-        }
-        if (currentResolution.getCalendarField() < Resolution.MONTH
-                .getCalendarField()) {
-            format = format.replaceAll("M", "");
-        }
-
-        // Remove unsupported patterns
-        // TODO support for 'G', era designator (used at least in Japan)
-        format = format.replaceAll("[GzZwWkK]", "");
-
-        // Remove extra delimiters ('/' and '.')
-        while (format.startsWith("/") || format.startsWith(".")
-                || format.startsWith("-")) {
-            format = format.substring(1);
-        }
-        while (format.endsWith("/") || format.endsWith(".")
-                || format.endsWith("-")) {
-            format = format.substring(0, format.length() - 1);
-        }
-
-        // Remove duplicate delimiters
-        format = format.replaceAll("//", "/");
-        format = format.replaceAll("\\.\\.", ".");
-        format = format.replaceAll("--", "-");
-
-        return format.trim();
-    }
-
-    @Override
-    public void focus() {
-        text.setFocus(true);
-    }
-
-    protected String getText() {
-        if (prompting) {
-            return "";
-        }
-        return text.getText();
-    }
-
-    protected void setText(String text) {
-        if (inputPrompt != null && (text == null || "".equals(text))) {
-            text = readonly ? "" : inputPrompt;
-            setPrompting(true);
-        } else {
-            setPrompting(false);
-        }
-
-        this.text.setText(text);
-    }
-
-    private final String TEXTFIELD_ID = "field";
-
-    @Override
-    public Element getSubPartElement(String subPart) {
-        if (subPart.equals(TEXTFIELD_ID)) {
-            return text.getElement();
-        }
-
-        return null;
-    }
-
-    @Override
-    public String getSubPartName(Element subElement) {
-        if (text.getElement().isOrHasChild(subElement)) {
-            return TEXTFIELD_ID;
-        }
-
-        return null;
-    }
-
-}
index c5923c7436e6e73a41501d87222c417f4b3d12fb..078b3eff8f5b78f3754ddd84559fdaead30a00a3 100644 (file)
@@ -20,8 +20,8 @@ package com.vaadin.client.ui.dd;
 
 import com.google.gwt.user.client.ui.Widget;
 import com.vaadin.client.UIDL;
-import com.vaadin.client.ui.tree.VTree;
-import com.vaadin.client.ui.tree.VTree.TreeNode;
+import com.vaadin.client.ui.VTree;
+import com.vaadin.client.ui.VTree.TreeNode;
 import com.vaadin.shared.ui.dd.AcceptCriterion;
 import com.vaadin.ui.Tree;
 
index 3bbc6f05d156cf6249da900e95407bfff0d8a904..65c62492e8364f2dbde0e4ed91bedad86ec4da17 100644 (file)
@@ -21,6 +21,7 @@ import java.util.Set;
 import com.vaadin.client.ApplicationConnection;
 import com.vaadin.client.Paintable;
 import com.vaadin.client.UIDL;
+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;
diff --git a/client/src/com/vaadin/client/ui/draganddropwrapper/VDragAndDropWrapper.java b/client/src/com/vaadin/client/ui/draganddropwrapper/VDragAndDropWrapper.java
deleted file mode 100644 (file)
index e983015..0000000
+++ /dev/null
@@ -1,606 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.vaadin.client.ui.draganddropwrapper;
-
-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.NativeEvent;
-import com.google.gwt.event.dom.client.MouseDownEvent;
-import com.google.gwt.event.dom.client.MouseDownHandler;
-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.Element;
-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.ui.customcomponent.VCustomComponent;
-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 {
-
-    private static final String CLASSNAME = "v-ddwrapper";
-    protected static final String DRAGGABLE = "draggable";
-
-    boolean hasTooltip = false;
-
-    public VDragAndDropWrapper() {
-        super();
-
-        hookHtml5Events(getElement());
-        setStyleName(CLASSNAME);
-        addDomHandler(new MouseDownHandler() {
-
-            @Override
-            public void onMouseDown(MouseDownEvent event) {
-                if (startDrag(event.getNativeEvent())) {
-                    event.preventDefault(); // prevent text selection
-                }
-            }
-        }, MouseDownEvent.getType());
-
-        addDomHandler(new TouchStartHandler() {
-
-            @Override
-            public void onTouchStart(TouchStartEvent event) {
-                if (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) {
-            VTransferable transferable = new VTransferable();
-            transferable.setDragSource(ConnectorMap.get(client).getConnector(
-                    VDragAndDropWrapper.this));
-
-            ComponentConnector paintable = Util.findPaintable(client,
-                    (Element) event.getEventTarget().cast());
-            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 {
-                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 int dragStartMode;
-
-    ApplicationConnection client;
-    VAbstractDropHandler dropHandler;
-    private VDragEvent vaadinDragEvent;
-
-    int filecounter = 0;
-    Map<String, String> fileIdToReceiver;
-    ValueMap html5DataFlavors;
-    private Element dragStartElement;
-
-    protected 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 Element getDragStartElement() {
-        return getElement();
-    }
-
-    private boolean uploading;
-
-    private ReadyStateChangeHandler readyStateChangeHandler = new ReadyStateChangeHandler() {
-
-        @Override
-        public void onReadyStateChange(XMLHttpRequest xhr) {
-            if (xhr.getReadyState() == XMLHttpRequest.DONE) {
-                // visit server for possible
-                // variable changes
-                client.sendPendingVariableChanges();
-                uploading = false;
-                startNextUpload();
-                xhr.clearOnReadyStateChange();
-            }
-        }
-    };
-    private Timer dragleavetimer;
-
-    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(ConnectorMap.get(client)
-                        .getConnector(this));
-
-                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);
-
-        String s = event.getEffectAllowed();
-        if ("all".equals(s) || s.contains("opy")) {
-            event.setDropEffect("copy");
-        } else {
-            event.setDropEffect(s);
-        }
-
-        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);
-                    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');
-            this.send(file);
-        }-*/;
-
-    }
-
-    /**
-     * Currently supports only FF36 as no other browser supports natively File
-     * api.
-     * 
-     * @param fileId
-     * @param data
-     */
-    List<Integer> fileIds = new ArrayList<Integer>();
-    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 static final String OVER_STYLE = "v-ddwrapper-over";
-
-    public class CustomDropHandler extends VAbstractDropHandler {
-
-        @Override
-        public void dragEnter(VDragEvent drag) {
-            updateDropDetails(drag);
-            currentlyValid = false;
-            super.dragEnter(drag);
-        }
-
-        @Override
-        public void dragLeave(VDragEvent drag) {
-            deEmphasis(true);
-            dragleavetimer = null;
-        }
-
-        @Override
-        public void dragOver(final VDragEvent drag) {
-            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) {
-            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) {
-            currentlyValid = true;
-            emphasis(drag);
-        }
-
-        @Override
-        public ComponentConnector getConnector() {
-            return ConnectorMap.get(client).getConnector(
-                    VDragAndDropWrapper.this);
-        }
-
-        @Override
-        public ApplicationConnection getApplicationConnection() {
-            return client;
-        }
-
-    }
-
-    protected native void hookHtml5DragStart(Element el)
-    /*-{
-        var me = this;
-        el.addEventListener("dragstart",  $entry(function(ev) {
-            return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragStart(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
-        }), false);
-    }-*/;
-
-    /**
-     * Prototype code, memory leak risk.
-     * 
-     * @param el
-     */
-    protected native void hookHtml5Events(Element el)
-    /*-{
-            var me = this;
-
-            el.addEventListener("dragenter",  $entry(function(ev) {
-                return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragEnter(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
-            }), false);
-
-            el.addEventListener("dragleave",  $entry(function(ev) {
-                return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragLeave(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
-            }), false);
-
-            el.addEventListener("dragover",  $entry(function(ev) {
-                return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragOver(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
-            }), false);
-
-            el.addEventListener("drop",  $entry(function(ev) {
-                return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragDrop(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
-            }), false);
-    }-*/;
-
-    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(
-                ConnectorMap.get(client).getConnector(getElement()));
-    }
-
-    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();
-    }
-
-}
diff --git a/client/src/com/vaadin/client/ui/draganddropwrapper/VDragAndDropWrapperIE.java b/client/src/com/vaadin/client/ui/draganddropwrapper/VDragAndDropWrapperIE.java
deleted file mode 100644 (file)
index b0323ab..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.draganddropwrapper;
-
-import com.google.gwt.dom.client.AnchorElement;
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.user.client.Element;
-import com.vaadin.client.VConsole;
-
-public class VDragAndDropWrapperIE extends VDragAndDropWrapper {
-    private AnchorElement anchor = null;
-
-    @Override
-    protected Element getDragStartElement() {
-        VConsole.log("IE get drag start element...");
-        Element div = getElement();
-        if (dragStartMode == HTML5) {
-            if (anchor == null) {
-                anchor = Document.get().createAnchorElement();
-                anchor.setHref("#");
-                anchor.setClassName("drag-start");
-                div.appendChild(anchor);
-            }
-            VConsole.log("IE get drag start element...");
-            return (Element) anchor.cast();
-        } else {
-            if (anchor != null) {
-                div.removeChild(anchor);
-                anchor = null;
-            }
-            return div;
-        }
-    }
-
-    @Override
-    protected native void hookHtml5DragStart(Element el)
-    /*-{
-        var me = this;
-
-        el.attachEvent("ondragstart",  $entry(function(ev) {
-            return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragStart(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
-        }));
-    }-*/;
-
-    @Override
-    protected native void hookHtml5Events(Element el)
-    /*-{
-        var me = this;
-
-        el.attachEvent("ondragenter",  $entry(function(ev) {
-            return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragEnter(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
-        }));
-
-        el.attachEvent("ondragleave",  $entry(function(ev) {
-            return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragLeave(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
-        }));
-
-        el.attachEvent("ondragover",  $entry(function(ev) {
-            return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragOver(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
-        }));
-
-        el.attachEvent("ondrop",  $entry(function(ev) {
-            return me.@com.vaadin.client.ui.draganddropwrapper.VDragAndDropWrapper::html5DragDrop(Lcom/vaadin/client/ui/dd/VHtml5DragEvent;)(ev);
-        }));
-    }-*/;
-
-}
index 1dfeadc1ffb31c1419c9d9581e1a6f61df682aca..16125f883ea5d1825119ee74c5f8e7ba89e7b92f 100644 (file)
@@ -30,7 +30,8 @@ import com.vaadin.client.VCaption;
 import com.vaadin.client.communication.StateChangeEvent;
 import com.vaadin.client.ui.AbstractComponentContainerConnector;
 import com.vaadin.client.ui.LayoutClickEventHandler;
-import com.vaadin.client.ui.gridlayout.VGridLayout.Cell;
+import com.vaadin.client.ui.VGridLayout;
+import com.vaadin.client.ui.VGridLayout.Cell;
 import com.vaadin.client.ui.layout.VLayoutSlot;
 import com.vaadin.shared.ui.AlignmentInfo;
 import com.vaadin.shared.ui.Connect;
diff --git a/client/src/com/vaadin/client/ui/gridlayout/VGridLayout.java b/client/src/com/vaadin/client/ui/gridlayout/VGridLayout.java
deleted file mode 100644 (file)
index 770499e..0000000
+++ /dev/null
@@ -1,716 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.gridlayout;
-
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-
-import com.google.gwt.dom.client.DivElement;
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.dom.client.Style;
-import com.google.gwt.dom.client.Style.Position;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.ui.ComplexPanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.ComponentConnector;
-import com.vaadin.client.ConnectorMap;
-import com.vaadin.client.LayoutManager;
-import com.vaadin.client.StyleConstants;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.Util;
-import com.vaadin.client.VCaption;
-import com.vaadin.client.ui.layout.ComponentConnectorLayoutSlot;
-import com.vaadin.client.ui.layout.VLayoutSlot;
-import com.vaadin.shared.ui.AlignmentInfo;
-import com.vaadin.shared.ui.MarginInfo;
-
-public class VGridLayout extends ComplexPanel {
-
-    public static final String CLASSNAME = "v-gridlayout";
-
-    ApplicationConnection client;
-
-    HashMap<Widget, Cell> widgetToCell = new HashMap<Widget, Cell>();
-
-    int[] columnWidths;
-    int[] rowHeights;
-
-    int[] colExpandRatioArray;
-
-    int[] rowExpandRatioArray;
-
-    int[] minColumnWidths;
-
-    private int[] minRowHeights;
-
-    DivElement spacingMeasureElement;
-
-    public VGridLayout() {
-        super();
-        setElement(Document.get().createDivElement());
-
-        spacingMeasureElement = Document.get().createDivElement();
-        Style spacingStyle = spacingMeasureElement.getStyle();
-        spacingStyle.setPosition(Position.ABSOLUTE);
-        getElement().appendChild(spacingMeasureElement);
-
-        setStyleName(CLASSNAME);
-        addStyleName(StyleConstants.UI_LAYOUT);
-    }
-
-    private GridLayoutConnector getConnector() {
-        return (GridLayoutConnector) ConnectorMap.get(client)
-                .getConnector(this);
-    }
-
-    /**
-     * Returns the column widths measured in pixels
-     * 
-     * @return
-     */
-    protected int[] getColumnWidths() {
-        return columnWidths;
-    }
-
-    /**
-     * Returns the row heights measured in pixels
-     * 
-     * @return
-     */
-    protected int[] getRowHeights() {
-        return rowHeights;
-    }
-
-    /**
-     * Returns the spacing between the cells horizontally in pixels
-     * 
-     * @return
-     */
-    protected int getHorizontalSpacing() {
-        return LayoutManager.get(client).getOuterWidth(spacingMeasureElement);
-    }
-
-    /**
-     * Returns the spacing between the cells vertically in pixels
-     * 
-     * @return
-     */
-    protected int getVerticalSpacing() {
-        return LayoutManager.get(client).getOuterHeight(spacingMeasureElement);
-    }
-
-    static int[] cloneArray(int[] toBeCloned) {
-        int[] clone = new int[toBeCloned.length];
-        for (int i = 0; i < clone.length; i++) {
-            clone[i] = toBeCloned[i] * 1;
-        }
-        return clone;
-    }
-
-    void expandRows() {
-        if (!isUndefinedHeight()) {
-            int usedSpace = minRowHeights[0];
-            int verticalSpacing = getVerticalSpacing();
-            for (int i = 1; i < minRowHeights.length; i++) {
-                usedSpace += verticalSpacing + minRowHeights[i];
-            }
-            int availableSpace = LayoutManager.get(client).getInnerHeight(
-                    getElement());
-            int excessSpace = availableSpace - usedSpace;
-            int distributed = 0;
-            if (excessSpace > 0) {
-                for (int i = 0; i < rowHeights.length; i++) {
-                    int ew = excessSpace * rowExpandRatioArray[i] / 1000;
-                    rowHeights[i] = minRowHeights[i] + ew;
-                    distributed += ew;
-                }
-                excessSpace -= distributed;
-                int c = 0;
-                while (excessSpace > 0) {
-                    rowHeights[c % rowHeights.length]++;
-                    excessSpace--;
-                    c++;
-                }
-            }
-        }
-    }
-
-    void updateHeight() {
-        // Detect minimum heights & calculate spans
-        detectRowHeights();
-
-        // Expand
-        expandRows();
-
-        // Position
-        layoutCellsVertically();
-    }
-
-    void updateWidth() {
-        // Detect widths & calculate spans
-        detectColWidths();
-        // Expand
-        expandColumns();
-        // Position
-        layoutCellsHorizontally();
-
-    }
-
-    void expandColumns() {
-        if (!isUndefinedWidth()) {
-            int usedSpace = minColumnWidths[0];
-            int horizontalSpacing = getHorizontalSpacing();
-            for (int i = 1; i < minColumnWidths.length; i++) {
-                usedSpace += horizontalSpacing + minColumnWidths[i];
-            }
-
-            int availableSpace = LayoutManager.get(client).getInnerWidth(
-                    getElement());
-            int excessSpace = availableSpace - usedSpace;
-            int distributed = 0;
-            if (excessSpace > 0) {
-                for (int i = 0; i < columnWidths.length; i++) {
-                    int ew = excessSpace * colExpandRatioArray[i] / 1000;
-                    columnWidths[i] = minColumnWidths[i] + ew;
-                    distributed += ew;
-                }
-                excessSpace -= distributed;
-                int c = 0;
-                while (excessSpace > 0) {
-                    columnWidths[c % columnWidths.length]++;
-                    excessSpace--;
-                    c++;
-                }
-            }
-        }
-    }
-
-    void layoutCellsVertically() {
-        int verticalSpacing = getVerticalSpacing();
-        LayoutManager layoutManager = LayoutManager.get(client);
-        Element element = getElement();
-        int paddingTop = layoutManager.getPaddingTop(element);
-        int paddingBottom = layoutManager.getPaddingBottom(element);
-        int y = paddingTop;
-
-        for (int i = 0; i < cells.length; i++) {
-            y = paddingTop;
-            for (int j = 0; j < cells[i].length; j++) {
-                Cell cell = cells[i][j];
-                if (cell != null) {
-                    int reservedMargin;
-                    if (cell.rowspan + j >= cells[i].length) {
-                        // Make room for layout padding for cells reaching the
-                        // bottom of the layout
-                        reservedMargin = paddingBottom;
-                    } else {
-                        reservedMargin = 0;
-                    }
-                    cell.layoutVertically(y, reservedMargin);
-                }
-                y += rowHeights[j] + verticalSpacing;
-            }
-        }
-
-        if (isUndefinedHeight()) {
-            int outerHeight = y - verticalSpacing
-                    + layoutManager.getPaddingBottom(element)
-                    + layoutManager.getBorderHeight(element);
-            element.getStyle().setHeight(outerHeight, Unit.PX);
-            getConnector().getLayoutManager().reportOuterHeight(getConnector(),
-                    outerHeight);
-        }
-    }
-
-    void layoutCellsHorizontally() {
-        LayoutManager layoutManager = LayoutManager.get(client);
-        Element element = getElement();
-        int x = layoutManager.getPaddingLeft(element);
-        int paddingRight = layoutManager.getPaddingRight(element);
-        int horizontalSpacing = getHorizontalSpacing();
-        for (int i = 0; i < cells.length; i++) {
-            for (int j = 0; j < cells[i].length; j++) {
-                Cell cell = cells[i][j];
-                if (cell != null) {
-                    int reservedMargin;
-                    // Make room for layout padding for cells reaching the
-                    // right edge of the layout
-                    if (i + cell.colspan >= cells.length) {
-                        reservedMargin = paddingRight;
-                    } else {
-                        reservedMargin = 0;
-                    }
-                    cell.layoutHorizontally(x, reservedMargin);
-                }
-            }
-            x += columnWidths[i] + horizontalSpacing;
-        }
-
-        if (isUndefinedWidth()) {
-            int outerWidth = x - horizontalSpacing
-                    + layoutManager.getPaddingRight(element)
-                    + layoutManager.getBorderWidth(element);
-            element.getStyle().setWidth(outerWidth, Unit.PX);
-            getConnector().getLayoutManager().reportOuterWidth(getConnector(),
-                    outerWidth);
-        }
-    }
-
-    private boolean isUndefinedHeight() {
-        return getConnector().isUndefinedHeight();
-    }
-
-    private boolean isUndefinedWidth() {
-        return getConnector().isUndefinedWidth();
-    }
-
-    private void detectRowHeights() {
-        for (int i = 0; i < rowHeights.length; i++) {
-            rowHeights[i] = 0;
-        }
-
-        // collect min rowheight from non-rowspanned cells
-        for (int i = 0; i < cells.length; i++) {
-            for (int j = 0; j < cells[i].length; j++) {
-                Cell cell = cells[i][j];
-                if (cell != null) {
-                    if (cell.rowspan == 1) {
-                        if (!cell.hasRelativeHeight()
-                                && rowHeights[j] < cell.getHeight()) {
-                            rowHeights[j] = cell.getHeight();
-                        }
-                    } else {
-                        storeRowSpannedCell(cell);
-                    }
-                }
-            }
-        }
-
-        distributeRowSpanHeights();
-
-        minRowHeights = cloneArray(rowHeights);
-    }
-
-    private void detectColWidths() {
-        // collect min colwidths from non-colspanned cells
-        for (int i = 0; i < columnWidths.length; i++) {
-            columnWidths[i] = 0;
-        }
-
-        for (int i = 0; i < cells.length; i++) {
-            for (int j = 0; j < cells[i].length; j++) {
-                Cell cell = cells[i][j];
-                if (cell != null) {
-                    if (cell.colspan == 1) {
-                        if (!cell.hasRelativeWidth()
-                                && columnWidths[i] < cell.getWidth()) {
-                            columnWidths[i] = cell.getWidth();
-                        }
-                    } else {
-                        storeColSpannedCell(cell);
-                    }
-                }
-            }
-        }
-
-        distributeColSpanWidths();
-
-        minColumnWidths = cloneArray(columnWidths);
-    }
-
-    private void storeRowSpannedCell(Cell cell) {
-        SpanList l = null;
-        for (SpanList list : rowSpans) {
-            if (list.span < cell.rowspan) {
-                continue;
-            } else {
-                // insert before this
-                l = list;
-                break;
-            }
-        }
-        if (l == null) {
-            l = new SpanList(cell.rowspan);
-            rowSpans.add(l);
-        } else if (l.span != cell.rowspan) {
-            SpanList newL = new SpanList(cell.rowspan);
-            rowSpans.add(rowSpans.indexOf(l), newL);
-            l = newL;
-        }
-        l.cells.add(cell);
-    }
-
-    /**
-     * Iterates colspanned cells, ensures cols have enough space to accommodate
-     * them
-     */
-    void distributeColSpanWidths() {
-        for (SpanList list : colSpans) {
-            for (Cell cell : list.cells) {
-                // cells with relative content may return non 0 here if on
-                // subsequent renders
-                int width = cell.hasRelativeWidth() ? 0 : cell.getWidth();
-                distributeSpanSize(columnWidths, cell.col, cell.colspan,
-                        getHorizontalSpacing(), width, colExpandRatioArray);
-            }
-        }
-    }
-
-    /**
-     * Iterates rowspanned cells, ensures rows have enough space to accommodate
-     * them
-     */
-    private void distributeRowSpanHeights() {
-        for (SpanList list : rowSpans) {
-            for (Cell cell : list.cells) {
-                // cells with relative content may return non 0 here if on
-                // subsequent renders
-                int height = cell.hasRelativeHeight() ? 0 : cell.getHeight();
-                distributeSpanSize(rowHeights, cell.row, cell.rowspan,
-                        getVerticalSpacing(), height, rowExpandRatioArray);
-            }
-        }
-    }
-
-    private static void distributeSpanSize(int[] dimensions,
-            int spanStartIndex, int spanSize, int spacingSize, int size,
-            int[] expansionRatios) {
-        int allocated = dimensions[spanStartIndex];
-        for (int i = 1; i < spanSize; i++) {
-            allocated += spacingSize + dimensions[spanStartIndex + i];
-        }
-        if (allocated < size) {
-            // dimensions needs to be expanded due spanned cell
-            int neededExtraSpace = size - allocated;
-            int allocatedExtraSpace = 0;
-
-            // Divide space according to expansion ratios if any span has a
-            // ratio
-            int totalExpansion = 0;
-            for (int i = 0; i < spanSize; i++) {
-                int itemIndex = spanStartIndex + i;
-                totalExpansion += expansionRatios[itemIndex];
-            }
-
-            for (int i = 0; i < spanSize; i++) {
-                int itemIndex = spanStartIndex + i;
-                int expansion;
-                if (totalExpansion == 0) {
-                    // Divide equally among all cells if there are no
-                    // expansion ratios
-                    expansion = neededExtraSpace / spanSize;
-                } else {
-                    expansion = neededExtraSpace * expansionRatios[itemIndex]
-                            / totalExpansion;
-                }
-                dimensions[itemIndex] += expansion;
-                allocatedExtraSpace += expansion;
-            }
-
-            // We might still miss a couple of pixels because of
-            // rounding errors...
-            if (neededExtraSpace > allocatedExtraSpace) {
-                for (int i = 0; i < spanSize; i++) {
-                    // Add one pixel to every cell until we have
-                    // compensated for any rounding error
-                    int itemIndex = spanStartIndex + i;
-                    dimensions[itemIndex] += 1;
-                    allocatedExtraSpace += 1;
-                    if (neededExtraSpace == allocatedExtraSpace) {
-                        break;
-                    }
-                }
-            }
-        }
-    }
-
-    private LinkedList<SpanList> colSpans = new LinkedList<SpanList>();
-    private LinkedList<SpanList> rowSpans = new LinkedList<SpanList>();
-
-    private class SpanList {
-        final int span;
-        List<Cell> cells = new LinkedList<Cell>();
-
-        public SpanList(int span) {
-            this.span = span;
-        }
-    }
-
-    void storeColSpannedCell(Cell cell) {
-        SpanList l = null;
-        for (SpanList list : colSpans) {
-            if (list.span < cell.colspan) {
-                continue;
-            } else {
-                // insert before this
-                l = list;
-                break;
-            }
-        }
-        if (l == null) {
-            l = new SpanList(cell.colspan);
-            colSpans.add(l);
-        } else if (l.span != cell.colspan) {
-
-            SpanList newL = new SpanList(cell.colspan);
-            colSpans.add(colSpans.indexOf(l), newL);
-            l = newL;
-        }
-        l.cells.add(cell);
-    }
-
-    Cell[][] cells;
-
-    /**
-     * Private helper class.
-     */
-    class Cell {
-        public Cell(int row, int col) {
-            this.row = row;
-            this.col = col;
-        }
-
-        public boolean hasContent() {
-            return hasContent;
-        }
-
-        public boolean hasRelativeHeight() {
-            if (slot != null) {
-                return slot.getChild().isRelativeHeight();
-            } else {
-                return true;
-            }
-        }
-
-        /**
-         * @return total of spanned cols
-         */
-        private int getAvailableWidth() {
-            int width = columnWidths[col];
-            for (int i = 1; i < colspan; i++) {
-                width += getHorizontalSpacing() + columnWidths[col + i];
-            }
-            return width;
-        }
-
-        /**
-         * @return total of spanned rows
-         */
-        private int getAvailableHeight() {
-            int height = rowHeights[row];
-            for (int i = 1; i < rowspan; i++) {
-                height += getVerticalSpacing() + rowHeights[row + i];
-            }
-            return height;
-        }
-
-        public void layoutHorizontally(int x, int marginRight) {
-            if (slot != null) {
-                slot.positionHorizontally(x, getAvailableWidth(), marginRight);
-            }
-        }
-
-        public void layoutVertically(int y, int marginBottom) {
-            if (slot != null) {
-                slot.positionVertically(y, getAvailableHeight(), marginBottom);
-            }
-        }
-
-        public int getWidth() {
-            if (slot != null) {
-                return slot.getUsedWidth();
-            } else {
-                return 0;
-            }
-        }
-
-        public int getHeight() {
-            if (slot != null) {
-                return slot.getUsedHeight();
-            } else {
-                return 0;
-            }
-        }
-
-        protected boolean hasRelativeWidth() {
-            if (slot != null) {
-                return slot.getChild().isRelativeWidth();
-            } else {
-                return true;
-            }
-        }
-
-        final int row;
-        final int col;
-        int colspan = 1;
-        int rowspan = 1;
-
-        private boolean hasContent;
-
-        private AlignmentInfo alignment;
-
-        ComponentConnectorLayoutSlot slot;
-
-        public void updateFromUidl(UIDL cellUidl) {
-            // Set cell width
-            colspan = cellUidl.hasAttribute("w") ? cellUidl
-                    .getIntAttribute("w") : 1;
-            // Set cell height
-            rowspan = cellUidl.hasAttribute("h") ? cellUidl
-                    .getIntAttribute("h") : 1;
-            // ensure we will lose reference to old cells, now overlapped by
-            // this cell
-            for (int i = 0; i < colspan; i++) {
-                for (int j = 0; j < rowspan; j++) {
-                    if (i > 0 || j > 0) {
-                        cells[col + i][row + j] = null;
-                    }
-                }
-            }
-
-            UIDL childUidl = cellUidl.getChildUIDL(0); // we are interested
-                                                       // about childUidl
-            hasContent = childUidl != null;
-            if (hasContent) {
-                ComponentConnector childConnector = client
-                        .getPaintable(childUidl);
-
-                if (slot == null || slot.getChild() != childConnector) {
-                    slot = new ComponentConnectorLayoutSlot(CLASSNAME,
-                            childConnector, getConnector());
-                    if (childConnector.isRelativeWidth()) {
-                        slot.getWrapperElement().getStyle()
-                                .setWidth(100, Unit.PCT);
-                    }
-                    Element slotWrapper = slot.getWrapperElement();
-                    getElement().appendChild(slotWrapper);
-
-                    Widget widget = childConnector.getWidget();
-                    insert(widget, slotWrapper, getWidgetCount(), false);
-                    Cell oldCell = widgetToCell.put(widget, this);
-                    if (oldCell != null) {
-                        oldCell.slot.getWrapperElement().removeFromParent();
-                        oldCell.slot = null;
-                    }
-                }
-
-            }
-        }
-
-        public void setAlignment(AlignmentInfo alignmentInfo) {
-            slot.setAlignment(alignmentInfo);
-        }
-    }
-
-    Cell getCell(int row, int col) {
-        return cells[col][row];
-    }
-
-    /**
-     * Creates a new Cell with the given coordinates. If an existing cell was
-     * found, returns that one.
-     * 
-     * @param row
-     * @param col
-     * @return
-     */
-    Cell createCell(int row, int col) {
-        Cell cell = getCell(row, col);
-        if (cell == null) {
-            cell = new Cell(row, col);
-            cells[col][row] = cell;
-        }
-        return cell;
-    }
-
-    /**
-     * Returns the deepest nested child component which contains "element". The
-     * child component is also returned if "element" is part of its caption.
-     * 
-     * @param element
-     *            An element that is a nested sub element of the root element in
-     *            this layout
-     * @return The Paintable which the element is a part of. Null if the element
-     *         belongs to the layout and not to a child.
-     */
-    ComponentConnector getComponent(Element element) {
-        return Util.getConnectorForElement(client, this, element);
-    }
-
-    void setCaption(Widget widget, VCaption caption) {
-        VLayoutSlot slot = widgetToCell.get(widget).slot;
-
-        if (caption != null) {
-            // Logical attach.
-            getChildren().add(caption);
-        }
-
-        // Physical attach if not null, also removes old caption
-        slot.setCaption(caption);
-
-        if (caption != null) {
-            // Adopt.
-            adopt(caption);
-        }
-    }
-
-    private void togglePrefixedStyleName(String name, boolean enabled) {
-        if (enabled) {
-            addStyleDependentName(name);
-        } else {
-            removeStyleDependentName(name);
-        }
-    }
-
-    void updateMarginStyleNames(MarginInfo marginInfo) {
-        togglePrefixedStyleName("margin-top", marginInfo.hasTop());
-        togglePrefixedStyleName("margin-right", marginInfo.hasRight());
-        togglePrefixedStyleName("margin-bottom", marginInfo.hasBottom());
-        togglePrefixedStyleName("margin-left", marginInfo.hasLeft());
-    }
-
-    void updateSpacingStyleName(boolean spacingEnabled) {
-        String styleName = getStylePrimaryName();
-        if (spacingEnabled) {
-            spacingMeasureElement.addClassName(styleName + "-spacing-on");
-            spacingMeasureElement.removeClassName(styleName + "-spacing-off");
-        } else {
-            spacingMeasureElement.removeClassName(styleName + "-spacing-on");
-            spacingMeasureElement.addClassName(styleName + "-spacing-off");
-        }
-    }
-
-    public void setSize(int rows, int cols) {
-        if (cells == null) {
-            cells = new Cell[cols][rows];
-        } else if (cells.length != cols || cells[0].length != rows) {
-            Cell[][] newCells = new Cell[cols][rows];
-            for (int i = 0; i < cells.length; i++) {
-                for (int j = 0; j < cells[i].length; j++) {
-                    if (i < cols && j < rows) {
-                        newCells[i][j] = cells[i][j];
-                    }
-                }
-            }
-            cells = newCells;
-        }
-    }
-
-}
index e3fdd74aabe5917442c84e43e203ad2561749950..a3b48ac9d673703ff1b73240659693322fb8f8ea 100644 (file)
@@ -6,6 +6,7 @@ import com.google.gwt.event.dom.client.LoadHandler;
 import com.vaadin.client.communication.StateChangeEvent;
 import com.vaadin.client.ui.AbstractComponentConnector;
 import com.vaadin.client.ui.ClickEventHandler;
+import com.vaadin.client.ui.VImage;
 import com.vaadin.shared.MouseEventDetails;
 import com.vaadin.shared.ui.AbstractEmbeddedState;
 import com.vaadin.shared.ui.Connect;
diff --git a/client/src/com/vaadin/client/ui/image/VImage.java b/client/src/com/vaadin/client/ui/image/VImage.java
deleted file mode 100644 (file)
index b05357b..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.vaadin.client.ui.image;
-
-import com.google.gwt.user.client.ui.Image;
-
-public class VImage extends Image {
-
-    public static final String CLASSNAME = "v-image";
-
-    public VImage() {
-        setStylePrimaryName(CLASSNAME);
-    }
-}
index 9ec530ba2c30d224f55b73fbb0d169ac41b64cd0..d2709b815b775e5b5f7acc64d8310684e056d128 100644 (file)
@@ -20,6 +20,7 @@ import com.google.gwt.dom.client.PreElement;
 import com.vaadin.client.Util;
 import com.vaadin.client.communication.StateChangeEvent;
 import com.vaadin.client.ui.AbstractComponentConnector;
+import com.vaadin.client.ui.VLabel;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.Connect.LoadStyle;
 import com.vaadin.shared.ui.label.LabelState;
diff --git a/client/src/com/vaadin/client/ui/label/VLabel.java b/client/src/com/vaadin/client/ui/label/VLabel.java
deleted file mode 100644 (file)
index 3874f2a..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-/* 
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.label;
-
-import com.google.gwt.dom.client.Style.Display;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.HTML;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.Util;
-import com.vaadin.client.VTooltip;
-
-public class VLabel extends HTML {
-
-    public static final String CLASSNAME = "v-label";
-    private static final String CLASSNAME_UNDEFINED_WIDTH = "v-label-undef-w";
-
-    private ApplicationConnection connection;
-
-    public VLabel() {
-        super();
-        setStyleName(CLASSNAME);
-        sinkEvents(VTooltip.TOOLTIP_EVENTS);
-    }
-
-    public VLabel(String text) {
-        super(text);
-        setStyleName(CLASSNAME);
-    }
-
-    @Override
-    public void onBrowserEvent(Event event) {
-        super.onBrowserEvent(event);
-        if (event.getTypeInt() == Event.ONLOAD) {
-            Util.notifyParentOfSizeChange(this, true);
-            event.stopPropagation();
-            return;
-        }
-    }
-
-    @Override
-    public void setWidth(String width) {
-        super.setWidth(width);
-        if (width == null || width.equals("")) {
-            setStyleName(getElement(), CLASSNAME_UNDEFINED_WIDTH, true);
-            getElement().getStyle().setDisplay(Display.INLINE_BLOCK);
-        } else {
-            setStyleName(getElement(), CLASSNAME_UNDEFINED_WIDTH, false);
-            getElement().getStyle().clearDisplay();
-        }
-    }
-
-    @Override
-    public void setText(String text) {
-        if (BrowserInfo.get().isIE8()) {
-            // #3983 - IE8 incorrectly replaces \n with <br> so we do the
-            // escaping manually and set as HTML
-            super.setHTML(Util.escapeHTML(text));
-        } else {
-            super.setText(text);
-        }
-    }
-
-    void setConnection(ApplicationConnection client) {
-        connection = client;
-    }
-}
index f97085576963ea28c831accdc8e640e37b6eddc7..a53eac9234e24cb4541f338fbacf585dbdb0f77f 100644 (file)
@@ -24,6 +24,7 @@ import com.vaadin.client.communication.StateChangeEvent;
 import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler;
 import com.vaadin.client.ui.AbstractComponentConnector;
 import com.vaadin.client.ui.Icon;
+import com.vaadin.client.ui.VLink;
 import com.vaadin.shared.ui.BorderStyle;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.link.LinkConstants;
diff --git a/client/src/com/vaadin/client/ui/link/VLink.java b/client/src/com/vaadin/client/ui/link/VLink.java
deleted file mode 100644 (file)
index 54794ac..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.link;
-
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.ui.HTML;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.Icon;
-import com.vaadin.shared.ui.BorderStyle;
-
-public class VLink extends HTML implements ClickHandler {
-
-    public static final String CLASSNAME = "v-link";
-
-    @Deprecated
-    protected static final BorderStyle BORDER_STYLE_DEFAULT = BorderStyle.DEFAULT;
-
-    @Deprecated
-    protected static final BorderStyle BORDER_STYLE_MINIMAL = BorderStyle.MINIMAL;
-
-    @Deprecated
-    protected static final BorderStyle BORDER_STYLE_NONE = BorderStyle.NONE;
-
-    protected String src;
-
-    protected String target;
-
-    protected BorderStyle borderStyle = BorderStyle.DEFAULT;
-
-    protected boolean enabled;
-
-    protected int targetWidth;
-
-    protected int targetHeight;
-
-    protected Element errorIndicatorElement;
-
-    protected final Element anchor = DOM.createAnchor();
-
-    protected final Element captionElement = DOM.createSpan();
-
-    protected Icon icon;
-
-    protected ApplicationConnection client;
-
-    public VLink() {
-        super();
-        getElement().appendChild(anchor);
-        anchor.appendChild(captionElement);
-        addClickHandler(this);
-        setStyleName(CLASSNAME);
-    }
-
-    @Override
-    public void onClick(ClickEvent event) {
-        if (enabled) {
-            if (target == null) {
-                target = "_self";
-            }
-            String features;
-            switch (borderStyle) {
-            case NONE:
-                features = "menubar=no,location=no,status=no";
-                break;
-            case MINIMAL:
-                features = "menubar=yes,location=no,status=no";
-                break;
-            default:
-                features = "";
-                break;
-            }
-
-            if (targetWidth > 0) {
-                features += (features.length() > 0 ? "," : "") + "width="
-                        + targetWidth;
-            }
-            if (targetHeight > 0) {
-                features += (features.length() > 0 ? "," : "") + "height="
-                        + targetHeight;
-            }
-
-            if (features.length() > 0) {
-                // if 'special features' are set, use window.open(), unless
-                // a modifier key is held (ctrl to open in new tab etc)
-                Event e = DOM.eventGetCurrentEvent();
-                if (!e.getCtrlKey() && !e.getAltKey() && !e.getShiftKey()
-                        && !e.getMetaKey()) {
-                    Window.open(src, target, features);
-                    e.preventDefault();
-                }
-            }
-        }
-    }
-
-    @Override
-    public void onBrowserEvent(Event event) {
-        final Element target = DOM.eventGetTarget(event);
-        if (event.getTypeInt() == Event.ONLOAD) {
-            Util.notifyParentOfSizeChange(this, true);
-        }
-        if (target == captionElement || target == anchor
-                || (icon != null && target == icon.getElement())) {
-            super.onBrowserEvent(event);
-        }
-        if (!enabled) {
-            event.preventDefault();
-        }
-
-    }
-
-}
index 640521e170d850b73aa58a06b7c0c8e02cbdd850..7abcaa95208f96a86382d752cb0fab24a38d4313 100644 (file)
@@ -16,6 +16,7 @@
 
 package com.vaadin.client.ui.listselect;
 
+import com.vaadin.client.ui.VListSelect;
 import com.vaadin.client.ui.optiongroup.OptionGroupBaseConnector;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.ui.ListSelect;
diff --git a/client/src/com/vaadin/client/ui/listselect/VListSelect.java b/client/src/com/vaadin/client/ui/listselect/VListSelect.java
deleted file mode 100644 (file)
index d8d0a43..0000000
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.listselect;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-
-import com.google.gwt.event.dom.client.ChangeEvent;
-import com.google.gwt.user.client.ui.ListBox;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.ui.optiongroup.VOptionGroupBase;
-
-public class VListSelect extends VOptionGroupBase {
-
-    public static final String CLASSNAME = "v-select";
-
-    private static final int VISIBLE_COUNT = 10;
-
-    protected ListBox select;
-
-    private int lastSelectedIndex = -1;
-
-    public VListSelect() {
-        super(new ListBox(true), CLASSNAME);
-        select = getOptionsContainer();
-        select.addChangeHandler(this);
-        select.addClickHandler(this);
-        select.setVisibleItemCount(VISIBLE_COUNT);
-        setStyleName(CLASSNAME);
-    }
-
-    @Override
-    public void setStyleName(String style) {
-        super.setStyleName(style);
-        updateStyleNames();
-    }
-
-    @Override
-    public void setStylePrimaryName(String style) {
-        super.setStylePrimaryName(style);
-        updateStyleNames();
-    }
-
-    protected void updateStyleNames() {
-        container.setStyleName(getStylePrimaryName());
-        select.setStyleName(getStylePrimaryName() + "-select");
-    }
-
-    protected ListBox getOptionsContainer() {
-        return (ListBox) optionsContainer;
-    }
-
-    @Override
-    protected void buildOptions(UIDL uidl) {
-        select.setMultipleSelect(isMultiselect());
-        select.setEnabled(!isDisabled() && !isReadonly());
-        select.clear();
-        if (!isMultiselect() && isNullSelectionAllowed()
-                && !isNullSelectionItemAvailable()) {
-            // can't unselect last item in singleselect mode
-            select.addItem("", (String) null);
-        }
-        for (final Iterator<?> i = uidl.getChildIterator(); i.hasNext();) {
-            final UIDL optionUidl = (UIDL) i.next();
-            select.addItem(optionUidl.getStringAttribute("caption"),
-                    optionUidl.getStringAttribute("key"));
-            if (optionUidl.hasAttribute("selected")) {
-                int itemIndex = select.getItemCount() - 1;
-                select.setItemSelected(itemIndex, true);
-                lastSelectedIndex = itemIndex;
-            }
-        }
-        if (getRows() > 0) {
-            select.setVisibleItemCount(getRows());
-        }
-    }
-
-    @Override
-    protected String[] getSelectedItems() {
-        final ArrayList<String> selectedItemKeys = new ArrayList<String>();
-        for (int i = 0; i < select.getItemCount(); i++) {
-            if (select.isItemSelected(i)) {
-                selectedItemKeys.add(select.getValue(i));
-            }
-        }
-        return selectedItemKeys.toArray(new String[selectedItemKeys.size()]);
-    }
-
-    @Override
-    public void onChange(ChangeEvent event) {
-        final int si = select.getSelectedIndex();
-        if (si == -1 && !isNullSelectionAllowed()) {
-            select.setSelectedIndex(lastSelectedIndex);
-        } else {
-            lastSelectedIndex = si;
-            if (isMultiselect()) {
-                client.updateVariable(paintableId, "selected",
-                        getSelectedItems(), isImmediate());
-            } else {
-                client.updateVariable(paintableId, "selected",
-                        new String[] { "" + getSelectedItem() }, isImmediate());
-            }
-        }
-    }
-
-    @Override
-    public void setHeight(String height) {
-        select.setHeight(height);
-        super.setHeight(height);
-    }
-
-    @Override
-    public void setWidth(String width) {
-        select.setWidth(width);
-        super.setWidth(width);
-    }
-
-    @Override
-    protected void setTabIndex(int tabIndex) {
-        getOptionsContainer().setTabIndex(tabIndex);
-    }
-
-    @Override
-    public void focus() {
-        select.setFocus(true);
-    }
-}
\ No newline at end of file
index 817e027283919e54e2885dc0dbf7283e7bf1c2a3..f3db9c98f118a0f51230424756416ed2042873ba 100644 (file)
@@ -29,6 +29,7 @@ import com.vaadin.client.Util;
 import com.vaadin.client.ui.AbstractComponentConnector;
 import com.vaadin.client.ui.Icon;
 import com.vaadin.client.ui.SimpleManagedLayout;
+import com.vaadin.client.ui.VMenuBar;
 import com.vaadin.shared.ui.ComponentStateUtil;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.Connect.LoadStyle;
diff --git a/client/src/com/vaadin/client/ui/menubar/VMenuBar.java b/client/src/com/vaadin/client/ui/menubar/VMenuBar.java
deleted file mode 100644 (file)
index 1837cbc..0000000
+++ /dev/null
@@ -1,1551 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.vaadin.client.ui.menubar;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
-import com.google.gwt.dom.client.Style;
-import com.google.gwt.dom.client.Style.Overflow;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.dom.client.KeyDownEvent;
-import com.google.gwt.event.dom.client.KeyDownHandler;
-import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.event.dom.client.KeyPressHandler;
-import com.google.gwt.event.logical.shared.CloseEvent;
-import com.google.gwt.event.logical.shared.CloseHandler;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.Timer;
-import com.google.gwt.user.client.ui.HasHTML;
-import com.google.gwt.user.client.ui.PopupPanel;
-import com.google.gwt.user.client.ui.RootPanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.LayoutManager;
-import com.vaadin.client.TooltipInfo;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.Icon;
-import com.vaadin.client.ui.SimpleFocusablePanel;
-import com.vaadin.client.ui.SubPartAware;
-import com.vaadin.client.ui.VLazyExecutor;
-import com.vaadin.client.ui.VOverlay;
-import com.vaadin.shared.ui.menubar.MenuBarConstants;
-
-public class VMenuBar extends SimpleFocusablePanel implements
-        CloseHandler<PopupPanel>, KeyPressHandler, KeyDownHandler,
-        FocusHandler, SubPartAware {
-
-    // The hierarchy of VMenuBar is a bit weird as VMenuBar is the Paintable,
-    // used for the root menu but also used for the sub menus.
-
-    /** Set the CSS class name to allow styling. */
-    public static final String CLASSNAME = "v-menubar";
-    public static final String SUBMENU_CLASSNAME_PREFIX = "-submenu";
-
-    /** For server connections **/
-    protected String uidlId;
-    protected ApplicationConnection client;
-
-    protected final VMenuBar hostReference = this;
-    protected CustomMenuItem moreItem = null;
-
-    // Only used by the root menu bar
-    protected VMenuBar collapsedRootItems;
-
-    // Construct an empty command to be used when the item has no command
-    // associated
-    protected static final Command emptyCommand = null;
-
-    /** Widget fields **/
-    protected boolean subMenu;
-    protected ArrayList<CustomMenuItem> items;
-    protected Element containerElement;
-    protected VOverlay popup;
-    protected VMenuBar visibleChildMenu;
-    protected boolean menuVisible = false;
-    protected VMenuBar parentMenu;
-    protected CustomMenuItem selected;
-
-    boolean enabled = true;
-
-    private VLazyExecutor iconLoadedExecutioner = new VLazyExecutor(100,
-            new ScheduledCommand() {
-
-                @Override
-                public void execute() {
-                    iLayout(true);
-                }
-            });
-
-    boolean openRootOnHover;
-
-    boolean htmlContentAllowed;
-
-    public VMenuBar() {
-        // Create an empty horizontal menubar
-        this(false, null);
-
-        // Navigation is only handled by the root bar
-        addFocusHandler(this);
-
-        /*
-         * Firefox auto-repeat works correctly only if we use a key press
-         * handler, other browsers handle it correctly when using a key down
-         * handler
-         */
-        if (BrowserInfo.get().isGecko()) {
-            addKeyPressHandler(this);
-        } else {
-            addKeyDownHandler(this);
-        }
-    }
-
-    public VMenuBar(boolean subMenu, VMenuBar parentMenu) {
-
-        items = new ArrayList<CustomMenuItem>();
-        popup = null;
-        visibleChildMenu = null;
-        this.subMenu = subMenu;
-
-        containerElement = getElement();
-
-        sinkEvents(Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT
-                | Event.ONLOAD);
-
-        if (parentMenu == null) {
-            // Root menu
-            setStyleName(CLASSNAME);
-        } else {
-            // Child menus inherits style name
-            setStyleName(parentMenu.getStyleName());
-        }
-    }
-
-    @Override
-    public void setStyleName(String style) {
-        super.setStyleName(style);
-        updateStyleNames();
-    }
-
-    @Override
-    public void setStylePrimaryName(String style) {
-        super.setStylePrimaryName(style);
-        updateStyleNames();
-    }
-
-    protected void updateStyleNames() {
-        String primaryStyleName = getParentMenu() != null ? getParentMenu()
-                .getStylePrimaryName() : getStylePrimaryName();
-
-        // Reset the style name for all the items
-        for (CustomMenuItem item : items) {
-            item.setStyleName(primaryStyleName + "-menuitem");
-        }
-
-        if (subMenu
-                && !getStylePrimaryName().endsWith(SUBMENU_CLASSNAME_PREFIX)) {
-            /*
-             * Sub-menus should get the sub-menu prefix
-             */
-            super.setStylePrimaryName(primaryStyleName
-                    + SUBMENU_CLASSNAME_PREFIX);
-        }
-    }
-
-    @Override
-    protected void onDetach() {
-        super.onDetach();
-        if (!subMenu) {
-            setSelected(null);
-            hideChildren();
-            menuVisible = false;
-        }
-    }
-
-    void updateSize() {
-        // Take from setWidth
-        if (!subMenu) {
-            // Only needed for root level menu
-            hideChildren();
-            setSelected(null);
-            menuVisible = false;
-        }
-    }
-
-    /**
-     * Build the HTML content for a menu item.
-     * 
-     * @param item
-     * @return
-     */
-    protected String buildItemHTML(UIDL item) {
-        // Construct html from the text and the optional icon
-        StringBuffer itemHTML = new StringBuffer();
-        if (item.hasAttribute("separator")) {
-            itemHTML.append("<span>---</span>");
-        } else {
-            // Add submenu indicator
-            if (item.getChildCount() > 0) {
-                String bgStyle = "";
-                itemHTML.append("<span class=\"" + getStylePrimaryName()
-                        + "-submenu-indicator\"" + bgStyle + ">&#x25BA;</span>");
-            }
-
-            itemHTML.append("<span class=\"" + getStylePrimaryName()
-                    + "-menuitem-caption\">");
-            if (item.hasAttribute("icon")) {
-                itemHTML.append("<img src=\""
-                        + Util.escapeAttribute(client.translateVaadinUri(item
-                                .getStringAttribute("icon"))) + "\" class=\""
-                        + Icon.CLASSNAME + "\" alt=\"\" />");
-            }
-            String itemText = item.getStringAttribute("text");
-            if (!htmlContentAllowed) {
-                itemText = Util.escapeHTML(itemText);
-            }
-            itemHTML.append(itemText);
-            itemHTML.append("</span>");
-        }
-        return itemHTML.toString();
-    }
-
-    /**
-     * This is called by the items in the menu and it communicates the
-     * information to the server
-     * 
-     * @param clickedItemId
-     *            id of the item that was clicked
-     */
-    public void onMenuClick(int clickedItemId) {
-        // Updating the state to the server can not be done before
-        // the server connection is known, i.e., before updateFromUIDL()
-        // has been called.
-        if (uidlId != null && client != null) {
-            // Communicate the user interaction parameters to server. This call
-            // will initiate an AJAX request to the server.
-            client.updateVariable(uidlId, "clickedId", clickedItemId, true);
-        }
-    }
-
-    /** Widget methods **/
-
-    /**
-     * Returns a list of items in this menu
-     */
-    public List<CustomMenuItem> getItems() {
-        return items;
-    }
-
-    /**
-     * Remove all the items in this menu
-     */
-    public void clearItems() {
-        Element e = getContainerElement();
-        while (DOM.getChildCount(e) > 0) {
-            DOM.removeChild(e, DOM.getChild(e, 0));
-        }
-        items.clear();
-    }
-
-    /**
-     * Returns the containing element of the menu
-     * 
-     * @return
-     */
-    @Override
-    public Element getContainerElement() {
-        return containerElement;
-    }
-
-    /**
-     * Add a new item to this menu
-     * 
-     * @param html
-     *            items text
-     * @param cmd
-     *            items command
-     * @return the item created
-     */
-    public CustomMenuItem addItem(String html, Command cmd) {
-        CustomMenuItem item = GWT.create(CustomMenuItem.class);
-        item.setHTML(html);
-        item.setCommand(cmd);
-        addItem(item);
-        return item;
-    }
-
-    /**
-     * Add a new item to this menu
-     * 
-     * @param item
-     */
-    public void addItem(CustomMenuItem item) {
-        if (items.contains(item)) {
-            return;
-        }
-        DOM.appendChild(getContainerElement(), item.getElement());
-        item.setParentMenu(this);
-        item.setSelected(false);
-        items.add(item);
-    }
-
-    public void addItem(CustomMenuItem item, int index) {
-        if (items.contains(item)) {
-            return;
-        }
-        DOM.insertChild(getContainerElement(), item.getElement(), index);
-        item.setParentMenu(this);
-        item.setSelected(false);
-        items.add(index, item);
-    }
-
-    /**
-     * Remove the given item from this menu
-     * 
-     * @param item
-     */
-    public void removeItem(CustomMenuItem item) {
-        if (items.contains(item)) {
-            int index = items.indexOf(item);
-
-            DOM.removeChild(getContainerElement(),
-                    DOM.getChild(getContainerElement(), index));
-            items.remove(index);
-        }
-    }
-
-    /*
-     * @see
-     * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt.user
-     * .client.Event)
-     */
-    @Override
-    public void onBrowserEvent(Event e) {
-        super.onBrowserEvent(e);
-
-        // Handle onload events (icon loaded, size changes)
-        if (DOM.eventGetType(e) == Event.ONLOAD) {
-            VMenuBar parent = getParentMenu();
-            if (parent != null) {
-                // The onload event for an image in a popup should be sent to
-                // the parent, which owns the popup
-                parent.iconLoaded();
-            } else {
-                // Onload events for images in the root menu are handled by the
-                // root menu itself
-                iconLoaded();
-            }
-            return;
-        }
-
-        Element targetElement = DOM.eventGetTarget(e);
-        CustomMenuItem targetItem = null;
-        for (int i = 0; i < items.size(); i++) {
-            CustomMenuItem item = items.get(i);
-            if (DOM.isOrHasChild(item.getElement(), targetElement)) {
-                targetItem = item;
-            }
-        }
-
-        if (targetItem != null) {
-            switch (DOM.eventGetType(e)) {
-
-            case Event.ONCLICK:
-                if (isEnabled() && targetItem.isEnabled()) {
-                    itemClick(targetItem);
-                }
-                if (subMenu) {
-                    // Prevent moving keyboard focus to child menus
-                    VMenuBar parent = parentMenu;
-                    while (parent.getParentMenu() != null) {
-                        parent = parent.getParentMenu();
-                    }
-                    parent.setFocus(true);
-                }
-
-                break;
-
-            case Event.ONMOUSEOVER:
-                LazyCloser.cancelClosing();
-
-                if (isEnabled() && targetItem.isEnabled()) {
-                    itemOver(targetItem);
-                }
-                break;
-
-            case Event.ONMOUSEOUT:
-                itemOut(targetItem);
-                LazyCloser.schedule();
-                break;
-            }
-        } else if (subMenu && DOM.eventGetType(e) == Event.ONCLICK && subMenu) {
-            // Prevent moving keyboard focus to child menus
-            VMenuBar parent = parentMenu;
-            while (parent.getParentMenu() != null) {
-                parent = parent.getParentMenu();
-            }
-            parent.setFocus(true);
-        }
-    }
-
-    private boolean isEnabled() {
-        return enabled;
-    }
-
-    private void iconLoaded() {
-        iconLoadedExecutioner.trigger();
-    }
-
-    /**
-     * When an item is clicked
-     * 
-     * @param item
-     */
-    public void itemClick(CustomMenuItem item) {
-        if (item.getCommand() != null) {
-            setSelected(null);
-
-            if (visibleChildMenu != null) {
-                visibleChildMenu.hideChildren();
-            }
-
-            hideParents(true);
-            menuVisible = false;
-            Scheduler.get().scheduleDeferred(item.getCommand());
-
-        } else {
-            if (item.getSubMenu() != null
-                    && item.getSubMenu() != visibleChildMenu) {
-                setSelected(item);
-                showChildMenu(item);
-                menuVisible = true;
-            } else if (!subMenu) {
-                setSelected(null);
-                hideChildren();
-                menuVisible = false;
-            }
-        }
-    }
-
-    /**
-     * When the user hovers the mouse over the item
-     * 
-     * @param item
-     */
-    public void itemOver(CustomMenuItem item) {
-        if ((openRootOnHover || subMenu || menuVisible) && !item.isSeparator()) {
-            setSelected(item);
-            if (!subMenu && openRootOnHover && !menuVisible) {
-                menuVisible = true; // start opening menus
-                LazyCloser.prepare(this);
-            }
-        }
-
-        if (menuVisible && visibleChildMenu != item.getSubMenu()
-                && popup != null) {
-            popup.hide();
-        }
-
-        if (menuVisible && item.getSubMenu() != null
-                && visibleChildMenu != item.getSubMenu()) {
-            showChildMenu(item);
-        }
-    }
-
-    /**
-     * When the mouse is moved away from an item
-     * 
-     * @param item
-     */
-    public void itemOut(CustomMenuItem item) {
-        if (visibleChildMenu != item.getSubMenu()) {
-            hideChildMenu(item);
-            setSelected(null);
-        } else if (visibleChildMenu == null) {
-            setSelected(null);
-        }
-    }
-
-    /**
-     * Used to autoclose submenus when they the menu is in a mode which opens
-     * root menus on mouse hover.
-     */
-    private static class LazyCloser extends Timer {
-        static LazyCloser INSTANCE;
-        private VMenuBar activeRoot;
-
-        @Override
-        public void run() {
-            activeRoot.hideChildren();
-            activeRoot.setSelected(null);
-            activeRoot.menuVisible = false;
-            activeRoot = null;
-        }
-
-        public static void cancelClosing() {
-            if (INSTANCE != null) {
-                INSTANCE.cancel();
-            }
-        }
-
-        public static void prepare(VMenuBar vMenuBar) {
-            if (INSTANCE == null) {
-                INSTANCE = new LazyCloser();
-            }
-            if (INSTANCE.activeRoot == vMenuBar) {
-                INSTANCE.cancel();
-            } else if (INSTANCE.activeRoot != null) {
-                INSTANCE.cancel();
-                INSTANCE.run();
-            }
-            INSTANCE.activeRoot = vMenuBar;
-        }
-
-        public static void schedule() {
-            if (INSTANCE != null && INSTANCE.activeRoot != null) {
-                INSTANCE.schedule(750);
-            }
-        }
-
-    }
-
-    /**
-     * Shows the child menu of an item. The caller must ensure that the item has
-     * a submenu.
-     * 
-     * @param item
-     */
-    public void showChildMenu(CustomMenuItem item) {
-
-        int left = 0;
-        int top = 0;
-        if (subMenu) {
-            left = item.getParentMenu().getAbsoluteLeft()
-                    + item.getParentMenu().getOffsetWidth();
-            top = item.getAbsoluteTop();
-        } else {
-            left = item.getAbsoluteLeft();
-            top = item.getParentMenu().getAbsoluteTop()
-                    + item.getParentMenu().getOffsetHeight();
-        }
-        showChildMenuAt(item, top, left);
-    }
-
-    protected void showChildMenuAt(CustomMenuItem item, int top, int left) {
-        final int shadowSpace = 10;
-
-        popup = new VOverlay(true, false, true);
-        popup.setOwner(this);
-
-        /*
-         * Use parents primary style name if possible and remove the submenu
-         * prefix if needed
-         */
-        String primaryStyleName = parentMenu != null ? parentMenu
-                .getStylePrimaryName() : getStylePrimaryName();
-        if (subMenu) {
-            primaryStyleName = primaryStyleName.replace(
-                    SUBMENU_CLASSNAME_PREFIX, "");
-        }
-        popup.setStyleName(primaryStyleName + "-popup");
-
-        // Setting owner and handlers to support tooltips. Needed for tooltip
-        // handling of overlay widgets (will direct queries to parent menu)
-        if (parentMenu == null) {
-            popup.setOwner(this);
-        } else {
-            VMenuBar parent = parentMenu;
-            while (parent.getParentMenu() != null) {
-                parent = parent.getParentMenu();
-            }
-            popup.setOwner(parent);
-        }
-        if (client != null) {
-            client.getVTooltip().connectHandlersToWidget(popup);
-        }
-
-        popup.setWidget(item.getSubMenu());
-        popup.addCloseHandler(this);
-        popup.addAutoHidePartner(item.getElement());
-
-        // at 0,0 because otherwise IE7 add extra scrollbars (#5547)
-        popup.setPopupPosition(0, 0);
-
-        item.getSubMenu().onShow();
-        visibleChildMenu = item.getSubMenu();
-        item.getSubMenu().setParentMenu(this);
-
-        popup.show();
-
-        if (left + popup.getOffsetWidth() >= RootPanel.getBodyElement()
-                .getOffsetWidth() - shadowSpace) {
-            if (subMenu) {
-                left = item.getParentMenu().getAbsoluteLeft()
-                        - popup.getOffsetWidth() - shadowSpace;
-            } else {
-                left = RootPanel.getBodyElement().getOffsetWidth()
-                        - popup.getOffsetWidth() - shadowSpace;
-            }
-            // Accommodate space for shadow
-            if (left < shadowSpace) {
-                left = shadowSpace;
-            }
-        }
-
-        top = adjustPopupHeight(top, shadowSpace);
-
-        popup.setPopupPosition(left, top);
-
-    }
-
-    private int adjustPopupHeight(int top, final int shadowSpace) {
-        // Check that the popup will fit the screen
-        int availableHeight = RootPanel.getBodyElement().getOffsetHeight()
-                - top - shadowSpace;
-        int missingHeight = popup.getOffsetHeight() - availableHeight;
-        if (missingHeight > 0) {
-            // First move the top of the popup to get more space
-            // Don't move above top of screen, don't move more than needed
-            int moveUpBy = Math.min(top - shadowSpace, missingHeight);
-
-            // Update state
-            top -= moveUpBy;
-            missingHeight -= moveUpBy;
-            availableHeight += moveUpBy;
-
-            if (missingHeight > 0) {
-                int contentWidth = visibleChildMenu.getOffsetWidth();
-
-                // If there's still not enough room, limit height to fit and add
-                // a scroll bar
-                Style style = popup.getElement().getStyle();
-                style.setHeight(availableHeight, Unit.PX);
-                style.setOverflowY(Overflow.SCROLL);
-
-                // Make room for the scroll bar by adjusting the width of the
-                // popup
-                style.setWidth(contentWidth + Util.getNativeScrollbarSize(),
-                        Unit.PX);
-                popup.positionOrSizeUpdated();
-            }
-        }
-        return top;
-    }
-
-    /**
-     * Hides the submenu of an item
-     * 
-     * @param item
-     */
-    public void hideChildMenu(CustomMenuItem item) {
-        if (visibleChildMenu != null
-                && !(visibleChildMenu == item.getSubMenu())) {
-            popup.hide();
-        }
-    }
-
-    /**
-     * When the menu is shown.
-     */
-    public void onShow() {
-        // remove possible previous selection
-        if (selected != null) {
-            selected.setSelected(false);
-            selected = null;
-        }
-        menuVisible = true;
-    }
-
-    /**
-     * Listener method, fired when this menu is closed
-     */
-    @Override
-    public void onClose(CloseEvent<PopupPanel> event) {
-        hideChildren();
-        if (event.isAutoClosed()) {
-            hideParents(true);
-            menuVisible = false;
-        }
-        visibleChildMenu = null;
-        popup = null;
-    }
-
-    /**
-     * Recursively hide all child menus
-     */
-    public void hideChildren() {
-        if (visibleChildMenu != null) {
-            visibleChildMenu.hideChildren();
-            popup.hide();
-        }
-    }
-
-    /**
-     * Recursively hide all parent menus
-     */
-    public void hideParents(boolean autoClosed) {
-        if (visibleChildMenu != null) {
-            popup.hide();
-            setSelected(null);
-            menuVisible = !autoClosed;
-        }
-
-        if (getParentMenu() != null) {
-            getParentMenu().hideParents(autoClosed);
-        }
-    }
-
-    /**
-     * Returns the parent menu of this menu, or null if this is the top-level
-     * menu
-     * 
-     * @return
-     */
-    public VMenuBar getParentMenu() {
-        return parentMenu;
-    }
-
-    /**
-     * Set the parent menu of this menu
-     * 
-     * @param parent
-     */
-    public void setParentMenu(VMenuBar parent) {
-        parentMenu = parent;
-    }
-
-    /**
-     * Returns the currently selected item of this menu, or null if nothing is
-     * selected
-     * 
-     * @return
-     */
-    public CustomMenuItem getSelected() {
-        return selected;
-    }
-
-    /**
-     * Set the currently selected item of this menu
-     * 
-     * @param item
-     */
-    public void setSelected(CustomMenuItem item) {
-        // If we had something selected, unselect
-        if (item != selected && selected != null) {
-            selected.setSelected(false);
-        }
-        // If we have a valid selection, select it
-        if (item != null) {
-            item.setSelected(true);
-        }
-
-        selected = item;
-    }
-
-    /**
-     * 
-     * A class to hold information on menu items
-     * 
-     */
-    public static class CustomMenuItem extends Widget implements HasHTML {
-
-        protected String html = null;
-        protected Command command = null;
-        protected VMenuBar subMenu = null;
-        protected VMenuBar parentMenu = null;
-        protected boolean enabled = true;
-        protected boolean isSeparator = false;
-        protected boolean checkable = false;
-        protected boolean checked = false;
-        protected boolean selected = false;
-        protected String description = null;
-
-        private String styleName;
-
-        /**
-         * Default menu item {@link Widget} constructor for GWT.create().
-         * 
-         * Use {@link #setHTML(String)} and {@link #setCommand(Command)} after
-         * constructing a menu item.
-         */
-        public CustomMenuItem() {
-            this("", null);
-        }
-
-        /**
-         * Creates a menu item {@link Widget}.
-         * 
-         * @param html
-         * @param cmd
-         * @deprecated use the default constructor and {@link #setHTML(String)}
-         *             and {@link #setCommand(Command)} instead
-         */
-        @Deprecated
-        public CustomMenuItem(String html, Command cmd) {
-            // We need spans to allow inline-block in IE
-            setElement(DOM.createSpan());
-
-            setHTML(html);
-            setCommand(cmd);
-            setSelected(false);
-        }
-
-        @Override
-        public void setStyleName(String style) {
-            super.setStyleName(style);
-            updateStyleNames();
-
-            // Pass stylename down to submenus
-            if (getSubMenu() != null) {
-                getSubMenu().setStyleName(style);
-            }
-        }
-
-        public void setSelected(boolean selected) {
-            this.selected = selected;
-            updateStyleNames();
-        }
-
-        public void setChecked(boolean checked) {
-            if (checkable && !isSeparator) {
-                this.checked = checked;
-            } else {
-                this.checked = false;
-            }
-            updateStyleNames();
-        }
-
-        public boolean isChecked() {
-            return checked;
-        }
-
-        public void setCheckable(boolean checkable) {
-            if (checkable && !isSeparator) {
-                this.checkable = true;
-            } else {
-                setChecked(false);
-                this.checkable = false;
-            }
-        }
-
-        public boolean isCheckable() {
-            return checkable;
-        }
-
-        /*
-         * setters and getters for the fields
-         */
-
-        public void setSubMenu(VMenuBar subMenu) {
-            this.subMenu = subMenu;
-        }
-
-        public VMenuBar getSubMenu() {
-            return subMenu;
-        }
-
-        public void setParentMenu(VMenuBar parentMenu) {
-            this.parentMenu = parentMenu;
-            updateStyleNames();
-        }
-
-        protected void updateStyleNames() {
-            if (parentMenu == null) {
-                // Style names depend on the parent menu's primary style name so
-                // don't do updates until the item has a parent
-                return;
-            }
-
-            String primaryStyleName = parentMenu.getStylePrimaryName();
-            if (parentMenu.subMenu) {
-                primaryStyleName = primaryStyleName.replace(
-                        SUBMENU_CLASSNAME_PREFIX, "");
-            }
-
-            if (isSeparator) {
-                super.setStyleName(primaryStyleName + "-separator");
-            } else {
-                super.setStyleName(primaryStyleName + "-menuitem");
-            }
-
-            if (styleName != null) {
-                addStyleDependentName(styleName);
-            }
-
-            if (enabled) {
-                removeStyleDependentName("disabled");
-            } else {
-                addStyleDependentName("disabled");
-            }
-
-            if (selected && isSelectable()) {
-                addStyleDependentName("selected");
-                // needed for IE6 to have a single style name to match for an
-                // element
-                // TODO Can be optimized now that IE6 is not supported any more
-                if (checkable) {
-                    if (checked) {
-                        removeStyleDependentName("selected-unchecked");
-                        addStyleDependentName("selected-checked");
-                    } else {
-                        removeStyleDependentName("selected-checked");
-                        addStyleDependentName("selected-unchecked");
-                    }
-                }
-            } else {
-                removeStyleDependentName("selected");
-                // needed for IE6 to have a single style name to match for an
-                // element
-                removeStyleDependentName("selected-checked");
-                removeStyleDependentName("selected-unchecked");
-            }
-
-            if (checkable && !isSeparator) {
-                if (checked) {
-                    addStyleDependentName("checked");
-                    removeStyleDependentName("unchecked");
-                } else {
-                    addStyleDependentName("unchecked");
-                    removeStyleDependentName("checked");
-                }
-            }
-        }
-
-        public VMenuBar getParentMenu() {
-            return parentMenu;
-        }
-
-        public void setCommand(Command command) {
-            this.command = command;
-        }
-
-        public Command getCommand() {
-            return command;
-        }
-
-        @Override
-        public String getHTML() {
-            return html;
-        }
-
-        @Override
-        public void setHTML(String html) {
-            this.html = html;
-            DOM.setInnerHTML(getElement(), html);
-
-            // Sink the onload event for any icons. The onload
-            // events are handled by the parent VMenuBar.
-            Util.sinkOnloadForImages(getElement());
-        }
-
-        @Override
-        public String getText() {
-            return html;
-        }
-
-        @Override
-        public void setText(String text) {
-            setHTML(Util.escapeHTML(text));
-        }
-
-        public void setEnabled(boolean enabled) {
-            this.enabled = enabled;
-            updateStyleNames();
-        }
-
-        public boolean isEnabled() {
-            return enabled;
-        }
-
-        private void setSeparator(boolean separator) {
-            isSeparator = separator;
-            updateStyleNames();
-            if (!separator) {
-                setEnabled(enabled);
-            }
-        }
-
-        public boolean isSeparator() {
-            return isSeparator;
-        }
-
-        public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
-            setSeparator(uidl.hasAttribute("separator"));
-            setEnabled(!uidl
-                    .hasAttribute(MenuBarConstants.ATTRIBUTE_ITEM_DISABLED));
-
-            if (!isSeparator()
-                    && uidl.hasAttribute(MenuBarConstants.ATTRIBUTE_CHECKED)) {
-                // if the selected attribute is present (either true or false),
-                // the item is selectable
-                setCheckable(true);
-                setChecked(uidl
-                        .getBooleanAttribute(MenuBarConstants.ATTRIBUTE_CHECKED));
-            } else {
-                setCheckable(false);
-            }
-
-            if (uidl.hasAttribute(MenuBarConstants.ATTRIBUTE_ITEM_STYLE)) {
-                styleName = uidl
-                        .getStringAttribute(MenuBarConstants.ATTRIBUTE_ITEM_STYLE);
-            }
-
-            if (uidl.hasAttribute(MenuBarConstants.ATTRIBUTE_ITEM_DESCRIPTION)) {
-                description = uidl
-                        .getStringAttribute(MenuBarConstants.ATTRIBUTE_ITEM_DESCRIPTION);
-            }
-
-            updateStyleNames();
-        }
-
-        public TooltipInfo getTooltip() {
-            if (description == null) {
-                return null;
-            }
-
-            return new TooltipInfo(description);
-        }
-
-        /**
-         * Checks if the item can be selected.
-         * 
-         * @return true if it is possible to select this item, false otherwise
-         */
-        public boolean isSelectable() {
-            return !isSeparator() && isEnabled();
-        }
-
-    }
-
-    /**
-     * @author Jouni Koivuviita / Vaadin Ltd.
-     */
-    public void iLayout() {
-        iLayout(false);
-        updateSize();
-    }
-
-    public void iLayout(boolean iconLoadEvent) {
-        // Only collapse if there is more than one item in the root menu and the
-        // menu has an explicit size
-        if ((getItems().size() > 1 || (collapsedRootItems != null && collapsedRootItems
-                .getItems().size() > 0))
-                && getElement().getStyle().getProperty("width") != null
-                && moreItem != null) {
-
-            // Measure the width of the "more" item
-            final boolean morePresent = getItems().contains(moreItem);
-            addItem(moreItem);
-            final int moreItemWidth = moreItem.getOffsetWidth();
-            if (!morePresent) {
-                removeItem(moreItem);
-            }
-
-            int availableWidth = LayoutManager.get(client).getInnerWidth(
-                    getElement());
-
-            // Used width includes the "more" item if present
-            int usedWidth = getConsumedWidth();
-            int diff = availableWidth - usedWidth;
-            removeItem(moreItem);
-
-            if (diff < 0) {
-                // Too many items: collapse last items from root menu
-                int widthNeeded = usedWidth - availableWidth;
-                if (!morePresent) {
-                    widthNeeded += moreItemWidth;
-                }
-                int widthReduced = 0;
-
-                while (widthReduced < widthNeeded && getItems().size() > 0) {
-                    // Move last root menu item to collapsed menu
-                    CustomMenuItem collapse = getItems().get(
-                            getItems().size() - 1);
-                    widthReduced += collapse.getOffsetWidth();
-                    removeItem(collapse);
-                    collapsedRootItems.addItem(collapse, 0);
-                }
-            } else if (collapsedRootItems.getItems().size() > 0) {
-                // Space available for items: expand first items from collapsed
-                // menu
-                int widthAvailable = diff + moreItemWidth;
-                int widthGrowth = 0;
-
-                while (widthAvailable > widthGrowth
-                        && collapsedRootItems.getItems().size() > 0) {
-                    // Move first item from collapsed menu to the root menu
-                    CustomMenuItem expand = collapsedRootItems.getItems()
-                            .get(0);
-                    collapsedRootItems.removeItem(expand);
-                    addItem(expand);
-                    widthGrowth += expand.getOffsetWidth();
-                    if (collapsedRootItems.getItems().size() > 0) {
-                        widthAvailable -= moreItemWidth;
-                    }
-                    if (widthGrowth > widthAvailable) {
-                        removeItem(expand);
-                        collapsedRootItems.addItem(expand, 0);
-                    } else {
-                        widthAvailable = diff + moreItemWidth;
-                    }
-                }
-            }
-            if (collapsedRootItems.getItems().size() > 0) {
-                addItem(moreItem);
-            }
-        }
-
-        // If a popup is open we might need to adjust the shadow as well if an
-        // icon shown in that popup was loaded
-        if (popup != null) {
-            // Forces a recalculation of the shadow size
-            popup.show();
-        }
-        if (iconLoadEvent) {
-            // Size have changed if the width is undefined
-            Util.notifyParentOfSizeChange(this, false);
-        }
-    }
-
-    private int getConsumedWidth() {
-        int w = 0;
-        for (CustomMenuItem item : getItems()) {
-            if (!collapsedRootItems.getItems().contains(item)) {
-                w += item.getOffsetWidth();
-            }
-        }
-        return w;
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see
-     * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google
-     * .gwt.event.dom.client.KeyPressEvent)
-     */
-    @Override
-    public void onKeyPress(KeyPressEvent event) {
-        if (handleNavigation(event.getNativeEvent().getKeyCode(),
-                event.isControlKeyDown() || event.isMetaKeyDown(),
-                event.isShiftKeyDown())) {
-            event.preventDefault();
-        }
-    }
-
-    /*
-     * (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 (handleNavigation(event.getNativeEvent().getKeyCode(),
-                event.isControlKeyDown() || event.isMetaKeyDown(),
-                event.isShiftKeyDown())) {
-            event.preventDefault();
-        }
-    }
-
-    /**
-     * Get the key that moves the selection upwards. By default it is the up
-     * arrow key but by overriding this you can change the key to whatever you
-     * want.
-     * 
-     * @return The keycode of the key
-     */
-    protected int getNavigationUpKey() {
-        return KeyCodes.KEY_UP;
-    }
-
-    /**
-     * Get the key that moves the selection downwards. By default it is the down
-     * arrow key but by overriding this you can change the key to whatever you
-     * want.
-     * 
-     * @return The keycode of the key
-     */
-    protected int getNavigationDownKey() {
-        return KeyCodes.KEY_DOWN;
-    }
-
-    /**
-     * Get the key that moves the selection left. By default it is the left
-     * arrow key but by overriding this you can change the key to whatever you
-     * want.
-     * 
-     * @return The keycode of the key
-     */
-    protected int getNavigationLeftKey() {
-        return KeyCodes.KEY_LEFT;
-    }
-
-    /**
-     * Get the key that moves the selection right. By default it is the right
-     * arrow key but by overriding this you can change the key to whatever you
-     * want.
-     * 
-     * @return The keycode of the key
-     */
-    protected int getNavigationRightKey() {
-        return KeyCodes.KEY_RIGHT;
-    }
-
-    /**
-     * Get the key that selects a menu item. By default it is the Enter key but
-     * by overriding this you can change the key to whatever you want.
-     * 
-     * @return
-     */
-    protected int getNavigationSelectKey() {
-        return KeyCodes.KEY_ENTER;
-    }
-
-    /**
-     * Get the key that closes the menu. By default it is the escape key but by
-     * overriding this yoy can change the key to whatever you want.
-     * 
-     * @return
-     */
-    protected int getCloseMenuKey() {
-        return KeyCodes.KEY_ESCAPE;
-    }
-
-    /**
-     * Handles the keyboard events handled by the MenuBar
-     * 
-     * @param event
-     *            The keyboard event received
-     * @return true iff the navigation event was handled
-     */
-    public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
-
-        // If tab or shift+tab close menus
-        if (keycode == KeyCodes.KEY_TAB) {
-            setSelected(null);
-            hideChildren();
-            menuVisible = false;
-            return false;
-        }
-
-        if (ctrl || shift || !isEnabled()) {
-            // Do not handle tab key, nor ctrl keys
-            return false;
-        }
-
-        if (keycode == getNavigationLeftKey()) {
-            if (getSelected() == null) {
-                // If nothing is selected then select the last item
-                setSelected(items.get(items.size() - 1));
-                if (!getSelected().isSelectable()) {
-                    handleNavigation(keycode, ctrl, shift);
-                }
-            } else if (visibleChildMenu == null && getParentMenu() == null) {
-                // If this is the root menu then move to the left
-                int idx = items.indexOf(getSelected());
-                if (idx > 0) {
-                    setSelected(items.get(idx - 1));
-                } else {
-                    setSelected(items.get(items.size() - 1));
-                }
-
-                if (!getSelected().isSelectable()) {
-                    handleNavigation(keycode, ctrl, shift);
-                }
-            } else if (visibleChildMenu != null) {
-                // Redirect all navigation to the submenu
-                visibleChildMenu.handleNavigation(keycode, ctrl, shift);
-
-            } else if (getParentMenu().getParentMenu() == null) {
-                // Inside a sub menu, whose parent is a root menu item
-                VMenuBar root = getParentMenu();
-
-                root.getSelected().getSubMenu().setSelected(null);
-                root.hideChildren();
-
-                // Get the root menus items and select the previous one
-                int idx = root.getItems().indexOf(root.getSelected());
-                idx = idx > 0 ? idx : root.getItems().size();
-                CustomMenuItem selected = root.getItems().get(--idx);
-
-                while (selected.isSeparator() || !selected.isEnabled()) {
-                    idx = idx > 0 ? idx : root.getItems().size();
-                    selected = root.getItems().get(--idx);
-                }
-
-                root.setSelected(selected);
-                openMenuAndFocusFirstIfPossible(selected);
-            } else {
-                getParentMenu().getSelected().getSubMenu().setSelected(null);
-                getParentMenu().hideChildren();
-            }
-
-            return true;
-
-        } else if (keycode == getNavigationRightKey()) {
-
-            if (getSelected() == null) {
-                // If nothing is selected then select the first item
-                setSelected(items.get(0));
-                if (!getSelected().isSelectable()) {
-                    handleNavigation(keycode, ctrl, shift);
-                }
-            } else if (visibleChildMenu == null && getParentMenu() == null) {
-                // If this is the root menu then move to the right
-                int idx = items.indexOf(getSelected());
-
-                if (idx < items.size() - 1) {
-                    setSelected(items.get(idx + 1));
-                } else {
-                    setSelected(items.get(0));
-                }
-
-                if (!getSelected().isSelectable()) {
-                    handleNavigation(keycode, ctrl, shift);
-                }
-            } else if (visibleChildMenu == null
-                    && getSelected().getSubMenu() != null) {
-                // If the item has a submenu then show it and move the selection
-                // there
-                showChildMenu(getSelected());
-                menuVisible = true;
-                visibleChildMenu.handleNavigation(keycode, ctrl, shift);
-            } else if (visibleChildMenu == null) {
-
-                // Get the root menu
-                VMenuBar root = getParentMenu();
-                while (root.getParentMenu() != null) {
-                    root = root.getParentMenu();
-                }
-
-                // Hide the submenu
-                root.hideChildren();
-
-                // Get the root menus items and select the next one
-                int idx = root.getItems().indexOf(root.getSelected());
-                idx = idx < root.getItems().size() - 1 ? idx : -1;
-                CustomMenuItem selected = root.getItems().get(++idx);
-
-                while (selected.isSeparator() || !selected.isEnabled()) {
-                    idx = idx < root.getItems().size() - 1 ? idx : -1;
-                    selected = root.getItems().get(++idx);
-                }
-
-                root.setSelected(selected);
-                openMenuAndFocusFirstIfPossible(selected);
-            } else if (visibleChildMenu != null) {
-                // Redirect all navigation to the submenu
-                visibleChildMenu.handleNavigation(keycode, ctrl, shift);
-            }
-
-            return true;
-
-        } else if (keycode == getNavigationUpKey()) {
-
-            if (getSelected() == null) {
-                // If nothing is selected then select the last item
-                setSelected(items.get(items.size() - 1));
-                if (!getSelected().isSelectable()) {
-                    handleNavigation(keycode, ctrl, shift);
-                }
-            } else if (visibleChildMenu != null) {
-                // Redirect all navigation to the submenu
-                visibleChildMenu.handleNavigation(keycode, ctrl, shift);
-            } else {
-                // Select the previous item if possible or loop to the last item
-                int idx = items.indexOf(getSelected());
-                if (idx > 0) {
-                    setSelected(items.get(idx - 1));
-                } else {
-                    setSelected(items.get(items.size() - 1));
-                }
-
-                if (!getSelected().isSelectable()) {
-                    handleNavigation(keycode, ctrl, shift);
-                }
-            }
-
-            return true;
-
-        } else if (keycode == getNavigationDownKey()) {
-
-            if (getSelected() == null) {
-                // If nothing is selected then select the first item
-                selectFirstItem();
-            } else if (visibleChildMenu == null && getParentMenu() == null) {
-                // If this is the root menu the show the child menu with arrow
-                // down, if there is a child menu
-                openMenuAndFocusFirstIfPossible(getSelected());
-            } else if (visibleChildMenu != null) {
-                // Redirect all navigation to the submenu
-                visibleChildMenu.handleNavigation(keycode, ctrl, shift);
-            } else {
-                // Select the next item if possible or loop to the first item
-                int idx = items.indexOf(getSelected());
-                if (idx < items.size() - 1) {
-                    setSelected(items.get(idx + 1));
-                } else {
-                    setSelected(items.get(0));
-                }
-
-                if (!getSelected().isSelectable()) {
-                    handleNavigation(keycode, ctrl, shift);
-                }
-            }
-            return true;
-
-        } else if (keycode == getCloseMenuKey()) {
-            setSelected(null);
-            hideChildren();
-            menuVisible = false;
-
-        } else if (keycode == getNavigationSelectKey()) {
-            if (getSelected() == null) {
-                // If nothing is selected then select the first item
-                selectFirstItem();
-            } else if (visibleChildMenu != null) {
-                // Redirect all navigation to the submenu
-                visibleChildMenu.handleNavigation(keycode, ctrl, shift);
-                menuVisible = false;
-            } else if (visibleChildMenu == null
-                    && getSelected().getSubMenu() != null) {
-                // If the item has a sub menu then show it and move the
-                // selection there
-                openMenuAndFocusFirstIfPossible(getSelected());
-            } else {
-                Command command = getSelected().getCommand();
-                if (command != null) {
-                    command.execute();
-                }
-
-                setSelected(null);
-                hideParents(true);
-            }
-        }
-
-        return false;
-    }
-
-    private void selectFirstItem() {
-        for (int i = 0; i < items.size(); i++) {
-            CustomMenuItem item = items.get(i);
-            if (item.isSelectable()) {
-                setSelected(item);
-                break;
-            }
-        }
-    }
-
-    private void openMenuAndFocusFirstIfPossible(CustomMenuItem menuItem) {
-        VMenuBar subMenu = menuItem.getSubMenu();
-        if (subMenu == null) {
-            // No child menu? Nothing to do
-            return;
-        }
-
-        VMenuBar parentMenu = menuItem.getParentMenu();
-        parentMenu.showChildMenu(menuItem);
-
-        menuVisible = true;
-        // Select the first item in the newly open submenu
-        subMenu.selectFirstItem();
-
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see
-     * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
-     * .dom.client.FocusEvent)
-     */
-    @Override
-    public void onFocus(FocusEvent event) {
-
-    }
-
-    private final String SUBPART_PREFIX = "item";
-
-    @Override
-    public Element getSubPartElement(String subPart) {
-        int index = Integer
-                .parseInt(subPart.substring(SUBPART_PREFIX.length()));
-        CustomMenuItem item = getItems().get(index);
-
-        return item.getElement();
-    }
-
-    @Override
-    public String getSubPartName(Element subElement) {
-        if (!getElement().isOrHasChild(subElement)) {
-            return null;
-        }
-
-        Element menuItemRoot = subElement;
-        while (menuItemRoot != null && menuItemRoot.getParentElement() != null
-                && menuItemRoot.getParentElement() != getElement()) {
-            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;
-    }
-
-    /**
-     * Get menu item with given DOM element
-     * 
-     * @param element
-     *            Element used in search
-     * @return Menu item or null if not found
-     */
-    public CustomMenuItem getMenuItemWithElement(Element element) {
-        for (int i = 0; i < items.size(); i++) {
-            CustomMenuItem item = items.get(i);
-            if (DOM.isOrHasChild(item.getElement(), element)) {
-                return item;
-            }
-
-            if (item.getSubMenu() != null) {
-                item = item.getSubMenu().getMenuItemWithElement(element);
-                if (item != null) {
-                    return item;
-                }
-            }
-        }
-
-        return null;
-    }
-}
index fa523e65c59723ceeaa6e2f2aab6c9d3b454ffc3..f8de70f578b2b4e97403973b536f2a8ddedb8b77 100644 (file)
@@ -25,6 +25,7 @@ import com.vaadin.client.EventHelper;
 import com.vaadin.client.communication.StateChangeEvent;
 import com.vaadin.client.ui.AbstractComponentConnector;
 import com.vaadin.client.ui.Icon;
+import com.vaadin.client.ui.VNativeButton;
 import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.button.ButtonServerRpc;
diff --git a/client/src/com/vaadin/client/ui/nativebutton/VNativeButton.java b/client/src/com/vaadin/client/ui/nativebutton/VNativeButton.java
deleted file mode 100644 (file)
index 2b1ea56..0000000
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.nativebutton;
-
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.Button;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.MouseEventDetailsBuilder;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.Icon;
-import com.vaadin.shared.MouseEventDetails;
-import com.vaadin.shared.ui.button.ButtonServerRpc;
-
-public class VNativeButton extends Button implements ClickHandler {
-
-    public static final String CLASSNAME = "v-nativebutton";
-
-    protected String width = null;
-
-    protected String paintableId;
-
-    protected ApplicationConnection client;
-
-    ButtonServerRpc buttonRpcProxy;
-
-    protected Element errorIndicatorElement;
-
-    protected final Element captionElement = DOM.createSpan();
-
-    protected Icon icon;
-
-    /**
-     * Helper flag to handle special-case where the button is moved from under
-     * mouse while clicking it. In this case mouse leaves the button without
-     * moving.
-     */
-    private boolean clickPending;
-
-    protected boolean disableOnClick = false;
-
-    public VNativeButton() {
-        setStyleName(CLASSNAME);
-
-        getElement().appendChild(captionElement);
-        captionElement.setClassName(getStyleName() + "-caption");
-
-        addClickHandler(this);
-
-        sinkEvents(Event.ONMOUSEDOWN);
-        sinkEvents(Event.ONMOUSEUP);
-    }
-
-    @Override
-    public void setText(String text) {
-        captionElement.setInnerText(text);
-    }
-
-    @Override
-    public void setHTML(String html) {
-        captionElement.setInnerHTML(html);
-    }
-
-    @Override
-    public void onBrowserEvent(Event event) {
-        super.onBrowserEvent(event);
-
-        if (DOM.eventGetType(event) == Event.ONLOAD) {
-            Util.notifyParentOfSizeChange(this, true);
-
-        } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN
-                && event.getButton() == Event.BUTTON_LEFT) {
-            clickPending = true;
-        } else if (DOM.eventGetType(event) == Event.ONMOUSEMOVE) {
-            clickPending = false;
-        } else if (DOM.eventGetType(event) == Event.ONMOUSEOUT) {
-            if (clickPending) {
-                click();
-            }
-            clickPending = false;
-        }
-    }
-
-    @Override
-    public void setWidth(String width) {
-        this.width = width;
-        super.setWidth(width);
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see
-     * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event
-     * .dom.client.ClickEvent)
-     */
-    @Override
-    public void onClick(ClickEvent event) {
-        if (paintableId == null || client == null) {
-            return;
-        }
-
-        if (BrowserInfo.get().isSafari()) {
-            VNativeButton.this.setFocus(true);
-        }
-        if (disableOnClick) {
-            setEnabled(false);
-            // FIXME: This should be moved to NativeButtonConnector along with
-            // buttonRpcProxy
-            addStyleName(ApplicationConnection.DISABLED_CLASSNAME);
-            buttonRpcProxy.disableOnClick();
-        }
-
-        // Add mouse details
-        MouseEventDetails details = MouseEventDetailsBuilder
-                .buildMouseEventDetails(event.getNativeEvent(), getElement());
-        buttonRpcProxy.click(details);
-
-        clickPending = false;
-    }
-
-}
index e579f9c241a053df49b838aa4a5109338de14963..1607520edbece25b98d7831f54c544c1e3d2f629 100644 (file)
@@ -16,6 +16,7 @@
 
 package com.vaadin.client.ui.nativeselect;
 
+import com.vaadin.client.ui.VNativeSelect;
 import com.vaadin.client.ui.optiongroup.OptionGroupBaseConnector;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.ui.NativeSelect;
diff --git a/client/src/com/vaadin/client/ui/nativeselect/VNativeSelect.java b/client/src/com/vaadin/client/ui/nativeselect/VNativeSelect.java
deleted file mode 100644 (file)
index 876bf27..0000000
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.nativeselect;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-
-import com.google.gwt.event.dom.client.ChangeEvent;
-import com.google.gwt.user.client.ui.ListBox;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.ui.Field;
-import com.vaadin.client.ui.optiongroup.VOptionGroupBase;
-
-public class VNativeSelect extends VOptionGroupBase implements Field {
-
-    public static final String CLASSNAME = "v-select";
-
-    protected ListBox select;
-
-    private boolean firstValueIsTemporaryNullItem = false;
-
-    public VNativeSelect() {
-        super(new ListBox(false), CLASSNAME);
-        select = getOptionsContainer();
-        select.setVisibleItemCount(1);
-        select.addChangeHandler(this);
-        select.setStyleName(CLASSNAME + "-select");
-
-    }
-
-    protected ListBox getOptionsContainer() {
-        return (ListBox) optionsContainer;
-    }
-
-    @Override
-    protected void buildOptions(UIDL uidl) {
-        select.setEnabled(!isDisabled() && !isReadonly());
-        select.clear();
-        firstValueIsTemporaryNullItem = false;
-
-        if (isNullSelectionAllowed() && !isNullSelectionItemAvailable()) {
-            // can't unselect last item in singleselect mode
-            select.addItem("", (String) null);
-        }
-        boolean selected = false;
-        for (final Iterator<?> i = uidl.getChildIterator(); i.hasNext();) {
-            final UIDL optionUidl = (UIDL) i.next();
-            select.addItem(optionUidl.getStringAttribute("caption"),
-                    optionUidl.getStringAttribute("key"));
-            if (optionUidl.hasAttribute("selected")) {
-                select.setItemSelected(select.getItemCount() - 1, true);
-                selected = true;
-            }
-        }
-        if (!selected && !isNullSelectionAllowed()) {
-            // null-select not allowed, but value not selected yet; add null and
-            // remove when something is selected
-            select.insertItem("", (String) null, 0);
-            select.setItemSelected(0, true);
-            firstValueIsTemporaryNullItem = true;
-        }
-    }
-
-    @Override
-    protected String[] getSelectedItems() {
-        final ArrayList<String> selectedItemKeys = new ArrayList<String>();
-        for (int i = 0; i < select.getItemCount(); i++) {
-            if (select.isItemSelected(i)) {
-                selectedItemKeys.add(select.getValue(i));
-            }
-        }
-        return selectedItemKeys.toArray(new String[selectedItemKeys.size()]);
-    }
-
-    @Override
-    public void onChange(ChangeEvent event) {
-
-        if (select.isMultipleSelect()) {
-            client.updateVariable(paintableId, "selected", getSelectedItems(),
-                    isImmediate());
-        } else {
-            client.updateVariable(paintableId, "selected", new String[] { ""
-                    + getSelectedItem() }, isImmediate());
-        }
-        if (firstValueIsTemporaryNullItem) {
-            // remove temporary empty item
-            select.removeItem(0);
-            firstValueIsTemporaryNullItem = false;
-        }
-    }
-
-    @Override
-    public void setHeight(String height) {
-        select.setHeight(height);
-        super.setHeight(height);
-    }
-
-    @Override
-    public void setWidth(String width) {
-        select.setWidth(width);
-        super.setWidth(width);
-    }
-
-    @Override
-    protected void setTabIndex(int tabIndex) {
-        getOptionsContainer().setTabIndex(tabIndex);
-    }
-
-    @Override
-    public void focus() {
-        select.setFocus(true);
-    }
-}
diff --git a/client/src/com/vaadin/client/ui/notification/VNotification.java b/client/src/com/vaadin/client/ui/notification/VNotification.java
deleted file mode 100644 (file)
index 436712a..0000000
+++ /dev/null
@@ -1,473 +0,0 @@
-/* 
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.notification;
-
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.EventObject;
-import java.util.Iterator;
-
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.Timer;
-import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.VOverlay;
-import com.vaadin.shared.Position;
-import com.vaadin.shared.ui.ui.UIConstants;
-
-public class VNotification extends VOverlay {
-
-    public static final Position CENTERED = Position.MIDDLE_CENTER;
-    public static final Position CENTERED_TOP = Position.TOP_CENTER;
-    public static final Position CENTERED_BOTTOM = Position.BOTTOM_CENTER;
-    public static final Position TOP_LEFT = Position.TOP_LEFT;
-    public static final Position TOP_RIGHT = Position.TOP_RIGHT;
-    public static final Position BOTTOM_LEFT = Position.BOTTOM_LEFT;
-    public static final Position BOTTOM_RIGHT = Position.BOTTOM_RIGHT;
-
-    public static final int DELAY_FOREVER = -1;
-    public static final int DELAY_NONE = 0;
-
-    private static final String STYLENAME = "v-Notification";
-    private static final int mouseMoveThreshold = 7;
-    private static final int Z_INDEX_BASE = 20000;
-    public static final String STYLE_SYSTEM = "system";
-    private static final int FADE_ANIMATION_INTERVAL = 50; // == 20 fps
-
-    private static final ArrayList<VNotification> notifications = new ArrayList<VNotification>();
-
-    private int startOpacity = 90;
-    private int fadeMsec = 400;
-    private int delayMsec = 1000;
-
-    private Timer fader;
-    private Timer delay;
-
-    private int x = -1;
-    private int y = -1;
-
-    private String temporaryStyle;
-
-    private ArrayList<EventListener> listeners;
-    private static final int TOUCH_DEVICE_IDLE_DELAY = 1000;
-
-    /**
-     * Default constructor. You should use GWT.create instead.
-     */
-    public VNotification() {
-        setStyleName(STYLENAME);
-        sinkEvents(Event.ONCLICK);
-        DOM.setStyleAttribute(getElement(), "zIndex", "" + Z_INDEX_BASE);
-    }
-
-    /**
-     * @deprecated Use static {@link #createNotification(int)} instead to enable
-     *             GWT deferred binding.
-     * 
-     * @param delayMsec
-     */
-    @Deprecated
-    public VNotification(int delayMsec) {
-        this();
-        this.delayMsec = delayMsec;
-        if (BrowserInfo.get().isTouchDevice()) {
-            new Timer() {
-                @Override
-                public void run() {
-                    if (isAttached()) {
-                        fade();
-                    }
-                }
-            }.schedule(delayMsec + TOUCH_DEVICE_IDLE_DELAY);
-        }
-    }
-
-    /**
-     * @deprecated Use static {@link #createNotification(int, int, int)} instead
-     *             to enable GWT deferred binding.
-     * 
-     * @param delayMsec
-     * @param fadeMsec
-     * @param startOpacity
-     */
-    @Deprecated
-    public VNotification(int delayMsec, int fadeMsec, int startOpacity) {
-        this(delayMsec);
-        this.fadeMsec = fadeMsec;
-        this.startOpacity = startOpacity;
-    }
-
-    public void startDelay() {
-        DOM.removeEventPreview(this);
-        if (delayMsec > 0) {
-            if (delay == null) {
-                delay = new Timer() {
-                    @Override
-                    public void run() {
-                        fade();
-                    }
-                };
-                delay.schedule(delayMsec);
-            }
-        } else if (delayMsec == 0) {
-            fade();
-        }
-    }
-
-    @Override
-    public void show() {
-        show(CENTERED);
-    }
-
-    public void show(String style) {
-        show(CENTERED, style);
-    }
-
-    public void show(com.vaadin.shared.Position position) {
-        show(position, null);
-    }
-
-    public void show(Widget widget, Position position, String style) {
-        setWidget(widget);
-        show(position, style);
-    }
-
-    public void show(String html, Position position, String style) {
-        setWidget(new HTML(html));
-        show(position, style);
-    }
-
-    public void show(Position position, String style) {
-        setOpacity(getElement(), startOpacity);
-        if (style != null) {
-            temporaryStyle = style;
-            addStyleName(style);
-            addStyleDependentName(style);
-        }
-        super.show();
-        notifications.add(this);
-        setPosition(position);
-        positionOrSizeUpdated();
-        /**
-         * Android 4 fails to render notifications correctly without a little
-         * nudge (#8551)
-         */
-        if (BrowserInfo.get().isAndroid()) {
-            Util.setStyleTemporarily(getElement(), "display", "none");
-        }
-    }
-
-    @Override
-    public void hide() {
-        DOM.removeEventPreview(this);
-        cancelDelay();
-        cancelFade();
-        if (temporaryStyle != null) {
-            removeStyleName(temporaryStyle);
-            removeStyleDependentName(temporaryStyle);
-            temporaryStyle = null;
-        }
-        super.hide();
-        notifications.remove(this);
-        fireEvent(new HideEvent(this));
-    }
-
-    public void fade() {
-        DOM.removeEventPreview(this);
-        cancelDelay();
-        if (fader == null) {
-            fader = new Timer() {
-                private final long start = new Date().getTime();
-
-                @Override
-                public void run() {
-                    /*
-                     * To make animation smooth, don't count that event happens
-                     * on time. Reduce opacity according to the actual time
-                     * spent instead of fixed decrement.
-                     */
-                    long now = new Date().getTime();
-                    long timeEplaced = now - start;
-                    float remainingFraction = 1 - timeEplaced
-                            / (float) fadeMsec;
-                    int opacity = (int) (startOpacity * remainingFraction);
-                    if (opacity <= 0) {
-                        cancel();
-                        hide();
-                        if (BrowserInfo.get().isOpera()) {
-                            // tray notification on opera needs to explicitly
-                            // define
-                            // size, reset it
-                            DOM.setStyleAttribute(getElement(), "width", "");
-                            DOM.setStyleAttribute(getElement(), "height", "");
-                        }
-                    } else {
-                        setOpacity(getElement(), opacity);
-                    }
-                }
-            };
-            fader.scheduleRepeating(FADE_ANIMATION_INTERVAL);
-        }
-    }
-
-    public void setPosition(com.vaadin.shared.Position position) {
-        final Element el = getElement();
-        DOM.setStyleAttribute(el, "top", "");
-        DOM.setStyleAttribute(el, "left", "");
-        DOM.setStyleAttribute(el, "bottom", "");
-        DOM.setStyleAttribute(el, "right", "");
-        switch (position) {
-        case TOP_LEFT:
-            DOM.setStyleAttribute(el, "top", "0px");
-            DOM.setStyleAttribute(el, "left", "0px");
-            break;
-        case TOP_RIGHT:
-            DOM.setStyleAttribute(el, "top", "0px");
-            DOM.setStyleAttribute(el, "right", "0px");
-            break;
-        case BOTTOM_RIGHT:
-            DOM.setStyleAttribute(el, "position", "absolute");
-            if (BrowserInfo.get().isOpera()) {
-                // tray notification on opera needs explicitly defined size
-                DOM.setStyleAttribute(el, "width", getOffsetWidth() + "px");
-                DOM.setStyleAttribute(el, "height", getOffsetHeight() + "px");
-            }
-            DOM.setStyleAttribute(el, "bottom", "0px");
-            DOM.setStyleAttribute(el, "right", "0px");
-            break;
-        case BOTTOM_LEFT:
-            DOM.setStyleAttribute(el, "bottom", "0px");
-            DOM.setStyleAttribute(el, "left", "0px");
-            break;
-        case TOP_CENTER:
-            center();
-            DOM.setStyleAttribute(el, "top", "0px");
-            break;
-        case BOTTOM_CENTER:
-            center();
-            DOM.setStyleAttribute(el, "top", "");
-            DOM.setStyleAttribute(el, "bottom", "0px");
-            break;
-        default:
-        case MIDDLE_CENTER:
-            center();
-            break;
-        }
-    }
-
-    private void cancelFade() {
-        if (fader != null) {
-            fader.cancel();
-            fader = null;
-        }
-    }
-
-    private void cancelDelay() {
-        if (delay != null) {
-            delay.cancel();
-            delay = null;
-        }
-    }
-
-    private void setOpacity(Element el, int opacity) {
-        DOM.setStyleAttribute(el, "opacity", "" + (opacity / 100.0));
-        if (BrowserInfo.get().isIE()) {
-            DOM.setStyleAttribute(el, "filter", "Alpha(opacity=" + opacity
-                    + ")");
-        }
-    }
-
-    @Override
-    public void onBrowserEvent(Event event) {
-        DOM.removeEventPreview(this);
-        if (fader == null) {
-            fade();
-        }
-    }
-
-    @Override
-    public boolean onEventPreview(Event event) {
-        int type = DOM.eventGetType(event);
-        // "modal"
-        if (delayMsec == -1 || temporaryStyle == STYLE_SYSTEM) {
-            if (type == Event.ONCLICK) {
-                if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) {
-                    fade();
-                    return false;
-                }
-            } else if (type == Event.ONKEYDOWN
-                    && event.getKeyCode() == KeyCodes.KEY_ESCAPE) {
-                fade();
-                return false;
-            }
-            if (temporaryStyle == STYLE_SYSTEM) {
-                return true;
-            } else {
-                return false;
-            }
-        }
-        // default
-        switch (type) {
-        case Event.ONMOUSEMOVE:
-
-            if (x < 0) {
-                x = DOM.eventGetClientX(event);
-                y = DOM.eventGetClientY(event);
-            } else if (Math.abs(DOM.eventGetClientX(event) - x) > mouseMoveThreshold
-                    || Math.abs(DOM.eventGetClientY(event) - y) > mouseMoveThreshold) {
-                startDelay();
-            }
-            break;
-        case Event.ONMOUSEDOWN:
-        case Event.ONMOUSEWHEEL:
-        case Event.ONSCROLL:
-            startDelay();
-            break;
-        case Event.ONKEYDOWN:
-            if (event.getRepeat()) {
-                return true;
-            }
-            startDelay();
-            break;
-        default:
-            break;
-        }
-        return true;
-    }
-
-    public void addEventListener(EventListener listener) {
-        if (listeners == null) {
-            listeners = new ArrayList<EventListener>();
-        }
-        listeners.add(listener);
-    }
-
-    public void removeEventListener(EventListener listener) {
-        if (listeners == null) {
-            return;
-        }
-        listeners.remove(listener);
-    }
-
-    private void fireEvent(HideEvent event) {
-        if (listeners != null) {
-            for (Iterator<EventListener> it = listeners.iterator(); it
-                    .hasNext();) {
-                EventListener l = it.next();
-                l.notificationHidden(event);
-            }
-        }
-    }
-
-    public static void showNotification(ApplicationConnection client,
-            final UIDL notification) {
-        boolean onlyPlainText = notification
-                .hasAttribute(UIConstants.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED);
-        String html = "";
-        if (notification.hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON)) {
-            final String parsedUri = client
-                    .translateVaadinUri(notification
-                            .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON));
-            html += "<img src=\"" + Util.escapeAttribute(parsedUri) + "\" />";
-        }
-        if (notification
-                .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION)) {
-            String caption = notification
-                    .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION);
-            if (onlyPlainText) {
-                caption = Util.escapeHTML(caption);
-                caption = caption.replaceAll("\\n", "<br />");
-            }
-            html += "<h1>" + caption + "</h1>";
-        }
-        if (notification
-                .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_MESSAGE)) {
-            String message = notification
-                    .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_MESSAGE);
-            if (onlyPlainText) {
-                message = Util.escapeHTML(message);
-                message = message.replaceAll("\\n", "<br />");
-            }
-            html += "<p>" + message + "</p>";
-        }
-
-        final String style = notification
-                .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_STYLE) ? notification
-                .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_STYLE)
-                : null;
-
-        final int pos = notification
-                .getIntAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_POSITION);
-        Position position = Position.values()[pos];
-
-        final int delay = notification
-                .getIntAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_DELAY);
-        createNotification(delay, client.getUIConnector().getWidget()).show(
-                html, position, style);
-    }
-
-    public static VNotification createNotification(int delayMsec, Widget owner) {
-        final VNotification notification = GWT.create(VNotification.class);
-        notification.delayMsec = delayMsec;
-        if (BrowserInfo.get().isTouchDevice()) {
-            new Timer() {
-                @Override
-                public void run() {
-                    if (notification.isAttached()) {
-                        notification.fade();
-                    }
-                }
-            }.schedule(notification.delayMsec + TOUCH_DEVICE_IDLE_DELAY);
-        }
-        notification.setOwner(owner);
-        return notification;
-    }
-
-    public class HideEvent extends EventObject {
-
-        public HideEvent(Object source) {
-            super(source);
-        }
-    }
-
-    public interface EventListener extends java.util.EventListener {
-        public void notificationHidden(HideEvent event);
-    }
-
-    /**
-     * Moves currently visible notifications to the top of the event preview
-     * stack. Can be called when opening other overlays such as subwindows to
-     * ensure the notifications receive the events they need and don't linger
-     * indefinitely. See #7136.
-     * 
-     * TODO Should this be a generic Overlay feature instead?
-     */
-    public static void bringNotificationsToFront() {
-        for (VNotification notification : notifications) {
-            DOM.removeEventPreview(notification);
-            DOM.addEventPreview(notification);
-        }
-    }
-}
index 0081ff3c700a9730b70c9f83fa59c76bbc880d56..5d50b848338be99c40f233892a9071824c902b32 100644 (file)
@@ -20,8 +20,9 @@ import com.vaadin.client.ApplicationConnection;
 import com.vaadin.client.Paintable;
 import com.vaadin.client.UIDL;
 import com.vaadin.client.ui.AbstractFieldConnector;
-import com.vaadin.client.ui.nativebutton.VNativeButton;
-import com.vaadin.client.ui.textfield.VTextField;
+import com.vaadin.client.ui.VNativeButton;
+import com.vaadin.client.ui.VOptionGroupBase;
+import com.vaadin.client.ui.VTextField;
 
 public abstract class OptionGroupBaseConnector extends AbstractFieldConnector
         implements Paintable {
index 0c3c06a27054346ac967a74f63819000bdcde6d9..045dde195952bb59e261842ff7a017676d2c7cae 100644 (file)
@@ -23,6 +23,7 @@ import com.google.gwt.user.client.ui.CheckBox;
 import com.google.gwt.user.client.ui.Widget;
 import com.vaadin.client.ApplicationConnection;
 import com.vaadin.client.UIDL;
+import com.vaadin.client.ui.VOptionGroup;
 import com.vaadin.shared.EventId;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.optiongroup.OptionGroupConstants;
diff --git a/client/src/com/vaadin/client/ui/optiongroup/VOptionGroup.java b/client/src/com/vaadin/client/ui/optiongroup/VOptionGroup.java
deleted file mode 100644 (file)
index 43bdee1..0000000
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.optiongroup;
-
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import com.google.gwt.core.client.Scheduler;
-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.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.event.dom.client.LoadEvent;
-import com.google.gwt.event.dom.client.LoadHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.ui.CheckBox;
-import com.google.gwt.user.client.ui.FocusWidget;
-import com.google.gwt.user.client.ui.Focusable;
-import com.google.gwt.user.client.ui.Panel;
-import com.google.gwt.user.client.ui.RadioButton;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.Icon;
-import com.vaadin.client.ui.checkbox.VCheckBox;
-import com.vaadin.shared.EventId;
-import com.vaadin.shared.ui.optiongroup.OptionGroupConstants;
-
-public class VOptionGroup extends VOptionGroupBase implements FocusHandler,
-        BlurHandler {
-
-    public static final String CLASSNAME = "v-select-optiongroup";
-
-    protected final Panel panel;
-
-    private final Map<CheckBox, String> optionsToKeys;
-
-    protected boolean sendFocusEvents = false;
-    protected boolean sendBlurEvents = false;
-    protected List<HandlerRegistration> focusHandlers = null;
-    protected List<HandlerRegistration> blurHandlers = null;
-
-    private final LoadHandler iconLoadHandler = new LoadHandler() {
-        @Override
-        public void onLoad(LoadEvent event) {
-            Util.notifyParentOfSizeChange(VOptionGroup.this, true);
-        }
-    };
-
-    /**
-     * used to check whether a blur really was a blur of the complete
-     * optiongroup: if a control inside this optiongroup gains focus right after
-     * blur of another control inside this optiongroup (meaning: if onFocus
-     * fires after onBlur has fired), the blur and focus won't be sent to the
-     * server side as only a focus change inside this optiongroup occured
-     */
-    private boolean blurOccured = false;
-
-    protected boolean htmlContentAllowed = false;
-
-    public VOptionGroup() {
-        super(CLASSNAME);
-        panel = (Panel) optionsContainer;
-        optionsToKeys = new HashMap<CheckBox, String>();
-    }
-
-    /*
-     * Return true if no elements were changed, false otherwise.
-     */
-    @Override
-    protected void buildOptions(UIDL uidl) {
-        panel.clear();
-        for (final Iterator<?> it = uidl.getChildIterator(); it.hasNext();) {
-            final UIDL opUidl = (UIDL) it.next();
-            CheckBox op;
-
-            String itemHtml = opUidl.getStringAttribute("caption");
-            if (!htmlContentAllowed) {
-                itemHtml = Util.escapeHTML(itemHtml);
-            }
-
-            String icon = opUidl.getStringAttribute("icon");
-            if (icon != null && icon.length() != 0) {
-                String iconUrl = client.translateVaadinUri(icon);
-                itemHtml = "<img src=\"" + iconUrl + "\" class=\""
-                        + Icon.CLASSNAME + "\" alt=\"\" />" + itemHtml;
-            }
-
-            if (isMultiselect()) {
-                op = new VCheckBox();
-                op.setHTML(itemHtml);
-            } else {
-                op = new RadioButton(paintableId, itemHtml, true);
-                op.setStyleName("v-radiobutton");
-            }
-
-            if (icon != null && icon.length() != 0) {
-                Util.sinkOnloadForImages(op.getElement());
-                op.addHandler(iconLoadHandler, LoadEvent.getType());
-            }
-
-            op.addStyleName(CLASSNAME_OPTION);
-            op.setValue(opUidl.getBooleanAttribute("selected"));
-            boolean enabled = !opUidl
-                    .getBooleanAttribute(OptionGroupConstants.ATTRIBUTE_OPTION_DISABLED)
-                    && !isReadonly() && !isDisabled();
-            op.setEnabled(enabled);
-            setStyleName(op.getElement(),
-                    ApplicationConnection.DISABLED_CLASSNAME, !enabled);
-            op.addClickHandler(this);
-            optionsToKeys.put(op, opUidl.getStringAttribute("key"));
-            panel.add(op);
-        }
-    }
-
-    @Override
-    protected String[] getSelectedItems() {
-        return selectedKeys.toArray(new String[selectedKeys.size()]);
-    }
-
-    @Override
-    public void onClick(ClickEvent event) {
-        super.onClick(event);
-        if (event.getSource() instanceof CheckBox) {
-            final boolean selected = ((CheckBox) event.getSource()).getValue();
-            final String key = optionsToKeys.get(event.getSource());
-            if (!isMultiselect()) {
-                selectedKeys.clear();
-            }
-            if (selected) {
-                selectedKeys.add(key);
-            } else {
-                selectedKeys.remove(key);
-            }
-            client.updateVariable(paintableId, "selected", getSelectedItems(),
-                    isImmediate());
-        }
-    }
-
-    @Override
-    protected void setTabIndex(int tabIndex) {
-        for (Iterator<Widget> iterator = panel.iterator(); iterator.hasNext();) {
-            FocusWidget widget = (FocusWidget) iterator.next();
-            widget.setTabIndex(tabIndex);
-        }
-    }
-
-    @Override
-    public void focus() {
-        Iterator<Widget> iterator = panel.iterator();
-        if (iterator.hasNext()) {
-            ((Focusable) iterator.next()).setFocus(true);
-        }
-    }
-
-    @Override
-    public void onFocus(FocusEvent arg0) {
-        if (!blurOccured) {
-            // no blur occured before this focus event
-            // panel was blurred => fire the event to the server side if
-            // requested by server side
-            if (sendFocusEvents) {
-                client.updateVariable(paintableId, EventId.FOCUS, "", true);
-            }
-        } else {
-            // blur occured before this focus event
-            // another control inside the panel (checkbox / radio box) was
-            // blurred => do not fire the focus and set blurOccured to false, so
-            // blur will not be fired, too
-            blurOccured = false;
-        }
-    }
-
-    @Override
-    public void onBlur(BlurEvent arg0) {
-        blurOccured = true;
-        if (sendBlurEvents) {
-            Scheduler.get().scheduleDeferred(new Command() {
-                @Override
-                public void execute() {
-                    // check whether blurOccured still is true and then send the
-                    // event out to the server
-                    if (blurOccured) {
-                        client.updateVariable(paintableId, EventId.BLUR, "",
-                                true);
-                        blurOccured = false;
-                    }
-                }
-            });
-        }
-    }
-}
diff --git a/client/src/com/vaadin/client/ui/optiongroup/VOptionGroupBase.java b/client/src/com/vaadin/client/ui/optiongroup/VOptionGroupBase.java
deleted file mode 100644 (file)
index 87f1cc5..0000000
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.optiongroup;
-
-import java.util.Set;
-
-import com.google.gwt.event.dom.client.ChangeEvent;
-import com.google.gwt.event.dom.client.ChangeHandler;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.event.dom.client.KeyPressHandler;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.Panel;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.Focusable;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.ui.Field;
-import com.vaadin.client.ui.nativebutton.VNativeButton;
-import com.vaadin.client.ui.textfield.VTextField;
-
-public abstract class VOptionGroupBase extends Composite implements Field,
-        ClickHandler, ChangeHandler, KeyPressHandler, Focusable {
-
-    public static final String CLASSNAME_OPTION = "v-select-option";
-
-    protected ApplicationConnection client;
-
-    protected String paintableId;
-
-    protected Set<String> selectedKeys;
-
-    protected boolean immediate;
-
-    protected boolean multiselect;
-
-    protected boolean disabled;
-
-    protected boolean readonly;
-
-    protected int cols = 0;
-
-    protected int rows = 0;
-
-    protected boolean nullSelectionAllowed = true;
-
-    protected boolean nullSelectionItemAvailable = false;
-
-    /**
-     * Widget holding the different options (e.g. ListBox or Panel for radio
-     * buttons) (optional, fallbacks to container Panel)
-     */
-    protected Widget optionsContainer;
-
-    /**
-     * Panel containing the component
-     */
-    protected final Panel container;
-
-    protected VTextField newItemField;
-
-    protected VNativeButton newItemButton;
-
-    public VOptionGroupBase(String classname) {
-        container = new FlowPanel();
-        initWidget(container);
-        optionsContainer = container;
-        container.setStyleName(classname);
-        immediate = false;
-        multiselect = false;
-    }
-
-    /*
-     * Call this if you wish to specify your own container for the option
-     * elements (e.g. SELECT)
-     */
-    public VOptionGroupBase(Widget w, String classname) {
-        this(classname);
-        optionsContainer = w;
-        container.add(optionsContainer);
-    }
-
-    protected boolean isImmediate() {
-        return immediate;
-    }
-
-    protected boolean isMultiselect() {
-        return multiselect;
-    }
-
-    protected boolean isDisabled() {
-        return disabled;
-    }
-
-    protected boolean isReadonly() {
-        return readonly;
-    }
-
-    protected boolean isNullSelectionAllowed() {
-        return nullSelectionAllowed;
-    }
-
-    protected boolean isNullSelectionItemAvailable() {
-        return nullSelectionItemAvailable;
-    }
-
-    /**
-     * @return "cols" specified in uidl, 0 if not specified
-     */
-    protected int getColumns() {
-        return cols;
-    }
-
-    /**
-     * @return "rows" specified in uidl, 0 if not specified
-     */
-
-    protected int getRows() {
-        return rows;
-    }
-
-    abstract protected void setTabIndex(int tabIndex);
-
-    @Override
-    public void onClick(ClickEvent event) {
-        if (event.getSource() == newItemButton
-                && !newItemField.getText().equals("")) {
-            client.updateVariable(paintableId, "newitem",
-                    newItemField.getText(), true);
-            newItemField.setText("");
-        }
-    }
-
-    @Override
-    public void onChange(ChangeEvent event) {
-        if (multiselect) {
-            client.updateVariable(paintableId, "selected", getSelectedItems(),
-                    immediate);
-        } else {
-            client.updateVariable(paintableId, "selected", new String[] { ""
-                    + getSelectedItem() }, immediate);
-        }
-    }
-
-    @Override
-    public void onKeyPress(KeyPressEvent event) {
-        if (event.getSource() == newItemField
-                && event.getCharCode() == KeyCodes.KEY_ENTER) {
-            newItemButton.click();
-        }
-    }
-
-    protected abstract void buildOptions(UIDL uidl);
-
-    protected abstract String[] getSelectedItems();
-
-    protected String getSelectedItem() {
-        final String[] sel = getSelectedItems();
-        if (sel.length > 0) {
-            return sel[0];
-        } else {
-            return null;
-        }
-    }
-
-}
index 23308e200407cd8e8963d3233083799e6ce75afe..c21155e26d35dad1b463f48bc2bfe95c9cccd2b3 100644 (file)
@@ -29,10 +29,11 @@ import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler;
 import com.vaadin.client.ui.AbstractFieldConnector;
 import com.vaadin.client.ui.AbstractLayoutConnector;
 import com.vaadin.client.ui.LayoutClickEventHandler;
+import com.vaadin.client.ui.VOrderedLayout;
+import com.vaadin.client.ui.VOrderedLayout.CaptionPosition;
+import com.vaadin.client.ui.VOrderedLayout.Slot;
 import com.vaadin.client.ui.layout.ElementResizeEvent;
 import com.vaadin.client.ui.layout.ElementResizeListener;
-import com.vaadin.client.ui.orderedlayout.VOrderedLayout.CaptionPosition;
-import com.vaadin.client.ui.orderedlayout.VOrderedLayout.Slot;
 import com.vaadin.shared.AbstractFieldState;
 import com.vaadin.shared.ComponentConstants;
 import com.vaadin.shared.communication.URLReference;
index e6829563f923d601b3c27809085ad02a9bdc0798..75cca64059ab82bf27e9ff75d669e73ed5978827 100644 (file)
@@ -15,6 +15,7 @@
  */
 package com.vaadin.client.ui.orderedlayout;
 
+import com.vaadin.client.ui.VHorizontalLayout;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.Connect.LoadStyle;
 import com.vaadin.shared.ui.orderedlayout.HorizontalLayoutState;
diff --git a/client/src/com/vaadin/client/ui/orderedlayout/VHorizontalLayout.java b/client/src/com/vaadin/client/ui/orderedlayout/VHorizontalLayout.java
deleted file mode 100644 (file)
index b1ebebe..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.vaadin.client.ui.orderedlayout;
-
-import com.vaadin.client.StyleConstants;
-
-/**
- * Represents a layout where the children is ordered vertically
- */
-public class VHorizontalLayout extends VOrderedLayout {
-
-    public static final String CLASSNAME = "v-horizontallayout";
-
-    /**
-     * Default constructor
-     */
-    public VHorizontalLayout() {
-        super(false);
-        setStyleName(CLASSNAME);
-    }
-
-    @Override
-    public void setStyleName(String style) {
-        super.setStyleName(style);
-        addStyleName(StyleConstants.UI_LAYOUT);
-        addStyleName("v-horizontal");
-    }
-}
diff --git a/client/src/com/vaadin/client/ui/orderedlayout/VOrderedLayout.java b/client/src/com/vaadin/client/ui/orderedlayout/VOrderedLayout.java
deleted file mode 100644 (file)
index ad1d837..0000000
+++ /dev/null
@@ -1,1189 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.vaadin.client.ui.orderedlayout;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import com.google.gwt.dom.client.Node;
-import com.google.gwt.dom.client.Style;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.regexp.shared.MatchResult;
-import com.google.gwt.regexp.shared.RegExp;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.SimplePanel;
-import com.google.gwt.user.client.ui.UIObject;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.LayoutManager;
-import com.vaadin.client.StyleConstants;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.layout.ElementResizeListener;
-import com.vaadin.shared.ui.AlignmentInfo;
-import com.vaadin.shared.ui.MarginInfo;
-
-/**
- * Base class for ordered layouts
- */
-public class VOrderedLayout extends FlowPanel {
-
-    private static final String ALIGN_CLASS_PREFIX = "v-align-";
-
-    protected boolean spacing = false;
-
-    protected boolean vertical = true;
-
-    protected boolean definedHeight = false;
-
-    private Map<Widget, Slot> widgetToSlot = new HashMap<Widget, Slot>();
-
-    private Element expandWrapper;
-
-    private LayoutManager layoutManager;
-
-    public VOrderedLayout(boolean vertical) {
-        this.vertical = vertical;
-    }
-
-    /**
-     * Add or move a slot to another index.
-     * 
-     * <p>
-     * You should note that the index does not refer to the DOM index if
-     * spacings are used. If spacings are used then the index will be adjusted
-     * to include the spacings when inserted.
-     * </p>
-     * <p>
-     * For instance when using spacing the index converts to DOM index in the
-     * following way:
-     * 
-     * <pre>
-     * index : 0 -> DOM index: 0
-     * index : 1 -> DOM index: 1
-     * index : 2 -> DOM index: 3
-     * index : 3 -> DOM index: 5
-     * index : 4 -> DOM index: 7
-     * </pre>
-     * 
-     * When using this method never account for spacings.
-     * </p>
-     * 
-     * @param slot
-     *            The slot to move or add
-     * @param index
-     *            The index where the slot should be placed.
-     */
-    void addOrMoveSlot(Slot slot, int index) {
-        if (slot.getParent() == this) {
-            int currentIndex = getWidgetIndex(slot);
-            if (index == currentIndex) {
-                return;
-            }
-        }
-
-        insert(slot, index);
-
-        /*
-         * We need to confirm spacings are correctly applied after each insert.
-         */
-        setSpacing(spacing);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    protected void insert(Widget child, Element container, int beforeIndex,
-            boolean domInsert) {
-        // Validate index; adjust if the widget is already a child of this
-        // panel.
-        beforeIndex = adjustIndex(child, beforeIndex);
-
-        // Detach new child.
-        child.removeFromParent();
-
-        // Logical attach.
-        getChildren().insert(child, beforeIndex);
-
-        // Physical attach.
-        container = expandWrapper != null ? expandWrapper : getElement();
-        if (domInsert) {
-            if (spacing) {
-                if (beforeIndex != 0) {
-                    /*
-                     * Since the spacing elements are located at the same DOM
-                     * level as the slots we need to take them into account when
-                     * calculating the slot position.
-                     * 
-                     * The spacing elements are always located before the actual
-                     * slot except for the first slot which do not have a
-                     * spacing element like this
-                     * 
-                     * |<slot1><spacing2><slot2><spacing3><slot3>...|
-                     */
-                    beforeIndex = beforeIndex * 2 - 1;
-                }
-            }
-            DOM.insertChild(container, child.getElement(), beforeIndex);
-        } else {
-            DOM.appendChild(container, child.getElement());
-        }
-
-        // Adopt.
-        adopt(child);
-    }
-
-    /**
-     * Remove a slot from the layout
-     * 
-     * @param widget
-     * @return
-     */
-    public void removeWidget(Widget widget) {
-        Slot slot = widgetToSlot.get(widget);
-        remove(slot);
-        widgetToSlot.remove(widget);
-    }
-
-    /**
-     * Get the containing slot for a widget. If no slot is found a new slot is
-     * created and returned.
-     * 
-     * @param widget
-     *            The widget whose slot you want to get
-     * 
-     * @return
-     */
-    public Slot getSlot(Widget widget) {
-        Slot slot = widgetToSlot.get(widget);
-        if (slot == null) {
-            slot = new Slot(widget);
-            widgetToSlot.put(widget, slot);
-        }
-        return slot;
-    }
-
-    /**
-     * Gets a slot based on the widget element. If no slot is found then null is
-     * returned.
-     * 
-     * @param widgetElement
-     *            The element of the widget ( Same as getWidget().getElement() )
-     * @return
-     */
-    public Slot getSlot(Element widgetElement) {
-        for (Map.Entry<Widget, Slot> entry : widgetToSlot.entrySet()) {
-            if (entry.getKey().getElement() == widgetElement) {
-                return entry.getValue();
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Defines where the caption should be placed
-     */
-    public enum CaptionPosition {
-        TOP, RIGHT, BOTTOM, LEFT
-    }
-
-    /**
-     * Represents a slot which contains the actual widget in the layout.
-     */
-    public final class Slot extends SimplePanel {
-
-        public static final String SLOT_CLASSNAME = "v-slot";
-
-        private Element spacer;
-        private Element captionWrap;
-        private Element caption;
-        private Element captionText;
-        private Icon icon;
-        private Element errorIcon;
-        private Element requiredIcon;
-
-        private ElementResizeListener captionResizeListener;
-
-        private ElementResizeListener widgetResizeListener;
-
-        private ElementResizeListener spacingResizeListener;
-
-        // Caption is placed after component unless there is some part which
-        // moves it above.
-        private CaptionPosition captionPosition = CaptionPosition.RIGHT;
-
-        private AlignmentInfo alignment;
-
-        private double expandRatio = -1;
-
-        /**
-         * Constructor
-         * 
-         * @param widget
-         *            The widget to put in the slot
-         * 
-         * @param layoutManager
-         *            The layout manager used by the layout
-         */
-        private Slot(Widget widget) {
-            setStyleName(SLOT_CLASSNAME);
-            setWidget(widget);
-        }
-
-        /*
-         * (non-Javadoc)
-         * 
-         * @see
-         * com.google.gwt.user.client.ui.SimplePanel#remove(com.google.gwt.user
-         * .client.ui.Widget)
-         */
-        @Override
-        public boolean remove(Widget w) {
-            detachListeners();
-            return super.remove(w);
-        }
-
-        /*
-         * (non-Javadoc)
-         * 
-         * @see
-         * com.google.gwt.user.client.ui.SimplePanel#setWidget(com.google.gwt
-         * .user.client.ui.Widget)
-         */
-        @Override
-        public void setWidget(Widget w) {
-            detachListeners();
-            super.setWidget(w);
-            attachListeners();
-        }
-
-        /**
-         * Attached resize listeners to the widget, caption and spacing elements
-         */
-        private void attachListeners() {
-            if (getWidget() != null && getLayoutManager() != null) {
-                LayoutManager lm = getLayoutManager();
-                if (getCaptionElement() != null
-                        && captionResizeListener != null) {
-                    lm.addElementResizeListener(getCaptionElement(),
-                            captionResizeListener);
-                }
-                if (widgetResizeListener != null) {
-                    lm.addElementResizeListener(getWidget().getElement(),
-                            widgetResizeListener);
-                }
-                if (getSpacingElement() != null
-                        && spacingResizeListener != null) {
-                    lm.addElementResizeListener(getSpacingElement(),
-                            spacingResizeListener);
-                }
-            }
-        }
-
-        /**
-         * Detaches resize listeners from the widget, caption and spacing
-         * elements
-         */
-        private void detachListeners() {
-            if (getWidget() != null && getLayoutManager() != null) {
-                LayoutManager lm = getLayoutManager();
-                if (getCaptionElement() != null
-                        && captionResizeListener != null) {
-                    lm.removeElementResizeListener(getCaptionElement(),
-                            captionResizeListener);
-                }
-                if (widgetResizeListener != null) {
-                    lm.removeElementResizeListener(getWidget().getElement(),
-                            widgetResizeListener);
-                }
-                if (getSpacingElement() != null
-                        && spacingResizeListener != null) {
-                    lm.removeElementResizeListener(getSpacingElement(),
-                            spacingResizeListener);
-                }
-            }
-        }
-
-        public ElementResizeListener getCaptionResizeListener() {
-            return captionResizeListener;
-        }
-
-        public void setCaptionResizeListener(
-                ElementResizeListener captionResizeListener) {
-            detachListeners();
-            this.captionResizeListener = captionResizeListener;
-            attachListeners();
-        }
-
-        public ElementResizeListener getWidgetResizeListener() {
-            return widgetResizeListener;
-        }
-
-        public void setWidgetResizeListener(
-                ElementResizeListener widgetResizeListener) {
-            detachListeners();
-            this.widgetResizeListener = widgetResizeListener;
-            attachListeners();
-        }
-
-        public ElementResizeListener getSpacingResizeListener() {
-            return spacingResizeListener;
-        }
-
-        public void setSpacingResizeListener(
-                ElementResizeListener spacingResizeListener) {
-            detachListeners();
-            this.spacingResizeListener = spacingResizeListener;
-            attachListeners();
-        }
-
-        /**
-         * Returns the alignment for the slot
-         * 
-         */
-        public AlignmentInfo getAlignment() {
-            return alignment;
-        }
-
-        /**
-         * Sets the style names for the slot containing the widget
-         * 
-         * @param stylenames
-         *            The style names for the slot
-         */
-        protected void setStyleNames(String... stylenames) {
-            setStyleName(SLOT_CLASSNAME);
-            if (stylenames != null) {
-                for (String stylename : stylenames) {
-                    addStyleDependentName(stylename);
-                }
-            }
-
-            // Ensure alignment style names are correct
-            setAlignment(alignment);
-        }
-
-        /**
-         * Sets how the widget is aligned inside the slot
-         * 
-         * @param alignment
-         *            The alignment inside the slot
-         */
-        public void setAlignment(AlignmentInfo alignment) {
-            this.alignment = alignment;
-
-            if (alignment != null && alignment.isHorizontalCenter()) {
-                addStyleName(ALIGN_CLASS_PREFIX + "center");
-                removeStyleName(ALIGN_CLASS_PREFIX + "right");
-            } else if (alignment != null && alignment.isRight()) {
-                addStyleName(ALIGN_CLASS_PREFIX + "right");
-                removeStyleName(ALIGN_CLASS_PREFIX + "center");
-            } else {
-                removeStyleName(ALIGN_CLASS_PREFIX + "right");
-                removeStyleName(ALIGN_CLASS_PREFIX + "center");
-            }
-
-            if (alignment != null && alignment.isVerticalCenter()) {
-                addStyleName(ALIGN_CLASS_PREFIX + "middle");
-                removeStyleName(ALIGN_CLASS_PREFIX + "bottom");
-            } else if (alignment != null && alignment.isBottom()) {
-                addStyleName(ALIGN_CLASS_PREFIX + "bottom");
-                removeStyleName(ALIGN_CLASS_PREFIX + "middle");
-            } else {
-                removeStyleName(ALIGN_CLASS_PREFIX + "middle");
-                removeStyleName(ALIGN_CLASS_PREFIX + "bottom");
-            }
-        }
-
-        /**
-         * Set how the slot should be expanded relative to the other slots
-         * 
-         * @param expandRatio
-         *            The ratio of the space the slot should occupy
-         * 
-         */
-        public void setExpandRatio(double expandRatio) {
-            this.expandRatio = expandRatio;
-        }
-
-        /**
-         * Get the expand ratio for the slot. The expand ratio describes how the
-         * slot should be resized compared to other slots in the layout
-         * 
-         * @return
-         */
-        public double getExpandRatio() {
-            return expandRatio;
-        }
-
-        /**
-         * Set the spacing for the slot. The spacing determines if there should
-         * be empty space around the slot when the slot.
-         * 
-         * @param spacing
-         *            Should spacing be enabled
-         */
-        public void setSpacing(boolean spacing) {
-            if (spacing && spacer == null) {
-                spacer = DOM.createDiv();
-                spacer.addClassName("v-spacing");
-                
-                /*
-                 * This has to be done here for the initial render. In other
-                 * cases where the spacer already exists onAttach will handle
-                 * it.
-                 */
-                getElement().getParentElement().insertBefore(spacer,
-                        getElement());
-            } else if (!spacing && spacer != null) {
-                spacer.removeFromParent();
-                spacer = null;
-            }
-        }
-
-        /**
-         * Get the element which is added to make the spacing
-         * 
-         * @return
-         */
-        public Element getSpacingElement() {
-            return spacer;
-        }
-
-        /**
-         * Does the slot have spacing
-         */
-        public boolean hasSpacing() {
-            return getSpacingElement() != null;
-        }
-
-        /**
-         * Get the vertical amount in pixels of the spacing
-         */
-        protected int getVerticalSpacing() {
-            if (spacer == null) {
-                return 0;
-            } else if (getLayoutManager() != null) {
-                return getLayoutManager().getOuterHeight(spacer);
-            }
-            return spacer.getOffsetHeight();
-        }
-
-        /**
-         * Get the horizontal amount of pixels of the spacing
-         * 
-         * @return
-         */
-        protected int getHorizontalSpacing() {
-            if (spacer == null) {
-                return 0;
-            } else if (getLayoutManager() != null) {
-                return getLayoutManager().getOuterWidth(spacer);
-            }
-            return spacer.getOffsetWidth();
-        }
-
-        /**
-         * Set the position of the caption relative to the slot
-         * 
-         * @param captionPosition
-         *            The position of the caption
-         */
-        public void setCaptionPosition(CaptionPosition captionPosition) {
-            if (caption == null) {
-                return;
-            }
-            captionWrap.removeClassName("v-caption-on-"
-                    + this.captionPosition.name().toLowerCase());
-
-            this.captionPosition = captionPosition;
-            if (captionPosition == CaptionPosition.BOTTOM
-                    || captionPosition == CaptionPosition.RIGHT) {
-                captionWrap.appendChild(caption);
-            } else {
-                captionWrap.insertFirst(caption);
-            }
-
-            captionWrap.addClassName("v-caption-on-"
-                    + captionPosition.name().toLowerCase());
-        }
-
-        /**
-         * Get the position of the caption relative to the slot
-         */
-        public CaptionPosition getCaptionPosition() {
-            return captionPosition;
-        }
-
-        /**
-         * Set the caption of the slot
-         * 
-         * @param captionText
-         *            The text of the caption
-         * @param iconUrl
-         *            The icon URL
-         * @param styles
-         *            The style names
-         * @param error
-         *            The error message
-         * @param showError
-         *            Should the error message be shown
-         * @param required
-         *            Is the (field) required
-         * @param enabled
-         *            Is the component enabled
-         */
-        public void setCaption(String captionText, String iconUrl,
-                List<String> styles, String error, boolean showError,
-                boolean required, boolean enabled) {
-
-            // TODO place for optimization: check if any of these have changed
-            // since last time, and only run those changes
-
-            // Caption wrappers
-            if (captionText != null || iconUrl != null || error != null
-                    || required) {
-                if (caption == null) {
-                    caption = DOM.createDiv();
-                    captionWrap = DOM.createDiv();
-                    captionWrap.addClassName(StyleConstants.UI_WIDGET);
-                    captionWrap.addClassName("v-has-caption");
-                    getElement().appendChild(captionWrap);
-                    captionWrap.appendChild(getWidget().getElement());
-                }
-            } else if (caption != null) {
-                getElement().appendChild(getWidget().getElement());
-                captionWrap.removeFromParent();
-                caption = null;
-                captionWrap = null;
-            }
-
-            // Caption text
-            if (captionText != null) {
-                if (this.captionText == null) {
-                    this.captionText = DOM.createSpan();
-                    this.captionText.addClassName("v-captiontext");
-                    caption.appendChild(this.captionText);
-                }
-                if (captionText.trim().equals("")) {
-                    this.captionText.setInnerHTML("&nbsp;");
-                } else {
-                    this.captionText.setInnerText(captionText);
-                }
-            } else if (this.captionText != null) {
-                this.captionText.removeFromParent();
-                this.captionText = null;
-            }
-
-            // Icon
-            if (iconUrl != null) {
-                if (icon == null) {
-                    icon = new Icon();
-                    caption.insertFirst(icon.getElement());
-                }
-                icon.setUri(iconUrl);
-            } else if (icon != null) {
-                icon.getElement().removeFromParent();
-                icon = null;
-            }
-
-            // Required
-            if (required) {
-                if (requiredIcon == null) {
-                    requiredIcon = DOM.createSpan();
-                    // TODO decide something better (e.g. use CSS to insert the
-                    // character)
-                    requiredIcon.setInnerHTML("*");
-                    requiredIcon.setClassName("v-required-field-indicator");
-                }
-                caption.appendChild(requiredIcon);
-            } else if (requiredIcon != null) {
-                requiredIcon.removeFromParent();
-                requiredIcon = null;
-            }
-
-            // Error
-            if (error != null && showError) {
-                if (errorIcon == null) {
-                    errorIcon = DOM.createSpan();
-                    errorIcon.setClassName("v-errorindicator");
-                }
-                caption.appendChild(errorIcon);
-            } else if (errorIcon != null) {
-                errorIcon.removeFromParent();
-                errorIcon = null;
-            }
-
-            if (caption != null) {
-                // Styles
-                caption.setClassName("v-caption");
-
-                if (styles != null) {
-                    for (String style : styles) {
-                        caption.addClassName("v-caption-" + style);
-                    }
-                }
-
-                if (enabled) {
-                    caption.removeClassName("v-disabled");
-                } else {
-                    caption.addClassName("v-disabled");
-                }
-
-                // Caption position
-                if (captionText != null || iconUrl != null) {
-                    setCaptionPosition(CaptionPosition.TOP);
-                } else {
-                    setCaptionPosition(CaptionPosition.RIGHT);
-                }
-            }
-        }
-
-        /**
-         * Does the slot have a caption
-         */
-        public boolean hasCaption() {
-            return caption != null;
-        }
-
-        /**
-         * Get the slots caption element
-         */
-        public Element getCaptionElement() {
-            return caption;
-        }
-
-        /**
-         * Set if the slot has a relative width
-         * 
-         * @param relativeWidth
-         *            True if slot uses relative width, false if the slot has a
-         *            static width
-         */
-        private boolean relativeWidth = false;
-
-        protected void setRelativeWidth(boolean relativeWidth) {
-            this.relativeWidth = relativeWidth;
-            updateRelativeSize(relativeWidth, "width");
-        }
-
-        /**
-         * Set if the slot has a relative height
-         * 
-         * @param relativeHeight
-         *            Trie if the slot uses a relative height, false if the slot
-         *            has a static height
-         */
-        private boolean relativeHeight = false;
-
-        protected void setRelativeHeight(boolean relativeHeight) {
-            this.relativeHeight = relativeHeight;
-            updateRelativeSize(relativeHeight, "height");
-        }
-
-        /**
-         * Updates the captions size if the slot is relative
-         * 
-         * @param isRelativeSize
-         *            Is the slot relatived sized
-         * @param direction
-         *            The directorion of the caption
-         */
-        private void updateRelativeSize(boolean isRelativeSize, String direction) {
-            if (isRelativeSize && hasCaption()) {
-                captionWrap.getStyle().setProperty(
-                        direction,
-                        getWidget().getElement().getStyle()
-                                .getProperty(direction));
-                captionWrap.addClassName("v-has-" + direction);
-            } else if (hasCaption()) {
-                if (direction.equals("height")) {
-                    captionWrap.getStyle().clearHeight();
-                } else {
-                    captionWrap.getStyle().clearWidth();
-                }
-                captionWrap.removeClassName("v-has-" + direction);
-                captionWrap.getStyle().clearPaddingTop();
-                captionWrap.getStyle().clearPaddingRight();
-                captionWrap.getStyle().clearPaddingBottom();
-                captionWrap.getStyle().clearPaddingLeft();
-                caption.getStyle().clearMarginTop();
-                caption.getStyle().clearMarginRight();
-                caption.getStyle().clearMarginBottom();
-                caption.getStyle().clearMarginLeft();
-            }
-        }
-
-        /*
-         * (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);
-            if (DOM.eventGetType(event) == Event.ONLOAD
-                    && icon.getElement() == DOM.eventGetTarget(event)) {
-                if (getLayoutManager() != null) {
-                    getLayoutManager().layoutLater();
-                } else {
-                    updateCaptionOffset(caption);
-                }
-            }
-        }
-
-        /*
-         * (non-Javadoc)
-         * 
-         * @see com.google.gwt.user.client.ui.SimplePanel#getContainerElement()
-         */
-        @Override
-        protected Element getContainerElement() {
-            if (captionWrap == null) {
-                return getElement();
-            } else {
-                return captionWrap;
-            }
-        }
-
-        /*
-         * (non-Javadoc)
-         * 
-         * @see com.google.gwt.user.client.ui.Widget#onDetach()
-         */
-        @Override
-        protected void onDetach() {
-            if (spacer != null) {
-                spacer.removeFromParent();
-            }
-            super.onDetach();
-        }
-
-        /*
-         * (non-Javadoc)
-         * 
-         * @see com.google.gwt.user.client.ui.Widget#onAttach()
-         */
-        @Override
-        protected void onAttach() {
-            super.onAttach();
-            if (spacer != null) {
-                getElement().getParentElement().insertBefore(spacer,
-                        getElement());
-            }
-        }
-    }
-
-    /**
-     * The icon for each widget. Located in the caption of the slot.
-     */
-    public static class Icon extends UIObject {
-
-        public static final String CLASSNAME = "v-icon";
-
-        private String myUrl;
-
-        /**
-         * Constructor
-         */
-        public Icon() {
-            setElement(DOM.createImg());
-            DOM.setElementProperty(getElement(), "alt", "");
-            setStyleName(CLASSNAME);
-        }
-
-        /**
-         * Set the URL where the icon is located
-         * 
-         * @param url
-         *            A fully qualified URL
-         */
-        public void setUri(String url) {
-            if (!url.equals(myUrl)) {
-                /*
-                 * Start sinking onload events, widgets responsibility to react.
-                 * We must do this BEFORE we set src as IE fires the event
-                 * immediately if the image is found in cache (#2592).
-                 */
-                sinkEvents(Event.ONLOAD);
-
-                DOM.setElementProperty(getElement(), "src", url);
-                myUrl = url;
-            }
-        }
-    }
-
-    /**
-     * Set the layout manager for the layout
-     * 
-     * @param manager
-     *            The layout manager to use
-     */
-    public void setLayoutManager(LayoutManager manager) {
-        layoutManager = manager;
-    }
-
-    /**
-     * Get the layout manager used by this layout
-     * 
-     */
-    public LayoutManager getLayoutManager() {
-        return layoutManager;
-    }
-
-    /**
-     * Deducts the caption position by examining the wrapping element
-     * 
-     * @param captionWrap
-     *            The wrapping element
-     * 
-     * @return The caption position
-     */
-    CaptionPosition getCaptionPositionFromElement(Element captionWrap) {
-        RegExp captionPositionRegexp = RegExp.compile("v-caption-on-(\\S+)");
-
-        // Get caption position from the classname
-        MatchResult matcher = captionPositionRegexp.exec(captionWrap
-                .getClassName());
-        if (matcher == null || matcher.getGroupCount() < 2) {
-            return CaptionPosition.TOP;
-        }
-        String captionClass = matcher.getGroup(1);
-        CaptionPosition captionPosition = CaptionPosition.valueOf(
-                CaptionPosition.class, captionClass.toUpperCase());
-        return captionPosition;
-    }
-
-    /**
-     * Update the offset off the caption relative to the slot
-     * 
-     * @param caption
-     *            The caption element
-     */
-    void updateCaptionOffset(Element caption) {
-
-        Element captionWrap = caption.getParentElement().cast();
-
-        Style captionWrapStyle = captionWrap.getStyle();
-        captionWrapStyle.clearPaddingTop();
-        captionWrapStyle.clearPaddingRight();
-        captionWrapStyle.clearPaddingBottom();
-        captionWrapStyle.clearPaddingLeft();
-
-        Style captionStyle = caption.getStyle();
-        captionStyle.clearMarginTop();
-        captionStyle.clearMarginRight();
-        captionStyle.clearMarginBottom();
-        captionStyle.clearMarginLeft();
-
-        // Get caption position from the classname
-        CaptionPosition captionPosition = getCaptionPositionFromElement(captionWrap);
-
-        if (captionPosition == CaptionPosition.LEFT
-                || captionPosition == CaptionPosition.RIGHT) {
-            int captionWidth;
-            if (layoutManager != null) {
-                captionWidth = layoutManager.getOuterWidth(caption)
-                        - layoutManager.getMarginWidth(caption);
-            } else {
-                captionWidth = caption.getOffsetWidth();
-            }
-            if (captionWidth > 0) {
-                if (captionPosition == CaptionPosition.LEFT) {
-                    captionWrapStyle.setPaddingLeft(captionWidth, Unit.PX);
-                    captionStyle.setMarginLeft(-captionWidth, Unit.PX);
-                } else {
-                    captionWrapStyle.setPaddingRight(captionWidth, Unit.PX);
-                    captionStyle.setMarginRight(-captionWidth, Unit.PX);
-                }
-            }
-        }
-        if (captionPosition == CaptionPosition.TOP
-                || captionPosition == CaptionPosition.BOTTOM) {
-            int captionHeight;
-            if (layoutManager != null) {
-                captionHeight = layoutManager.getOuterHeight(caption)
-                        - layoutManager.getMarginHeight(caption);
-            } else {
-                captionHeight = caption.getOffsetHeight();
-            }
-            if (captionHeight > 0) {
-                if (captionPosition == CaptionPosition.TOP) {
-                    captionWrapStyle.setPaddingTop(captionHeight, Unit.PX);
-                    captionStyle.setMarginTop(-captionHeight, Unit.PX);
-                } else {
-                    captionWrapStyle.setPaddingBottom(captionHeight, Unit.PX);
-                    captionStyle.setMarginBottom(-captionHeight, Unit.PX);
-                }
-            }
-        }
-    }
-
-    /**
-     * Set the margin of the layout
-     * 
-     * @param marginInfo
-     *            The margin information
-     */
-    public void setMargin(MarginInfo marginInfo) {
-        if (marginInfo != null) {
-            setStyleName("v-margin-top", marginInfo.hasTop());
-            setStyleName("v-margin-right", marginInfo.hasRight());
-            setStyleName("v-margin-bottom", marginInfo.hasBottom());
-            setStyleName("v-margin-left", marginInfo.hasLeft());
-        }
-    }
-
-    /**
-     * Turn on or off spacing in the layout
-     * 
-     * @param spacing
-     *            True if spacing should be used, false if not
-     */
-    public void setSpacing(boolean spacing) {
-        this.spacing = spacing;
-        for (Slot slot : widgetToSlot.values()) {
-            if (getWidgetIndex(slot) > 0) {
-                slot.setSpacing(spacing);
-            } else {
-                slot.setSpacing(false);
-            }
-        }
-    }
-
-    /**
-     * Triggers a recalculation of the expand width and heights
-     */
-    private void recalculateExpands() {
-        double total = 0;
-        for (Slot slot : widgetToSlot.values()) {
-            if (slot.getExpandRatio() > -1) {
-                total += slot.getExpandRatio();
-            } else {
-                if (vertical) {
-                    slot.getElement().getStyle().clearHeight();
-                } else {
-                    slot.getElement().getStyle().clearWidth();
-                }
-            }
-        }
-        for (Slot slot : widgetToSlot.values()) {
-            if (slot.getExpandRatio() > -1) {
-                if (vertical) {
-                    slot.setHeight((100 * (slot.getExpandRatio() / total))
-                            + "%");
-                    if (slot.relativeHeight) {
-                        Util.notifyParentOfSizeChange(this, true);
-                    }
-                } else {
-                    slot.setWidth((100 * (slot.getExpandRatio() / total)) + "%");
-                    if (slot.relativeWidth) {
-                        Util.notifyParentOfSizeChange(this, true);
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Removes elements used to expand a slot
-     */
-    void clearExpand() {
-        if (expandWrapper != null) {
-            for (; expandWrapper.getChildCount() > 0;) {
-                Element el = expandWrapper.getChild(0).cast();
-                getElement().appendChild(el);
-                if (vertical) {
-                    el.getStyle().clearHeight();
-                    el.getStyle().clearMarginTop();
-                } else {
-                    el.getStyle().clearWidth();
-                    el.getStyle().clearMarginLeft();
-                }
-            }
-            expandWrapper.removeFromParent();
-            expandWrapper = null;
-        }
-    }
-
-    /**
-     * Adds elements used to expand a slot
-     */
-    public void updateExpand() {
-        boolean isExpanding = false;
-        for (Widget slot : getChildren()) {
-            if (((Slot) slot).getExpandRatio() > -1) {
-                isExpanding = true;
-            } else {
-                if (vertical) {
-                    slot.getElement().getStyle().clearHeight();
-                } else {
-                    slot.getElement().getStyle().clearWidth();
-                }
-            }
-            slot.getElement().getStyle().clearMarginLeft();
-            slot.getElement().getStyle().clearMarginTop();
-        }
-
-        if (isExpanding) {
-            if (expandWrapper == null) {
-                expandWrapper = DOM.createDiv();
-                expandWrapper.setClassName("v-expand");
-                for (; getElement().getChildCount() > 0;) {
-                    Node el = getElement().getChild(0);
-                    expandWrapper.appendChild(el);
-                }
-                getElement().appendChild(expandWrapper);
-            }
-
-            int totalSize = 0;
-            for (Widget w : getChildren()) {
-                Slot slot = (Slot) w;
-                if (slot.getExpandRatio() == -1) {
-
-                    if (layoutManager != null) {
-                        // TODO check caption position
-                        if (vertical) {
-                            int size = layoutManager.getOuterHeight(slot
-                                    .getWidget().getElement())
-                                    - layoutManager.getMarginHeight(slot
-                                            .getWidget().getElement());
-                            if (slot.hasCaption()) {
-                                size += layoutManager.getOuterHeight(slot
-                                        .getCaptionElement())
-                                        - layoutManager.getMarginHeight(slot
-                                                .getCaptionElement());
-                            }
-                            if (size > 0) {
-                                totalSize += size;
-                            }
-                        } else {
-                            int max = -1;
-                            max = layoutManager.getOuterWidth(slot.getWidget()
-                                    .getElement())
-                                    - layoutManager.getMarginWidth(slot
-                                            .getWidget().getElement());
-                            if (slot.hasCaption()) {
-                                int max2 = layoutManager.getOuterWidth(slot
-                                        .getCaptionElement())
-                                        - layoutManager.getMarginWidth(slot
-                                                .getCaptionElement());
-                                max = Math.max(max, max2);
-                            }
-                            if (max > 0) {
-                                totalSize += max;
-                            }
-                        }
-                    } else {
-                        totalSize += vertical ? slot.getOffsetHeight() : slot
-                                .getOffsetWidth();
-                    }
-                }
-                // TODO fails in Opera, always returns 0
-                int spacingSize = vertical ? slot.getVerticalSpacing() : slot
-                        .getHorizontalSpacing();
-                if (spacingSize > 0) {
-                    totalSize += spacingSize;
-                }
-            }
-
-            // When we set the margin to the first child, we don't need
-            // overflow:hidden in the layout root element, since the wrapper
-            // would otherwise be placed outside of the layout root element
-            // and block events on elements below it.
-            if (vertical) {
-                expandWrapper.getStyle().setPaddingTop(totalSize, Unit.PX);
-                expandWrapper.getFirstChildElement().getStyle()
-                        .setMarginTop(-totalSize, Unit.PX);
-            } else {
-                expandWrapper.getStyle().setPaddingLeft(totalSize, Unit.PX);
-                expandWrapper.getFirstChildElement().getStyle()
-                        .setMarginLeft(-totalSize, Unit.PX);
-            }
-
-            recalculateExpands();
-        }
-    }
-
-    /**
-     * Perform a recalculation of the layout height
-     */
-    public void recalculateLayoutHeight() {
-        // Only needed if a horizontal layout is undefined high, and contains
-        // relative height children or vertical alignments
-        if (vertical || definedHeight) {
-            return;
-        }
-
-        boolean hasRelativeHeightChildren = false;
-        boolean hasVAlign = false;
-
-        for (Widget slot : getChildren()) {
-            Widget widget = ((Slot) slot).getWidget();
-            String h = widget.getElement().getStyle().getHeight();
-            if (h != null && h.indexOf("%") > -1) {
-                hasRelativeHeightChildren = true;
-            }
-            AlignmentInfo a = ((Slot) slot).getAlignment();
-            if (a != null && (a.isVerticalCenter() || a.isBottom())) {
-                hasVAlign = true;
-            }
-        }
-
-        if (hasRelativeHeightChildren || hasVAlign) {
-            int newHeight;
-            if (layoutManager != null) {
-                newHeight = layoutManager.getOuterHeight(getElement())
-                        - layoutManager.getMarginHeight(getElement());
-            } else {
-                newHeight = getElement().getOffsetHeight();
-            }
-            VOrderedLayout.this.getElement().getStyle()
-                    .setHeight(newHeight, Unit.PX);
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void setHeight(String height) {
-        super.setHeight(height);
-        definedHeight = (height != null && !"".equals(height));
-    }
-
-    /**
-     * Sets the slots style names. The style names will be prefixed with the
-     * v-slot prefix.
-     * 
-     * @param stylenames
-     *            The style names of the slot.
-     */
-    public void setSlotStyleNames(Widget widget, String... stylenames) {
-        Slot slot = getSlot(widget);
-        if (slot == null) {
-            throw new IllegalArgumentException(
-                    "A slot for the widget could not be found. Has the widget been added to the layout?");
-        }
-        slot.setStyleNames(stylenames);
-    }
-
-}
diff --git a/client/src/com/vaadin/client/ui/orderedlayout/VVerticalLayout.java b/client/src/com/vaadin/client/ui/orderedlayout/VVerticalLayout.java
deleted file mode 100644 (file)
index bea85f7..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.vaadin.client.ui.orderedlayout;
-
-import com.vaadin.client.StyleConstants;
-
-/**
- * Represents a layout where the children is ordered vertically
- */
-public class VVerticalLayout extends VOrderedLayout {
-
-    public static final String CLASSNAME = "v-verticallayout";
-
-    /**
-     * Default constructor
-     */
-    public VVerticalLayout() {
-        super(true);
-        setStyleName(CLASSNAME);
-    }
-
-    @Override
-    public void setStyleName(String style) {
-        super.setStyleName(style);
-        addStyleName(StyleConstants.UI_LAYOUT);
-        addStyleName("v-vertical");
-    }
-}
index d073e78c9cc816afbdc9605f1aefc69ab6ea6dcc..993607a157bb50942132b9b011b0d75cb1601480 100644 (file)
@@ -15,6 +15,7 @@
  */
 package com.vaadin.client.ui.orderedlayout;
 
+import com.vaadin.client.ui.VVerticalLayout;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.Connect.LoadStyle;
 import com.vaadin.shared.ui.orderedlayout.VerticalLayoutState;
index 679a04651869af4b275c1995ed623c7f5d69e644..9642aaa268b56ffe74f6781622931bffa3a197e6 100644 (file)
@@ -30,6 +30,7 @@ import com.vaadin.client.ui.ClickEventHandler;
 import com.vaadin.client.ui.PostLayoutListener;
 import com.vaadin.client.ui.ShortcutActionHandler;
 import com.vaadin.client.ui.SimpleManagedLayout;
+import com.vaadin.client.ui.VPanel;
 import com.vaadin.client.ui.layout.MayScrollChildren;
 import com.vaadin.shared.MouseEventDetails;
 import com.vaadin.shared.ui.ComponentStateUtil;
diff --git a/client/src/com/vaadin/client/ui/panel/VPanel.java b/client/src/com/vaadin/client/ui/panel/VPanel.java
deleted file mode 100644 (file)
index 91fdf74..0000000
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.panel;
-
-import com.google.gwt.dom.client.DivElement;
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.SimplePanel;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.Focusable;
-import com.vaadin.client.ui.Icon;
-import com.vaadin.client.ui.ShortcutActionHandler;
-import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
-import com.vaadin.client.ui.TouchScrollDelegate;
-import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
-
-public class VPanel extends SimplePanel implements ShortcutActionHandlerOwner,
-        Focusable {
-
-    public static final String CLASSNAME = "v-panel";
-
-    ApplicationConnection client;
-
-    String id;
-
-    final Element captionNode = DOM.createDiv();
-
-    private final Element captionText = DOM.createSpan();
-
-    private Icon icon;
-
-    final Element bottomDecoration = DOM.createDiv();
-
-    final Element contentNode = DOM.createDiv();
-
-    private Element errorIndicatorElement;
-
-    ShortcutActionHandler shortcutHandler;
-
-    int scrollTop;
-
-    int scrollLeft;
-
-    private TouchScrollHandler touchScrollHandler;
-
-    public VPanel() {
-        super();
-        DivElement captionWrap = Document.get().createDivElement();
-        captionWrap.appendChild(captionNode);
-        captionNode.appendChild(captionText);
-
-        captionWrap.setClassName(CLASSNAME + "-captionwrap");
-        captionNode.setClassName(CLASSNAME + "-caption");
-        contentNode.setClassName(CLASSNAME + "-content");
-        bottomDecoration.setClassName(CLASSNAME + "-deco");
-
-        getElement().appendChild(captionWrap);
-
-        /*
-         * Make contentNode focusable only by using the setFocus() method. This
-         * behaviour can be changed by invoking setTabIndex() in the serverside
-         * implementation
-         */
-        contentNode.setTabIndex(-1);
-
-        getElement().appendChild(contentNode);
-
-        getElement().appendChild(bottomDecoration);
-        setStyleName(CLASSNAME);
-        DOM.sinkEvents(getElement(), Event.ONKEYDOWN);
-        DOM.sinkEvents(contentNode, Event.ONSCROLL | Event.TOUCHEVENTS);
-
-        contentNode.getStyle().setProperty("position", "relative");
-        getElement().getStyle().setProperty("overflow", "hidden");
-
-        makeScrollable();
-    }
-
-    /**
-     * Sets the keyboard focus on the Panel
-     * 
-     * @param focus
-     *            Should the panel have focus or not.
-     */
-    public void setFocus(boolean focus) {
-        if (focus) {
-            getContainerElement().focus();
-        } else {
-            getContainerElement().blur();
-        }
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see com.vaadin.client.Focusable#focus()
-     */
-
-    @Override
-    public void focus() {
-        setFocus(true);
-
-    }
-
-    @Override
-    protected Element getContainerElement() {
-        return contentNode;
-    }
-
-    void setCaption(String text) {
-        DOM.setInnerHTML(captionText, text);
-    }
-
-    void setErrorIndicatorVisible(boolean showError) {
-        if (showError) {
-            if (errorIndicatorElement == null) {
-                errorIndicatorElement = DOM.createSpan();
-                DOM.setElementProperty(errorIndicatorElement, "className",
-                        "v-errorindicator");
-                DOM.sinkEvents(errorIndicatorElement, Event.MOUSEEVENTS);
-                sinkEvents(Event.MOUSEEVENTS);
-            }
-            DOM.insertBefore(captionNode, errorIndicatorElement, captionText);
-        } else if (errorIndicatorElement != null) {
-            DOM.removeChild(captionNode, errorIndicatorElement);
-            errorIndicatorElement = null;
-        }
-    }
-
-    void setIconUri(String iconUri, ApplicationConnection client) {
-        if (iconUri == null) {
-            if (icon != null) {
-                DOM.removeChild(captionNode, icon.getElement());
-                icon = null;
-            }
-        } else {
-            if (icon == null) {
-                icon = new Icon(client);
-                DOM.insertChild(captionNode, icon.getElement(), 0);
-            }
-            icon.setUri(iconUri);
-        }
-    }
-
-    @Override
-    public void onBrowserEvent(Event event) {
-        super.onBrowserEvent(event);
-
-        final Element target = DOM.eventGetTarget(event);
-        final int type = DOM.eventGetType(event);
-        if (type == Event.ONKEYDOWN && shortcutHandler != null) {
-            shortcutHandler.handleKeyboardEvent(event);
-            return;
-        }
-        if (type == Event.ONSCROLL) {
-            int newscrollTop = DOM.getElementPropertyInt(contentNode,
-                    "scrollTop");
-            int newscrollLeft = DOM.getElementPropertyInt(contentNode,
-                    "scrollLeft");
-            if (client != null
-                    && (newscrollLeft != scrollLeft || newscrollTop != scrollTop)) {
-                scrollLeft = newscrollLeft;
-                scrollTop = newscrollTop;
-                client.updateVariable(id, "scrollTop", scrollTop, false);
-                client.updateVariable(id, "scrollLeft", scrollLeft, false);
-            }
-        }
-    }
-
-    @Override
-    public ShortcutActionHandler getShortcutActionHandler() {
-        return shortcutHandler;
-    }
-
-    /**
-     * Ensures the panel is scrollable eg. after style name changes
-     */
-    void makeScrollable() {
-        if (touchScrollHandler == null) {
-            touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this);
-        }
-        touchScrollHandler.addElement(contentNode);
-    }
-}
index 35e555c5f0c26e4891028136c7df8b8466b79530..22ff9181c2e115e8310f427c7703c7223cc64112 100644 (file)
@@ -16,6 +16,7 @@
 
 package com.vaadin.client.ui.passwordfield;
 
+import com.vaadin.client.ui.VPasswordField;
 import com.vaadin.client.ui.textfield.TextFieldConnector;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.ui.PasswordField;
diff --git a/client/src/com/vaadin/client/ui/passwordfield/VPasswordField.java b/client/src/com/vaadin/client/ui/passwordfield/VPasswordField.java
deleted file mode 100644 (file)
index 4713bc2..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/* 
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.passwordfield;
-
-import com.google.gwt.user.client.DOM;
-import com.vaadin.client.ui.textfield.VTextField;
-
-/**
- * This class represents a password field.
- * 
- * @author Vaadin Ltd.
- * 
- */
-public class VPasswordField extends VTextField {
-
-    public VPasswordField() {
-        super(DOM.createInputPassword());
-    }
-
-}
index ca06b28bddf84f8e14b896d2e9e3ec6a14f05206..c1048766fe9a8930ca6f1e04d9495daaf4cf6cfd 100644 (file)
@@ -26,6 +26,7 @@ import com.vaadin.client.VCaptionWrapper;
 import com.vaadin.client.communication.StateChangeEvent;
 import com.vaadin.client.ui.AbstractComponentContainerConnector;
 import com.vaadin.client.ui.PostLayoutListener;
+import com.vaadin.client.ui.VPopupView;
 import com.vaadin.shared.ui.ComponentStateUtil;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.popupview.PopupViewServerRpc;
@@ -138,4 +139,4 @@ public class PopupViewConnector extends AbstractComponentContainerConnector
                 event.isVisible());
     }
 
-}
\ No newline at end of file
+}
diff --git a/client/src/com/vaadin/client/ui/popupview/VPopupView.java b/client/src/com/vaadin/client/ui/popupview/VPopupView.java
deleted file mode 100644 (file)
index e8bc190..0000000
+++ /dev/null
@@ -1,373 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.vaadin.client.ui.popupview;
-
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
-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.logical.shared.CloseEvent;
-import com.google.gwt.event.logical.shared.CloseHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.Focusable;
-import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.HasWidgets;
-import com.google.gwt.user.client.ui.Label;
-import com.google.gwt.user.client.ui.PopupPanel;
-import com.google.gwt.user.client.ui.RootPanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ComponentConnector;
-import com.vaadin.client.VCaptionWrapper;
-import com.vaadin.client.VConsole;
-import com.vaadin.client.ui.ShortcutActionHandler;
-import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
-import com.vaadin.client.ui.VOverlay;
-import com.vaadin.client.ui.richtextarea.VRichTextArea;
-
-public class VPopupView extends HTML {
-
-    public static final String CLASSNAME = "v-popupview";
-
-    /** This variable helps to communicate popup visibility to the server */
-    boolean hostPopupVisible;
-
-    final CustomPopup popup;
-    private final Label loading = new Label();
-
-    /**
-     * loading constructor
-     */
-    public VPopupView() {
-        super();
-        popup = new CustomPopup();
-
-        setStyleName(CLASSNAME);
-        popup.setStyleName(CLASSNAME + "-popup");
-        loading.setStyleName(CLASSNAME + "-loading");
-
-        setHTML("");
-        popup.setWidget(loading);
-
-        // When we click to open the popup...
-        addClickHandler(new ClickHandler() {
-            @Override
-            public void onClick(ClickEvent event) {
-                fireEvent(new VisibilityChangeEvent(true));
-            }
-        });
-
-        // ..and when we close it
-        popup.addCloseHandler(new CloseHandler<PopupPanel>() {
-            @Override
-            public void onClose(CloseEvent<PopupPanel> event) {
-                fireEvent(new VisibilityChangeEvent(false));
-            }
-        });
-
-        popup.setAnimationEnabled(true);
-    }
-
-
-    void preparePopup(final CustomPopup popup) {
-        popup.setVisible(false);
-        popup.show();
-    }
-
-    /**
-     * Determines the correct position for a popup and displays the popup at
-     * that position.
-     * 
-     * By default, the popup is shown centered relative to its host component,
-     * ensuring it is visible on the screen if possible.
-     * 
-     * Can be overridden to customize the popup position.
-     * 
-     * @param popup
-     */
-    protected void showPopup(final CustomPopup popup) {
-        popup.setPopupPosition(0, 0);
-
-        popup.setVisible(true);
-    }
-
-    void center() {
-        int windowTop = RootPanel.get().getAbsoluteTop();
-        int windowLeft = RootPanel.get().getAbsoluteLeft();
-        int windowRight = windowLeft + RootPanel.get().getOffsetWidth();
-        int windowBottom = windowTop + RootPanel.get().getOffsetHeight();
-
-        int offsetWidth = popup.getOffsetWidth();
-        int offsetHeight = popup.getOffsetHeight();
-
-        int hostHorizontalCenter = VPopupView.this.getAbsoluteLeft()
-                + VPopupView.this.getOffsetWidth() / 2;
-        int hostVerticalCenter = VPopupView.this.getAbsoluteTop()
-                + VPopupView.this.getOffsetHeight() / 2;
-
-        int left = hostHorizontalCenter - offsetWidth / 2;
-        int top = hostVerticalCenter - offsetHeight / 2;
-
-        // Don't show the popup outside the screen.
-        if ((left + offsetWidth) > windowRight) {
-            left -= (left + offsetWidth) - windowRight;
-        }
-
-        if ((top + offsetHeight) > windowBottom) {
-            top -= (top + offsetHeight) - windowBottom;
-        }
-
-        if (left < 0) {
-            left = 0;
-        }
-
-        if (top < 0) {
-            top = 0;
-        }
-
-        popup.setPopupPosition(left, top);
-    }
-
-    /**
-     * Make sure that we remove the popup when the main widget is removed.
-     * 
-     * @see com.google.gwt.user.client.ui.Widget#onUnload()
-     */
-    @Override
-    protected void onDetach() {
-        popup.hide();
-        super.onDetach();
-    }
-
-    private static native void nativeBlur(Element e)
-    /*-{
-        if(e && e.blur) {
-            e.blur();
-        }
-    }-*/;
-
-    /**
-     * This class is only protected to enable overriding showPopup, and is
-     * currently not intended to be extended or otherwise used directly. Its API
-     * (other than it being a VOverlay) is to be considered private and
-     * potentially subject to change.
-     */
-    protected class CustomPopup extends VOverlay {
-
-        private ComponentConnector popupComponentConnector = null;
-        Widget popupComponentWidget = null;
-        VCaptionWrapper captionWrapper = null;
-
-        private boolean hasHadMouseOver = false;
-        private boolean hideOnMouseOut = true;
-        private final Set<Element> activeChildren = new HashSet<Element>();
-        private boolean hiding = false;
-
-        private ShortcutActionHandler shortcutActionHandler;
-
-        public CustomPopup() {
-            super(true, false, true); // autoHide, not modal, dropshadow
-            setOwner(VPopupView.this);
-            // Delegate popup keyboard events to the relevant handler. The
-            // events do not propagate automatically because the popup is
-            // directly attached to the RootPanel.
-            addDomHandler(new KeyDownHandler() {
-                @Override
-                public void onKeyDown(KeyDownEvent event) {
-                    if (shortcutActionHandler != null) {
-                        shortcutActionHandler.handleKeyboardEvent(Event
-                                .as(event.getNativeEvent()));
-                    }
-                }
-            }, KeyDownEvent.getType());
-        }
-
-        // For some reason ONMOUSEOUT events are not always received, so we have
-        // to use ONMOUSEMOVE that doesn't target the popup
-        @Override
-        public boolean onEventPreview(Event event) {
-            Element target = DOM.eventGetTarget(event);
-            boolean eventTargetsPopup = DOM.isOrHasChild(getElement(), target);
-            int type = DOM.eventGetType(event);
-
-            // Catch children that use keyboard, so we can unfocus them when
-            // hiding
-            if (eventTargetsPopup && type == Event.ONKEYPRESS) {
-                activeChildren.add(target);
-            }
-
-            if (eventTargetsPopup && type == Event.ONMOUSEMOVE) {
-                hasHadMouseOver = true;
-            }
-
-            if (!eventTargetsPopup && type == Event.ONMOUSEMOVE) {
-                if (hasHadMouseOver && hideOnMouseOut) {
-                    hide();
-                    return true;
-                }
-            }
-
-            // Was the TAB key released outside of our popup?
-            if (!eventTargetsPopup && type == Event.ONKEYUP
-                    && event.getKeyCode() == KeyCodes.KEY_TAB) {
-                // Should we hide on focus out (mouse out)?
-                if (hideOnMouseOut) {
-                    hide();
-                    return true;
-                }
-            }
-
-            return super.onEventPreview(event);
-        }
-
-        @Override
-        public void hide(boolean autoClosed) {
-            VConsole.log("Hiding popupview");
-            hiding = true;
-            syncChildren();
-            if (popupComponentWidget != null && popupComponentWidget != loading) {
-                remove(popupComponentWidget);
-            }
-            hasHadMouseOver = false;
-            shortcutActionHandler = null;
-            super.hide(autoClosed);
-        }
-
-        @Override
-        public void show() {
-            hiding = false;
-
-            // Find the shortcut action handler that should handle keyboard
-            // events from the popup. The events do not propagate automatically
-            // because the popup is directly attached to the RootPanel.
-            Widget widget = VPopupView.this;
-            while (shortcutActionHandler == null && widget != null) {
-                if (widget instanceof ShortcutActionHandlerOwner) {
-                    shortcutActionHandler = ((ShortcutActionHandlerOwner) widget)
-                            .getShortcutActionHandler();
-                }
-                widget = widget.getParent();
-            }
-
-            super.show();
-        }
-
-        /**
-         * Try to sync all known active child widgets to server
-         */
-        public void syncChildren() {
-            // Notify children with focus
-            if ((popupComponentWidget instanceof Focusable)) {
-                ((Focusable) popupComponentWidget).setFocus(false);
-            } else {
-
-                checkForRTE(popupComponentWidget);
-            }
-
-            // Notify children that have used the keyboard
-            for (Element e : activeChildren) {
-                try {
-                    nativeBlur(e);
-                } catch (Exception ignored) {
-                }
-            }
-            activeChildren.clear();
-        }
-
-        private void checkForRTE(Widget popupComponentWidget2) {
-            if (popupComponentWidget2 instanceof VRichTextArea) {
-                ((VRichTextArea) popupComponentWidget2)
-                        .synchronizeContentToServer();
-            } else if (popupComponentWidget2 instanceof HasWidgets) {
-                HasWidgets hw = (HasWidgets) popupComponentWidget2;
-                Iterator<Widget> iterator = hw.iterator();
-                while (iterator.hasNext()) {
-                    checkForRTE(iterator.next());
-                }
-            }
-        }
-
-        @Override
-        public boolean remove(Widget w) {
-
-            popupComponentConnector = null;
-            popupComponentWidget = null;
-            captionWrapper = null;
-
-            return super.remove(w);
-        }
-
-        public void setPopupConnector(ComponentConnector newPopupComponent) {
-
-            if (newPopupComponent != popupComponentConnector) {
-                Widget newWidget = newPopupComponent.getWidget();
-                setWidget(newWidget);
-                popupComponentWidget = newWidget;
-                popupComponentConnector = newPopupComponent;
-            }
-
-        }
-
-        public void setHideOnMouseOut(boolean hideOnMouseOut) {
-            this.hideOnMouseOut = hideOnMouseOut;
-        }
-
-        /*
-         * 
-         * We need a hack make popup act as a child of VPopupView in Vaadin's
-         * component tree, but work in default GWT manner when closing or
-         * opening.
-         * 
-         * (non-Javadoc)
-         * 
-         * @see com.google.gwt.user.client.ui.Widget#getParent()
-         */
-        @Override
-        public Widget getParent() {
-            if (!isAttached() || hiding) {
-                return super.getParent();
-            } else {
-                return VPopupView.this;
-            }
-        }
-
-        @Override
-        protected void onDetach() {
-            super.onDetach();
-            hiding = false;
-        }
-
-        @Override
-        public Element getContainerElement() {
-            return super.getContainerElement();
-        }
-
-    }// class CustomPopup
-
-    public HandlerRegistration addVisibilityChangeHandler(
-            final VisibilityChangeHandler visibilityChangeHandler) {
-        return addHandler(visibilityChangeHandler,
-                VisibilityChangeEvent.getType());
-    }
-
-}// class VPopupView
index 6e7a556629c8c5da6692fae247e3eef69311bc98..823412755daf8fb4098469da4e9b3c216e361ba3 100644 (file)
@@ -19,6 +19,7 @@ package com.vaadin.client.ui.progressindicator;
 import com.google.gwt.user.client.Timer;
 import com.vaadin.client.communication.StateChangeEvent;
 import com.vaadin.client.ui.AbstractFieldConnector;
+import com.vaadin.client.ui.VProgressIndicator;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.progressindicator.ProgressIndicatorServerRpc;
 import com.vaadin.shared.ui.progressindicator.ProgressIndicatorState;
diff --git a/client/src/com/vaadin/client/ui/progressindicator/VProgressIndicator.java b/client/src/com/vaadin/client/ui/progressindicator/VProgressIndicator.java
deleted file mode 100644 (file)
index ef3be03..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-/* 
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.progressindicator;
-
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.ui.HasEnabled;
-import com.google.gwt.user.client.ui.Widget;
-
-public class VProgressIndicator extends Widget implements HasEnabled {
-
-    public static final String CLASSNAME = "v-progressindicator";
-    Element wrapper = DOM.createDiv();
-    Element indicator = DOM.createDiv();
-
-    protected boolean indeterminate = false;
-    protected float state = 0.0f;
-    private boolean enabled;
-
-    public VProgressIndicator() {
-        setElement(DOM.createDiv());
-        getElement().appendChild(wrapper);
-        setStyleName(CLASSNAME);
-        wrapper.appendChild(indicator);
-        indicator.setClassName(CLASSNAME + "-indicator");
-        wrapper.setClassName(CLASSNAME + "-wrapper");
-    }
-
-    public void setIndeterminate(boolean indeterminate) {
-        this.indeterminate = indeterminate;
-        setStyleName(CLASSNAME + "-indeterminate", indeterminate);
-    }
-
-    public void setState(float state) {
-        final int size = Math.round(100 * state);
-        indicator.getStyle().setWidth(size, Unit.PCT);
-    }
-
-    public boolean isIndeterminate() {
-        return indeterminate;
-    }
-
-    public float getState() {
-        return state;
-    }
-
-    @Override
-    public boolean isEnabled() {
-        return enabled;
-    }
-
-    @Override
-    public void setEnabled(boolean enabled) {
-        this.enabled = enabled;
-        setStyleName("v-disabled", !enabled);
-
-    }
-
-}
index 5933286f8f95827ba31c3076c997a93789743494..79479fd3ed1696af8d20fa52da5dd9c5ca92a19a 100644 (file)
@@ -20,6 +20,7 @@ import com.vaadin.client.ApplicationConnection;
 import com.vaadin.client.Paintable;
 import com.vaadin.client.UIDL;
 import com.vaadin.client.ui.AbstractFieldConnector;
+import com.vaadin.client.ui.VRichTextArea;
 import com.vaadin.client.ui.ShortcutActionHandler.BeforeShortcutActionListener;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.Connect.LoadStyle;
diff --git a/client/src/com/vaadin/client/ui/richtextarea/VRichTextArea.java b/client/src/com/vaadin/client/ui/richtextarea/VRichTextArea.java
deleted file mode 100644 (file)
index ed5d48a..0000000
+++ /dev/null
@@ -1,373 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.richtextarea;
-
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.event.dom.client.ChangeEvent;
-import com.google.gwt.event.dom.client.ChangeHandler;
-import com.google.gwt.event.dom.client.KeyDownEvent;
-import com.google.gwt.event.dom.client.KeyDownHandler;
-import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.event.dom.client.KeyPressHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Timer;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.Focusable;
-import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.RichTextArea;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.ConnectorMap;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.Field;
-import com.vaadin.client.ui.ShortcutActionHandler;
-import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
-import com.vaadin.client.ui.TouchScrollDelegate;
-
-/**
- * This class implements a basic client side rich text editor component.
- * 
- * @author Vaadin Ltd.
- * 
- */
-public class VRichTextArea extends Composite implements Field, ChangeHandler,
-        BlurHandler, KeyPressHandler, KeyDownHandler, Focusable {
-
-    /**
-     * The input node CSS classname.
-     */
-    public static final String CLASSNAME = "v-richtextarea";
-
-    protected String id;
-
-    protected ApplicationConnection client;
-
-    boolean immediate = false;
-
-    RichTextArea rta;
-
-    private VRichTextToolbar formatter;
-
-    HTML html = new HTML();
-
-    private final FlowPanel fp = new FlowPanel();
-
-    private boolean enabled = true;
-
-    private int extraHorizontalPixels = -1;
-    private int extraVerticalPixels = -1;
-
-    int maxLength = -1;
-
-    private int toolbarNaturalWidth = 500;
-
-    HandlerRegistration keyPressHandler;
-
-    private ShortcutActionHandlerOwner hasShortcutActionHandler;
-
-    String currentValue = "";
-
-    private boolean readOnly = false;
-
-    public VRichTextArea() {
-        createRTAComponents();
-        fp.add(formatter);
-        fp.add(rta);
-
-        initWidget(fp);
-        setStyleName(CLASSNAME);
-
-        TouchScrollDelegate.enableTouchScrolling(html, html.getElement());
-    }
-
-    private void createRTAComponents() {
-        rta = new RichTextArea();
-        rta.setWidth("100%");
-        rta.addBlurHandler(this);
-        rta.addKeyDownHandler(this);
-        formatter = new VRichTextToolbar(rta);
-    }
-
-    public void setEnabled(boolean enabled) {
-        if (this.enabled != enabled) {
-            // rta.setEnabled(enabled);
-            swapEditableArea();
-            this.enabled = enabled;
-        }
-    }
-
-    /**
-     * Swaps html to rta and visa versa.
-     */
-    private void swapEditableArea() {
-        if (html.isAttached()) {
-            fp.remove(html);
-            if (BrowserInfo.get().isWebkit()) {
-                fp.remove(formatter);
-                createRTAComponents(); // recreate new RTA to bypass #5379
-                fp.add(formatter);
-            }
-            rta.setHTML(currentValue);
-            fp.add(rta);
-        } else {
-            html.setHTML(currentValue);
-            fp.remove(rta);
-            fp.add(html);
-        }
-    }
-
-    void selectAll() {
-        /*
-         * There is a timing issue if trying to select all immediately on first
-         * render. Simple deferred command is not enough. Using Timer with
-         * moderated timeout. If this appears to fail on many (most likely slow)
-         * environments, consider increasing the timeout.
-         * 
-         * FF seems to require the most time to stabilize its RTA. On Vaadin
-         * tiergarden test machines, 200ms was not enough always (about 50%
-         * success rate) - 300 ms was 100% successful. This however was not
-         * enough on a sluggish old non-virtualized XP test machine. A bullet
-         * proof solution would be nice, GWT 2.1 might however solve these. At
-         * least setFocus has a workaround for this kind of issue.
-         */
-        new Timer() {
-            @Override
-            public void run() {
-                rta.getFormatter().selectAll();
-            }
-        }.schedule(320);
-    }
-
-    void setReadOnly(boolean b) {
-        if (isReadOnly() != b) {
-            swapEditableArea();
-            readOnly = b;
-        }
-        // reset visibility in case enabled state changed and the formatter was
-        // recreated
-        formatter.setVisible(!readOnly);
-    }
-
-    private boolean isReadOnly() {
-        return readOnly;
-    }
-
-    // TODO is this really used, or does everything go via onBlur() only?
-    @Override
-    public void onChange(ChangeEvent event) {
-        synchronizeContentToServer();
-    }
-
-    /**
-     * Method is public to let popupview force synchronization on close.
-     */
-    public void synchronizeContentToServer() {
-        if (client != null && id != null) {
-            final String html = rta.getHTML();
-            if (!html.equals(currentValue)) {
-                client.updateVariable(id, "text", html, immediate);
-                currentValue = html;
-            }
-        }
-    }
-
-    @Override
-    public void onBlur(BlurEvent event) {
-        synchronizeContentToServer();
-        // TODO notify possible server side blur/focus listeners
-    }
-
-    /**
-     * @return space used by components paddings and borders
-     */
-    private int getExtraHorizontalPixels() {
-        if (extraHorizontalPixels < 0) {
-            detectExtraSizes();
-        }
-        return extraHorizontalPixels;
-    }
-
-    /**
-     * @return space used by components paddings and borders
-     */
-    private int getExtraVerticalPixels() {
-        if (extraVerticalPixels < 0) {
-            detectExtraSizes();
-        }
-        return extraVerticalPixels;
-    }
-
-    /**
-     * Detects space used by components paddings and borders.
-     */
-    private void detectExtraSizes() {
-        Element clone = Util.cloneNode(getElement(), false);
-        DOM.setElementAttribute(clone, "id", "");
-        DOM.setStyleAttribute(clone, "visibility", "hidden");
-        DOM.setStyleAttribute(clone, "position", "absolute");
-        // due FF3 bug set size to 10px and later subtract it from extra pixels
-        DOM.setStyleAttribute(clone, "width", "10px");
-        DOM.setStyleAttribute(clone, "height", "10px");
-        DOM.appendChild(DOM.getParent(getElement()), clone);
-        extraHorizontalPixels = DOM.getElementPropertyInt(clone, "offsetWidth") - 10;
-        extraVerticalPixels = DOM.getElementPropertyInt(clone, "offsetHeight") - 10;
-
-        DOM.removeChild(DOM.getParent(getElement()), clone);
-    }
-
-    @Override
-    public void setHeight(String height) {
-        if (height.endsWith("px")) {
-            int h = Integer.parseInt(height.substring(0, height.length() - 2));
-            h -= getExtraVerticalPixels();
-            if (h < 0) {
-                h = 0;
-            }
-
-            super.setHeight(h + "px");
-        } else {
-            super.setHeight(height);
-        }
-
-        if (height == null || height.equals("")) {
-            rta.setHeight("");
-        } else {
-            /*
-             * The formatter height will be initially calculated wrong so we
-             * delay the height setting so the DOM has had time to stabilize.
-             */
-            Scheduler.get().scheduleDeferred(new Command() {
-                @Override
-                public void execute() {
-                    int editorHeight = getOffsetHeight()
-                            - getExtraVerticalPixels()
-                            - formatter.getOffsetHeight();
-                    if (editorHeight < 0) {
-                        editorHeight = 0;
-                    }
-                    rta.setHeight(editorHeight + "px");
-                }
-            });
-        }
-    }
-
-    @Override
-    public void setWidth(String width) {
-        if (width.endsWith("px")) {
-            int w = Integer.parseInt(width.substring(0, width.length() - 2));
-            w -= getExtraHorizontalPixels();
-            if (w < 0) {
-                w = 0;
-            }
-
-            super.setWidth(w + "px");
-        } else if (width.equals("")) {
-            /*
-             * IE cannot calculate the width of the 100% iframe correctly if
-             * there is no width specified for the parent. In this case we would
-             * use the toolbar but IE cannot calculate the width of that one
-             * correctly either in all cases. So we end up using a default width
-             * for a RichTextArea with no width definition in all browsers (for
-             * compatibility).
-             */
-
-            super.setWidth(toolbarNaturalWidth + "px");
-        } else {
-            super.setWidth(width);
-        }
-    }
-
-    @Override
-    public void onKeyPress(KeyPressEvent event) {
-        if (maxLength >= 0) {
-            Scheduler.get().scheduleDeferred(new Command() {
-                @Override
-                public void execute() {
-                    if (rta.getHTML().length() > maxLength) {
-                        rta.setHTML(rta.getHTML().substring(0, maxLength));
-                    }
-                }
-            });
-        }
-    }
-
-    @Override
-    public void onKeyDown(KeyDownEvent event) {
-        // delegate to closest shortcut action handler
-        // throw event from the iframe forward to the shortcuthandler
-        ShortcutActionHandler shortcutHandler = getShortcutHandlerOwner()
-                .getShortcutActionHandler();
-        if (shortcutHandler != null) {
-            shortcutHandler
-                    .handleKeyboardEvent(com.google.gwt.user.client.Event
-                            .as(event.getNativeEvent()),
-                            ConnectorMap.get(client).getConnector(this));
-        }
-    }
-
-    private ShortcutActionHandlerOwner getShortcutHandlerOwner() {
-        if (hasShortcutActionHandler == null) {
-            Widget parent = getParent();
-            while (parent != null) {
-                if (parent instanceof ShortcutActionHandlerOwner) {
-                    break;
-                }
-                parent = parent.getParent();
-            }
-            hasShortcutActionHandler = (ShortcutActionHandlerOwner) parent;
-        }
-        return hasShortcutActionHandler;
-    }
-
-    @Override
-    public int getTabIndex() {
-        return rta.getTabIndex();
-    }
-
-    @Override
-    public void setAccessKey(char key) {
-        rta.setAccessKey(key);
-    }
-
-    @Override
-    public void setFocus(boolean focused) {
-        /*
-         * Similar issue as with selectAll. Focusing must happen before possible
-         * selectall, so keep the timeout here lower.
-         */
-        new Timer() {
-
-            @Override
-            public void run() {
-                rta.setFocus(true);
-            }
-        }.schedule(300);
-    }
-
-    @Override
-    public void setTabIndex(int index) {
-        rta.setTabIndex(index);
-    }
-
-}
diff --git a/client/src/com/vaadin/client/ui/richtextarea/VRichTextToolbar.java b/client/src/com/vaadin/client/ui/richtextarea/VRichTextToolbar.java
deleted file mode 100644 (file)
index 840fb3b..0000000
+++ /dev/null
@@ -1,476 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-/*
- * Copyright 2007 Google Inc.
- *
- * 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.richtextarea;
-
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.event.dom.client.ChangeEvent;
-import com.google.gwt.event.dom.client.ChangeHandler;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.KeyUpEvent;
-import com.google.gwt.event.dom.client.KeyUpHandler;
-import com.google.gwt.i18n.client.Constants;
-import com.google.gwt.resources.client.ClientBundle;
-import com.google.gwt.resources.client.ImageResource;
-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.Image;
-import com.google.gwt.user.client.ui.ListBox;
-import com.google.gwt.user.client.ui.PushButton;
-import com.google.gwt.user.client.ui.RichTextArea;
-import com.google.gwt.user.client.ui.ToggleButton;
-
-/**
- * A modified version of sample toolbar for use with {@link RichTextArea}. It
- * provides a simple UI for all rich text formatting, dynamically displayed only
- * for the available functionality.
- */
-public class VRichTextToolbar extends Composite {
-
-    /**
-     * This {@link ClientBundle} is used for all the button icons. Using a
-     * bundle allows all of these images to be packed into a single image, which
-     * saves a lot of HTTP requests, drastically improving startup time.
-     */
-    public interface Images extends ClientBundle {
-
-        ImageResource bold();
-
-        ImageResource createLink();
-
-        ImageResource hr();
-
-        ImageResource indent();
-
-        ImageResource insertImage();
-
-        ImageResource italic();
-
-        ImageResource justifyCenter();
-
-        ImageResource justifyLeft();
-
-        ImageResource justifyRight();
-
-        ImageResource ol();
-
-        ImageResource outdent();
-
-        ImageResource removeFormat();
-
-        ImageResource removeLink();
-
-        ImageResource strikeThrough();
-
-        ImageResource subscript();
-
-        ImageResource superscript();
-
-        ImageResource ul();
-
-        ImageResource underline();
-    }
-
-    /**
-     * This {@link Constants} interface is used to make the toolbar's strings
-     * internationalizable.
-     */
-    public interface Strings extends Constants {
-
-        String black();
-
-        String blue();
-
-        String bold();
-
-        String color();
-
-        String createLink();
-
-        String font();
-
-        String green();
-
-        String hr();
-
-        String indent();
-
-        String insertImage();
-
-        String italic();
-
-        String justifyCenter();
-
-        String justifyLeft();
-
-        String justifyRight();
-
-        String large();
-
-        String medium();
-
-        String normal();
-
-        String ol();
-
-        String outdent();
-
-        String red();
-
-        String removeFormat();
-
-        String removeLink();
-
-        String size();
-
-        String small();
-
-        String strikeThrough();
-
-        String subscript();
-
-        String superscript();
-
-        String ul();
-
-        String underline();
-
-        String white();
-
-        String xlarge();
-
-        String xsmall();
-
-        String xxlarge();
-
-        String xxsmall();
-
-        String yellow();
-    }
-
-    /**
-     * We use an inner EventHandler class to avoid exposing event methods on the
-     * RichTextToolbar itself.
-     */
-    private class EventHandler implements ClickHandler, ChangeHandler,
-            KeyUpHandler {
-
-        @Override
-        @SuppressWarnings("deprecation")
-        public void onChange(ChangeEvent event) {
-            Object sender = event.getSource();
-            if (sender == backColors) {
-                basic.setBackColor(backColors.getValue(backColors
-                        .getSelectedIndex()));
-                backColors.setSelectedIndex(0);
-            } else if (sender == foreColors) {
-                basic.setForeColor(foreColors.getValue(foreColors
-                        .getSelectedIndex()));
-                foreColors.setSelectedIndex(0);
-            } else if (sender == fonts) {
-                basic.setFontName(fonts.getValue(fonts.getSelectedIndex()));
-                fonts.setSelectedIndex(0);
-            } else if (sender == fontSizes) {
-                basic.setFontSize(fontSizesConstants[fontSizes
-                        .getSelectedIndex() - 1]);
-                fontSizes.setSelectedIndex(0);
-            }
-        }
-
-        @Override
-        @SuppressWarnings("deprecation")
-        public void onClick(ClickEvent event) {
-            Object sender = event.getSource();
-            if (sender == bold) {
-                basic.toggleBold();
-            } else if (sender == italic) {
-                basic.toggleItalic();
-            } else if (sender == underline) {
-                basic.toggleUnderline();
-            } else if (sender == subscript) {
-                basic.toggleSubscript();
-            } else if (sender == superscript) {
-                basic.toggleSuperscript();
-            } else if (sender == strikethrough) {
-                extended.toggleStrikethrough();
-            } else if (sender == indent) {
-                extended.rightIndent();
-            } else if (sender == outdent) {
-                extended.leftIndent();
-            } else if (sender == justifyLeft) {
-                basic.setJustification(RichTextArea.Justification.LEFT);
-            } else if (sender == justifyCenter) {
-                basic.setJustification(RichTextArea.Justification.CENTER);
-            } else if (sender == justifyRight) {
-                basic.setJustification(RichTextArea.Justification.RIGHT);
-            } else if (sender == insertImage) {
-                final String url = Window.prompt("Enter an image URL:",
-                        "http://");
-                if (url != null) {
-                    extended.insertImage(url);
-                }
-            } else if (sender == createLink) {
-                final String url = Window
-                        .prompt("Enter a link URL:", "http://");
-                if (url != null) {
-                    extended.createLink(url);
-                }
-            } else if (sender == removeLink) {
-                extended.removeLink();
-            } else if (sender == hr) {
-                extended.insertHorizontalRule();
-            } else if (sender == ol) {
-                extended.insertOrderedList();
-            } else if (sender == ul) {
-                extended.insertUnorderedList();
-            } else if (sender == removeFormat) {
-                extended.removeFormat();
-            } else if (sender == richText) {
-                // We use the RichTextArea's onKeyUp event to update the toolbar
-                // status. This will catch any cases where the user moves the
-                // cursur using the keyboard, or uses one of the browser's
-                // built-in keyboard shortcuts.
-                updateStatus();
-            }
-        }
-
-        @Override
-        public void onKeyUp(KeyUpEvent event) {
-            if (event.getSource() == richText) {
-                // We use the RichTextArea's onKeyUp event to update the toolbar
-                // status. This will catch any cases where the user moves the
-                // cursor using the keyboard, or uses one of the browser's
-                // built-in keyboard shortcuts.
-                updateStatus();
-            }
-        }
-    }
-
-    private static final RichTextArea.FontSize[] fontSizesConstants = new RichTextArea.FontSize[] {
-            RichTextArea.FontSize.XX_SMALL, RichTextArea.FontSize.X_SMALL,
-            RichTextArea.FontSize.SMALL, RichTextArea.FontSize.MEDIUM,
-            RichTextArea.FontSize.LARGE, RichTextArea.FontSize.X_LARGE,
-            RichTextArea.FontSize.XX_LARGE };
-
-    private final Images images = (Images) GWT.create(Images.class);
-    private final Strings strings = (Strings) GWT.create(Strings.class);
-    private final EventHandler handler = new EventHandler();
-
-    private final RichTextArea richText;
-    @SuppressWarnings("deprecation")
-    private final RichTextArea.BasicFormatter basic;
-    @SuppressWarnings("deprecation")
-    private final RichTextArea.ExtendedFormatter extended;
-
-    private final FlowPanel outer = new FlowPanel();
-    private final FlowPanel topPanel = new FlowPanel();
-    private final FlowPanel bottomPanel = new FlowPanel();
-    private ToggleButton bold;
-    private ToggleButton italic;
-    private ToggleButton underline;
-    private ToggleButton subscript;
-    private ToggleButton superscript;
-    private ToggleButton strikethrough;
-    private PushButton indent;
-    private PushButton outdent;
-    private PushButton justifyLeft;
-    private PushButton justifyCenter;
-    private PushButton justifyRight;
-    private PushButton hr;
-    private PushButton ol;
-    private PushButton ul;
-    private PushButton insertImage;
-    private PushButton createLink;
-    private PushButton removeLink;
-    private PushButton removeFormat;
-
-    private ListBox backColors;
-    private ListBox foreColors;
-    private ListBox fonts;
-    private ListBox fontSizes;
-
-    /**
-     * Creates a new toolbar that drives the given rich text area.
-     * 
-     * @param richText
-     *            the rich text area to be controlled
-     */
-    @SuppressWarnings("deprecation")
-    public VRichTextToolbar(RichTextArea richText) {
-        this.richText = richText;
-        basic = richText.getBasicFormatter();
-        extended = richText.getExtendedFormatter();
-
-        outer.add(topPanel);
-        outer.add(bottomPanel);
-        topPanel.setStyleName("gwt-RichTextToolbar-top");
-        bottomPanel.setStyleName("gwt-RichTextToolbar-bottom");
-
-        initWidget(outer);
-        setStyleName("gwt-RichTextToolbar");
-
-        if (basic != null) {
-            topPanel.add(bold = createToggleButton(images.bold(),
-                    strings.bold()));
-            topPanel.add(italic = createToggleButton(images.italic(),
-                    strings.italic()));
-            topPanel.add(underline = createToggleButton(images.underline(),
-                    strings.underline()));
-            topPanel.add(subscript = createToggleButton(images.subscript(),
-                    strings.subscript()));
-            topPanel.add(superscript = createToggleButton(images.superscript(),
-                    strings.superscript()));
-            topPanel.add(justifyLeft = createPushButton(images.justifyLeft(),
-                    strings.justifyLeft()));
-            topPanel.add(justifyCenter = createPushButton(
-                    images.justifyCenter(), strings.justifyCenter()));
-            topPanel.add(justifyRight = createPushButton(images.justifyRight(),
-                    strings.justifyRight()));
-        }
-
-        if (extended != null) {
-            topPanel.add(strikethrough = createToggleButton(
-                    images.strikeThrough(), strings.strikeThrough()));
-            topPanel.add(indent = createPushButton(images.indent(),
-                    strings.indent()));
-            topPanel.add(outdent = createPushButton(images.outdent(),
-                    strings.outdent()));
-            topPanel.add(hr = createPushButton(images.hr(), strings.hr()));
-            topPanel.add(ol = createPushButton(images.ol(), strings.ol()));
-            topPanel.add(ul = createPushButton(images.ul(), strings.ul()));
-            topPanel.add(insertImage = createPushButton(images.insertImage(),
-                    strings.insertImage()));
-            topPanel.add(createLink = createPushButton(images.createLink(),
-                    strings.createLink()));
-            topPanel.add(removeLink = createPushButton(images.removeLink(),
-                    strings.removeLink()));
-            topPanel.add(removeFormat = createPushButton(images.removeFormat(),
-                    strings.removeFormat()));
-        }
-
-        if (basic != null) {
-            bottomPanel.add(backColors = createColorList("Background"));
-            bottomPanel.add(foreColors = createColorList("Foreground"));
-            bottomPanel.add(fonts = createFontList());
-            bottomPanel.add(fontSizes = createFontSizes());
-
-            // We only use these handlers for updating status, so don't hook
-            // them up unless at least basic editing is supported.
-            richText.addKeyUpHandler(handler);
-            richText.addClickHandler(handler);
-        }
-    }
-
-    private ListBox createColorList(String caption) {
-        final ListBox lb = new ListBox();
-        lb.addChangeHandler(handler);
-        lb.setVisibleItemCount(1);
-
-        lb.addItem(caption);
-        lb.addItem(strings.white(), "white");
-        lb.addItem(strings.black(), "black");
-        lb.addItem(strings.red(), "red");
-        lb.addItem(strings.green(), "green");
-        lb.addItem(strings.yellow(), "yellow");
-        lb.addItem(strings.blue(), "blue");
-        lb.setTabIndex(-1);
-        return lb;
-    }
-
-    private ListBox createFontList() {
-        final ListBox lb = new ListBox();
-        lb.addChangeHandler(handler);
-        lb.setVisibleItemCount(1);
-
-        lb.addItem(strings.font(), "");
-        lb.addItem(strings.normal(), "inherit");
-        lb.addItem("Times New Roman", "Times New Roman");
-        lb.addItem("Arial", "Arial");
-        lb.addItem("Courier New", "Courier New");
-        lb.addItem("Georgia", "Georgia");
-        lb.addItem("Trebuchet", "Trebuchet");
-        lb.addItem("Verdana", "Verdana");
-        lb.setTabIndex(-1);
-        return lb;
-    }
-
-    private ListBox createFontSizes() {
-        final ListBox lb = new ListBox();
-        lb.addChangeHandler(handler);
-        lb.setVisibleItemCount(1);
-
-        lb.addItem(strings.size());
-        lb.addItem(strings.xxsmall());
-        lb.addItem(strings.xsmall());
-        lb.addItem(strings.small());
-        lb.addItem(strings.medium());
-        lb.addItem(strings.large());
-        lb.addItem(strings.xlarge());
-        lb.addItem(strings.xxlarge());
-        lb.setTabIndex(-1);
-        return lb;
-    }
-
-    private PushButton createPushButton(ImageResource img, String tip) {
-        final PushButton pb = new PushButton(new Image(img));
-        pb.addClickHandler(handler);
-        pb.setTitle(tip);
-        pb.setTabIndex(-1);
-        return pb;
-    }
-
-    private ToggleButton createToggleButton(ImageResource img, String tip) {
-        final ToggleButton tb = new ToggleButton(new Image(img));
-        tb.addClickHandler(handler);
-        tb.setTitle(tip);
-        tb.setTabIndex(-1);
-        return tb;
-    }
-
-    /**
-     * Updates the status of all the stateful buttons.
-     */
-    @SuppressWarnings("deprecation")
-    private void updateStatus() {
-        if (basic != null) {
-            bold.setDown(basic.isBold());
-            italic.setDown(basic.isItalic());
-            underline.setDown(basic.isUnderlined());
-            subscript.setDown(basic.isSubscript());
-            superscript.setDown(basic.isSuperscript());
-        }
-
-        if (extended != null) {
-            strikethrough.setDown(extended.isStrikethrough());
-        }
-    }
-}
index e4c76b6ce8f888ae551a2c263a2fe0c33850387e..e155336d7515da47d3b7646de6da3203990178e7 100644 (file)
@@ -20,6 +20,7 @@ import com.google.gwt.event.logical.shared.ValueChangeHandler;
 import com.vaadin.client.communication.RpcProxy;
 import com.vaadin.client.communication.StateChangeEvent;
 import com.vaadin.client.ui.AbstractFieldConnector;
+import com.vaadin.client.ui.VSlider;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.slider.SliderServerRpc;
 import com.vaadin.shared.ui.slider.SliderState;
diff --git a/client/src/com/vaadin/client/ui/slider/VSlider.java b/client/src/com/vaadin/client/ui/slider/VSlider.java
deleted file mode 100644 (file)
index ab080dc..0000000
+++ /dev/null
@@ -1,656 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-//
-package com.vaadin.client.ui.slider;
-
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
-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.dom.client.KeyCodes;
-import com.google.gwt.event.logical.shared.ValueChangeEvent;
-import com.google.gwt.event.logical.shared.ValueChangeHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.HasValue;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.ContainerResizedListener;
-import com.vaadin.client.Util;
-import com.vaadin.client.VConsole;
-import com.vaadin.client.ui.Field;
-import com.vaadin.client.ui.SimpleFocusablePanel;
-import com.vaadin.client.ui.VLazyExecutor;
-import com.vaadin.client.ui.VOverlay;
-import com.vaadin.shared.ui.slider.SliderOrientation;
-
-public class VSlider extends SimpleFocusablePanel implements Field,
-        ContainerResizedListener, HasValue<Double> {
-
-    public static final String CLASSNAME = "v-slider";
-
-    /**
-     * Minimum size (width or height, depending on orientation) of the slider
-     * base.
-     */
-    private static final int MIN_SIZE = 50;
-
-    protected ApplicationConnection client;
-
-    protected String id;
-
-    protected boolean immediate;
-    protected boolean disabled;
-    protected boolean readonly;
-
-    private int acceleration = 1;
-    protected double min;
-    protected double max;
-    protected int resolution;
-    protected Double value;
-    protected SliderOrientation orientation = SliderOrientation.HORIZONTAL;
-
-    private final HTML feedback = new HTML("", false);
-    private final VOverlay feedbackPopup = new VOverlay(true, false, true) {
-        {
-            setOwner(VSlider.this);
-        }
-
-        @Override
-        public void show() {
-            super.show();
-            updateFeedbackPosition();
-        }
-    };
-
-    /* DOM element for slider's base */
-    private final Element base;
-    private final int BASE_BORDER_WIDTH = 1;
-
-    /* DOM element for slider's handle */
-    private final Element handle;
-
-    /* DOM element for decrement arrow */
-    private final Element smaller;
-
-    /* DOM element for increment arrow */
-    private final Element bigger;
-
-    /* Temporary dragging/animation variables */
-    private boolean dragging = false;
-
-    private VLazyExecutor delayedValueUpdater = new VLazyExecutor(100,
-            new ScheduledCommand() {
-
-                @Override
-                public void execute() {
-                    fireValueChanged();
-                    acceleration = 1;
-                }
-            });
-
-    public VSlider() {
-        super();
-
-        base = DOM.createDiv();
-        handle = DOM.createDiv();
-        smaller = DOM.createDiv();
-        bigger = DOM.createDiv();
-
-        setStyleName(CLASSNAME);
-
-        getElement().appendChild(bigger);
-        getElement().appendChild(smaller);
-        getElement().appendChild(base);
-        base.appendChild(handle);
-
-        // Hide initially
-        smaller.getStyle().setDisplay(Display.NONE);
-        bigger.getStyle().setDisplay(Display.NONE);
-        handle.getStyle().setVisibility(Visibility.HIDDEN);
-
-        sinkEvents(Event.MOUSEEVENTS | Event.ONMOUSEWHEEL | Event.KEYEVENTS
-                | Event.FOCUSEVENTS | Event.TOUCHEVENTS);
-
-        feedbackPopup.setWidget(feedback);
-    }
-
-    @Override
-    public void setStyleName(String style) {
-        updateStyleNames(style, false);
-    }
-
-    @Override
-    public void setStylePrimaryName(String style) {
-        updateStyleNames(style, true);
-    }
-
-    protected void updateStyleNames(String styleName, boolean isPrimaryStyleName) {
-
-        feedbackPopup.removeStyleName(getStylePrimaryName() + "-feedback");
-        removeStyleName(getStylePrimaryName() + "-vertical");
-
-        if (isPrimaryStyleName) {
-            super.setStylePrimaryName(styleName);
-        } else {
-            super.setStyleName(styleName);
-        }
-
-        feedbackPopup.addStyleName(getStylePrimaryName() + "-feedback");
-        base.setClassName(getStylePrimaryName() + "-base");
-        handle.setClassName(getStylePrimaryName() + "-handle");
-        smaller.setClassName(getStylePrimaryName() + "-smaller");
-        bigger.setClassName(getStylePrimaryName() + "-bigger");
-
-        if (isVertical()) {
-            addStyleName(getStylePrimaryName() + "-vertical");
-        }
-    }
-
-    void setFeedbackValue(double value) {
-        String currentValue = "" + value;
-        if (resolution == 0) {
-            currentValue = "" + new Double(value).intValue();
-        }
-        feedback.setText(currentValue);
-    }
-
-    private void updateFeedbackPosition() {
-        if (isVertical()) {
-            feedbackPopup.setPopupPosition(
-                    handle.getAbsoluteLeft() + handle.getOffsetWidth(),
-                    handle.getAbsoluteTop() + handle.getOffsetHeight() / 2
-                            - feedbackPopup.getOffsetHeight() / 2);
-        } else {
-            feedbackPopup.setPopupPosition(
-                    handle.getAbsoluteLeft() + handle.getOffsetWidth() / 2
-                            - feedbackPopup.getOffsetWidth() / 2,
-                    handle.getAbsoluteTop() - feedbackPopup.getOffsetHeight());
-        }
-    }
-
-    void buildBase() {
-        final String styleAttribute = isVertical() ? "height" : "width";
-        final String oppositeStyleAttribute = isVertical() ? "width" : "height";
-        final String domProperty = isVertical() ? "offsetHeight"
-                : "offsetWidth";
-
-        // clear unnecessary opposite style attribute
-        base.getStyle().clearProperty(oppositeStyleAttribute);
-
-        final Element p = getElement().getParentElement().cast();
-        if (p.getPropertyInt(domProperty) > 50) {
-            if (isVertical()) {
-                setHeight();
-            } else {
-                base.getStyle().clearProperty(styleAttribute);
-            }
-        } else {
-            // Set minimum size and adjust after all components have
-            // (supposedly) been drawn completely.
-            base.getStyle().setPropertyPx(styleAttribute, MIN_SIZE);
-            Scheduler.get().scheduleDeferred(new Command() {
-
-                @Override
-                public void execute() {
-                    final Element p = getElement().getParentElement().cast();
-                    if (p.getPropertyInt(domProperty) > (MIN_SIZE + 5)) {
-                        if (isVertical()) {
-                            setHeight();
-                        } else {
-                            base.getStyle().clearProperty(styleAttribute);
-                        }
-                        // Ensure correct position
-                        setValue(value, false);
-                    }
-                }
-            });
-        }
-
-        if (!isVertical()) {
-            // Draw handle with a delay to allow base to gain maximum width
-            Scheduler.get().scheduleDeferred(new Command() {
-                @Override
-                public void execute() {
-                    buildHandle();
-                    setValue(value, false);
-                }
-            });
-        } else {
-            buildHandle();
-            setValue(value, false);
-        }
-
-        // TODO attach listeners for focusing and arrow keys
-    }
-
-    void buildHandle() {
-        final String handleAttribute = isVertical() ? "marginTop"
-                : "marginLeft";
-        final String oppositeHandleAttribute = isVertical() ? "marginLeft"
-                : "marginTop";
-
-        handle.getStyle().setProperty(handleAttribute, "0");
-
-        // clear unnecessary opposite handle attribute
-        handle.getStyle().clearProperty(oppositeHandleAttribute);
-
-        // Restore visibility
-        handle.getStyle().setVisibility(Visibility.VISIBLE);
-    }
-
-    @Override
-    public void onBrowserEvent(Event event) {
-        if (disabled || readonly) {
-            return;
-        }
-        final Element targ = DOM.eventGetTarget(event);
-
-        if (DOM.eventGetType(event) == Event.ONMOUSEWHEEL) {
-            processMouseWheelEvent(event);
-        } else if (dragging || targ == handle) {
-            processHandleEvent(event);
-        } else if (targ == smaller) {
-            decreaseValue(true);
-        } else if (targ == bigger) {
-            increaseValue(true);
-        } else if (DOM.eventGetType(event) == Event.MOUSEEVENTS) {
-            processBaseEvent(event);
-        } else if ((BrowserInfo.get().isGecko() && DOM.eventGetType(event) == Event.ONKEYPRESS)
-                || (!BrowserInfo.get().isGecko() && DOM.eventGetType(event) == Event.ONKEYDOWN)) {
-
-            if (handleNavigation(event.getKeyCode(), event.getCtrlKey(),
-                    event.getShiftKey())) {
-
-                feedbackPopup.show();
-
-                delayedValueUpdater.trigger();
-
-                DOM.eventPreventDefault(event);
-                DOM.eventCancelBubble(event, true);
-            }
-        } else if (targ.equals(getElement())
-                && DOM.eventGetType(event) == Event.ONFOCUS) {
-            feedbackPopup.show();
-        } else if (targ.equals(getElement())
-                && DOM.eventGetType(event) == Event.ONBLUR) {
-            feedbackPopup.hide();
-        } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) {
-            feedbackPopup.show();
-        }
-        if (Util.isTouchEvent(event)) {
-            event.preventDefault(); // avoid simulated events
-            event.stopPropagation();
-        }
-    }
-
-    private void processMouseWheelEvent(final Event event) {
-        final int dir = DOM.eventGetMouseWheelVelocityY(event);
-
-        if (dir < 0) {
-            increaseValue(false);
-        } else {
-            decreaseValue(false);
-        }
-
-        delayedValueUpdater.trigger();
-
-        DOM.eventPreventDefault(event);
-        DOM.eventCancelBubble(event, true);
-    }
-
-    private void processHandleEvent(Event event) {
-        switch (DOM.eventGetType(event)) {
-        case Event.ONMOUSEDOWN:
-        case Event.ONTOUCHSTART:
-            if (!disabled && !readonly) {
-                focus();
-                feedbackPopup.show();
-                dragging = true;
-                handle.setClassName(getStylePrimaryName() + "-handle");
-                handle.addClassName(getStylePrimaryName() + "-handle-active");
-
-                DOM.setCapture(getElement());
-                DOM.eventPreventDefault(event); // prevent selecting text
-                DOM.eventCancelBubble(event, true);
-                event.stopPropagation();
-                VConsole.log("Slider move start");
-            }
-            break;
-        case Event.ONMOUSEMOVE:
-        case Event.ONTOUCHMOVE:
-            if (dragging) {
-                VConsole.log("Slider move");
-                setValueByEvent(event, false);
-                updateFeedbackPosition();
-                event.stopPropagation();
-            }
-            break;
-        case Event.ONTOUCHEND:
-            feedbackPopup.hide();
-        case Event.ONMOUSEUP:
-            // feedbackPopup.hide();
-            VConsole.log("Slider move end");
-            dragging = false;
-            handle.setClassName(getStylePrimaryName() + "-handle");
-            DOM.releaseCapture(getElement());
-            setValueByEvent(event, true);
-            event.stopPropagation();
-            break;
-        default:
-            break;
-        }
-    }
-
-    private void processBaseEvent(Event event) {
-        if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) {
-            if (!disabled && !readonly && !dragging) {
-                setValueByEvent(event, true);
-                DOM.eventCancelBubble(event, true);
-            }
-        }
-    }
-
-    private void decreaseValue(boolean updateToServer) {
-        setValue(new Double(value.doubleValue() - Math.pow(10, -resolution)),
-                updateToServer);
-    }
-
-    private void increaseValue(boolean updateToServer) {
-        setValue(new Double(value.doubleValue() + Math.pow(10, -resolution)),
-                updateToServer);
-    }
-
-    private void setValueByEvent(Event event, boolean updateToServer) {
-        double v = min; // Fallback to min
-
-        final int coord = getEventPosition(event);
-
-        final int handleSize, baseSize, baseOffset;
-        if (isVertical()) {
-            handleSize = handle.getOffsetHeight();
-            baseSize = base.getOffsetHeight();
-            baseOffset = base.getAbsoluteTop() - Window.getScrollTop()
-                    - handleSize / 2;
-        } else {
-            handleSize = handle.getOffsetWidth();
-            baseSize = base.getOffsetWidth();
-            baseOffset = base.getAbsoluteLeft() - Window.getScrollLeft()
-                    + handleSize / 2;
-        }
-
-        if (isVertical()) {
-            v = ((baseSize - (coord - baseOffset)) / (double) (baseSize - handleSize))
-                    * (max - min) + min;
-        } else {
-            v = ((coord - baseOffset) / (double) (baseSize - handleSize))
-                    * (max - min) + min;
-        }
-
-        if (v < min) {
-            v = min;
-        } else if (v > max) {
-            v = max;
-        }
-
-        setValue(v, updateToServer);
-    }
-
-    /**
-     * TODO consider extracting touches support to an impl class specific for
-     * webkit (only browser that really supports touches).
-     * 
-     * @param event
-     * @return
-     */
-    protected int getEventPosition(Event event) {
-        if (isVertical()) {
-            return Util.getTouchOrMouseClientY(event);
-        } else {
-            return Util.getTouchOrMouseClientX(event);
-        }
-    }
-
-    @Override
-    public void iLayout() {
-        if (isVertical()) {
-            setHeight();
-        }
-        // Update handle position
-        setValue(value, false);
-    }
-
-    private void setHeight() {
-        // Calculate decoration size
-        base.getStyle().setHeight(0, Unit.PX);
-        base.getStyle().setOverflow(Overflow.HIDDEN);
-        int h = getElement().getOffsetHeight();
-        if (h < MIN_SIZE) {
-            h = MIN_SIZE;
-        }
-        base.getStyle().setHeight(h, Unit.PX);
-        base.getStyle().clearOverflow();
-    }
-
-    private void fireValueChanged() {
-        ValueChangeEvent.fire(VSlider.this, value);
-    }
-
-    /**
-     * Handles the keyboard events handled by the Slider
-     * 
-     * @param event
-     *            The keyboard event received
-     * @return true iff the navigation event was handled
-     */
-    public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
-
-        // No support for ctrl moving
-        if (ctrl) {
-            return false;
-        }
-
-        if ((keycode == getNavigationUpKey() && isVertical())
-                || (keycode == getNavigationRightKey() && !isVertical())) {
-            if (shift) {
-                for (int a = 0; a < acceleration; a++) {
-                    increaseValue(false);
-                }
-                acceleration++;
-            } else {
-                increaseValue(false);
-            }
-            return true;
-        } else if (keycode == getNavigationDownKey() && isVertical()
-                || (keycode == getNavigationLeftKey() && !isVertical())) {
-            if (shift) {
-                for (int a = 0; a < acceleration; a++) {
-                    decreaseValue(false);
-                }
-                acceleration++;
-            } else {
-                decreaseValue(false);
-            }
-            return true;
-        }
-
-        return false;
-    }
-
-    /**
-     * Get the key that increases the vertical slider. By default it is the up
-     * arrow key but by overriding this you can change the key to whatever you
-     * want.
-     * 
-     * @return The keycode of the key
-     */
-    protected int getNavigationUpKey() {
-        return KeyCodes.KEY_UP;
-    }
-
-    /**
-     * Get the key that decreases the vertical slider. By default it is the down
-     * arrow key but by overriding this you can change the key to whatever you
-     * want.
-     * 
-     * @return The keycode of the key
-     */
-    protected int getNavigationDownKey() {
-        return KeyCodes.KEY_DOWN;
-    }
-
-    /**
-     * Get the key that decreases the horizontal slider. By default it is the
-     * left arrow key but by overriding this you can change the key to whatever
-     * you want.
-     * 
-     * @return The keycode of the key
-     */
-    protected int getNavigationLeftKey() {
-        return KeyCodes.KEY_LEFT;
-    }
-
-    /**
-     * Get the key that increases the horizontal slider. By default it is the
-     * right arrow key but by overriding this you can change the key to whatever
-     * you want.
-     * 
-     * @return The keycode of the key
-     */
-    protected int getNavigationRightKey() {
-        return KeyCodes.KEY_RIGHT;
-    }
-
-    public void setConnection(ApplicationConnection client) {
-        this.client = client;
-    }
-
-    public void setId(String id) {
-        this.id = id;
-    }
-
-    public void setImmediate(boolean immediate) {
-        this.immediate = immediate;
-    }
-
-    public void setDisabled(boolean disabled) {
-        this.disabled = disabled;
-    }
-
-    public void setReadOnly(boolean readonly) {
-        this.readonly = readonly;
-    }
-
-    private boolean isVertical() {
-        return orientation == SliderOrientation.VERTICAL;
-    }
-
-    public void setOrientation(SliderOrientation orientation) {
-        if (this.orientation != orientation) {
-            this.orientation = orientation;
-            updateStyleNames(getStylePrimaryName(), true);
-        }
-    }
-
-    public void setMinValue(double value) {
-        min = value;
-    }
-
-    public void setMaxValue(double value) {
-        max = value;
-    }
-
-    public void setResolution(int resolution) {
-        this.resolution = resolution;
-    }
-
-    @Override
-    public HandlerRegistration addValueChangeHandler(
-            ValueChangeHandler<Double> handler) {
-        return addHandler(handler, ValueChangeEvent.getType());
-    }
-
-    @Override
-    public Double getValue() {
-        return value;
-    }
-
-    @Override
-    public void setValue(Double value) {
-        if (value < min) {
-            value = min;
-        } else if (value > max) {
-            value = max;
-        }
-
-        // Update handle position
-        final String styleAttribute = isVertical() ? "marginTop" : "marginLeft";
-        final String domProperty = isVertical() ? "offsetHeight"
-                : "offsetWidth";
-        final int handleSize = handle.getPropertyInt(domProperty);
-        final int baseSize = base.getPropertyInt(domProperty)
-                - (2 * BASE_BORDER_WIDTH);
-
-        final int range = baseSize - handleSize;
-        double v = value.doubleValue();
-
-        // Round value to resolution
-        if (resolution > 0) {
-            v = Math.round(v * Math.pow(10, resolution));
-            v = v / Math.pow(10, resolution);
-        } else {
-            v = Math.round(v);
-        }
-        final double valueRange = max - min;
-        double p = 0;
-        if (valueRange > 0) {
-            p = range * ((v - min) / valueRange);
-        }
-        if (p < 0) {
-            p = 0;
-        }
-        if (isVertical()) {
-            p = range - p;
-        }
-        final double pos = p;
-
-        handle.getStyle().setPropertyPx(styleAttribute, (int) Math.round(pos));
-
-        // Update value
-        this.value = new Double(v);
-        setFeedbackValue(v);
-    }
-
-    @Override
-    public void setValue(Double value, boolean fireEvents) {
-        if (value == null) {
-            return;
-        }
-
-        setValue(value);
-
-        if (fireEvents) {
-            fireValueChanged();
-        }
-    }
-}
index aa144928490c31da5d209807ae38458b4abd6f37..35485340386ba5e0586e6b1fa034be51823e887b 100644 (file)
@@ -33,8 +33,9 @@ import com.vaadin.client.communication.StateChangeEvent;
 import com.vaadin.client.ui.AbstractComponentContainerConnector;
 import com.vaadin.client.ui.ClickEventHandler;
 import com.vaadin.client.ui.SimpleManagedLayout;
-import com.vaadin.client.ui.splitpanel.VAbstractSplitPanel.SplitterMoveHandler;
-import com.vaadin.client.ui.splitpanel.VAbstractSplitPanel.SplitterMoveHandler.SplitterMoveEvent;
+import com.vaadin.client.ui.VAbstractSplitPanel;
+import com.vaadin.client.ui.VAbstractSplitPanel.SplitterMoveHandler;
+import com.vaadin.client.ui.VAbstractSplitPanel.SplitterMoveHandler.SplitterMoveEvent;
 import com.vaadin.shared.MouseEventDetails;
 import com.vaadin.shared.ui.ComponentStateUtil;
 import com.vaadin.shared.ui.splitpanel.AbstractSplitPanelRpc;
index 848e9068fe2e6f1cddfd34c644c290058ef58a61..4f6f7efa04ec097a356c60b9fe8b03b23e0a0477 100644 (file)
@@ -15,6 +15,7 @@
  */
 package com.vaadin.client.ui.splitpanel;
 
+import com.vaadin.client.ui.VSplitPanelHorizontal;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.Connect.LoadStyle;
 import com.vaadin.shared.ui.splitpanel.HorizontalSplitPanelState;
diff --git a/client/src/com/vaadin/client/ui/splitpanel/VAbstractSplitPanel.java b/client/src/com/vaadin/client/ui/splitpanel/VAbstractSplitPanel.java
deleted file mode 100644 (file)
index bcb8809..0000000
+++ /dev/null
@@ -1,783 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.splitpanel;
-
-import java.util.Collections;
-import java.util.List;
-
-import com.google.gwt.dom.client.Node;
-import com.google.gwt.dom.client.Style;
-import com.google.gwt.event.dom.client.TouchCancelEvent;
-import com.google.gwt.event.dom.client.TouchCancelHandler;
-import com.google.gwt.event.dom.client.TouchEndEvent;
-import com.google.gwt.event.dom.client.TouchEndHandler;
-import com.google.gwt.event.dom.client.TouchMoveEvent;
-import com.google.gwt.event.dom.client.TouchMoveHandler;
-import com.google.gwt.event.dom.client.TouchStartEvent;
-import com.google.gwt.event.dom.client.TouchStartHandler;
-import com.google.gwt.event.shared.EventHandler;
-import com.google.gwt.event.shared.GwtEvent;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.ComplexPanel;
-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.ConnectorMap;
-import com.vaadin.client.LayoutManager;
-import com.vaadin.client.StyleConstants;
-import com.vaadin.client.Util;
-import com.vaadin.client.VConsole;
-import com.vaadin.client.ui.TouchScrollDelegate;
-import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
-import com.vaadin.client.ui.VOverlay;
-import com.vaadin.client.ui.splitpanel.VAbstractSplitPanel.SplitterMoveHandler.SplitterMoveEvent;
-import com.vaadin.shared.ui.Orientation;
-
-public class VAbstractSplitPanel extends ComplexPanel {
-
-    private boolean enabled = false;
-
-    public static final String CLASSNAME = "v-splitpanel";
-
-    private static final int MIN_SIZE = 30;
-
-    private Orientation orientation = Orientation.HORIZONTAL;
-
-    Widget firstChild;
-
-    Widget secondChild;
-
-    private final Element wrapper = DOM.createDiv();
-
-    private final Element firstContainer = DOM.createDiv();
-
-    private final Element secondContainer = DOM.createDiv();
-
-    final Element splitter = DOM.createDiv();
-
-    private boolean resizing;
-
-    private boolean resized = false;
-
-    private int origX;
-
-    private int origY;
-
-    private int origMouseX;
-
-    private int origMouseY;
-
-    private boolean locked = false;
-
-    private boolean positionReversed = false;
-
-    List<String> componentStyleNames = Collections.emptyList();
-
-    private Element draggingCurtain;
-
-    ApplicationConnection client;
-
-    boolean immediate;
-
-    /* The current position of the split handle in either percentages or pixels */
-    String position;
-
-    String maximumPosition;
-
-    String minimumPosition;
-
-    private TouchScrollHandler touchScrollHandler;
-
-    protected Element scrolledContainer;
-
-    protected int origScrollTop;
-
-    public VAbstractSplitPanel() {
-        this(Orientation.HORIZONTAL);
-    }
-
-    public VAbstractSplitPanel(Orientation orientation) {
-        setElement(DOM.createDiv());
-        setStyleName(StyleConstants.UI_LAYOUT);
-        switch (orientation) {
-        case HORIZONTAL:
-            addStyleName(CLASSNAME + "-horizontal");
-            break;
-        case VERTICAL:
-        default:
-            addStyleName(CLASSNAME + "-vertical");
-            break;
-        }
-        // size below will be overridden in update from uidl, initial size
-        // needed to keep IE alive
-        setWidth(MIN_SIZE + "px");
-        setHeight(MIN_SIZE + "px");
-        constructDom();
-        setOrientation(orientation);
-        sinkEvents(Event.MOUSEEVENTS);
-
-        makeScrollable();
-
-        addDomHandler(new TouchCancelHandler() {
-            @Override
-            public void onTouchCancel(TouchCancelEvent event) {
-                // TODO When does this actually happen??
-                VConsole.log("TOUCH CANCEL");
-            }
-        }, TouchCancelEvent.getType());
-        addDomHandler(new TouchStartHandler() {
-            @Override
-            public void onTouchStart(TouchStartEvent event) {
-                Node target = event.getTouches().get(0).getTarget().cast();
-                if (splitter.isOrHasChild(target)) {
-                    onMouseDown(Event.as(event.getNativeEvent()));
-                }
-            }
-        }, TouchStartEvent.getType());
-        addDomHandler(new TouchMoveHandler() {
-            @Override
-            public void onTouchMove(TouchMoveEvent event) {
-                if (resizing) {
-                    onMouseMove(Event.as(event.getNativeEvent()));
-                }
-            }
-        }, TouchMoveEvent.getType());
-        addDomHandler(new TouchEndHandler() {
-            @Override
-            public void onTouchEnd(TouchEndEvent event) {
-                if (resizing) {
-                    onMouseUp(Event.as(event.getNativeEvent()));
-                }
-            }
-        }, TouchEndEvent.getType());
-
-    }
-
-    protected void constructDom() {
-        DOM.appendChild(splitter, DOM.createDiv()); // for styling
-        DOM.appendChild(getElement(), wrapper);
-        DOM.setStyleAttribute(wrapper, "position", "relative");
-        DOM.setStyleAttribute(wrapper, "width", "100%");
-        DOM.setStyleAttribute(wrapper, "height", "100%");
-
-        DOM.appendChild(wrapper, secondContainer);
-        DOM.appendChild(wrapper, firstContainer);
-        DOM.appendChild(wrapper, splitter);
-
-        DOM.setStyleAttribute(splitter, "position", "absolute");
-        DOM.setStyleAttribute(secondContainer, "position", "absolute");
-
-        setStylenames();
-    }
-
-    private void setOrientation(Orientation orientation) {
-        this.orientation = orientation;
-        if (orientation == Orientation.HORIZONTAL) {
-            DOM.setStyleAttribute(splitter, "height", "100%");
-            DOM.setStyleAttribute(splitter, "top", "0");
-            DOM.setStyleAttribute(firstContainer, "height", "100%");
-            DOM.setStyleAttribute(secondContainer, "height", "100%");
-        } else {
-            DOM.setStyleAttribute(splitter, "width", "100%");
-            DOM.setStyleAttribute(splitter, "left", "0");
-            DOM.setStyleAttribute(firstContainer, "width", "100%");
-            DOM.setStyleAttribute(secondContainer, "width", "100%");
-        }
-    }
-
-    @Override
-    public boolean remove(Widget w) {
-        boolean removed = super.remove(w);
-        if (removed) {
-            if (firstChild == w) {
-                firstChild = null;
-            } else {
-                secondChild = null;
-            }
-        }
-        return removed;
-    }
-
-    void setLocked(boolean newValue) {
-        if (locked != newValue) {
-            locked = newValue;
-            splitterSize = -1;
-            setStylenames();
-        }
-    }
-
-    void setPositionReversed(boolean reversed) {
-        if (positionReversed != reversed) {
-            if (orientation == Orientation.HORIZONTAL) {
-                DOM.setStyleAttribute(splitter, "right", "");
-                DOM.setStyleAttribute(splitter, "left", "");
-            } else if (orientation == Orientation.VERTICAL) {
-                DOM.setStyleAttribute(splitter, "top", "");
-                DOM.setStyleAttribute(splitter, "bottom", "");
-            }
-
-            positionReversed = reversed;
-        }
-    }
-
-    /**
-     * Converts given split position string (in pixels or percentage) to a
-     * floating point pixel value.
-     * 
-     * @param pos
-     * @return
-     */
-    private float convertToPixels(String pos) {
-        float posAsFloat;
-        if (pos.indexOf("%") > 0) {
-            posAsFloat = Math.round(Float.parseFloat(pos.substring(0,
-                    pos.length() - 1))
-                    / 100
-                    * (orientation == Orientation.HORIZONTAL ? getOffsetWidth()
-                            : getOffsetHeight()));
-        } else {
-            posAsFloat = Float.parseFloat(pos.substring(0, pos.length() - 2));
-        }
-        return posAsFloat;
-    }
-
-    /**
-     * Converts given split position string (in pixels or percentage) to a float
-     * percentage value.
-     * 
-     * @param pos
-     * @return
-     */
-    private float convertToPercentage(String pos) {
-        if (pos.endsWith("px")) {
-            float pixelPosition = Float.parseFloat(pos.substring(0,
-                    pos.length() - 2));
-            int offsetLength = orientation == Orientation.HORIZONTAL ? getOffsetWidth()
-                    : getOffsetHeight();
-
-            // Take splitter size into account at the edge
-            if (pixelPosition + getSplitterSize() >= offsetLength) {
-                return 100;
-            }
-
-            return pixelPosition / offsetLength * 100;
-        } else {
-            assert pos.endsWith("%");
-            return Float.parseFloat(pos.substring(0, pos.length() - 1));
-        }
-    }
-
-    /**
-     * Returns the given position clamped to the range between current minimum
-     * and maximum positions.
-     * 
-     * TODO Should this be in the connector?
-     * 
-     * @param pos
-     *            Position of the splitter as a CSS string, either pixels or a
-     *            percentage.
-     * @return minimumPosition if pos is less than minimumPosition;
-     *         maximumPosition if pos is greater than maximumPosition; pos
-     *         otherwise.
-     */
-    private String checkSplitPositionLimits(String pos) {
-        float positionAsFloat = convertToPixels(pos);
-
-        if (maximumPosition != null
-                && convertToPixels(maximumPosition) < positionAsFloat) {
-            pos = maximumPosition;
-        } else if (minimumPosition != null
-                && convertToPixels(minimumPosition) > positionAsFloat) {
-            pos = minimumPosition;
-        }
-        return pos;
-    }
-
-    /**
-     * Converts given string to the same units as the split position is.
-     * 
-     * @param pos
-     *            position to be converted
-     * @return converted position string
-     */
-    private String convertToPositionUnits(String pos) {
-        if (position.indexOf("%") != -1 && pos.indexOf("%") == -1) {
-            // position is in percentage, pos in pixels
-            pos = convertToPercentage(pos) + "%";
-        } else if (position.indexOf("px") > 0 && pos.indexOf("px") == -1) {
-            // position is in pixels and pos in percentage
-            pos = convertToPixels(pos) + "px";
-        }
-
-        return pos;
-    }
-
-    void setSplitPosition(String pos) {
-        if (pos == null) {
-            return;
-        }
-
-        pos = checkSplitPositionLimits(pos);
-        if (!pos.equals(position)) {
-            position = convertToPositionUnits(pos);
-        }
-
-        // Convert percentage values to pixels
-        if (pos.indexOf("%") > 0) {
-            int size = orientation == Orientation.HORIZONTAL ? getOffsetWidth()
-                    : getOffsetHeight();
-            float percentage = Float.parseFloat(pos.substring(0,
-                    pos.length() - 1));
-            pos = percentage / 100 * size + "px";
-        }
-
-        String attributeName;
-        if (orientation == Orientation.HORIZONTAL) {
-            if (positionReversed) {
-                attributeName = "right";
-            } else {
-                attributeName = "left";
-            }
-        } else {
-            if (positionReversed) {
-                attributeName = "bottom";
-            } else {
-                attributeName = "top";
-            }
-        }
-
-        Style style = splitter.getStyle();
-        if (!pos.equals(style.getProperty(attributeName))) {
-            style.setProperty(attributeName, pos);
-            updateSizes();
-        }
-    }
-
-    void updateSizes() {
-        if (!isAttached()) {
-            return;
-        }
-
-        int wholeSize;
-        int pixelPosition;
-
-        switch (orientation) {
-        case HORIZONTAL:
-            wholeSize = DOM.getElementPropertyInt(wrapper, "clientWidth");
-            pixelPosition = DOM.getElementPropertyInt(splitter, "offsetLeft");
-
-            // reposition splitter in case it is out of box
-            if ((pixelPosition > 0 && pixelPosition + getSplitterSize() > wholeSize)
-                    || (positionReversed && pixelPosition < 0)) {
-                pixelPosition = wholeSize - getSplitterSize();
-                if (pixelPosition < 0) {
-                    pixelPosition = 0;
-                }
-                setSplitPosition(pixelPosition + "px");
-                return;
-            }
-
-            DOM.setStyleAttribute(firstContainer, "width", pixelPosition + "px");
-            int secondContainerWidth = (wholeSize - pixelPosition - getSplitterSize());
-            if (secondContainerWidth < 0) {
-                secondContainerWidth = 0;
-            }
-            DOM.setStyleAttribute(secondContainer, "width",
-                    secondContainerWidth + "px");
-            DOM.setStyleAttribute(secondContainer, "left",
-                    (pixelPosition + getSplitterSize()) + "px");
-
-            LayoutManager layoutManager = LayoutManager.get(client);
-            ConnectorMap connectorMap = ConnectorMap.get(client);
-            if (firstChild != null) {
-                ComponentConnector connector = connectorMap
-                        .getConnector(firstChild);
-                if (connector.isRelativeWidth()) {
-                    layoutManager.reportWidthAssignedToRelative(connector,
-                            pixelPosition);
-                } else {
-                    layoutManager.setNeedsMeasure(connector);
-                }
-            }
-            if (secondChild != null) {
-                ComponentConnector connector = connectorMap
-                        .getConnector(secondChild);
-                if (connector.isRelativeWidth()) {
-                    layoutManager.reportWidthAssignedToRelative(connector,
-                            secondContainerWidth);
-                } else {
-                    layoutManager.setNeedsMeasure(connector);
-                }
-            }
-            break;
-        case VERTICAL:
-            wholeSize = DOM.getElementPropertyInt(wrapper, "clientHeight");
-            pixelPosition = DOM.getElementPropertyInt(splitter, "offsetTop");
-
-            // reposition splitter in case it is out of box
-            if ((pixelPosition > 0 && pixelPosition + getSplitterSize() > wholeSize)
-                    || (positionReversed && pixelPosition < 0)) {
-                pixelPosition = wholeSize - getSplitterSize();
-                if (pixelPosition < 0) {
-                    pixelPosition = 0;
-                }
-                setSplitPosition(pixelPosition + "px");
-                return;
-            }
-
-            DOM.setStyleAttribute(firstContainer, "height", pixelPosition
-                    + "px");
-            int secondContainerHeight = (wholeSize - pixelPosition - getSplitterSize());
-            if (secondContainerHeight < 0) {
-                secondContainerHeight = 0;
-            }
-            DOM.setStyleAttribute(secondContainer, "height",
-                    secondContainerHeight + "px");
-            DOM.setStyleAttribute(secondContainer, "top",
-                    (pixelPosition + getSplitterSize()) + "px");
-
-            layoutManager = LayoutManager.get(client);
-            connectorMap = ConnectorMap.get(client);
-            if (firstChild != null) {
-                ComponentConnector connector = connectorMap
-                        .getConnector(firstChild);
-                if (connector.isRelativeHeight()) {
-                    layoutManager.reportHeightAssignedToRelative(connector,
-                            pixelPosition);
-                } else {
-                    layoutManager.setNeedsMeasure(connector);
-                }
-            }
-            if (secondChild != null) {
-                ComponentConnector connector = connectorMap
-                        .getConnector(secondChild);
-                if (connector.isRelativeHeight()) {
-                    layoutManager.reportHeightAssignedToRelative(connector,
-                            secondContainerHeight);
-                } else {
-                    layoutManager.setNeedsMeasure(connector);
-                }
-            }
-            break;
-        }
-    }
-
-    void setFirstWidget(Widget w) {
-        if (firstChild != null) {
-            firstChild.removeFromParent();
-        }
-        if (w != null) {
-            super.add(w, firstContainer);
-        }
-        firstChild = w;
-    }
-
-    void setSecondWidget(Widget w) {
-        if (secondChild != null) {
-            secondChild.removeFromParent();
-        }
-        if (w != null) {
-            super.add(w, secondContainer);
-        }
-        secondChild = w;
-    }
-
-    @Override
-    public void onBrowserEvent(Event event) {
-        switch (DOM.eventGetType(event)) {
-        case Event.ONMOUSEMOVE:
-            // case Event.ONTOUCHMOVE:
-            if (resizing) {
-                onMouseMove(event);
-            }
-            break;
-        case Event.ONMOUSEDOWN:
-            // case Event.ONTOUCHSTART:
-            onMouseDown(event);
-            break;
-        case Event.ONMOUSEOUT:
-            // Dragging curtain interferes with click events if added in
-            // mousedown so we add it only when needed i.e., if the mouse moves
-            // outside the splitter.
-            if (resizing) {
-                showDraggingCurtain();
-            }
-            break;
-        case Event.ONMOUSEUP:
-            // case Event.ONTOUCHEND:
-            if (resizing) {
-                onMouseUp(event);
-            }
-            break;
-        case Event.ONCLICK:
-            resizing = false;
-            break;
-        }
-        // Only fire click event listeners if the splitter isn't moved
-        if (Util.isTouchEvent(event) || !resized) {
-            super.onBrowserEvent(event);
-        } else if (DOM.eventGetType(event) == Event.ONMOUSEUP) {
-            // Reset the resized flag after a mouseup has occured so the next
-            // mousedown/mouseup can be interpreted as a click.
-            resized = false;
-        }
-    }
-
-    public void onMouseDown(Event event) {
-        if (locked || !isEnabled()) {
-            return;
-        }
-        final Element trg = event.getEventTarget().cast();
-        if (trg == splitter || trg == DOM.getChild(splitter, 0)) {
-            resizing = true;
-            DOM.setCapture(getElement());
-            origX = DOM.getElementPropertyInt(splitter, "offsetLeft");
-            origY = DOM.getElementPropertyInt(splitter, "offsetTop");
-            origMouseX = Util.getTouchOrMouseClientX(event);
-            origMouseY = Util.getTouchOrMouseClientY(event);
-            event.stopPropagation();
-            event.preventDefault();
-        }
-    }
-
-    public void onMouseMove(Event event) {
-        switch (orientation) {
-        case HORIZONTAL:
-            final int x = Util.getTouchOrMouseClientX(event);
-            onHorizontalMouseMove(x);
-            break;
-        case VERTICAL:
-        default:
-            final int y = Util.getTouchOrMouseClientY(event);
-            onVerticalMouseMove(y);
-            break;
-        }
-
-    }
-
-    private void onHorizontalMouseMove(int x) {
-        int newX = origX + x - origMouseX;
-        if (newX < 0) {
-            newX = 0;
-        }
-        if (newX + getSplitterSize() > getOffsetWidth()) {
-            newX = getOffsetWidth() - getSplitterSize();
-        }
-
-        if (position.indexOf("%") > 0) {
-            position = convertToPositionUnits(newX + "px");
-        } else {
-            // Reversed position
-            if (positionReversed) {
-                position = (getOffsetWidth() - newX - getSplitterSize()) + "px";
-            } else {
-                position = newX + "px";
-            }
-        }
-
-        if (origX != newX) {
-            resized = true;
-        }
-
-        // Reversed position
-        if (positionReversed) {
-            newX = getOffsetWidth() - newX - getSplitterSize();
-        }
-
-        setSplitPosition(newX + "px");
-    }
-
-    private void onVerticalMouseMove(int y) {
-        int newY = origY + y - origMouseY;
-        if (newY < 0) {
-            newY = 0;
-        }
-
-        if (newY + getSplitterSize() > getOffsetHeight()) {
-            newY = getOffsetHeight() - getSplitterSize();
-        }
-
-        if (position.indexOf("%") > 0) {
-            position = convertToPositionUnits(newY + "px");
-        } else {
-            // Reversed position
-            if (positionReversed) {
-                position = (getOffsetHeight() - newY - getSplitterSize())
-                        + "px";
-            } else {
-                position = newY + "px";
-            }
-        }
-
-        if (origY != newY) {
-            resized = true;
-        }
-
-        // Reversed position
-        if (positionReversed) {
-            newY = getOffsetHeight() - newY - getSplitterSize();
-        }
-
-        setSplitPosition(newY + "px");
-    }
-
-    public void onMouseUp(Event event) {
-        DOM.releaseCapture(getElement());
-        hideDraggingCurtain();
-        resizing = false;
-        if (!Util.isTouchEvent(event)) {
-            onMouseMove(event);
-        }
-        fireEvent(new SplitterMoveEvent(this));
-    }
-
-    public interface SplitterMoveHandler extends EventHandler {
-        public void splitterMoved(SplitterMoveEvent event);
-
-        public static class SplitterMoveEvent extends
-                GwtEvent<SplitterMoveHandler> {
-
-            public static final Type<SplitterMoveHandler> TYPE = new Type<SplitterMoveHandler>();
-
-            private Widget splitPanel;
-
-            public SplitterMoveEvent(Widget splitPanel) {
-                this.splitPanel = splitPanel;
-            }
-
-            @Override
-            public com.google.gwt.event.shared.GwtEvent.Type<SplitterMoveHandler> getAssociatedType() {
-                return TYPE;
-            }
-
-            @Override
-            protected void dispatch(SplitterMoveHandler handler) {
-                handler.splitterMoved(this);
-            }
-
-        }
-    }
-
-    String getSplitterPosition() {
-        return position;
-    }
-
-    /**
-     * Used in FF to avoid losing mouse capture when pointer is moved on an
-     * iframe.
-     */
-    private void showDraggingCurtain() {
-        if (!isDraggingCurtainRequired()) {
-            return;
-        }
-        if (draggingCurtain == null) {
-            draggingCurtain = DOM.createDiv();
-            DOM.setStyleAttribute(draggingCurtain, "position", "absolute");
-            DOM.setStyleAttribute(draggingCurtain, "top", "0px");
-            DOM.setStyleAttribute(draggingCurtain, "left", "0px");
-            DOM.setStyleAttribute(draggingCurtain, "width", "100%");
-            DOM.setStyleAttribute(draggingCurtain, "height", "100%");
-            DOM.setStyleAttribute(draggingCurtain, "zIndex", ""
-                    + VOverlay.Z_INDEX);
-
-            DOM.appendChild(wrapper, draggingCurtain);
-        }
-    }
-
-    /**
-     * A dragging curtain is required in Gecko and Webkit.
-     * 
-     * @return true if the browser requires a dragging curtain
-     */
-    private boolean isDraggingCurtainRequired() {
-        return (BrowserInfo.get().isGecko() || BrowserInfo.get().isWebkit());
-    }
-
-    /**
-     * Hides dragging curtain
-     */
-    private void hideDraggingCurtain() {
-        if (draggingCurtain != null) {
-            DOM.removeChild(wrapper, draggingCurtain);
-            draggingCurtain = null;
-        }
-    }
-
-    private int splitterSize = -1;
-
-    private int getSplitterSize() {
-        if (splitterSize < 0) {
-            if (isAttached()) {
-                switch (orientation) {
-                case HORIZONTAL:
-                    splitterSize = DOM.getElementPropertyInt(splitter,
-                            "offsetWidth");
-                    break;
-
-                default:
-                    splitterSize = DOM.getElementPropertyInt(splitter,
-                            "offsetHeight");
-                    break;
-                }
-            }
-        }
-        return splitterSize;
-    }
-
-    void setStylenames() {
-        final String splitterClass = CLASSNAME
-                + (orientation == Orientation.HORIZONTAL ? "-hsplitter"
-                        : "-vsplitter");
-        final String firstContainerClass = CLASSNAME + "-first-container";
-        final String secondContainerClass = CLASSNAME + "-second-container";
-        final String lockedSuffix = locked ? "-locked" : "";
-
-        splitter.setClassName(splitterClass + lockedSuffix);
-        firstContainer.setClassName(firstContainerClass);
-        secondContainer.setClassName(secondContainerClass);
-
-        for (String styleName : componentStyleNames) {
-            splitter.addClassName(splitterClass + "-" + styleName
-                    + lockedSuffix);
-            firstContainer.addClassName(firstContainerClass + "-" + styleName);
-            secondContainer
-                    .addClassName(secondContainerClass + "-" + styleName);
-        }
-    }
-
-    public void setEnabled(boolean enabled) {
-        this.enabled = enabled;
-    }
-
-    public boolean isEnabled() {
-        return enabled;
-    }
-
-    /**
-     * Ensures the panels are scrollable eg. after style name changes
-     */
-    void makeScrollable() {
-        if (touchScrollHandler == null) {
-            touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this);
-        }
-        touchScrollHandler.addElement(firstContainer);
-        touchScrollHandler.addElement(secondContainer);
-    }
-}
diff --git a/client/src/com/vaadin/client/ui/splitpanel/VSplitPanelHorizontal.java b/client/src/com/vaadin/client/ui/splitpanel/VSplitPanelHorizontal.java
deleted file mode 100644 (file)
index c277d59..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-/* 
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.splitpanel;
-
-import com.vaadin.shared.ui.Orientation;
-
-public class VSplitPanelHorizontal extends VAbstractSplitPanel {
-
-    public VSplitPanelHorizontal() {
-        super(Orientation.HORIZONTAL);
-    }
-}
diff --git a/client/src/com/vaadin/client/ui/splitpanel/VSplitPanelVertical.java b/client/src/com/vaadin/client/ui/splitpanel/VSplitPanelVertical.java
deleted file mode 100644 (file)
index 4f96d7f..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-/* 
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.splitpanel;
-
-import com.vaadin.shared.ui.Orientation;
-
-public class VSplitPanelVertical extends VAbstractSplitPanel {
-
-    public VSplitPanelVertical() {
-        super(Orientation.VERTICAL);
-    }
-}
index 2a57be75230ead80df0032cdbf4aae9296976a61..5232fa539b55455daea2f81089755c330664fcb0 100644 (file)
@@ -15,6 +15,7 @@
  */
 package com.vaadin.client.ui.splitpanel;
 
+import com.vaadin.client.ui.VSplitPanelVertical;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.Connect.LoadStyle;
 import com.vaadin.shared.ui.splitpanel.VerticalSplitPanelState;
index b79cc39297d972632a1f6330a70aafb6dd968fe4..35e7020ce0e0fb1b7b8b040728277218510f96e1 100644 (file)
@@ -35,8 +35,9 @@ import com.vaadin.client.UIDL;
 import com.vaadin.client.Util;
 import com.vaadin.client.ui.AbstractComponentContainerConnector;
 import com.vaadin.client.ui.PostLayoutListener;
-import com.vaadin.client.ui.table.VScrollTable.ContextMenuDetails;
-import com.vaadin.client.ui.table.VScrollTable.VScrollTableBody.VScrollTableRow;
+import com.vaadin.client.ui.VScrollTable;
+import com.vaadin.client.ui.VScrollTable.ContextMenuDetails;
+import com.vaadin.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.table.TableConstants;
 import com.vaadin.shared.ui.table.TableState;
diff --git a/client/src/com/vaadin/client/ui/table/VScrollTable.java b/client/src/com/vaadin/client/ui/table/VScrollTable.java
deleted file mode 100644 (file)
index 066c60a..0000000
+++ /dev/null
@@ -1,7077 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.table;
-
-import java.util.ArrayList;
-import java.util.Collection;
-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.Set;
-
-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.Document;
-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.Overflow;
-import com.google.gwt.dom.client.Style.Position;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.dom.client.Style.Visibility;
-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.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.event.dom.client.ContextMenuEvent;
-import com.google.gwt.event.dom.client.ContextMenuHandler;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.dom.client.KeyDownEvent;
-import com.google.gwt.event.dom.client.KeyDownHandler;
-import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.event.dom.client.KeyPressHandler;
-import com.google.gwt.event.dom.client.KeyUpEvent;
-import com.google.gwt.event.dom.client.KeyUpHandler;
-import com.google.gwt.event.dom.client.ScrollEvent;
-import com.google.gwt.event.dom.client.ScrollHandler;
-import com.google.gwt.event.logical.shared.CloseEvent;
-import com.google.gwt.event.logical.shared.CloseHandler;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-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.FlowPanel;
-import com.google.gwt.user.client.ui.HasWidgets;
-import com.google.gwt.user.client.ui.Panel;
-import com.google.gwt.user.client.ui.PopupPanel;
-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.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.ComponentConnector;
-import com.vaadin.client.ConnectorMap;
-import com.vaadin.client.Focusable;
-import com.vaadin.client.MouseEventDetailsBuilder;
-import com.vaadin.client.TooltipInfo;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.Util;
-import com.vaadin.client.VConsole;
-import com.vaadin.client.VTooltip;
-import com.vaadin.client.ui.Action;
-import com.vaadin.client.ui.ActionOwner;
-import com.vaadin.client.ui.FocusableScrollPanel;
-import com.vaadin.client.ui.TouchScrollDelegate;
-import com.vaadin.client.ui.TreeAction;
-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.VHasDropHandler;
-import com.vaadin.client.ui.dd.VTransferable;
-import com.vaadin.client.ui.embedded.VEmbedded;
-import com.vaadin.client.ui.label.VLabel;
-import com.vaadin.client.ui.table.VScrollTable.VScrollTableBody.VScrollTableRow;
-import com.vaadin.client.ui.textfield.VTextField;
-import com.vaadin.shared.ComponentState;
-import com.vaadin.shared.MouseEventDetails;
-import com.vaadin.shared.ui.dd.VerticalDropLocation;
-import com.vaadin.shared.ui.table.TableConstants;
-
-/**
- * VScrollTable
- * 
- * VScrollTable is a FlowPanel having two widgets in it: * TableHead component *
- * ScrollPanel
- * 
- * TableHead contains table's header and widgets + logic for resizing,
- * reordering and hiding columns.
- * 
- * ScrollPanel contains VScrollTableBody object which handles content. To save
- * some bandwidth and to improve clients responsiveness with loads of data, in
- * VScrollTableBody all rows are not necessary rendered. There are "spacers" in
- * VScrollTableBody to use the exact same space as non-rendered rows would use.
- * This way we can use seamlessly traditional scrollbars and scrolling to fetch
- * more rows instead of "paging".
- * 
- * In VScrollTable we listen to scroll events. On horizontal scrolling we also
- * update TableHeads scroll position which has its scrollbars hidden. On
- * vertical scroll events we will check if we are reaching the end of area where
- * we have rows rendered and
- * 
- * TODO implement unregistering for child components in Cells
- */
-public class VScrollTable extends FlowPanel implements HasWidgets,
-        ScrollHandler, VHasDropHandler, FocusHandler, BlurHandler, Focusable,
-        ActionOwner {
-
-    public static final String STYLENAME = "v-table";
-
-    public enum SelectMode {
-        NONE(0), SINGLE(1), MULTI(2);
-        private int id;
-
-        private SelectMode(int id) {
-            this.id = id;
-        }
-
-        public int getId() {
-            return id;
-        }
-    }
-
-    private static final String ROW_HEADER_COLUMN_KEY = "0";
-
-    private static final double CACHE_RATE_DEFAULT = 2;
-
-    /**
-     * The default multi select mode where simple left clicks only selects one
-     * item, CTRL+left click selects multiple items and SHIFT-left click selects
-     * a range of items.
-     */
-    private static final int MULTISELECT_MODE_DEFAULT = 0;
-
-    /**
-     * The simple multiselect mode is what the table used to have before
-     * ctrl/shift selections were added. That is that when this is set clicking
-     * on an item selects/deselects the item and no ctrl/shift selections are
-     * available.
-     */
-    private static final int MULTISELECT_MODE_SIMPLE = 1;
-
-    /**
-     * multiple of pagelength which component will cache when requesting more
-     * rows
-     */
-    private double cache_rate = CACHE_RATE_DEFAULT;
-    /**
-     * fraction of pageLenght which can be scrolled without making new request
-     */
-    private double cache_react_rate = 0.75 * cache_rate;
-
-    public static final char ALIGN_CENTER = 'c';
-    public static final char ALIGN_LEFT = 'b';
-    public static final char ALIGN_RIGHT = 'e';
-    private static final int CHARCODE_SPACE = 32;
-    private int firstRowInViewPort = 0;
-    private int pageLength = 15;
-    private int lastRequestedFirstvisible = 0; // to detect "serverside scroll"
-
-    protected boolean showRowHeaders = false;
-
-    private String[] columnOrder;
-
-    protected ApplicationConnection client;
-    protected String paintableId;
-
-    boolean immediate;
-    private boolean nullSelectionAllowed = true;
-
-    private SelectMode selectMode = SelectMode.NONE;
-
-    private final HashSet<String> selectedRowKeys = new HashSet<String>();
-
-    /*
-     * When scrolling and selecting at the same time, the selections are not in
-     * sync with the server while retrieving new rows (until key is released).
-     */
-    private HashSet<Object> unSyncedselectionsBeforeRowFetch;
-
-    /*
-     * These are used when jumping between pages when pressing Home and End
-     */
-    boolean selectLastItemInNextRender = false;
-    boolean selectFirstItemInNextRender = false;
-    boolean focusFirstItemInNextRender = false;
-    boolean focusLastItemInNextRender = false;
-
-    /*
-     * The currently focused row
-     */
-    VScrollTableRow focusedRow;
-
-    /*
-     * Helper to store selection range start in when using the keyboard
-     */
-    VScrollTableRow selectionRangeStart;
-
-    /*
-     * Flag for notifying when the selection has changed and should be sent to
-     * the server
-     */
-    boolean selectionChanged = false;
-
-    /*
-     * The speed (in pixels) which the scrolling scrolls vertically/horizontally
-     */
-    private int scrollingVelocity = 10;
-
-    private Timer scrollingVelocityTimer = null;
-
-    String[] bodyActionKeys;
-
-    private boolean enableDebug = false;
-
-    private static final boolean hasNativeTouchScrolling = BrowserInfo.get()
-            .isTouchDevice()
-            && !BrowserInfo.get().requiresTouchScrollDelegate();
-
-    private Set<String> noncollapsibleColumns;
-
-    /**
-     * The last known row height used to preserve the height of a table with
-     * custom row heights and a fixed page length after removing the last row
-     * from the table.
-     * 
-     * A new VScrollTableBody instance is created every time the number of rows
-     * changes causing {@link VScrollTableBody#rowHeight} to be discarded and
-     * the height recalculated by {@link VScrollTableBody#getRowHeight(boolean)}
-     * to avoid some rounding problems, e.g. round(2 * 19.8) / 2 = 20 but
-     * round(3 * 19.8) / 3 = 19.66.
-     */
-    private double lastKnownRowHeight = Double.NaN;
-
-    /**
-     * Represents a select range of rows
-     */
-    private class SelectionRange {
-        private VScrollTableRow startRow;
-        private final int length;
-
-        /**
-         * Constuctor.
-         */
-        public SelectionRange(VScrollTableRow row1, VScrollTableRow row2) {
-            VScrollTableRow endRow;
-            if (row2.isBefore(row1)) {
-                startRow = row2;
-                endRow = row1;
-            } else {
-                startRow = row1;
-                endRow = row2;
-            }
-            length = endRow.getIndex() - startRow.getIndex() + 1;
-        }
-
-        public SelectionRange(VScrollTableRow row, int length) {
-            startRow = row;
-            this.length = length;
-        }
-
-        /*
-         * (non-Javadoc)
-         * 
-         * @see java.lang.Object#toString()
-         */
-
-        @Override
-        public String toString() {
-            return startRow.getKey() + "-" + length;
-        }
-
-        private boolean inRange(VScrollTableRow row) {
-            return row.getIndex() >= startRow.getIndex()
-                    && row.getIndex() < startRow.getIndex() + length;
-        }
-
-        public Collection<SelectionRange> split(VScrollTableRow row) {
-            assert row.isAttached();
-            ArrayList<SelectionRange> ranges = new ArrayList<SelectionRange>(2);
-
-            int endOfFirstRange = row.getIndex() - 1;
-            if (!(endOfFirstRange - startRow.getIndex() < 0)) {
-                // create range of first part unless its length is < 1
-                ranges.add(new SelectionRange(startRow, endOfFirstRange
-                        - startRow.getIndex() + 1));
-            }
-            int startOfSecondRange = row.getIndex() + 1;
-            if (!(getEndIndex() - startOfSecondRange < 0)) {
-                // create range of second part unless its length is < 1
-                VScrollTableRow startOfRange = scrollBody
-                        .getRowByRowIndex(startOfSecondRange);
-                ranges.add(new SelectionRange(startOfRange, getEndIndex()
-                        - startOfSecondRange + 1));
-            }
-            return ranges;
-        }
-
-        private int getEndIndex() {
-            return startRow.getIndex() + length - 1;
-        }
-
-    };
-
-    private final HashSet<SelectionRange> selectedRowRanges = new HashSet<SelectionRange>();
-
-    boolean initializedAndAttached = false;
-
-    /**
-     * Flag to indicate if a column width recalculation is needed due update.
-     */
-    boolean headerChangedDuringUpdate = false;
-
-    protected final TableHead tHead = new TableHead();
-
-    final TableFooter tFoot = new TableFooter();
-
-    final FocusableScrollPanel scrollBodyPanel = new FocusableScrollPanel(true);
-
-    private KeyPressHandler navKeyPressHandler = new KeyPressHandler() {
-
-        @Override
-        public void onKeyPress(KeyPressEvent keyPressEvent) {
-            // This is used for Firefox only, since Firefox auto-repeat
-            // works correctly only if we use a key press handler, other
-            // browsers handle it correctly when using a key down handler
-            if (!BrowserInfo.get().isGecko()) {
-                return;
-            }
-
-            NativeEvent event = keyPressEvent.getNativeEvent();
-            if (!enabled) {
-                // Cancel default keyboard events on a disabled Table
-                // (prevents scrolling)
-                event.preventDefault();
-            } else if (hasFocus) {
-                // Key code in Firefox/onKeyPress is present only for
-                // special keys, otherwise 0 is returned
-                int keyCode = event.getKeyCode();
-                if (keyCode == 0 && event.getCharCode() == ' ') {
-                    // Provide a keyCode for space to be compatible with
-                    // FireFox keypress event
-                    keyCode = CHARCODE_SPACE;
-                }
-
-                if (handleNavigation(keyCode,
-                        event.getCtrlKey() || event.getMetaKey(),
-                        event.getShiftKey())) {
-                    event.preventDefault();
-                }
-
-                startScrollingVelocityTimer();
-            }
-        }
-
-    };
-
-    private KeyUpHandler navKeyUpHandler = new KeyUpHandler() {
-
-        @Override
-        public void onKeyUp(KeyUpEvent keyUpEvent) {
-            NativeEvent event = keyUpEvent.getNativeEvent();
-            int keyCode = event.getKeyCode();
-
-            if (!isFocusable()) {
-                cancelScrollingVelocityTimer();
-            } else if (isNavigationKey(keyCode)) {
-                if (keyCode == getNavigationDownKey()
-                        || keyCode == getNavigationUpKey()) {
-                    /*
-                     * in multiselect mode the server may still have value from
-                     * previous page. Clear it unless doing multiselection or
-                     * just moving focus.
-                     */
-                    if (!event.getShiftKey() && !event.getCtrlKey()) {
-                        instructServerToForgetPreviousSelections();
-                    }
-                    sendSelectedRows();
-                }
-                cancelScrollingVelocityTimer();
-                navKeyDown = false;
-            }
-        }
-    };
-
-    private KeyDownHandler navKeyDownHandler = new KeyDownHandler() {
-
-        @Override
-        public void onKeyDown(KeyDownEvent keyDownEvent) {
-            NativeEvent event = keyDownEvent.getNativeEvent();
-            // This is not used for Firefox
-            if (BrowserInfo.get().isGecko()) {
-                return;
-            }
-
-            if (!enabled) {
-                // Cancel default keyboard events on a disabled Table
-                // (prevents scrolling)
-                event.preventDefault();
-            } else if (hasFocus) {
-                if (handleNavigation(event.getKeyCode(), event.getCtrlKey()
-                        || event.getMetaKey(), event.getShiftKey())) {
-                    navKeyDown = true;
-                    event.preventDefault();
-                }
-
-                startScrollingVelocityTimer();
-            }
-        }
-    };
-    int totalRows;
-
-    private Set<String> collapsedColumns;
-
-    final RowRequestHandler rowRequestHandler;
-    VScrollTableBody scrollBody;
-    private int firstvisible = 0;
-    private boolean sortAscending;
-    private String sortColumn;
-    private String oldSortColumn;
-    private boolean columnReordering;
-
-    /**
-     * This map contains captions and icon urls for actions like: * "33_c" ->
-     * "Edit" * "33_i" -> "http://dom.com/edit.png"
-     */
-    private final HashMap<Object, String> actionMap = new HashMap<Object, String>();
-    private String[] visibleColOrder;
-    private boolean initialContentReceived = false;
-    private Element scrollPositionElement;
-    boolean enabled;
-    boolean showColHeaders;
-    boolean showColFooters;
-
-    /** flag to indicate that table body has changed */
-    private boolean isNewBody = true;
-
-    /*
-     * Read from the "recalcWidths" -attribute. When it is true, the table will
-     * recalculate the widths for columns - desirable in some cases. For #1983,
-     * marked experimental.
-     */
-    boolean recalcWidths = false;
-
-    boolean rendering = false;
-    private boolean hasFocus = false;
-    private int dragmode;
-
-    private int multiselectmode;
-    int tabIndex;
-    private TouchScrollDelegate touchScrollDelegate;
-
-    int lastRenderedHeight;
-
-    /**
-     * Values (serverCacheFirst+serverCacheLast) sent by server that tells which
-     * rows (indexes) are in the server side cache (page buffer). -1 means
-     * unknown. The server side cache row MUST MATCH the client side cache rows.
-     * 
-     * If the client side cache contains additional rows with e.g. buttons, it
-     * will cause out of sync when such a button is pressed.
-     * 
-     * If the server side cache contains additional rows with e.g. buttons,
-     * scrolling in the client will cause empty buttons to be rendered
-     * (cached=true request for non-existing components)
-     */
-    int serverCacheFirst = -1;
-    int serverCacheLast = -1;
-
-    boolean sizeNeedsInit = true;
-
-    /**
-     * Used to recall the position of an open context menu if we need to close
-     * and reopen it during a row update.
-     */
-    class ContextMenuDetails {
-        String rowKey;
-        int left;
-        int top;
-
-        ContextMenuDetails(String rowKey, int left, int top) {
-            this.rowKey = rowKey;
-            this.left = left;
-            this.top = top;
-        }
-    }
-
-    protected ContextMenuDetails contextMenu = null;
-
-    public VScrollTable() {
-        setMultiSelectMode(MULTISELECT_MODE_DEFAULT);
-
-        scrollBodyPanel.addFocusHandler(this);
-        scrollBodyPanel.addBlurHandler(this);
-
-        scrollBodyPanel.addScrollHandler(this);
-
-        /*
-         * Firefox auto-repeat works correctly only if we use a key press
-         * handler, other browsers handle it correctly when using a key down
-         * handler
-         */
-        if (BrowserInfo.get().isGecko()) {
-            scrollBodyPanel.addKeyPressHandler(navKeyPressHandler);
-        } else {
-            scrollBodyPanel.addKeyDownHandler(navKeyDownHandler);
-        }
-        scrollBodyPanel.addKeyUpHandler(navKeyUpHandler);
-
-        scrollBodyPanel.sinkEvents(Event.TOUCHEVENTS);
-
-        scrollBodyPanel.sinkEvents(Event.ONCONTEXTMENU);
-        scrollBodyPanel.addDomHandler(new ContextMenuHandler() {
-
-            @Override
-            public void onContextMenu(ContextMenuEvent event) {
-                handleBodyContextMenu(event);
-            }
-        }, ContextMenuEvent.getType());
-
-        setStyleName(STYLENAME);
-
-        add(tHead);
-        add(scrollBodyPanel);
-        add(tFoot);
-
-        rowRequestHandler = new RowRequestHandler();
-    }
-
-    @Override
-    public void setStyleName(String style) {
-        updateStyleNames(style, false);
-    }
-
-    @Override
-    public void setStylePrimaryName(String style) {
-        updateStyleNames(style, true);
-    }
-
-    private void updateStyleNames(String newStyle, boolean isPrimary) {
-        scrollBodyPanel
-                .removeStyleName(getStylePrimaryName() + "-body-wrapper");
-        scrollBodyPanel.removeStyleName(getStylePrimaryName() + "-body");
-
-        if (scrollBody != null) {
-            scrollBody.removeStyleName(getStylePrimaryName()
-                    + "-body-noselection");
-        }
-
-        if (isPrimary) {
-            super.setStylePrimaryName(newStyle);
-        } else {
-            super.setStyleName(newStyle);
-        }
-
-        scrollBodyPanel.addStyleName(getStylePrimaryName() + "-body-wrapper");
-        scrollBodyPanel.addStyleName(getStylePrimaryName() + "-body");
-
-        tHead.updateStyleNames(getStylePrimaryName());
-        tFoot.updateStyleNames(getStylePrimaryName());
-
-        if (scrollBody != null) {
-            scrollBody.updateStyleNames(getStylePrimaryName());
-        }
-    }
-
-    public void init(ApplicationConnection client) {
-        this.client = client;
-        // Add a handler to clear saved context menu details when the menu
-        // closes. See #8526.
-        client.getContextMenu().addCloseHandler(new CloseHandler<PopupPanel>() {
-
-            @Override
-            public void onClose(CloseEvent<PopupPanel> event) {
-                contextMenu = null;
-            }
-        });
-    }
-
-    private void handleBodyContextMenu(ContextMenuEvent event) {
-        if (enabled && bodyActionKeys != null) {
-            int left = Util.getTouchOrMouseClientX(event.getNativeEvent());
-            int top = Util.getTouchOrMouseClientY(event.getNativeEvent());
-            top += Window.getScrollTop();
-            left += Window.getScrollLeft();
-            client.getContextMenu().showAt(this, left, top);
-
-            // Only prevent browser context menu if there are action handlers
-            // registered
-            event.stopPropagation();
-            event.preventDefault();
-        }
-    }
-
-    /**
-     * Fires a column resize event which sends the resize information to the
-     * server.
-     * 
-     * @param columnId
-     *            The columnId of the column which was resized
-     * @param originalWidth
-     *            The width in pixels of the column before the resize event
-     * @param newWidth
-     *            The width in pixels of the column after the resize event
-     */
-    private void fireColumnResizeEvent(String columnId, int originalWidth,
-            int newWidth) {
-        client.updateVariable(paintableId, "columnResizeEventColumn", columnId,
-                false);
-        client.updateVariable(paintableId, "columnResizeEventPrev",
-                originalWidth, false);
-        client.updateVariable(paintableId, "columnResizeEventCurr", newWidth,
-                immediate);
-
-    }
-
-    /**
-     * Non-immediate variable update of column widths for a collection of
-     * columns.
-     * 
-     * @param columns
-     *            the columns to trigger the events for.
-     */
-    private void sendColumnWidthUpdates(Collection<HeaderCell> columns) {
-        String[] newSizes = new String[columns.size()];
-        int ix = 0;
-        for (HeaderCell cell : columns) {
-            newSizes[ix++] = cell.getColKey() + ":" + cell.getWidth();
-        }
-        client.updateVariable(paintableId, "columnWidthUpdates", newSizes,
-                false);
-    }
-
-    /**
-     * Moves the focus one step down
-     * 
-     * @return Returns true if succeeded
-     */
-    private boolean moveFocusDown() {
-        return moveFocusDown(0);
-    }
-
-    /**
-     * Moves the focus down by 1+offset rows
-     * 
-     * @return Returns true if succeeded, else false if the selection could not
-     *         be move downwards
-     */
-    private boolean moveFocusDown(int offset) {
-        if (isSelectable()) {
-            if (focusedRow == null && scrollBody.iterator().hasNext()) {
-                // FIXME should focus first visible from top, not first rendered
-                // ??
-                return setRowFocus((VScrollTableRow) scrollBody.iterator()
-                        .next());
-            } else {
-                VScrollTableRow next = getNextRow(focusedRow, offset);
-                if (next != null) {
-                    return setRowFocus(next);
-                }
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Moves the selection one step up
-     * 
-     * @return Returns true if succeeded
-     */
-    private boolean moveFocusUp() {
-        return moveFocusUp(0);
-    }
-
-    /**
-     * Moves the focus row upwards
-     * 
-     * @return Returns true if succeeded, else false if the selection could not
-     *         be move upwards
-     * 
-     */
-    private boolean moveFocusUp(int offset) {
-        if (isSelectable()) {
-            if (focusedRow == null && scrollBody.iterator().hasNext()) {
-                // FIXME logic is exactly the same as in moveFocusDown, should
-                // be the opposite??
-                return setRowFocus((VScrollTableRow) scrollBody.iterator()
-                        .next());
-            } else {
-                VScrollTableRow prev = getPreviousRow(focusedRow, offset);
-                if (prev != null) {
-                    return setRowFocus(prev);
-                } else {
-                    VConsole.log("no previous available");
-                }
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Selects a row where the current selection head is
-     * 
-     * @param ctrlSelect
-     *            Is the selection a ctrl+selection
-     * @param shiftSelect
-     *            Is the selection a shift+selection
-     * @return Returns truw
-     */
-    private void selectFocusedRow(boolean ctrlSelect, boolean shiftSelect) {
-        if (focusedRow != null) {
-            // Arrows moves the selection and clears previous selections
-            if (isSelectable() && !ctrlSelect && !shiftSelect) {
-                deselectAll();
-                focusedRow.toggleSelection();
-                selectionRangeStart = focusedRow;
-            } else if (isSelectable() && ctrlSelect && !shiftSelect) {
-                // Ctrl+arrows moves selection head
-                selectionRangeStart = focusedRow;
-                // No selection, only selection head is moved
-            } else if (isMultiSelectModeAny() && !ctrlSelect && shiftSelect) {
-                // Shift+arrows selection selects a range
-                focusedRow.toggleShiftSelection(shiftSelect);
-            }
-        }
-    }
-
-    /**
-     * Sends the selection to the server if changed since the last update/visit.
-     */
-    protected void sendSelectedRows() {
-        sendSelectedRows(immediate);
-    }
-
-    /**
-     * Sends the selection to the server if it has been changed since the last
-     * update/visit.
-     * 
-     * @param immediately
-     *            set to true to immediately send the rows
-     */
-    protected void sendSelectedRows(boolean immediately) {
-        // Don't send anything if selection has not changed
-        if (!selectionChanged) {
-            return;
-        }
-
-        // Reset selection changed flag
-        selectionChanged = false;
-
-        // Note: changing the immediateness of this might require changes to
-        // "clickEvent" immediateness also.
-        if (isMultiSelectModeDefault()) {
-            // Convert ranges to a set of strings
-            Set<String> ranges = new HashSet<String>();
-            for (SelectionRange range : selectedRowRanges) {
-                ranges.add(range.toString());
-            }
-
-            // Send the selected row ranges
-            client.updateVariable(paintableId, "selectedRanges",
-                    ranges.toArray(new String[selectedRowRanges.size()]), false);
-
-            // clean selectedRowKeys so that they don't contain excess values
-            for (Iterator<String> iterator = selectedRowKeys.iterator(); iterator
-                    .hasNext();) {
-                String key = iterator.next();
-                VScrollTableRow renderedRowByKey = getRenderedRowByKey(key);
-                if (renderedRowByKey != null) {
-                    for (SelectionRange range : selectedRowRanges) {
-                        if (range.inRange(renderedRowByKey)) {
-                            iterator.remove();
-                        }
-                    }
-                } else {
-                    // orphaned selected key, must be in a range, ignore
-                    iterator.remove();
-                }
-
-            }
-        }
-
-        // Send the selected rows
-        client.updateVariable(paintableId, "selected",
-                selectedRowKeys.toArray(new String[selectedRowKeys.size()]),
-                immediately);
-
-    }
-
-    /**
-     * Get the key that moves the selection head upwards. By default it is the
-     * up arrow key but by overriding this you can change the key to whatever
-     * you want.
-     * 
-     * @return The keycode of the key
-     */
-    protected int getNavigationUpKey() {
-        return KeyCodes.KEY_UP;
-    }
-
-    /**
-     * Get the key that moves the selection head downwards. By default it is the
-     * down arrow key but by overriding this you can change the key to whatever
-     * you want.
-     * 
-     * @return The keycode of the key
-     */
-    protected int getNavigationDownKey() {
-        return KeyCodes.KEY_DOWN;
-    }
-
-    /**
-     * Get the key that scrolls to the left in the table. By default it is the
-     * left arrow key but by overriding this you can change the key to whatever
-     * you want.
-     * 
-     * @return The keycode of the key
-     */
-    protected int getNavigationLeftKey() {
-        return KeyCodes.KEY_LEFT;
-    }
-
-    /**
-     * Get the key that scroll to the right on the table. By default it is the
-     * right arrow key but by overriding this you can change the key to whatever
-     * you want.
-     * 
-     * @return The keycode of the key
-     */
-    protected int getNavigationRightKey() {
-        return KeyCodes.KEY_RIGHT;
-    }
-
-    /**
-     * Get the key that selects an item in the table. By default it is the space
-     * bar key but by overriding this you can change the key to whatever you
-     * want.
-     * 
-     * @return
-     */
-    protected int getNavigationSelectKey() {
-        return CHARCODE_SPACE;
-    }
-
-    /**
-     * Get the key the moves the selection one page up in the table. By default
-     * this is the Page Up key but by overriding this you can change the key to
-     * whatever you want.
-     * 
-     * @return
-     */
-    protected int getNavigationPageUpKey() {
-        return KeyCodes.KEY_PAGEUP;
-    }
-
-    /**
-     * Get the key the moves the selection one page down in the table. By
-     * default this is the Page Down key but by overriding this you can change
-     * the key to whatever you want.
-     * 
-     * @return
-     */
-    protected int getNavigationPageDownKey() {
-        return KeyCodes.KEY_PAGEDOWN;
-    }
-
-    /**
-     * Get the key the moves the selection to the beginning of the table. By
-     * default this is the Home key but by overriding this you can change the
-     * key to whatever you want.
-     * 
-     * @return
-     */
-    protected int getNavigationStartKey() {
-        return KeyCodes.KEY_HOME;
-    }
-
-    /**
-     * Get the key the moves the selection to the end of the table. By default
-     * this is the End key but by overriding this you can change the key to
-     * whatever you want.
-     * 
-     * @return
-     */
-    protected int getNavigationEndKey() {
-        return KeyCodes.KEY_END;
-    }
-
-    void initializeRows(UIDL uidl, UIDL rowData) {
-        if (scrollBody != null) {
-            scrollBody.removeFromParent();
-        }
-        scrollBody = createScrollBody();
-
-        scrollBody.renderInitialRows(rowData, uidl.getIntAttribute("firstrow"),
-                uidl.getIntAttribute("rows"));
-        scrollBodyPanel.add(scrollBody);
-
-        // New body starts scrolled to the left, make sure the header and footer
-        // are also scrolled to the left
-        tHead.setHorizontalScrollPosition(0);
-        tFoot.setHorizontalScrollPosition(0);
-
-        initialContentReceived = true;
-        sizeNeedsInit = true;
-        scrollBody.restoreRowVisibility();
-    }
-
-    void updateColumnProperties(UIDL uidl) {
-        updateColumnOrder(uidl);
-
-        updateCollapsedColumns(uidl);
-
-        UIDL vc = uidl.getChildByTagName("visiblecolumns");
-        if (vc != null) {
-            tHead.updateCellsFromUIDL(vc);
-            tFoot.updateCellsFromUIDL(vc);
-        }
-
-        updateHeader(uidl.getStringArrayAttribute("vcolorder"));
-        updateFooter(uidl.getStringArrayAttribute("vcolorder"));
-        if (uidl.hasVariable("noncollapsiblecolumns")) {
-            noncollapsibleColumns = uidl
-                    .getStringArrayVariableAsSet("noncollapsiblecolumns");
-        }
-    }
-
-    private void updateCollapsedColumns(UIDL uidl) {
-        if (uidl.hasVariable("collapsedcolumns")) {
-            tHead.setColumnCollapsingAllowed(true);
-            collapsedColumns = uidl
-                    .getStringArrayVariableAsSet("collapsedcolumns");
-        } else {
-            tHead.setColumnCollapsingAllowed(false);
-        }
-    }
-
-    private void updateColumnOrder(UIDL uidl) {
-        if (uidl.hasVariable("columnorder")) {
-            columnReordering = true;
-            columnOrder = uidl.getStringArrayVariable("columnorder");
-        } else {
-            columnReordering = false;
-            columnOrder = null;
-        }
-    }
-
-    boolean selectSelectedRows(UIDL uidl) {
-        boolean keyboardSelectionOverRowFetchInProgress = false;
-
-        if (uidl.hasVariable("selected")) {
-            final Set<String> selectedKeys = uidl
-                    .getStringArrayVariableAsSet("selected");
-            if (scrollBody != null) {
-                Iterator<Widget> iterator = scrollBody.iterator();
-                while (iterator.hasNext()) {
-                    /*
-                     * Make the focus reflect to the server side state unless we
-                     * are currently selecting multiple rows with keyboard.
-                     */
-                    VScrollTableRow row = (VScrollTableRow) iterator.next();
-                    boolean selected = selectedKeys.contains(row.getKey());
-                    if (!selected
-                            && unSyncedselectionsBeforeRowFetch != null
-                            && unSyncedselectionsBeforeRowFetch.contains(row
-                                    .getKey())) {
-                        selected = true;
-                        keyboardSelectionOverRowFetchInProgress = true;
-                    }
-                    if (selected != row.isSelected()) {
-                        row.toggleSelection();
-                        if (!isSingleSelectMode() && !selected) {
-                            // Update selection range in case a row is
-                            // unselected from the middle of a range - #8076
-                            removeRowFromUnsentSelectionRanges(row);
-                        }
-                    }
-                }
-            }
-        }
-        unSyncedselectionsBeforeRowFetch = null;
-        return keyboardSelectionOverRowFetchInProgress;
-    }
-
-    void updateSortingProperties(UIDL uidl) {
-        oldSortColumn = sortColumn;
-        if (uidl.hasVariable("sortascending")) {
-            sortAscending = uidl.getBooleanVariable("sortascending");
-            sortColumn = uidl.getStringVariable("sortcolumn");
-        }
-    }
-
-    void resizeSortedColumnForSortIndicator() {
-        // Force recalculation of the captionContainer element inside the header
-        // cell to accomodate for the size of the sort arrow.
-        HeaderCell sortedHeader = tHead.getHeaderCell(sortColumn);
-        if (sortedHeader != null) {
-            tHead.resizeCaptionContainer(sortedHeader);
-        }
-        // Also recalculate the width of the captionContainer element in the
-        // previously sorted header, since this now has more room.
-        HeaderCell oldSortedHeader = tHead.getHeaderCell(oldSortColumn);
-        if (oldSortedHeader != null) {
-            tHead.resizeCaptionContainer(oldSortedHeader);
-        }
-    }
-
-    void updateFirstVisibleAndScrollIfNeeded(UIDL uidl) {
-        firstvisible = uidl.hasVariable("firstvisible") ? uidl
-                .getIntVariable("firstvisible") : 0;
-        if (firstvisible != lastRequestedFirstvisible && scrollBody != null) {
-            // received 'surprising' firstvisible from server: scroll there
-            firstRowInViewPort = firstvisible;
-            scrollBodyPanel
-                    .setScrollPosition(measureRowHeightOffset(firstvisible));
-        }
-    }
-
-    protected int measureRowHeightOffset(int rowIx) {
-        return (int) (rowIx * scrollBody.getRowHeight());
-    }
-
-    void updatePageLength(UIDL uidl) {
-        int oldPageLength = pageLength;
-        if (uidl.hasAttribute("pagelength")) {
-            pageLength = uidl.getIntAttribute("pagelength");
-        } else {
-            // pagelenght is "0" meaning scrolling is turned off
-            pageLength = totalRows;
-        }
-
-        if (oldPageLength != pageLength && initializedAndAttached) {
-            // page length changed, need to update size
-            sizeNeedsInit = true;
-        }
-    }
-
-    void updateSelectionProperties(UIDL uidl, ComponentState state,
-            boolean readOnly) {
-        setMultiSelectMode(uidl.hasAttribute("multiselectmode") ? uidl
-                .getIntAttribute("multiselectmode") : MULTISELECT_MODE_DEFAULT);
-
-        nullSelectionAllowed = uidl.hasAttribute("nsa") ? uidl
-                .getBooleanAttribute("nsa") : true;
-
-        if (uidl.hasAttribute("selectmode")) {
-            if (readOnly) {
-                selectMode = SelectMode.NONE;
-            } else if (uidl.getStringAttribute("selectmode").equals("multi")) {
-                selectMode = SelectMode.MULTI;
-            } else if (uidl.getStringAttribute("selectmode").equals("single")) {
-                selectMode = SelectMode.SINGLE;
-            } else {
-                selectMode = SelectMode.NONE;
-            }
-        }
-    }
-
-    void updateDragMode(UIDL uidl) {
-        dragmode = uidl.hasAttribute("dragmode") ? uidl
-                .getIntAttribute("dragmode") : 0;
-        if (BrowserInfo.get().isIE()) {
-            if (dragmode > 0) {
-                getElement().setPropertyJSO("onselectstart",
-                        getPreventTextSelectionIEHack());
-            } else {
-                getElement().setPropertyJSO("onselectstart", null);
-            }
-        }
-    }
-
-    protected void updateTotalRows(UIDL uidl) {
-        int newTotalRows = uidl.getIntAttribute("totalrows");
-        if (newTotalRows != getTotalRows()) {
-            if (scrollBody != null) {
-                if (getTotalRows() == 0) {
-                    tHead.clear();
-                    tFoot.clear();
-                }
-                initializedAndAttached = false;
-                initialContentReceived = false;
-                isNewBody = true;
-            }
-            setTotalRows(newTotalRows);
-        }
-    }
-
-    protected void setTotalRows(int newTotalRows) {
-        totalRows = newTotalRows;
-    }
-
-    public int getTotalRows() {
-        return totalRows;
-    }
-
-    void focusRowFromBody() {
-        if (selectedRowKeys.size() == 1) {
-            // try to focus a row currently selected and in viewport
-            String selectedRowKey = selectedRowKeys.iterator().next();
-            if (selectedRowKey != null) {
-                VScrollTableRow renderedRow = getRenderedRowByKey(selectedRowKey);
-                if (renderedRow == null || !renderedRow.isInViewPort()) {
-                    setRowFocus(scrollBody.getRowByRowIndex(firstRowInViewPort));
-                } else {
-                    setRowFocus(renderedRow);
-                }
-            }
-        } else {
-            // multiselect mode
-            setRowFocus(scrollBody.getRowByRowIndex(firstRowInViewPort));
-        }
-    }
-
-    protected VScrollTableBody createScrollBody() {
-        return new VScrollTableBody();
-    }
-
-    /**
-     * Selects the last row visible in the table
-     * 
-     * @param focusOnly
-     *            Should the focus only be moved to the last row
-     */
-    void selectLastRenderedRowInViewPort(boolean focusOnly) {
-        int index = firstRowInViewPort + getFullyVisibleRowCount();
-        VScrollTableRow lastRowInViewport = scrollBody.getRowByRowIndex(index);
-        if (lastRowInViewport == null) {
-            // this should not happen in normal situations (white space at the
-            // end of viewport). Select the last rendered as a fallback.
-            lastRowInViewport = scrollBody.getRowByRowIndex(scrollBody
-                    .getLastRendered());
-            if (lastRowInViewport == null) {
-                return; // empty table
-            }
-        }
-        setRowFocus(lastRowInViewport);
-        if (!focusOnly) {
-            selectFocusedRow(false, multiselectPending);
-            sendSelectedRows();
-        }
-    }
-
-    /**
-     * Selects the first row visible in the table
-     * 
-     * @param focusOnly
-     *            Should the focus only be moved to the first row
-     */
-    void selectFirstRenderedRowInViewPort(boolean focusOnly) {
-        int index = firstRowInViewPort;
-        VScrollTableRow firstInViewport = scrollBody.getRowByRowIndex(index);
-        if (firstInViewport == null) {
-            // this should not happen in normal situations
-            return;
-        }
-        setRowFocus(firstInViewport);
-        if (!focusOnly) {
-            selectFocusedRow(false, multiselectPending);
-            sendSelectedRows();
-        }
-    }
-
-    void setCacheRateFromUIDL(UIDL uidl) {
-        setCacheRate(uidl.hasAttribute("cr") ? uidl.getDoubleAttribute("cr")
-                : CACHE_RATE_DEFAULT);
-    }
-
-    private void setCacheRate(double d) {
-        if (cache_rate != d) {
-            cache_rate = d;
-            cache_react_rate = 0.75 * d;
-        }
-    }
-
-    void updateActionMap(UIDL mainUidl) {
-        UIDL actionsUidl = mainUidl.getChildByTagName("actions");
-        if (actionsUidl == null) {
-            return;
-        }
-
-        final Iterator<?> it = actionsUidl.getChildIterator();
-        while (it.hasNext()) {
-            final UIDL action = (UIDL) it.next();
-            final String key = action.getStringAttribute("key");
-            final String caption = action.getStringAttribute("caption");
-            actionMap.put(key + "_c", caption);
-            if (action.hasAttribute("icon")) {
-                // TODO need some uri handling ??
-                actionMap.put(key + "_i", client.translateVaadinUri(action
-                        .getStringAttribute("icon")));
-            } else {
-                actionMap.remove(key + "_i");
-            }
-        }
-
-    }
-
-    public String getActionCaption(String actionKey) {
-        return actionMap.get(actionKey + "_c");
-    }
-
-    public String getActionIcon(String actionKey) {
-        return actionMap.get(actionKey + "_i");
-    }
-
-    private void updateHeader(String[] strings) {
-        if (strings == null) {
-            return;
-        }
-
-        int visibleCols = strings.length;
-        int colIndex = 0;
-        if (showRowHeaders) {
-            tHead.enableColumn(ROW_HEADER_COLUMN_KEY, colIndex);
-            visibleCols++;
-            visibleColOrder = new String[visibleCols];
-            visibleColOrder[colIndex] = ROW_HEADER_COLUMN_KEY;
-            colIndex++;
-        } else {
-            visibleColOrder = new String[visibleCols];
-            tHead.removeCell(ROW_HEADER_COLUMN_KEY);
-        }
-
-        int i;
-        for (i = 0; i < strings.length; i++) {
-            final String cid = strings[i];
-            visibleColOrder[colIndex] = cid;
-            tHead.enableColumn(cid, colIndex);
-            colIndex++;
-        }
-
-        tHead.setVisible(showColHeaders);
-        setContainerHeight();
-
-    }
-
-    /**
-     * Updates footers.
-     * <p>
-     * Update headers whould be called before this method is called!
-     * </p>
-     * 
-     * @param strings
-     */
-    private void updateFooter(String[] strings) {
-        if (strings == null) {
-            return;
-        }
-
-        // Add dummy column if row headers are present
-        int colIndex = 0;
-        if (showRowHeaders) {
-            tFoot.enableColumn(ROW_HEADER_COLUMN_KEY, colIndex);
-            colIndex++;
-        } else {
-            tFoot.removeCell(ROW_HEADER_COLUMN_KEY);
-        }
-
-        int i;
-        for (i = 0; i < strings.length; i++) {
-            final String cid = strings[i];
-            tFoot.enableColumn(cid, colIndex);
-            colIndex++;
-        }
-
-        tFoot.setVisible(showColFooters);
-    }
-
-    /**
-     * @param uidl
-     *            which contains row data
-     * @param firstRow
-     *            first row in data set
-     * @param reqRows
-     *            amount of rows in data set
-     */
-    void updateBody(UIDL uidl, int firstRow, int reqRows) {
-        if (uidl == null || reqRows < 1) {
-            // container is empty, remove possibly existing rows
-            if (firstRow <= 0) {
-                while (scrollBody.getLastRendered() > scrollBody.firstRendered) {
-                    scrollBody.unlinkRow(false);
-                }
-                scrollBody.unlinkRow(false);
-            }
-            return;
-        }
-
-        scrollBody.renderRows(uidl, firstRow, reqRows);
-
-        discardRowsOutsideCacheWindow();
-    }
-
-    void updateRowsInBody(UIDL partialRowUpdates) {
-        if (partialRowUpdates == null) {
-            return;
-        }
-        int firstRowIx = partialRowUpdates.getIntAttribute("firsturowix");
-        int count = partialRowUpdates.getIntAttribute("numurows");
-        scrollBody.unlinkRows(firstRowIx, count);
-        scrollBody.insertRows(partialRowUpdates, firstRowIx, count);
-    }
-
-    /**
-     * Updates the internal cache by unlinking rows that fall outside of the
-     * caching window.
-     */
-    protected void discardRowsOutsideCacheWindow() {
-        int firstRowToKeep = (int) (firstRowInViewPort - pageLength
-                * cache_rate);
-        int lastRowToKeep = (int) (firstRowInViewPort + pageLength + pageLength
-                * cache_rate);
-        debug("Client side calculated cache rows to keep: " + firstRowToKeep
-                + "-" + lastRowToKeep);
-
-        if (serverCacheFirst != -1) {
-            firstRowToKeep = serverCacheFirst;
-            lastRowToKeep = serverCacheLast;
-            debug("Server cache rows that override: " + serverCacheFirst + "-"
-                    + serverCacheLast);
-            if (firstRowToKeep < scrollBody.getFirstRendered()
-                    || lastRowToKeep > scrollBody.getLastRendered()) {
-                debug("*** Server wants us to keep " + serverCacheFirst + "-"
-                        + serverCacheLast + " but we only have rows "
-                        + scrollBody.getFirstRendered() + "-"
-                        + scrollBody.getLastRendered() + " rendered!");
-            }
-        }
-        discardRowsOutsideOf(firstRowToKeep, lastRowToKeep);
-
-        scrollBody.fixSpacers();
-
-        scrollBody.restoreRowVisibility();
-    }
-
-    private void discardRowsOutsideOf(int optimalFirstRow, int optimalLastRow) {
-        /*
-         * firstDiscarded and lastDiscarded are only calculated for debug
-         * purposes
-         */
-        int firstDiscarded = -1, lastDiscarded = -1;
-        boolean cont = true;
-        while (cont && scrollBody.getLastRendered() > optimalFirstRow
-                && scrollBody.getFirstRendered() < optimalFirstRow) {
-            if (firstDiscarded == -1) {
-                firstDiscarded = scrollBody.getFirstRendered();
-            }
-
-            // removing row from start
-            cont = scrollBody.unlinkRow(true);
-        }
-        if (firstDiscarded != -1) {
-            lastDiscarded = scrollBody.getFirstRendered() - 1;
-            debug("Discarded rows " + firstDiscarded + "-" + lastDiscarded);
-        }
-        firstDiscarded = lastDiscarded = -1;
-
-        cont = true;
-        while (cont && scrollBody.getLastRendered() > optimalLastRow) {
-            if (lastDiscarded == -1) {
-                lastDiscarded = scrollBody.getLastRendered();
-            }
-
-            // removing row from the end
-            cont = scrollBody.unlinkRow(false);
-        }
-        if (lastDiscarded != -1) {
-            firstDiscarded = scrollBody.getLastRendered() + 1;
-            debug("Discarded rows " + firstDiscarded + "-" + lastDiscarded);
-        }
-
-        debug("Now in cache: " + scrollBody.getFirstRendered() + "-"
-                + scrollBody.getLastRendered());
-    }
-
-    /**
-     * Inserts rows in the table body or removes them from the table body based
-     * on the commands in the UIDL.
-     * 
-     * @param partialRowAdditions
-     *            the UIDL containing row updates.
-     */
-    protected void addAndRemoveRows(UIDL partialRowAdditions) {
-        if (partialRowAdditions == null) {
-            return;
-        }
-        if (partialRowAdditions.hasAttribute("hide")) {
-            scrollBody.unlinkAndReindexRows(
-                    partialRowAdditions.getIntAttribute("firstprowix"),
-                    partialRowAdditions.getIntAttribute("numprows"));
-            scrollBody.ensureCacheFilled();
-        } else {
-            if (partialRowAdditions.hasAttribute("delbelow")) {
-                scrollBody.insertRowsDeleteBelow(partialRowAdditions,
-                        partialRowAdditions.getIntAttribute("firstprowix"),
-                        partialRowAdditions.getIntAttribute("numprows"));
-            } else {
-                scrollBody.insertAndReindexRows(partialRowAdditions,
-                        partialRowAdditions.getIntAttribute("firstprowix"),
-                        partialRowAdditions.getIntAttribute("numprows"));
-            }
-        }
-
-        discardRowsOutsideCacheWindow();
-    }
-
-    /**
-     * Gives correct column index for given column key ("cid" in UIDL).
-     * 
-     * @param colKey
-     * @return column index of visible columns, -1 if column not visible
-     */
-    private int getColIndexByKey(String colKey) {
-        // return 0 if asked for rowHeaders
-        if (ROW_HEADER_COLUMN_KEY.equals(colKey)) {
-            return 0;
-        }
-        for (int i = 0; i < visibleColOrder.length; i++) {
-            if (visibleColOrder[i].equals(colKey)) {
-                return i;
-            }
-        }
-        return -1;
-    }
-
-    private boolean isMultiSelectModeSimple() {
-        return selectMode == SelectMode.MULTI
-                && multiselectmode == MULTISELECT_MODE_SIMPLE;
-    }
-
-    private boolean isSingleSelectMode() {
-        return selectMode == SelectMode.SINGLE;
-    }
-
-    private boolean isMultiSelectModeAny() {
-        return selectMode == SelectMode.MULTI;
-    }
-
-    private boolean isMultiSelectModeDefault() {
-        return selectMode == SelectMode.MULTI
-                && multiselectmode == MULTISELECT_MODE_DEFAULT;
-    }
-
-    private void setMultiSelectMode(int multiselectmode) {
-        if (BrowserInfo.get().isTouchDevice()) {
-            // Always use the simple mode for touch devices that do not have
-            // shift/ctrl keys
-            this.multiselectmode = MULTISELECT_MODE_SIMPLE;
-        } else {
-            this.multiselectmode = multiselectmode;
-        }
-
-    }
-
-    protected boolean isSelectable() {
-        return selectMode.getId() > SelectMode.NONE.getId();
-    }
-
-    private boolean isCollapsedColumn(String colKey) {
-        if (collapsedColumns == null) {
-            return false;
-        }
-        if (collapsedColumns.contains(colKey)) {
-            return true;
-        }
-        return false;
-    }
-
-    private String getColKeyByIndex(int index) {
-        return tHead.getHeaderCell(index).getColKey();
-    }
-
-    private void setColWidth(int colIndex, int w, boolean isDefinedWidth) {
-        final HeaderCell hcell = tHead.getHeaderCell(colIndex);
-
-        // Make sure that the column grows to accommodate the sort indicator if
-        // necessary.
-        if (w < hcell.getMinWidth()) {
-            w = hcell.getMinWidth();
-        }
-
-        // Set header column width
-        hcell.setWidth(w, isDefinedWidth);
-
-        // Ensure indicators have been taken into account
-        tHead.resizeCaptionContainer(hcell);
-
-        // Set body column width
-        scrollBody.setColWidth(colIndex, w);
-
-        // Set footer column width
-        FooterCell fcell = tFoot.getFooterCell(colIndex);
-        fcell.setWidth(w, isDefinedWidth);
-    }
-
-    private int getColWidth(String colKey) {
-        return tHead.getHeaderCell(colKey).getWidth();
-    }
-
-    /**
-     * Get a rendered row by its key
-     * 
-     * @param key
-     *            The key to search with
-     * @return
-     */
-    public VScrollTableRow getRenderedRowByKey(String key) {
-        if (scrollBody != null) {
-            final Iterator<Widget> it = scrollBody.iterator();
-            VScrollTableRow r = null;
-            while (it.hasNext()) {
-                r = (VScrollTableRow) it.next();
-                if (r.getKey().equals(key)) {
-                    return r;
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Returns the next row to the given row
-     * 
-     * @param row
-     *            The row to calculate from
-     * 
-     * @return The next row or null if no row exists
-     */
-    private VScrollTableRow getNextRow(VScrollTableRow row, int offset) {
-        final Iterator<Widget> it = scrollBody.iterator();
-        VScrollTableRow r = null;
-        while (it.hasNext()) {
-            r = (VScrollTableRow) it.next();
-            if (r == row) {
-                r = null;
-                while (offset >= 0 && it.hasNext()) {
-                    r = (VScrollTableRow) it.next();
-                    offset--;
-                }
-                return r;
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * Returns the previous row from the given row
-     * 
-     * @param row
-     *            The row to calculate from
-     * @return The previous row or null if no row exists
-     */
-    private VScrollTableRow getPreviousRow(VScrollTableRow row, int offset) {
-        final Iterator<Widget> it = scrollBody.iterator();
-        final Iterator<Widget> offsetIt = scrollBody.iterator();
-        VScrollTableRow r = null;
-        VScrollTableRow prev = null;
-        while (it.hasNext()) {
-            r = (VScrollTableRow) it.next();
-            if (offset < 0) {
-                prev = (VScrollTableRow) offsetIt.next();
-            }
-            if (r == row) {
-                return prev;
-            }
-            offset--;
-        }
-
-        return null;
-    }
-
-    protected void reOrderColumn(String columnKey, int newIndex) {
-
-        final int oldIndex = getColIndexByKey(columnKey);
-
-        // Change header order
-        tHead.moveCell(oldIndex, newIndex);
-
-        // Change body order
-        scrollBody.moveCol(oldIndex, newIndex);
-
-        // Change footer order
-        tFoot.moveCell(oldIndex, newIndex);
-
-        /*
-         * Build new columnOrder and update it to server Note that columnOrder
-         * also contains collapsed columns so we cannot directly build it from
-         * cells vector Loop the old columnOrder and append in order to new
-         * array unless on moved columnKey. On new index also put the moved key
-         * i == index on columnOrder, j == index on newOrder
-         */
-        final String oldKeyOnNewIndex = visibleColOrder[newIndex];
-        if (showRowHeaders) {
-            newIndex--; // columnOrder don't have rowHeader
-        }
-        // add back hidden rows,
-        for (int i = 0; i < columnOrder.length; i++) {
-            if (columnOrder[i].equals(oldKeyOnNewIndex)) {
-                break; // break loop at target
-            }
-            if (isCollapsedColumn(columnOrder[i])) {
-                newIndex++;
-            }
-        }
-        // finally we can build the new columnOrder for server
-        final String[] newOrder = new String[columnOrder.length];
-        for (int i = 0, j = 0; j < newOrder.length; i++) {
-            if (j == newIndex) {
-                newOrder[j] = columnKey;
-                j++;
-            }
-            if (i == columnOrder.length) {
-                break;
-            }
-            if (columnOrder[i].equals(columnKey)) {
-                continue;
-            }
-            newOrder[j] = columnOrder[i];
-            j++;
-        }
-        columnOrder = newOrder;
-        // also update visibleColumnOrder
-        int i = showRowHeaders ? 1 : 0;
-        for (int j = 0; j < newOrder.length; j++) {
-            final String cid = newOrder[j];
-            if (!isCollapsedColumn(cid)) {
-                visibleColOrder[i++] = cid;
-            }
-        }
-        client.updateVariable(paintableId, "columnorder", columnOrder, false);
-        if (client.hasEventListeners(this,
-                TableConstants.COLUMN_REORDER_EVENT_ID)) {
-            client.sendPendingVariableChanges();
-        }
-    }
-
-    @Override
-    protected void onDetach() {
-        rowRequestHandler.cancel();
-        super.onDetach();
-        // ensure that scrollPosElement will be detached
-        if (scrollPositionElement != null) {
-            final Element parent = DOM.getParent(scrollPositionElement);
-            if (parent != null) {
-                DOM.removeChild(parent, scrollPositionElement);
-            }
-        }
-    }
-
-    /**
-     * Run only once when component is attached and received its initial
-     * content. This function:
-     * 
-     * * Syncs headers and bodys "natural widths and saves the values.
-     * 
-     * * Sets proper width and height
-     * 
-     * * Makes deferred request to get some cache rows
-     */
-    void sizeInit() {
-        sizeNeedsInit = false;
-
-        scrollBody.setContainerHeight();
-
-        /*
-         * We will use browsers table rendering algorithm to find proper column
-         * widths. If content and header take less space than available, we will
-         * divide extra space relatively to each column which has not width set.
-         * 
-         * Overflow pixels are added to last column.
-         */
-
-        Iterator<Widget> headCells = tHead.iterator();
-        Iterator<Widget> footCells = tFoot.iterator();
-        int i = 0;
-        int totalExplicitColumnsWidths = 0;
-        int total = 0;
-        float expandRatioDivider = 0;
-
-        final int[] widths = new int[tHead.visibleCells.size()];
-
-        tHead.enableBrowserIntelligence();
-        tFoot.enableBrowserIntelligence();
-
-        // first loop: collect natural widths
-        while (headCells.hasNext()) {
-            final HeaderCell hCell = (HeaderCell) headCells.next();
-            final FooterCell fCell = (FooterCell) footCells.next();
-            int w = hCell.getWidth();
-            if (hCell.isDefinedWidth()) {
-                // server has defined column width explicitly
-                totalExplicitColumnsWidths += w;
-            } else {
-                if (hCell.getExpandRatio() > 0) {
-                    expandRatioDivider += hCell.getExpandRatio();
-                    w = 0;
-                } else {
-                    // get and store greater of header width and column width,
-                    // and
-                    // store it as a minimumn natural col width
-                    int headerWidth = hCell.getNaturalColumnWidth(i);
-                    int footerWidth = fCell.getNaturalColumnWidth(i);
-                    w = headerWidth > footerWidth ? headerWidth : footerWidth;
-                }
-                hCell.setNaturalMinimumColumnWidth(w);
-                fCell.setNaturalMinimumColumnWidth(w);
-            }
-            widths[i] = w;
-            total += w;
-            i++;
-        }
-
-        tHead.disableBrowserIntelligence();
-        tFoot.disableBrowserIntelligence();
-
-        boolean willHaveScrollbarz = willHaveScrollbars();
-
-        // fix "natural" width if width not set
-        if (isDynamicWidth()) {
-            int w = total;
-            w += scrollBody.getCellExtraWidth() * visibleColOrder.length;
-            if (willHaveScrollbarz) {
-                w += Util.getNativeScrollbarSize();
-            }
-            setContentWidth(w);
-        }
-
-        int availW = scrollBody.getAvailableWidth();
-        if (BrowserInfo.get().isIE()) {
-            // Hey IE, are you really sure about this?
-            availW = scrollBody.getAvailableWidth();
-        }
-        availW -= scrollBody.getCellExtraWidth() * visibleColOrder.length;
-
-        if (willHaveScrollbarz) {
-            availW -= Util.getNativeScrollbarSize();
-        }
-
-        // TODO refactor this code to be the same as in resize timer
-        boolean needsReLayout = false;
-
-        if (availW > total) {
-            // natural size is smaller than available space
-            final int extraSpace = availW - total;
-            final int totalWidthR = total - totalExplicitColumnsWidths;
-            int checksum = 0;
-            needsReLayout = true;
-
-            if (extraSpace == 1) {
-                // We cannot divide one single pixel so we give it the first
-                // undefined column
-                headCells = tHead.iterator();
-                i = 0;
-                checksum = availW;
-                while (headCells.hasNext()) {
-                    HeaderCell hc = (HeaderCell) headCells.next();
-                    if (!hc.isDefinedWidth()) {
-                        widths[i]++;
-                        break;
-                    }
-                    i++;
-                }
-
-            } else if (expandRatioDivider > 0) {
-                // visible columns have some active expand ratios, excess
-                // space is divided according to them
-                headCells = tHead.iterator();
-                i = 0;
-                while (headCells.hasNext()) {
-                    HeaderCell hCell = (HeaderCell) headCells.next();
-                    if (hCell.getExpandRatio() > 0) {
-                        int w = widths[i];
-                        final int newSpace = Math.round((extraSpace * (hCell
-                                .getExpandRatio() / expandRatioDivider)));
-                        w += newSpace;
-                        widths[i] = w;
-                    }
-                    checksum += widths[i];
-                    i++;
-                }
-            } else if (totalWidthR > 0) {
-                // no expand ratios defined, we will share extra space
-                // relatively to "natural widths" among those without
-                // explicit width
-                headCells = tHead.iterator();
-                i = 0;
-                while (headCells.hasNext()) {
-                    HeaderCell hCell = (HeaderCell) headCells.next();
-                    if (!hCell.isDefinedWidth()) {
-                        int w = widths[i];
-                        final int newSpace = Math.round((float) extraSpace
-                                * (float) w / totalWidthR);
-                        w += newSpace;
-                        widths[i] = w;
-                    }
-                    checksum += widths[i];
-                    i++;
-                }
-            }
-
-            if (extraSpace > 0 && checksum != availW) {
-                /*
-                 * There might be in some cases a rounding error of 1px when
-                 * extra space is divided so if there is one then we give the
-                 * first undefined column 1 more pixel
-                 */
-                headCells = tHead.iterator();
-                i = 0;
-                while (headCells.hasNext()) {
-                    HeaderCell hc = (HeaderCell) headCells.next();
-                    if (!hc.isDefinedWidth()) {
-                        widths[i] += availW - checksum;
-                        break;
-                    }
-                    i++;
-                }
-            }
-
-        } else {
-            // bodys size will be more than available and scrollbar will appear
-        }
-
-        // last loop: set possibly modified values or reset if new tBody
-        i = 0;
-        headCells = tHead.iterator();
-        while (headCells.hasNext()) {
-            final HeaderCell hCell = (HeaderCell) headCells.next();
-            if (isNewBody || hCell.getWidth() == -1) {
-                final int w = widths[i];
-                setColWidth(i, w, false);
-            }
-            i++;
-        }
-
-        initializedAndAttached = true;
-
-        if (needsReLayout) {
-            scrollBody.reLayoutComponents();
-        }
-
-        updatePageLength();
-
-        /*
-         * Fix "natural" height if height is not set. This must be after width
-         * fixing so the components' widths have been adjusted.
-         */
-        if (isDynamicHeight()) {
-            /*
-             * We must force an update of the row height as this point as it
-             * might have been (incorrectly) calculated earlier
-             */
-
-            int bodyHeight;
-            if (pageLength == totalRows) {
-                /*
-                 * A hack to support variable height rows when paging is off.
-                 * Generally this is not supported by scrolltable. We want to
-                 * show all rows so the bodyHeight should be equal to the table
-                 * height.
-                 */
-                // int bodyHeight = scrollBody.getOffsetHeight();
-                bodyHeight = scrollBody.getRequiredHeight();
-            } else {
-                bodyHeight = (int) Math.round(scrollBody.getRowHeight(true)
-                        * pageLength);
-            }
-            boolean needsSpaceForHorizontalSrollbar = (total > availW);
-            if (needsSpaceForHorizontalSrollbar) {
-                bodyHeight += Util.getNativeScrollbarSize();
-            }
-            scrollBodyPanel.setHeight(bodyHeight + "px");
-            Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
-        }
-
-        isNewBody = false;
-
-        if (firstvisible > 0) {
-            // Deferred due to some Firefox oddities
-            Scheduler.get().scheduleDeferred(new Command() {
-
-                @Override
-                public void execute() {
-                    scrollBodyPanel
-                            .setScrollPosition(measureRowHeightOffset(firstvisible));
-                    firstRowInViewPort = firstvisible;
-                }
-            });
-        }
-
-        if (enabled) {
-            // Do we need cache rows
-            if (scrollBody.getLastRendered() + 1 < firstRowInViewPort
-                    + pageLength + (int) cache_react_rate * pageLength) {
-                if (totalRows - 1 > scrollBody.getLastRendered()) {
-                    // fetch cache rows
-                    int firstInNewSet = scrollBody.getLastRendered() + 1;
-                    rowRequestHandler.setReqFirstRow(firstInNewSet);
-                    int lastInNewSet = (int) (firstRowInViewPort + pageLength + cache_rate
-                            * pageLength);
-                    if (lastInNewSet > totalRows - 1) {
-                        lastInNewSet = totalRows - 1;
-                    }
-                    rowRequestHandler.setReqRows(lastInNewSet - firstInNewSet
-                            + 1);
-                    rowRequestHandler.deferRowFetch(1);
-                }
-            }
-        }
-
-        /*
-         * Ensures the column alignments are correct at initial loading. <br/>
-         * (child components widths are correct)
-         */
-        scrollBody.reLayoutComponents();
-        Scheduler.get().scheduleDeferred(new Command() {
-
-            @Override
-            public void execute() {
-                Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
-            }
-        });
-    }
-
-    /**
-     * Note, this method is not official api although declared as protected.
-     * Extend at you own risk.
-     * 
-     * @return true if content area will have scrollbars visible.
-     */
-    protected boolean willHaveScrollbars() {
-        if (isDynamicHeight()) {
-            if (pageLength < totalRows) {
-                return true;
-            }
-        } else {
-            int fakeheight = (int) Math.round(scrollBody.getRowHeight()
-                    * totalRows);
-            int availableHeight = scrollBodyPanel.getElement().getPropertyInt(
-                    "clientHeight");
-            if (fakeheight > availableHeight) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private void announceScrollPosition() {
-        if (scrollPositionElement == null) {
-            scrollPositionElement = DOM.createDiv();
-            scrollPositionElement.setClassName(getStylePrimaryName()
-                    + "-scrollposition");
-            scrollPositionElement.getStyle().setPosition(Position.ABSOLUTE);
-            scrollPositionElement.getStyle().setDisplay(Display.NONE);
-            getElement().appendChild(scrollPositionElement);
-        }
-
-        Style style = scrollPositionElement.getStyle();
-        style.setMarginLeft(getElement().getOffsetWidth() / 2 - 80, Unit.PX);
-        style.setMarginTop(-scrollBodyPanel.getOffsetHeight(), Unit.PX);
-
-        // indexes go from 1-totalRows, as rowheaders in index-mode indicate
-        int last = (firstRowInViewPort + pageLength);
-        if (last > totalRows) {
-            last = totalRows;
-        }
-        scrollPositionElement.setInnerHTML("<span>" + (firstRowInViewPort + 1)
-                + " &ndash; " + (last) + "..." + "</span>");
-        style.setDisplay(Display.BLOCK);
-    }
-
-    void hideScrollPositionAnnotation() {
-        if (scrollPositionElement != null) {
-            DOM.setStyleAttribute(scrollPositionElement, "display", "none");
-        }
-    }
-
-    boolean isScrollPositionVisible() {
-        return scrollPositionElement != null
-                && !scrollPositionElement.getStyle().getDisplay()
-                        .equals(Display.NONE.toString());
-    }
-
-    class RowRequestHandler extends Timer {
-
-        private int reqFirstRow = 0;
-        private int reqRows = 0;
-        private boolean isRunning = false;
-
-        public void deferRowFetch() {
-            deferRowFetch(250);
-        }
-
-        public boolean isRunning() {
-            return isRunning;
-        }
-
-        public void deferRowFetch(int msec) {
-            isRunning = true;
-            if (reqRows > 0 && reqFirstRow < totalRows) {
-                schedule(msec);
-
-                // tell scroll position to user if currently "visible" rows are
-                // not rendered
-                if (totalRows > pageLength
-                        && ((firstRowInViewPort + pageLength > scrollBody
-                                .getLastRendered()) || (firstRowInViewPort < scrollBody
-                                .getFirstRendered()))) {
-                    announceScrollPosition();
-                } else {
-                    hideScrollPositionAnnotation();
-                }
-            }
-        }
-
-        public void setReqFirstRow(int reqFirstRow) {
-            if (reqFirstRow < 0) {
-                reqFirstRow = 0;
-            } else if (reqFirstRow >= totalRows) {
-                reqFirstRow = totalRows - 1;
-            }
-            this.reqFirstRow = reqFirstRow;
-        }
-
-        public void setReqRows(int reqRows) {
-            this.reqRows = reqRows;
-        }
-
-        @Override
-        public void run() {
-            if (client.hasActiveRequest() || navKeyDown) {
-                // if client connection is busy, don't bother loading it more
-                VConsole.log("Postponed rowfetch");
-                schedule(250);
-            } else {
-
-                int firstToBeRendered = scrollBody.firstRendered;
-                if (reqFirstRow < firstToBeRendered) {
-                    firstToBeRendered = reqFirstRow;
-                } else if (firstRowInViewPort - (int) (cache_rate * pageLength) > firstToBeRendered) {
-                    firstToBeRendered = firstRowInViewPort
-                            - (int) (cache_rate * pageLength);
-                    if (firstToBeRendered < 0) {
-                        firstToBeRendered = 0;
-                    }
-                }
-
-                int lastToBeRendered = scrollBody.lastRendered;
-
-                if (reqFirstRow + reqRows - 1 > lastToBeRendered) {
-                    lastToBeRendered = reqFirstRow + reqRows - 1;
-                } else if (firstRowInViewPort + pageLength + pageLength
-                        * cache_rate < lastToBeRendered) {
-                    lastToBeRendered = (firstRowInViewPort + pageLength + (int) (pageLength * cache_rate));
-                    if (lastToBeRendered >= totalRows) {
-                        lastToBeRendered = totalRows - 1;
-                    }
-                    // due Safari 3.1 bug (see #2607), verify reqrows, original
-                    // problem unknown, but this should catch the issue
-                    if (reqFirstRow + reqRows - 1 > lastToBeRendered) {
-                        reqRows = lastToBeRendered - reqFirstRow;
-                    }
-                }
-
-                client.updateVariable(paintableId, "firstToBeRendered",
-                        firstToBeRendered, false);
-
-                client.updateVariable(paintableId, "lastToBeRendered",
-                        lastToBeRendered, false);
-                // remember which firstvisible we requested, in case the server
-                // has
-                // a differing opinion
-                lastRequestedFirstvisible = firstRowInViewPort;
-                client.updateVariable(paintableId, "firstvisible",
-                        firstRowInViewPort, false);
-                client.updateVariable(paintableId, "reqfirstrow", reqFirstRow,
-                        false);
-                client.updateVariable(paintableId, "reqrows", reqRows, true);
-
-                if (selectionChanged) {
-                    unSyncedselectionsBeforeRowFetch = new HashSet<Object>(
-                            selectedRowKeys);
-                }
-                isRunning = false;
-            }
-        }
-
-        public int getReqFirstRow() {
-            return reqFirstRow;
-        }
-
-        /**
-         * Sends request to refresh content at this position.
-         */
-        public void refreshContent() {
-            isRunning = true;
-            int first = (int) (firstRowInViewPort - pageLength * cache_rate);
-            int reqRows = (int) (2 * pageLength * cache_rate + pageLength);
-            if (first < 0) {
-                reqRows = reqRows + first;
-                first = 0;
-            }
-            setReqFirstRow(first);
-            setReqRows(reqRows);
-            run();
-        }
-    }
-
-    public class HeaderCell extends Widget {
-
-        Element td = DOM.createTD();
-
-        Element captionContainer = DOM.createDiv();
-
-        Element sortIndicator = DOM.createDiv();
-
-        Element colResizeWidget = DOM.createDiv();
-
-        Element floatingCopyOfHeaderCell;
-
-        private boolean sortable = false;
-        private final String cid;
-        private boolean dragging;
-
-        private int dragStartX;
-        private int colIndex;
-        private int originalWidth;
-
-        private boolean isResizing;
-
-        private int headerX;
-
-        private boolean moved;
-
-        private int closestSlot;
-
-        private int width = -1;
-
-        private int naturalWidth = -1;
-
-        private char align = ALIGN_LEFT;
-
-        boolean definedWidth = false;
-
-        private float expandRatio = 0;
-
-        private boolean sorted;
-
-        public void setSortable(boolean b) {
-            sortable = b;
-        }
-
-        /**
-         * Makes room for the sorting indicator in case the column that the
-         * header cell belongs to is sorted. This is done by resizing the width
-         * of the caption container element by the correct amount
-         */
-        public void resizeCaptionContainer(int rightSpacing) {
-            int captionContainerWidth = width
-                    - colResizeWidget.getOffsetWidth() - rightSpacing;
-
-            if (td.getClassName().contains("-asc")
-                    || td.getClassName().contains("-desc")) {
-                // Leave room for the sort indicator
-                captionContainerWidth -= sortIndicator.getOffsetWidth();
-            }
-
-            if (captionContainerWidth < 0) {
-                rightSpacing += captionContainerWidth;
-                captionContainerWidth = 0;
-            }
-
-            captionContainer.getStyle().setPropertyPx("width",
-                    captionContainerWidth);
-
-            // Apply/Remove spacing if defined
-            if (rightSpacing > 0) {
-                colResizeWidget.getStyle().setMarginLeft(rightSpacing, Unit.PX);
-            } else {
-                colResizeWidget.getStyle().clearMarginLeft();
-            }
-        }
-
-        public void setNaturalMinimumColumnWidth(int w) {
-            naturalWidth = w;
-        }
-
-        public HeaderCell(String colId, String headerText) {
-            cid = colId;
-
-            setText(headerText);
-
-            td.appendChild(colResizeWidget);
-
-            // ensure no clipping initially (problem on column additions)
-            captionContainer.getStyle().setOverflow(Overflow.VISIBLE);
-
-            td.appendChild(sortIndicator);
-            td.appendChild(captionContainer);
-
-            DOM.sinkEvents(td, Event.MOUSEEVENTS | Event.ONDBLCLICK
-                    | Event.ONCONTEXTMENU | Event.TOUCHEVENTS);
-
-            setElement(td);
-
-            setAlign(ALIGN_LEFT);
-        }
-
-        protected void updateStyleNames(String primaryStyleName) {
-            colResizeWidget.setClassName(primaryStyleName + "-resizer");
-            sortIndicator.setClassName(primaryStyleName + "-sort-indicator");
-            captionContainer.setClassName(primaryStyleName
-                    + "-caption-container");
-            if (sorted) {
-                if (sortAscending) {
-                    setStyleName(primaryStyleName + "-header-cell-asc");
-                } else {
-                    setStyleName(primaryStyleName + "-header-cell-desc");
-                }
-            } else {
-                setStyleName(primaryStyleName + "-header-cell");
-            }
-
-            final String ALIGN_PREFIX = primaryStyleName
-                    + "-caption-container-align-";
-
-            switch (align) {
-            case ALIGN_CENTER:
-                captionContainer.addClassName(ALIGN_PREFIX + "center");
-                break;
-            case ALIGN_RIGHT:
-                captionContainer.addClassName(ALIGN_PREFIX + "right");
-                break;
-            default:
-                captionContainer.addClassName(ALIGN_PREFIX + "left");
-                break;
-            }
-
-        }
-
-        public void disableAutoWidthCalculation() {
-            definedWidth = true;
-            expandRatio = 0;
-        }
-
-        public void setWidth(int w, boolean ensureDefinedWidth) {
-            if (ensureDefinedWidth) {
-                definedWidth = true;
-                // on column resize expand ratio becomes zero
-                expandRatio = 0;
-            }
-            if (width == -1) {
-                // go to default mode, clip content if necessary
-                DOM.setStyleAttribute(captionContainer, "overflow", "");
-            }
-            width = w;
-            if (w == -1) {
-                DOM.setStyleAttribute(captionContainer, "width", "");
-                setWidth("");
-            } else {
-                tHead.resizeCaptionContainer(this);
-
-                /*
-                 * if we already have tBody, set the header width properly, if
-                 * not defer it. IE will fail with complex float in table header
-                 * unless TD width is not explicitly set.
-                 */
-                if (scrollBody != null) {
-                    int tdWidth = width + scrollBody.getCellExtraWidth();
-                    setWidth(tdWidth + "px");
-                } else {
-                    Scheduler.get().scheduleDeferred(new Command() {
-
-                        @Override
-                        public void execute() {
-                            int tdWidth = width
-                                    + scrollBody.getCellExtraWidth();
-                            setWidth(tdWidth + "px");
-                        }
-                    });
-                }
-            }
-        }
-
-        public void setUndefinedWidth() {
-            definedWidth = false;
-            setWidth(-1, false);
-        }
-
-        /**
-         * Detects if width is fixed by developer on server side or resized to
-         * current width by user.
-         * 
-         * @return true if defined, false if "natural" width
-         */
-        public boolean isDefinedWidth() {
-            return definedWidth && width >= 0;
-        }
-
-        public int getWidth() {
-            return width;
-        }
-
-        public void setText(String headerText) {
-            DOM.setInnerHTML(captionContainer, headerText);
-        }
-
-        public String getColKey() {
-            return cid;
-        }
-
-        private void setSorted(boolean sorted) {
-            this.sorted = sorted;
-            updateStyleNames(VScrollTable.this.getStylePrimaryName());
-        }
-
-        /**
-         * Handle column reordering.
-         */
-
-        @Override
-        public void onBrowserEvent(Event event) {
-            if (enabled && event != null) {
-                if (isResizing
-                        || event.getEventTarget().cast() == colResizeWidget) {
-                    if (dragging
-                            && (event.getTypeInt() == Event.ONMOUSEUP || event
-                                    .getTypeInt() == Event.ONTOUCHEND)) {
-                        // Handle releasing column header on spacer #5318
-                        handleCaptionEvent(event);
-                    } else {
-                        onResizeEvent(event);
-                    }
-                } else {
-                    /*
-                     * Ensure focus before handling caption event. Otherwise
-                     * variables changed from caption event may be before
-                     * variables from other components that fire variables when
-                     * they lose focus.
-                     */
-                    if (event.getTypeInt() == Event.ONMOUSEDOWN
-                            || event.getTypeInt() == Event.ONTOUCHSTART) {
-                        scrollBodyPanel.setFocus(true);
-                    }
-                    handleCaptionEvent(event);
-                    boolean stopPropagation = true;
-                    if (event.getTypeInt() == Event.ONCONTEXTMENU
-                            && !client.hasEventListeners(VScrollTable.this,
-                                    TableConstants.HEADER_CLICK_EVENT_ID)) {
-                        // Prevent showing the browser's context menu only when
-                        // there is a header click listener.
-                        stopPropagation = false;
-                    }
-                    if (stopPropagation) {
-                        event.stopPropagation();
-                        event.preventDefault();
-                    }
-                }
-            }
-        }
-
-        private void createFloatingCopy() {
-            floatingCopyOfHeaderCell = DOM.createDiv();
-            DOM.setInnerHTML(floatingCopyOfHeaderCell, DOM.getInnerHTML(td));
-            floatingCopyOfHeaderCell = DOM
-                    .getChild(floatingCopyOfHeaderCell, 2);
-            DOM.setElementProperty(floatingCopyOfHeaderCell, "className",
-                    VScrollTable.this.getStylePrimaryName() + "-header-drag");
-            // otherwise might wrap or be cut if narrow column
-            DOM.setStyleAttribute(floatingCopyOfHeaderCell, "width", "auto");
-            updateFloatingCopysPosition(DOM.getAbsoluteLeft(td),
-                    DOM.getAbsoluteTop(td));
-            DOM.appendChild(RootPanel.get().getElement(),
-                    floatingCopyOfHeaderCell);
-        }
-
-        private void updateFloatingCopysPosition(int x, int y) {
-            x -= DOM.getElementPropertyInt(floatingCopyOfHeaderCell,
-                    "offsetWidth") / 2;
-            DOM.setStyleAttribute(floatingCopyOfHeaderCell, "left", x + "px");
-            if (y > 0) {
-                DOM.setStyleAttribute(floatingCopyOfHeaderCell, "top", (y + 7)
-                        + "px");
-            }
-        }
-
-        private void hideFloatingCopy() {
-            DOM.removeChild(RootPanel.get().getElement(),
-                    floatingCopyOfHeaderCell);
-            floatingCopyOfHeaderCell = null;
-        }
-
-        /**
-         * Fires a header click event after the user has clicked a column header
-         * cell
-         * 
-         * @param event
-         *            The click event
-         */
-        private void fireHeaderClickedEvent(Event event) {
-            if (client.hasEventListeners(VScrollTable.this,
-                    TableConstants.HEADER_CLICK_EVENT_ID)) {
-                MouseEventDetails details = MouseEventDetailsBuilder
-                        .buildMouseEventDetails(event);
-                client.updateVariable(paintableId, "headerClickEvent",
-                        details.toString(), false);
-                client.updateVariable(paintableId, "headerClickCID", cid, true);
-            }
-        }
-
-        protected void handleCaptionEvent(Event event) {
-            switch (DOM.eventGetType(event)) {
-            case Event.ONTOUCHSTART:
-            case Event.ONMOUSEDOWN:
-                if (columnReordering
-                        && Util.isTouchEventOrLeftMouseButton(event)) {
-                    if (event.getTypeInt() == Event.ONTOUCHSTART) {
-                        /*
-                         * prevent using this event in e.g. scrolling
-                         */
-                        event.stopPropagation();
-                    }
-                    dragging = true;
-                    moved = false;
-                    colIndex = getColIndexByKey(cid);
-                    DOM.setCapture(getElement());
-                    headerX = tHead.getAbsoluteLeft();
-                    event.preventDefault(); // prevent selecting text &&
-                                            // generated touch events
-                }
-                break;
-            case Event.ONMOUSEUP:
-            case Event.ONTOUCHEND:
-            case Event.ONTOUCHCANCEL:
-                if (columnReordering
-                        && Util.isTouchEventOrLeftMouseButton(event)) {
-                    dragging = false;
-                    DOM.releaseCapture(getElement());
-                    if (moved) {
-                        hideFloatingCopy();
-                        tHead.removeSlotFocus();
-                        if (closestSlot != colIndex
-                                && closestSlot != (colIndex + 1)) {
-                            if (closestSlot > colIndex) {
-                                reOrderColumn(cid, closestSlot - 1);
-                            } else {
-                                reOrderColumn(cid, closestSlot);
-                            }
-                        }
-                    }
-                    if (Util.isTouchEvent(event)) {
-                        /*
-                         * Prevent using in e.g. scrolling and prevent generated
-                         * events.
-                         */
-                        event.preventDefault();
-                        event.stopPropagation();
-                    }
-                }
-
-                if (!moved) {
-                    // mouse event was a click to header -> sort column
-                    if (sortable && Util.isTouchEventOrLeftMouseButton(event)) {
-                        if (sortColumn.equals(cid)) {
-                            // just toggle order
-                            client.updateVariable(paintableId, "sortascending",
-                                    !sortAscending, false);
-                        } else {
-                            // set table sorted by this column
-                            client.updateVariable(paintableId, "sortcolumn",
-                                    cid, false);
-                        }
-                        // get also cache columns at the same request
-                        scrollBodyPanel.setScrollPosition(0);
-                        firstvisible = 0;
-                        rowRequestHandler.setReqFirstRow(0);
-                        rowRequestHandler.setReqRows((int) (2 * pageLength
-                                * cache_rate + pageLength));
-                        rowRequestHandler.deferRowFetch(); // some validation +
-                                                           // defer 250ms
-                        rowRequestHandler.cancel(); // instead of waiting
-                        rowRequestHandler.run(); // run immediately
-                    }
-                    fireHeaderClickedEvent(event);
-                    if (Util.isTouchEvent(event)) {
-                        /*
-                         * Prevent using in e.g. scrolling and prevent generated
-                         * events.
-                         */
-                        event.preventDefault();
-                        event.stopPropagation();
-                    }
-                    break;
-                }
-                break;
-            case Event.ONDBLCLICK:
-                fireHeaderClickedEvent(event);
-                break;
-            case Event.ONTOUCHMOVE:
-            case Event.ONMOUSEMOVE:
-                if (dragging && Util.isTouchEventOrLeftMouseButton(event)) {
-                    if (event.getTypeInt() == Event.ONTOUCHMOVE) {
-                        /*
-                         * prevent using this event in e.g. scrolling
-                         */
-                        event.stopPropagation();
-                    }
-                    if (!moved) {
-                        createFloatingCopy();
-                        moved = true;
-                    }
-
-                    final int clientX = Util.getTouchOrMouseClientX(event);
-                    final int x = clientX + tHead.hTableWrapper.getScrollLeft();
-                    int slotX = headerX;
-                    closestSlot = colIndex;
-                    int closestDistance = -1;
-                    int start = 0;
-                    if (showRowHeaders) {
-                        start++;
-                    }
-                    final int visibleCellCount = tHead.getVisibleCellCount();
-                    for (int i = start; i <= visibleCellCount; i++) {
-                        if (i > 0) {
-                            final String colKey = getColKeyByIndex(i - 1);
-                            slotX += getColWidth(colKey);
-                        }
-                        final int dist = Math.abs(x - slotX);
-                        if (closestDistance == -1 || dist < closestDistance) {
-                            closestDistance = dist;
-                            closestSlot = i;
-                        }
-                    }
-                    tHead.focusSlot(closestSlot);
-
-                    updateFloatingCopysPosition(clientX, -1);
-                }
-                break;
-            default:
-                break;
-            }
-        }
-
-        private void onResizeEvent(Event event) {
-            switch (DOM.eventGetType(event)) {
-            case Event.ONMOUSEDOWN:
-                if (!Util.isTouchEventOrLeftMouseButton(event)) {
-                    return;
-                }
-                isResizing = true;
-                DOM.setCapture(getElement());
-                dragStartX = DOM.eventGetClientX(event);
-                colIndex = getColIndexByKey(cid);
-                originalWidth = getWidth();
-                DOM.eventPreventDefault(event);
-                break;
-            case Event.ONMOUSEUP:
-                if (!Util.isTouchEventOrLeftMouseButton(event)) {
-                    return;
-                }
-                isResizing = false;
-                DOM.releaseCapture(getElement());
-                tHead.disableAutoColumnWidthCalculation(this);
-
-                // Ensure last header cell is taking into account possible
-                // column selector
-                HeaderCell lastCell = tHead.getHeaderCell(tHead
-                        .getVisibleCellCount() - 1);
-                tHead.resizeCaptionContainer(lastCell);
-                triggerLazyColumnAdjustment(true);
-
-                fireColumnResizeEvent(cid, originalWidth, getColWidth(cid));
-                break;
-            case Event.ONMOUSEMOVE:
-                if (!Util.isTouchEventOrLeftMouseButton(event)) {
-                    return;
-                }
-                if (isResizing) {
-                    final int deltaX = DOM.eventGetClientX(event) - dragStartX;
-                    if (deltaX == 0) {
-                        return;
-                    }
-                    tHead.disableAutoColumnWidthCalculation(this);
-
-                    int newWidth = originalWidth + deltaX;
-                    if (newWidth < getMinWidth()) {
-                        newWidth = getMinWidth();
-                    }
-                    setColWidth(colIndex, newWidth, true);
-                    triggerLazyColumnAdjustment(false);
-                    forceRealignColumnHeaders();
-                }
-                break;
-            default:
-                break;
-            }
-        }
-
-        public int getMinWidth() {
-            int cellExtraWidth = 0;
-            if (scrollBody != null) {
-                cellExtraWidth += scrollBody.getCellExtraWidth();
-            }
-            return cellExtraWidth + sortIndicator.getOffsetWidth();
-        }
-
-        public String getCaption() {
-            return DOM.getInnerText(captionContainer);
-        }
-
-        public boolean isEnabled() {
-            return getParent() != null;
-        }
-
-        public void setAlign(char c) {
-            align = c;
-            updateStyleNames(VScrollTable.this.getStylePrimaryName());
-        }
-
-        public char getAlign() {
-            return align;
-        }
-
-        /**
-         * Detects the natural minimum width for the column of this header cell.
-         * If column is resized by user or the width is defined by server the
-         * actual width is returned. Else the natural min width is returned.
-         * 
-         * @param columnIndex
-         *            column index hint, if -1 (unknown) it will be detected
-         * 
-         * @return
-         */
-        public int getNaturalColumnWidth(int columnIndex) {
-            if (isDefinedWidth()) {
-                return width;
-            } else {
-                if (naturalWidth < 0) {
-                    // This is recently revealed column. Try to detect a proper
-                    // value (greater of header and data
-                    // cols)
-
-                    int hw = captionContainer.getOffsetWidth()
-                            + scrollBody.getCellExtraWidth();
-                    if (BrowserInfo.get().isGecko()) {
-                        hw += sortIndicator.getOffsetWidth();
-                    }
-                    if (columnIndex < 0) {
-                        columnIndex = 0;
-                        for (Iterator<Widget> it = tHead.iterator(); it
-                                .hasNext(); columnIndex++) {
-                            if (it.next() == this) {
-                                break;
-                            }
-                        }
-                    }
-                    final int cw = scrollBody.getColWidth(columnIndex);
-                    naturalWidth = (hw > cw ? hw : cw);
-                }
-                return naturalWidth;
-            }
-        }
-
-        public void setExpandRatio(float floatAttribute) {
-            if (floatAttribute != expandRatio) {
-                triggerLazyColumnAdjustment(false);
-            }
-            expandRatio = floatAttribute;
-        }
-
-        public float getExpandRatio() {
-            return expandRatio;
-        }
-
-        public boolean isSorted() {
-            return sorted;
-        }
-    }
-
-    /**
-     * HeaderCell that is header cell for row headers.
-     * 
-     * Reordering disabled and clicking on it resets sorting.
-     */
-    public class RowHeadersHeaderCell extends HeaderCell {
-
-        RowHeadersHeaderCell() {
-            super(ROW_HEADER_COLUMN_KEY, "");
-            updateStyleNames(VScrollTable.this.getStylePrimaryName());
-        }
-
-        @Override
-        protected void updateStyleNames(String primaryStyleName) {
-            super.updateStyleNames(primaryStyleName);
-            setStyleName(primaryStyleName + "-header-cell-rowheader");
-        }
-
-        @Override
-        protected void handleCaptionEvent(Event event) {
-            // NOP: RowHeaders cannot be reordered
-            // TODO It'd be nice to reset sorting here
-        }
-    }
-
-    public class TableHead extends Panel implements ActionOwner {
-
-        private static final int WRAPPER_WIDTH = 900000;
-
-        ArrayList<Widget> visibleCells = new ArrayList<Widget>();
-
-        HashMap<String, HeaderCell> availableCells = new HashMap<String, HeaderCell>();
-
-        Element div = DOM.createDiv();
-        Element hTableWrapper = DOM.createDiv();
-        Element hTableContainer = DOM.createDiv();
-        Element table = DOM.createTable();
-        Element headerTableBody = DOM.createTBody();
-        Element tr = DOM.createTR();
-
-        private final Element columnSelector = DOM.createDiv();
-
-        private int focusedSlot = -1;
-
-        public TableHead() {
-            if (BrowserInfo.get().isIE()) {
-                table.setPropertyInt("cellSpacing", 0);
-            }
-
-            DOM.setStyleAttribute(hTableWrapper, "overflow", "hidden");
-            DOM.setStyleAttribute(columnSelector, "display", "none");
-
-            DOM.appendChild(table, headerTableBody);
-            DOM.appendChild(headerTableBody, tr);
-            DOM.appendChild(hTableContainer, table);
-            DOM.appendChild(hTableWrapper, hTableContainer);
-            DOM.appendChild(div, hTableWrapper);
-            DOM.appendChild(div, columnSelector);
-            setElement(div);
-
-            DOM.sinkEvents(columnSelector, Event.ONCLICK);
-
-            availableCells.put(ROW_HEADER_COLUMN_KEY,
-                    new RowHeadersHeaderCell());
-        }
-
-        protected void updateStyleNames(String primaryStyleName) {
-            hTableWrapper.setClassName(primaryStyleName + "-header");
-            columnSelector.setClassName(primaryStyleName + "-column-selector");
-            setStyleName(primaryStyleName + "-header-wrap");
-            for (HeaderCell c : availableCells.values()) {
-                c.updateStyleNames(primaryStyleName);
-            }
-        }
-
-        public void resizeCaptionContainer(HeaderCell cell) {
-            HeaderCell lastcell = getHeaderCell(visibleCells.size() - 1);
-
-            // Measure column widths
-            int columnTotalWidth = 0;
-            for (Widget w : visibleCells) {
-                columnTotalWidth += w.getOffsetWidth();
-            }
-
-            if (cell == lastcell
-                    && columnSelector.getOffsetWidth() > 0
-                    && columnTotalWidth >= div.getOffsetWidth()
-                            - columnSelector.getOffsetWidth()
-                    && !hasVerticalScrollbar()) {
-                // Ensure column caption is visible when placed under the column
-                // selector widget by shifting and resizing the caption.
-                int offset = 0;
-                int diff = div.getOffsetWidth() - columnTotalWidth;
-                if (diff < columnSelector.getOffsetWidth() && diff > 0) {
-                    // If the difference is less than the column selectors width
-                    // then just offset by the
-                    // difference
-                    offset = columnSelector.getOffsetWidth() - diff;
-                } else {
-                    // Else offset by the whole column selector
-                    offset = columnSelector.getOffsetWidth();
-                }
-                lastcell.resizeCaptionContainer(offset);
-            } else {
-                cell.resizeCaptionContainer(0);
-            }
-        }
-
-        @Override
-        public void clear() {
-            for (String cid : availableCells.keySet()) {
-                removeCell(cid);
-            }
-            availableCells.clear();
-            availableCells.put(ROW_HEADER_COLUMN_KEY,
-                    new RowHeadersHeaderCell());
-        }
-
-        public void updateCellsFromUIDL(UIDL uidl) {
-            Iterator<?> it = uidl.getChildIterator();
-            HashSet<String> updated = new HashSet<String>();
-            boolean refreshContentWidths = false;
-            while (it.hasNext()) {
-                final UIDL col = (UIDL) it.next();
-                final String cid = col.getStringAttribute("cid");
-                updated.add(cid);
-
-                String caption = buildCaptionHtmlSnippet(col);
-                HeaderCell c = getHeaderCell(cid);
-                if (c == null) {
-                    c = new HeaderCell(cid, caption);
-                    availableCells.put(cid, c);
-                    if (initializedAndAttached) {
-                        // we will need a column width recalculation
-                        initializedAndAttached = false;
-                        initialContentReceived = false;
-                        isNewBody = true;
-                    }
-                } else {
-                    c.setText(caption);
-                }
-
-                if (col.hasAttribute("sortable")) {
-                    c.setSortable(true);
-                    if (cid.equals(sortColumn)) {
-                        c.setSorted(true);
-                    } else {
-                        c.setSorted(false);
-                    }
-                } else {
-                    c.setSortable(false);
-                }
-
-                if (col.hasAttribute("align")) {
-                    c.setAlign(col.getStringAttribute("align").charAt(0));
-                } else {
-                    c.setAlign(ALIGN_LEFT);
-
-                }
-                if (col.hasAttribute("width")) {
-                    final String widthStr = col.getStringAttribute("width");
-                    // Make sure to accomodate for the sort indicator if
-                    // necessary.
-                    int width = Integer.parseInt(widthStr);
-                    if (width < c.getMinWidth()) {
-                        width = c.getMinWidth();
-                    }
-                    if (width != c.getWidth() && scrollBody != null) {
-                        // Do a more thorough update if a column is resized from
-                        // the server *after* the header has been properly
-                        // initialized
-                        final int colIx = getColIndexByKey(c.cid);
-                        final int newWidth = width;
-                        Scheduler.get().scheduleDeferred(
-                                new ScheduledCommand() {
-
-                                    @Override
-                                    public void execute() {
-                                        setColWidth(colIx, newWidth, true);
-                                    }
-                                });
-                        refreshContentWidths = true;
-                    } else {
-                        c.setWidth(width, true);
-                    }
-                } else if (recalcWidths) {
-                    c.setUndefinedWidth();
-                }
-                if (col.hasAttribute("er")) {
-                    c.setExpandRatio(col.getFloatAttribute("er"));
-                }
-                if (col.hasAttribute("collapsed")) {
-                    // ensure header is properly removed from parent (case when
-                    // collapsing happens via servers side api)
-                    if (c.isAttached()) {
-                        c.removeFromParent();
-                        headerChangedDuringUpdate = true;
-                    }
-                }
-            }
-
-            if (refreshContentWidths) {
-                // Recalculate the column sizings if any column has changed
-                Scheduler.get().scheduleDeferred(new ScheduledCommand() {
-
-                    @Override
-                    public void execute() {
-                        triggerLazyColumnAdjustment(true);
-                    }
-                });
-            }
-
-            // check for orphaned header cells
-            for (Iterator<String> cit = availableCells.keySet().iterator(); cit
-                    .hasNext();) {
-                String cid = cit.next();
-                if (!updated.contains(cid)) {
-                    removeCell(cid);
-                    cit.remove();
-                    // we will need a column width recalculation, since columns
-                    // with expand ratios should expand to fill the void.
-                    initializedAndAttached = false;
-                    initialContentReceived = false;
-                    isNewBody = true;
-                }
-            }
-        }
-
-        public void enableColumn(String cid, int index) {
-            final HeaderCell c = getHeaderCell(cid);
-            if (!c.isEnabled() || getHeaderCell(index) != c) {
-                setHeaderCell(index, c);
-                if (initializedAndAttached) {
-                    headerChangedDuringUpdate = true;
-                }
-            }
-        }
-
-        public int getVisibleCellCount() {
-            return visibleCells.size();
-        }
-
-        public void setHorizontalScrollPosition(int scrollLeft) {
-            hTableWrapper.setScrollLeft(scrollLeft);
-        }
-
-        public void setColumnCollapsingAllowed(boolean cc) {
-            if (cc) {
-                columnSelector.getStyle().setDisplay(Display.BLOCK);
-            } else {
-                columnSelector.getStyle().setDisplay(Display.NONE);
-            }
-        }
-
-        public void disableBrowserIntelligence() {
-            hTableContainer.getStyle().setWidth(WRAPPER_WIDTH, Unit.PX);
-        }
-
-        public void enableBrowserIntelligence() {
-            hTableContainer.getStyle().clearWidth();
-        }
-
-        public void setHeaderCell(int index, HeaderCell cell) {
-            if (cell.isEnabled()) {
-                // we're moving the cell
-                DOM.removeChild(tr, cell.getElement());
-                orphan(cell);
-                visibleCells.remove(cell);
-            }
-            if (index < visibleCells.size()) {
-                // insert to right slot
-                DOM.insertChild(tr, cell.getElement(), index);
-                adopt(cell);
-                visibleCells.add(index, cell);
-            } else if (index == visibleCells.size()) {
-                // simply append
-                DOM.appendChild(tr, cell.getElement());
-                adopt(cell);
-                visibleCells.add(cell);
-            } else {
-                throw new RuntimeException(
-                        "Header cells must be appended in order");
-            }
-        }
-
-        public HeaderCell getHeaderCell(int index) {
-            if (index >= 0 && index < visibleCells.size()) {
-                return (HeaderCell) visibleCells.get(index);
-            } else {
-                return null;
-            }
-        }
-
-        /**
-         * Get's HeaderCell by it's column Key.
-         * 
-         * Note that this returns HeaderCell even if it is currently collapsed.
-         * 
-         * @param cid
-         *            Column key of accessed HeaderCell
-         * @return HeaderCell
-         */
-        public HeaderCell getHeaderCell(String cid) {
-            return availableCells.get(cid);
-        }
-
-        public void moveCell(int oldIndex, int newIndex) {
-            final HeaderCell hCell = getHeaderCell(oldIndex);
-            final Element cell = hCell.getElement();
-
-            visibleCells.remove(oldIndex);
-            DOM.removeChild(tr, cell);
-
-            DOM.insertChild(tr, cell, newIndex);
-            visibleCells.add(newIndex, hCell);
-        }
-
-        @Override
-        public Iterator<Widget> iterator() {
-            return visibleCells.iterator();
-        }
-
-        @Override
-        public boolean remove(Widget w) {
-            if (visibleCells.contains(w)) {
-                visibleCells.remove(w);
-                orphan(w);
-                DOM.removeChild(DOM.getParent(w.getElement()), w.getElement());
-                return true;
-            }
-            return false;
-        }
-
-        public void removeCell(String colKey) {
-            final HeaderCell c = getHeaderCell(colKey);
-            remove(c);
-        }
-
-        private void focusSlot(int index) {
-            removeSlotFocus();
-            if (index > 0) {
-                Element child = tr.getChild(index - 1).getFirstChild().cast();
-                child.setClassName(VScrollTable.this.getStylePrimaryName()
-                        + "-resizer");
-                child.addClassName(VScrollTable.this.getStylePrimaryName()
-                        + "-focus-slot-right");
-            } else {
-                Element child = tr.getChild(index).getFirstChild().cast();
-                child.setClassName(VScrollTable.this.getStylePrimaryName()
-                        + "-resizer");
-                child.addClassName(VScrollTable.this.getStylePrimaryName()
-                        + "-focus-slot-left");
-            }
-            focusedSlot = index;
-        }
-
-        private void removeSlotFocus() {
-            if (focusedSlot < 0) {
-                return;
-            }
-            if (focusedSlot == 0) {
-                Element child = tr.getChild(focusedSlot).getFirstChild().cast();
-                child.setClassName(VScrollTable.this.getStylePrimaryName()
-                        + "-resizer");
-            } else if (focusedSlot > 0) {
-                Element child = tr.getChild(focusedSlot - 1).getFirstChild()
-                        .cast();
-                child.setClassName(VScrollTable.this.getStylePrimaryName()
-                        + "-resizer");
-            }
-            focusedSlot = -1;
-        }
-
-        @Override
-        public void onBrowserEvent(Event event) {
-            if (enabled) {
-                if (event.getEventTarget().cast() == columnSelector) {
-                    final int left = DOM.getAbsoluteLeft(columnSelector);
-                    final int top = DOM.getAbsoluteTop(columnSelector)
-                            + DOM.getElementPropertyInt(columnSelector,
-                                    "offsetHeight");
-                    client.getContextMenu().showAt(this, left, top);
-                }
-            }
-        }
-
-        @Override
-        protected void onDetach() {
-            super.onDetach();
-            if (client != null) {
-                client.getContextMenu().ensureHidden(this);
-            }
-        }
-
-        class VisibleColumnAction extends Action {
-
-            String colKey;
-            private boolean collapsed;
-            private boolean noncollapsible = false;
-            private VScrollTableRow currentlyFocusedRow;
-
-            public VisibleColumnAction(String colKey) {
-                super(VScrollTable.TableHead.this);
-                this.colKey = colKey;
-                caption = tHead.getHeaderCell(colKey).getCaption();
-                currentlyFocusedRow = focusedRow;
-            }
-
-            @Override
-            public void execute() {
-                if (noncollapsible) {
-                    return;
-                }
-                client.getContextMenu().hide();
-                // toggle selected column
-                if (collapsedColumns.contains(colKey)) {
-                    collapsedColumns.remove(colKey);
-                } else {
-                    tHead.removeCell(colKey);
-                    collapsedColumns.add(colKey);
-                    triggerLazyColumnAdjustment(true);
-                }
-
-                // update variable to server
-                client.updateVariable(paintableId, "collapsedcolumns",
-                        collapsedColumns.toArray(new String[collapsedColumns
-                                .size()]), false);
-                // let rowRequestHandler determine proper rows
-                rowRequestHandler.refreshContent();
-                lazyRevertFocusToRow(currentlyFocusedRow);
-            }
-
-            public void setCollapsed(boolean b) {
-                collapsed = b;
-            }
-
-            public void setNoncollapsible(boolean b) {
-                noncollapsible = b;
-            }
-
-            /**
-             * Override default method to distinguish on/off columns
-             */
-
-            @Override
-            public String getHTML() {
-                final StringBuffer buf = new StringBuffer();
-                buf.append("<span class=\"");
-                if (collapsed) {
-                    buf.append("v-off");
-                } else {
-                    buf.append("v-on");
-                }
-                if (noncollapsible) {
-                    buf.append(" v-disabled");
-                }
-                buf.append("\">");
-
-                buf.append(super.getHTML());
-                buf.append("</span>");
-
-                return buf.toString();
-            }
-
-        }
-
-        /*
-         * Returns columns as Action array for column select popup
-         */
-
-        @Override
-        public Action[] getActions() {
-            Object[] cols;
-            if (columnReordering && columnOrder != null) {
-                cols = columnOrder;
-            } else {
-                // if columnReordering is disabled, we need different way to get
-                // all available columns
-                cols = visibleColOrder;
-                cols = new Object[visibleColOrder.length
-                        + collapsedColumns.size()];
-                int i;
-                for (i = 0; i < visibleColOrder.length; i++) {
-                    cols[i] = visibleColOrder[i];
-                }
-                for (final Iterator<String> it = collapsedColumns.iterator(); it
-                        .hasNext();) {
-                    cols[i++] = it.next();
-                }
-            }
-            final Action[] actions = new Action[cols.length];
-
-            for (int i = 0; i < cols.length; i++) {
-                final String cid = (String) cols[i];
-                final HeaderCell c = getHeaderCell(cid);
-                final VisibleColumnAction a = new VisibleColumnAction(
-                        c.getColKey());
-                a.setCaption(c.getCaption());
-                if (!c.isEnabled()) {
-                    a.setCollapsed(true);
-                }
-                if (noncollapsibleColumns.contains(cid)) {
-                    a.setNoncollapsible(true);
-                }
-                actions[i] = a;
-            }
-            return actions;
-        }
-
-        @Override
-        public ApplicationConnection getClient() {
-            return client;
-        }
-
-        @Override
-        public String getPaintableId() {
-            return paintableId;
-        }
-
-        /**
-         * Returns column alignments for visible columns
-         */
-        public char[] getColumnAlignments() {
-            final Iterator<Widget> it = visibleCells.iterator();
-            final char[] aligns = new char[visibleCells.size()];
-            int colIndex = 0;
-            while (it.hasNext()) {
-                aligns[colIndex++] = ((HeaderCell) it.next()).getAlign();
-            }
-            return aligns;
-        }
-
-        /**
-         * Disables the automatic calculation of all column widths by forcing
-         * the widths to be "defined" thus turning off expand ratios and such.
-         */
-        public void disableAutoColumnWidthCalculation(HeaderCell source) {
-            for (HeaderCell cell : availableCells.values()) {
-                cell.disableAutoWidthCalculation();
-            }
-            // fire column resize events for all columns but the source of the
-            // resize action, since an event will fire separately for this.
-            ArrayList<HeaderCell> columns = new ArrayList<HeaderCell>(
-                    availableCells.values());
-            columns.remove(source);
-            sendColumnWidthUpdates(columns);
-            forceRealignColumnHeaders();
-        }
-    }
-
-    /**
-     * A cell in the footer
-     */
-    public class FooterCell extends Widget {
-        private final Element td = DOM.createTD();
-        private final Element captionContainer = DOM.createDiv();
-        private char align = ALIGN_LEFT;
-        private int width = -1;
-        private float expandRatio = 0;
-        private final String cid;
-        boolean definedWidth = false;
-        private int naturalWidth = -1;
-
-        public FooterCell(String colId, String headerText) {
-            cid = colId;
-
-            setText(headerText);
-
-            // ensure no clipping initially (problem on column additions)
-            DOM.setStyleAttribute(captionContainer, "overflow", "visible");
-
-            DOM.sinkEvents(captionContainer, Event.MOUSEEVENTS);
-
-            DOM.appendChild(td, captionContainer);
-
-            DOM.sinkEvents(td, Event.MOUSEEVENTS | Event.ONDBLCLICK
-                    | Event.ONCONTEXTMENU);
-
-            setElement(td);
-
-            updateStyleNames(VScrollTable.this.getStylePrimaryName());
-        }
-
-        protected void updateStyleNames(String primaryStyleName) {
-            captionContainer.setClassName(primaryStyleName
-                    + "-footer-container");
-        }
-
-        /**
-         * Sets the text of the footer
-         * 
-         * @param footerText
-         *            The text in the footer
-         */
-        public void setText(String footerText) {
-            if (footerText == null || footerText.equals("")) {
-                footerText = "&nbsp;";
-            }
-
-            DOM.setInnerHTML(captionContainer, footerText);
-        }
-
-        /**
-         * Set alignment of the text in the cell
-         * 
-         * @param c
-         *            The alignment which can be ALIGN_CENTER, ALIGN_LEFT,
-         *            ALIGN_RIGHT
-         */
-        public void setAlign(char c) {
-            if (align != c) {
-                switch (c) {
-                case ALIGN_CENTER:
-                    DOM.setStyleAttribute(captionContainer, "textAlign",
-                            "center");
-                    break;
-                case ALIGN_RIGHT:
-                    DOM.setStyleAttribute(captionContainer, "textAlign",
-                            "right");
-                    break;
-                default:
-                    DOM.setStyleAttribute(captionContainer, "textAlign", "");
-                    break;
-                }
-            }
-            align = c;
-        }
-
-        /**
-         * Get the alignment of the text int the cell
-         * 
-         * @return Returns either ALIGN_CENTER, ALIGN_LEFT or ALIGN_RIGHT
-         */
-        public char getAlign() {
-            return align;
-        }
-
-        /**
-         * Sets the width of the cell
-         * 
-         * @param w
-         *            The width of the cell
-         * @param ensureDefinedWidth
-         *            Ensures the the given width is not recalculated
-         */
-        public void setWidth(int w, boolean ensureDefinedWidth) {
-
-            if (ensureDefinedWidth) {
-                definedWidth = true;
-                // on column resize expand ratio becomes zero
-                expandRatio = 0;
-            }
-            if (width == w) {
-                return;
-            }
-            if (width == -1) {
-                // go to default mode, clip content if necessary
-                DOM.setStyleAttribute(captionContainer, "overflow", "");
-            }
-            width = w;
-            if (w == -1) {
-                DOM.setStyleAttribute(captionContainer, "width", "");
-                setWidth("");
-            } else {
-                /*
-                 * Reduce width with one pixel for the right border since the
-                 * footers does not have any spacers between them.
-                 */
-                final int borderWidths = 1;
-
-                // Set the container width (check for negative value)
-                captionContainer.getStyle().setPropertyPx("width",
-                        Math.max(w - borderWidths, 0));
-
-                /*
-                 * if we already have tBody, set the header width properly, if
-                 * not defer it. IE will fail with complex float in table header
-                 * unless TD width is not explicitly set.
-                 */
-                if (scrollBody != null) {
-                    int tdWidth = width + scrollBody.getCellExtraWidth()
-                            - borderWidths;
-                    setWidth(Math.max(tdWidth, 0) + "px");
-                } else {
-                    Scheduler.get().scheduleDeferred(new Command() {
-
-                        @Override
-                        public void execute() {
-                            int tdWidth = width
-                                    + scrollBody.getCellExtraWidth()
-                                    - borderWidths;
-                            setWidth(Math.max(tdWidth, 0) + "px");
-                        }
-                    });
-                }
-            }
-        }
-
-        /**
-         * Sets the width to undefined
-         */
-        public void setUndefinedWidth() {
-            setWidth(-1, false);
-        }
-
-        /**
-         * Detects if width is fixed by developer on server side or resized to
-         * current width by user.
-         * 
-         * @return true if defined, false if "natural" width
-         */
-        public boolean isDefinedWidth() {
-            return definedWidth && width >= 0;
-        }
-
-        /**
-         * Returns the pixels width of the footer cell
-         * 
-         * @return The width in pixels
-         */
-        public int getWidth() {
-            return width;
-        }
-
-        /**
-         * Sets the expand ratio of the cell
-         * 
-         * @param floatAttribute
-         *            The expand ratio
-         */
-        public void setExpandRatio(float floatAttribute) {
-            expandRatio = floatAttribute;
-        }
-
-        /**
-         * Returns the expand ration of the cell
-         * 
-         * @return The expand ratio
-         */
-        public float getExpandRatio() {
-            return expandRatio;
-        }
-
-        /**
-         * Is the cell enabled?
-         * 
-         * @return True if enabled else False
-         */
-        public boolean isEnabled() {
-            return getParent() != null;
-        }
-
-        /**
-         * Handle column clicking
-         */
-
-        @Override
-        public void onBrowserEvent(Event event) {
-            if (enabled && event != null) {
-                handleCaptionEvent(event);
-
-                if (DOM.eventGetType(event) == Event.ONMOUSEUP) {
-                    scrollBodyPanel.setFocus(true);
-                }
-                boolean stopPropagation = true;
-                if (event.getTypeInt() == Event.ONCONTEXTMENU
-                        && !client.hasEventListeners(VScrollTable.this,
-                                TableConstants.FOOTER_CLICK_EVENT_ID)) {
-                    // Show browser context menu if a footer click listener is
-                    // not present
-                    stopPropagation = false;
-                }
-                if (stopPropagation) {
-                    event.stopPropagation();
-                    event.preventDefault();
-                }
-            }
-        }
-
-        /**
-         * Handles a event on the captions
-         * 
-         * @param event
-         *            The event to handle
-         */
-        protected void handleCaptionEvent(Event event) {
-            if (event.getTypeInt() == Event.ONMOUSEUP
-                    || event.getTypeInt() == Event.ONDBLCLICK) {
-                fireFooterClickedEvent(event);
-            }
-        }
-
-        /**
-         * Fires a footer click event after the user has clicked a column footer
-         * cell
-         * 
-         * @param event
-         *            The click event
-         */
-        private void fireFooterClickedEvent(Event event) {
-            if (client.hasEventListeners(VScrollTable.this,
-                    TableConstants.FOOTER_CLICK_EVENT_ID)) {
-                MouseEventDetails details = MouseEventDetailsBuilder
-                        .buildMouseEventDetails(event);
-                client.updateVariable(paintableId, "footerClickEvent",
-                        details.toString(), false);
-                client.updateVariable(paintableId, "footerClickCID", cid, true);
-            }
-        }
-
-        /**
-         * Returns the column key of the column
-         * 
-         * @return The column key
-         */
-        public String getColKey() {
-            return cid;
-        }
-
-        /**
-         * Detects the natural minimum width for the column of this header cell.
-         * If column is resized by user or the width is defined by server the
-         * actual width is returned. Else the natural min width is returned.
-         * 
-         * @param columnIndex
-         *            column index hint, if -1 (unknown) it will be detected
-         * 
-         * @return
-         */
-        public int getNaturalColumnWidth(int columnIndex) {
-            if (isDefinedWidth()) {
-                return width;
-            } else {
-                if (naturalWidth < 0) {
-                    // This is recently revealed column. Try to detect a proper
-                    // value (greater of header and data
-                    // cols)
-
-                    final int hw = ((Element) getElement().getLastChild())
-                            .getOffsetWidth() + scrollBody.getCellExtraWidth();
-                    if (columnIndex < 0) {
-                        columnIndex = 0;
-                        for (Iterator<Widget> it = tHead.iterator(); it
-                                .hasNext(); columnIndex++) {
-                            if (it.next() == this) {
-                                break;
-                            }
-                        }
-                    }
-                    final int cw = scrollBody.getColWidth(columnIndex);
-                    naturalWidth = (hw > cw ? hw : cw);
-                }
-                return naturalWidth;
-            }
-        }
-
-        public void setNaturalMinimumColumnWidth(int w) {
-            naturalWidth = w;
-        }
-    }
-
-    /**
-     * HeaderCell that is header cell for row headers.
-     * 
-     * Reordering disabled and clicking on it resets sorting.
-     */
-    public class RowHeadersFooterCell extends FooterCell {
-
-        RowHeadersFooterCell() {
-            super(ROW_HEADER_COLUMN_KEY, "");
-        }
-
-        @Override
-        protected void handleCaptionEvent(Event event) {
-            // NOP: RowHeaders cannot be reordered
-            // TODO It'd be nice to reset sorting here
-        }
-    }
-
-    /**
-     * The footer of the table which can be seen in the bottom of the Table.
-     */
-    public class TableFooter extends Panel {
-
-        private static final int WRAPPER_WIDTH = 900000;
-
-        ArrayList<Widget> visibleCells = new ArrayList<Widget>();
-        HashMap<String, FooterCell> availableCells = new HashMap<String, FooterCell>();
-
-        Element div = DOM.createDiv();
-        Element hTableWrapper = DOM.createDiv();
-        Element hTableContainer = DOM.createDiv();
-        Element table = DOM.createTable();
-        Element headerTableBody = DOM.createTBody();
-        Element tr = DOM.createTR();
-
-        public TableFooter() {
-
-            DOM.setStyleAttribute(hTableWrapper, "overflow", "hidden");
-
-            DOM.appendChild(table, headerTableBody);
-            DOM.appendChild(headerTableBody, tr);
-            DOM.appendChild(hTableContainer, table);
-            DOM.appendChild(hTableWrapper, hTableContainer);
-            DOM.appendChild(div, hTableWrapper);
-            setElement(div);
-
-            availableCells.put(ROW_HEADER_COLUMN_KEY,
-                    new RowHeadersFooterCell());
-
-            updateStyleNames(VScrollTable.this.getStylePrimaryName());
-        }
-
-        protected void updateStyleNames(String primaryStyleName) {
-            hTableWrapper.setClassName(primaryStyleName + "-footer");
-            setStyleName(primaryStyleName + "-footer-wrap");
-            for (FooterCell c : availableCells.values()) {
-                c.updateStyleNames(primaryStyleName);
-            }
-        }
-
-        @Override
-        public void clear() {
-            for (String cid : availableCells.keySet()) {
-                removeCell(cid);
-            }
-            availableCells.clear();
-            availableCells.put(ROW_HEADER_COLUMN_KEY,
-                    new RowHeadersFooterCell());
-        }
-
-        /*
-         * (non-Javadoc)
-         * 
-         * @see
-         * com.google.gwt.user.client.ui.Panel#remove(com.google.gwt.user.client
-         * .ui.Widget)
-         */
-
-        @Override
-        public boolean remove(Widget w) {
-            if (visibleCells.contains(w)) {
-                visibleCells.remove(w);
-                orphan(w);
-                DOM.removeChild(DOM.getParent(w.getElement()), w.getElement());
-                return true;
-            }
-            return false;
-        }
-
-        /*
-         * (non-Javadoc)
-         * 
-         * @see com.google.gwt.user.client.ui.HasWidgets#iterator()
-         */
-
-        @Override
-        public Iterator<Widget> iterator() {
-            return visibleCells.iterator();
-        }
-
-        /**
-         * Gets a footer cell which represents the given columnId
-         * 
-         * @param cid
-         *            The columnId
-         * 
-         * @return The cell
-         */
-        public FooterCell getFooterCell(String cid) {
-            return availableCells.get(cid);
-        }
-
-        /**
-         * Gets a footer cell by using a column index
-         * 
-         * @param index
-         *            The index of the column
-         * @return The Cell
-         */
-        public FooterCell getFooterCell(int index) {
-            if (index < visibleCells.size()) {
-                return (FooterCell) visibleCells.get(index);
-            } else {
-                return null;
-            }
-        }
-
-        /**
-         * Updates the cells contents when updateUIDL request is received
-         * 
-         * @param uidl
-         *            The UIDL
-         */
-        public void updateCellsFromUIDL(UIDL uidl) {
-            Iterator<?> columnIterator = uidl.getChildIterator();
-            HashSet<String> updated = new HashSet<String>();
-            while (columnIterator.hasNext()) {
-                final UIDL col = (UIDL) columnIterator.next();
-                final String cid = col.getStringAttribute("cid");
-                updated.add(cid);
-
-                String caption = col.hasAttribute("fcaption") ? col
-                        .getStringAttribute("fcaption") : "";
-                FooterCell c = getFooterCell(cid);
-                if (c == null) {
-                    c = new FooterCell(cid, caption);
-                    availableCells.put(cid, c);
-                    if (initializedAndAttached) {
-                        // we will need a column width recalculation
-                        initializedAndAttached = false;
-                        initialContentReceived = false;
-                        isNewBody = true;
-                    }
-                } else {
-                    c.setText(caption);
-                }
-
-                if (col.hasAttribute("align")) {
-                    c.setAlign(col.getStringAttribute("align").charAt(0));
-                } else {
-                    c.setAlign(ALIGN_LEFT);
-
-                }
-                if (col.hasAttribute("width")) {
-                    if (scrollBody == null) {
-                        // Already updated by setColWidth called from
-                        // TableHeads.updateCellsFromUIDL in case of a server
-                        // side resize
-                        final String width = col.getStringAttribute("width");
-                        c.setWidth(Integer.parseInt(width), true);
-                    }
-                } else if (recalcWidths) {
-                    c.setUndefinedWidth();
-                }
-                if (col.hasAttribute("er")) {
-                    c.setExpandRatio(col.getFloatAttribute("er"));
-                }
-                if (col.hasAttribute("collapsed")) {
-                    // ensure header is properly removed from parent (case when
-                    // collapsing happens via servers side api)
-                    if (c.isAttached()) {
-                        c.removeFromParent();
-                        headerChangedDuringUpdate = true;
-                    }
-                }
-            }
-
-            // check for orphaned header cells
-            for (Iterator<String> cit = availableCells.keySet().iterator(); cit
-                    .hasNext();) {
-                String cid = cit.next();
-                if (!updated.contains(cid)) {
-                    removeCell(cid);
-                    cit.remove();
-                }
-            }
-        }
-
-        /**
-         * Set a footer cell for a specified column index
-         * 
-         * @param index
-         *            The index
-         * @param cell
-         *            The footer cell
-         */
-        public void setFooterCell(int index, FooterCell cell) {
-            if (cell.isEnabled()) {
-                // we're moving the cell
-                DOM.removeChild(tr, cell.getElement());
-                orphan(cell);
-                visibleCells.remove(cell);
-            }
-            if (index < visibleCells.size()) {
-                // insert to right slot
-                DOM.insertChild(tr, cell.getElement(), index);
-                adopt(cell);
-                visibleCells.add(index, cell);
-            } else if (index == visibleCells.size()) {
-                // simply append
-                DOM.appendChild(tr, cell.getElement());
-                adopt(cell);
-                visibleCells.add(cell);
-            } else {
-                throw new RuntimeException(
-                        "Header cells must be appended in order");
-            }
-        }
-
-        /**
-         * Remove a cell by using the columnId
-         * 
-         * @param colKey
-         *            The columnId to remove
-         */
-        public void removeCell(String colKey) {
-            final FooterCell c = getFooterCell(colKey);
-            remove(c);
-        }
-
-        /**
-         * Enable a column (Sets the footer cell)
-         * 
-         * @param cid
-         *            The columnId
-         * @param index
-         *            The index of the column
-         */
-        public void enableColumn(String cid, int index) {
-            final FooterCell c = getFooterCell(cid);
-            if (!c.isEnabled() || getFooterCell(index) != c) {
-                setFooterCell(index, c);
-                if (initializedAndAttached) {
-                    headerChangedDuringUpdate = true;
-                }
-            }
-        }
-
-        /**
-         * Disable browser measurement of the table width
-         */
-        public void disableBrowserIntelligence() {
-            DOM.setStyleAttribute(hTableContainer, "width", WRAPPER_WIDTH
-                    + "px");
-        }
-
-        /**
-         * Enable browser measurement of the table width
-         */
-        public void enableBrowserIntelligence() {
-            DOM.setStyleAttribute(hTableContainer, "width", "");
-        }
-
-        /**
-         * Set the horizontal position in the cell in the footer. This is done
-         * when a horizontal scrollbar is present.
-         * 
-         * @param scrollLeft
-         *            The value of the leftScroll
-         */
-        public void setHorizontalScrollPosition(int scrollLeft) {
-            hTableWrapper.setScrollLeft(scrollLeft);
-        }
-
-        /**
-         * Swap cells when the column are dragged
-         * 
-         * @param oldIndex
-         *            The old index of the cell
-         * @param newIndex
-         *            The new index of the cell
-         */
-        public void moveCell(int oldIndex, int newIndex) {
-            final FooterCell hCell = getFooterCell(oldIndex);
-            final Element cell = hCell.getElement();
-
-            visibleCells.remove(oldIndex);
-            DOM.removeChild(tr, cell);
-
-            DOM.insertChild(tr, cell, newIndex);
-            visibleCells.add(newIndex, hCell);
-        }
-    }
-
-    /**
-     * This Panel can only contain VScrollTableRow type of widgets. This
-     * "simulates" very large table, keeping spacers which take room of
-     * unrendered rows.
-     * 
-     */
-    public class VScrollTableBody extends Panel {
-
-        public static final int DEFAULT_ROW_HEIGHT = 24;
-
-        private double rowHeight = -1;
-
-        private final LinkedList<Widget> renderedRows = new LinkedList<Widget>();
-
-        /**
-         * Due some optimizations row height measuring is deferred and initial
-         * set of rows is rendered detached. Flag set on when table body has
-         * been attached in dom and rowheight has been measured.
-         */
-        private boolean tBodyMeasurementsDone = false;
-
-        Element preSpacer = DOM.createDiv();
-        Element postSpacer = DOM.createDiv();
-
-        Element container = DOM.createDiv();
-
-        TableSectionElement tBodyElement = Document.get().createTBodyElement();
-        Element table = DOM.createTable();
-
-        private int firstRendered;
-        private int lastRendered;
-
-        private char[] aligns;
-
-        protected VScrollTableBody() {
-            constructDOM();
-            setElement(container);
-        }
-
-        public VScrollTableRow getRowByRowIndex(int indexInTable) {
-            int internalIndex = indexInTable - firstRendered;
-            if (internalIndex >= 0 && internalIndex < renderedRows.size()) {
-                return (VScrollTableRow) renderedRows.get(internalIndex);
-            } else {
-                return null;
-            }
-        }
-
-        /**
-         * @return the height of scrollable body, subpixels ceiled.
-         */
-        public int getRequiredHeight() {
-            return preSpacer.getOffsetHeight() + postSpacer.getOffsetHeight()
-                    + Util.getRequiredHeight(table);
-        }
-
-        private void constructDOM() {
-            if (BrowserInfo.get().isIE()) {
-                table.setPropertyInt("cellSpacing", 0);
-            }
-
-            table.appendChild(tBodyElement);
-            DOM.appendChild(container, preSpacer);
-            DOM.appendChild(container, table);
-            DOM.appendChild(container, postSpacer);
-            if (BrowserInfo.get().requiresTouchScrollDelegate()) {
-                NodeList<Node> childNodes = container.getChildNodes();
-                for (int i = 0; i < childNodes.getLength(); i++) {
-                    Element item = (Element) childNodes.getItem(i);
-                    item.getStyle().setProperty("webkitTransform",
-                            "translate3d(0,0,0)");
-                }
-            }
-            updateStyleNames(VScrollTable.this.getStylePrimaryName());
-        }
-
-        protected void updateStyleNames(String primaryStyleName) {
-            table.setClassName(primaryStyleName + "-table");
-            preSpacer.setClassName(primaryStyleName + "-row-spacer");
-            postSpacer.setClassName(primaryStyleName + "-row-spacer");
-            for (Widget w : renderedRows) {
-                VScrollTableRow row = (VScrollTableRow) w;
-                row.updateStyleNames(primaryStyleName);
-            }
-        }
-
-        public int getAvailableWidth() {
-            int availW = scrollBodyPanel.getOffsetWidth() - getBorderWidth();
-            return availW;
-        }
-
-        public void renderInitialRows(UIDL rowData, int firstIndex, int rows) {
-            firstRendered = firstIndex;
-            lastRendered = firstIndex + rows - 1;
-            final Iterator<?> it = rowData.getChildIterator();
-            aligns = tHead.getColumnAlignments();
-            while (it.hasNext()) {
-                final VScrollTableRow row = createRow((UIDL) it.next(), aligns);
-                addRow(row);
-            }
-            if (isAttached()) {
-                fixSpacers();
-            }
-        }
-
-        public void renderRows(UIDL rowData, int firstIndex, int rows) {
-            // FIXME REVIEW
-            aligns = tHead.getColumnAlignments();
-            final Iterator<?> it = rowData.getChildIterator();
-            if (firstIndex == lastRendered + 1) {
-                while (it.hasNext()) {
-                    final VScrollTableRow row = prepareRow((UIDL) it.next());
-                    addRow(row);
-                    lastRendered++;
-                }
-                fixSpacers();
-            } else if (firstIndex + rows == firstRendered) {
-                final VScrollTableRow[] rowArray = new VScrollTableRow[rows];
-                int i = rows;
-                while (it.hasNext()) {
-                    i--;
-                    rowArray[i] = prepareRow((UIDL) it.next());
-                }
-                for (i = 0; i < rows; i++) {
-                    addRowBeforeFirstRendered(rowArray[i]);
-                    firstRendered--;
-                }
-            } else {
-                // completely new set of rows
-                while (lastRendered + 1 > firstRendered) {
-                    unlinkRow(false);
-                }
-                final VScrollTableRow row = prepareRow((UIDL) it.next());
-                firstRendered = firstIndex;
-                lastRendered = firstIndex - 1;
-                addRow(row);
-                lastRendered++;
-                setContainerHeight();
-                fixSpacers();
-                while (it.hasNext()) {
-                    addRow(prepareRow((UIDL) it.next()));
-                    lastRendered++;
-                }
-                fixSpacers();
-            }
-
-            // this may be a new set of rows due content change,
-            // ensure we have proper cache rows
-            ensureCacheFilled();
-        }
-
-        /**
-         * Ensure we have the correct set of rows on client side, e.g. if the
-         * content on the server side has changed, or the client scroll position
-         * has changed since the last request.
-         */
-        protected void ensureCacheFilled() {
-            int reactFirstRow = (int) (firstRowInViewPort - pageLength
-                    * cache_react_rate);
-            int reactLastRow = (int) (firstRowInViewPort + pageLength + pageLength
-                    * cache_react_rate);
-            if (reactFirstRow < 0) {
-                reactFirstRow = 0;
-            }
-            if (reactLastRow >= totalRows) {
-                reactLastRow = totalRows - 1;
-            }
-            if (lastRendered < reactFirstRow || firstRendered > reactLastRow) {
-                /*
-                 * #8040 - scroll position is completely changed since the
-                 * latest request, so request a new set of rows.
-                 * 
-                 * TODO: We should probably check whether the fetched rows match
-                 * the current scroll position right when they arrive, so as to
-                 * not waste time rendering a set of rows that will never be
-                 * visible...
-                 */
-                rowRequestHandler.setReqFirstRow(reactFirstRow);
-                rowRequestHandler.setReqRows(reactLastRow - reactFirstRow + 1);
-                rowRequestHandler.deferRowFetch(1);
-            } else if (lastRendered < reactLastRow) {
-                // get some cache rows below visible area
-                rowRequestHandler.setReqFirstRow(lastRendered + 1);
-                rowRequestHandler.setReqRows(reactLastRow - lastRendered);
-                rowRequestHandler.deferRowFetch(1);
-            } else if (firstRendered > reactFirstRow) {
-                /*
-                 * Branch for fetching cache above visible area.
-                 * 
-                 * If cache needed for both before and after visible area, this
-                 * will be rendered after-cache is received and rendered. So in
-                 * some rare situations the table may make two cache visits to
-                 * server.
-                 */
-                rowRequestHandler.setReqFirstRow(reactFirstRow);
-                rowRequestHandler.setReqRows(firstRendered - reactFirstRow);
-                rowRequestHandler.deferRowFetch(1);
-            }
-        }
-
-        /**
-         * Inserts rows as provided in the rowData starting at firstIndex.
-         * 
-         * @param rowData
-         * @param firstIndex
-         * @param rows
-         *            the number of rows
-         * @return a list of the rows added.
-         */
-        protected List<VScrollTableRow> insertRows(UIDL rowData,
-                int firstIndex, int rows) {
-            aligns = tHead.getColumnAlignments();
-            final Iterator<?> it = rowData.getChildIterator();
-            List<VScrollTableRow> insertedRows = new ArrayList<VScrollTableRow>();
-
-            if (firstIndex == lastRendered + 1) {
-                while (it.hasNext()) {
-                    final VScrollTableRow row = prepareRow((UIDL) it.next());
-                    addRow(row);
-                    insertedRows.add(row);
-                    lastRendered++;
-                }
-                fixSpacers();
-            } else if (firstIndex + rows == firstRendered) {
-                final VScrollTableRow[] rowArray = new VScrollTableRow[rows];
-                int i = rows;
-                while (it.hasNext()) {
-                    i--;
-                    rowArray[i] = prepareRow((UIDL) it.next());
-                }
-                for (i = 0; i < rows; i++) {
-                    addRowBeforeFirstRendered(rowArray[i]);
-                    insertedRows.add(rowArray[i]);
-                    firstRendered--;
-                }
-            } else {
-                // insert in the middle
-                int ix = firstIndex;
-                while (it.hasNext()) {
-                    VScrollTableRow row = prepareRow((UIDL) it.next());
-                    insertRowAt(row, ix);
-                    insertedRows.add(row);
-                    lastRendered++;
-                    ix++;
-                }
-                fixSpacers();
-            }
-            return insertedRows;
-        }
-
-        protected List<VScrollTableRow> insertAndReindexRows(UIDL rowData,
-                int firstIndex, int rows) {
-            List<VScrollTableRow> inserted = insertRows(rowData, firstIndex,
-                    rows);
-            int actualIxOfFirstRowAfterInserted = firstIndex + rows
-                    - firstRendered;
-            for (int ix = actualIxOfFirstRowAfterInserted; ix < renderedRows
-                    .size(); ix++) {
-                VScrollTableRow r = (VScrollTableRow) renderedRows.get(ix);
-                r.setIndex(r.getIndex() + rows);
-            }
-            setContainerHeight();
-            return inserted;
-        }
-
-        protected void insertRowsDeleteBelow(UIDL rowData, int firstIndex,
-                int rows) {
-            unlinkAllRowsStartingAt(firstIndex);
-            insertRows(rowData, firstIndex, rows);
-            setContainerHeight();
-        }
-
-        /**
-         * This method is used to instantiate new rows for this table. It
-         * automatically sets correct widths to rows cells and assigns correct
-         * client reference for child widgets.
-         * 
-         * This method can be called only after table has been initialized
-         * 
-         * @param uidl
-         */
-        private VScrollTableRow prepareRow(UIDL uidl) {
-            final VScrollTableRow row = createRow(uidl, aligns);
-            row.initCellWidths();
-            return row;
-        }
-
-        protected VScrollTableRow createRow(UIDL uidl, char[] aligns2) {
-            if (uidl.hasAttribute("gen_html")) {
-                // This is a generated row.
-                return new VScrollTableGeneratedRow(uidl, aligns2);
-            }
-            return new VScrollTableRow(uidl, aligns2);
-        }
-
-        private void addRowBeforeFirstRendered(VScrollTableRow row) {
-            row.setIndex(firstRendered - 1);
-            if (row.isSelected()) {
-                row.addStyleName("v-selected");
-            }
-            tBodyElement.insertBefore(row.getElement(),
-                    tBodyElement.getFirstChild());
-            adopt(row);
-            renderedRows.add(0, row);
-        }
-
-        private void addRow(VScrollTableRow row) {
-            row.setIndex(firstRendered + renderedRows.size());
-            if (row.isSelected()) {
-                row.addStyleName("v-selected");
-            }
-            tBodyElement.appendChild(row.getElement());
-            // Add to renderedRows before adopt so iterator() will return also
-            // this row if called in an attach handler (#9264)
-            renderedRows.add(row);
-            adopt(row);
-        }
-
-        private void insertRowAt(VScrollTableRow row, int index) {
-            row.setIndex(index);
-            if (row.isSelected()) {
-                row.addStyleName("v-selected");
-            }
-            if (index > 0) {
-                VScrollTableRow sibling = getRowByRowIndex(index - 1);
-                tBodyElement
-                        .insertAfter(row.getElement(), sibling.getElement());
-            } else {
-                VScrollTableRow sibling = getRowByRowIndex(index);
-                tBodyElement.insertBefore(row.getElement(),
-                        sibling.getElement());
-            }
-            adopt(row);
-            int actualIx = index - firstRendered;
-            renderedRows.add(actualIx, row);
-        }
-
-        @Override
-        public Iterator<Widget> iterator() {
-            return renderedRows.iterator();
-        }
-
-        /**
-         * @return false if couldn't remove row
-         */
-        protected boolean unlinkRow(boolean fromBeginning) {
-            if (lastRendered - firstRendered < 0) {
-                return false;
-            }
-            int actualIx;
-            if (fromBeginning) {
-                actualIx = 0;
-                firstRendered++;
-            } else {
-                actualIx = renderedRows.size() - 1;
-                lastRendered--;
-            }
-            if (actualIx >= 0) {
-                unlinkRowAtActualIndex(actualIx);
-                fixSpacers();
-                return true;
-            }
-            return false;
-        }
-
-        protected void unlinkRows(int firstIndex, int count) {
-            if (count < 1) {
-                return;
-            }
-            if (firstRendered > firstIndex
-                    && firstRendered < firstIndex + count) {
-                firstIndex = firstRendered;
-            }
-            int lastIndex = firstIndex + count - 1;
-            if (lastRendered < lastIndex) {
-                lastIndex = lastRendered;
-            }
-            for (int ix = lastIndex; ix >= firstIndex; ix--) {
-                unlinkRowAtActualIndex(actualIndex(ix));
-                lastRendered--;
-            }
-            fixSpacers();
-        }
-
-        protected void unlinkAndReindexRows(int firstIndex, int count) {
-            unlinkRows(firstIndex, count);
-            int actualFirstIx = firstIndex - firstRendered;
-            for (int ix = actualFirstIx; ix < renderedRows.size(); ix++) {
-                VScrollTableRow r = (VScrollTableRow) renderedRows.get(ix);
-                r.setIndex(r.getIndex() - count);
-            }
-            setContainerHeight();
-        }
-
-        protected void unlinkAllRowsStartingAt(int index) {
-            if (firstRendered > index) {
-                index = firstRendered;
-            }
-            for (int ix = renderedRows.size() - 1; ix >= index; ix--) {
-                unlinkRowAtActualIndex(actualIndex(ix));
-                lastRendered--;
-            }
-            fixSpacers();
-        }
-
-        private int actualIndex(int index) {
-            return index - firstRendered;
-        }
-
-        private void unlinkRowAtActualIndex(int index) {
-            final VScrollTableRow toBeRemoved = (VScrollTableRow) renderedRows
-                    .get(index);
-            tBodyElement.removeChild(toBeRemoved.getElement());
-            orphan(toBeRemoved);
-            renderedRows.remove(index);
-        }
-
-        @Override
-        public boolean remove(Widget w) {
-            throw new UnsupportedOperationException();
-        }
-
-        /**
-         * Fix container blocks height according to totalRows to avoid
-         * "bouncing" when scrolling
-         */
-        private void setContainerHeight() {
-            fixSpacers();
-            DOM.setStyleAttribute(container, "height",
-                    measureRowHeightOffset(totalRows) + "px");
-        }
-
-        private void fixSpacers() {
-            int prepx = measureRowHeightOffset(firstRendered);
-            if (prepx < 0) {
-                prepx = 0;
-            }
-            preSpacer.getStyle().setPropertyPx("height", prepx);
-            int postpx = measureRowHeightOffset(totalRows - 1)
-                    - measureRowHeightOffset(lastRendered);
-            if (postpx < 0) {
-                postpx = 0;
-            }
-            postSpacer.getStyle().setPropertyPx("height", postpx);
-        }
-
-        public double getRowHeight() {
-            return getRowHeight(false);
-        }
-
-        public double getRowHeight(boolean forceUpdate) {
-            if (tBodyMeasurementsDone && !forceUpdate) {
-                return rowHeight;
-            } else {
-                if (tBodyElement.getRows().getLength() > 0) {
-                    int tableHeight = getTableHeight();
-                    int rowCount = tBodyElement.getRows().getLength();
-                    rowHeight = tableHeight / (double) rowCount;
-                } else {
-                    // Special cases if we can't just measure the current rows
-                    if (!Double.isNaN(lastKnownRowHeight)) {
-                        // Use previous value if available
-                        if (BrowserInfo.get().isIE()) {
-                            /*
-                             * IE needs to reflow the table element at this
-                             * point to work correctly (e.g.
-                             * com.vaadin.tests.components.table.
-                             * ContainerSizeChange) - the other code paths
-                             * already trigger reflows, but here it must be done
-                             * explicitly.
-                             */
-                            getTableHeight();
-                        }
-                        rowHeight = lastKnownRowHeight;
-                    } else if (isAttached()) {
-                        // measure row height by adding a dummy row
-                        VScrollTableRow scrollTableRow = new VScrollTableRow();
-                        tBodyElement.appendChild(scrollTableRow.getElement());
-                        getRowHeight(forceUpdate);
-                        tBodyElement.removeChild(scrollTableRow.getElement());
-                    } else {
-                        // TODO investigate if this can never happen anymore
-                        return DEFAULT_ROW_HEIGHT;
-                    }
-                }
-                lastKnownRowHeight = rowHeight;
-                tBodyMeasurementsDone = true;
-                return rowHeight;
-            }
-        }
-
-        public int getTableHeight() {
-            return table.getOffsetHeight();
-        }
-
-        /**
-         * Returns the width available for column content.
-         * 
-         * @param columnIndex
-         * @return
-         */
-        public int getColWidth(int columnIndex) {
-            if (tBodyMeasurementsDone) {
-                if (renderedRows.isEmpty()) {
-                    // no rows yet rendered
-                    return 0;
-                }
-                for (Widget row : renderedRows) {
-                    if (!(row instanceof VScrollTableGeneratedRow)) {
-                        TableRowElement tr = row.getElement().cast();
-                        Element wrapperdiv = tr.getCells().getItem(columnIndex)
-                                .getFirstChildElement().cast();
-                        return wrapperdiv.getOffsetWidth();
-                    }
-                }
-                return 0;
-            } else {
-                return 0;
-            }
-        }
-
-        /**
-         * Sets the content width of a column.
-         * 
-         * Due IE limitation, we must set the width to a wrapper elements inside
-         * table cells (with overflow hidden, which does not work on td
-         * elements).
-         * 
-         * To get this work properly crossplatform, we will also set the width
-         * of td.
-         * 
-         * @param colIndex
-         * @param w
-         */
-        public void setColWidth(int colIndex, int w) {
-            for (Widget row : renderedRows) {
-                ((VScrollTableRow) row).setCellWidth(colIndex, w);
-            }
-        }
-
-        private int cellExtraWidth = -1;
-
-        /**
-         * Method to return the space used for cell paddings + border.
-         */
-        private int getCellExtraWidth() {
-            if (cellExtraWidth < 0) {
-                detectExtrawidth();
-            }
-            return cellExtraWidth;
-        }
-
-        private void detectExtrawidth() {
-            NodeList<TableRowElement> rows = tBodyElement.getRows();
-            if (rows.getLength() == 0) {
-                /* need to temporary add empty row and detect */
-                VScrollTableRow scrollTableRow = new VScrollTableRow();
-                scrollTableRow.updateStyleNames(VScrollTable.this
-                        .getStylePrimaryName());
-                tBodyElement.appendChild(scrollTableRow.getElement());
-                detectExtrawidth();
-                tBodyElement.removeChild(scrollTableRow.getElement());
-            } else {
-                boolean noCells = false;
-                TableRowElement item = rows.getItem(0);
-                TableCellElement firstTD = item.getCells().getItem(0);
-                if (firstTD == null) {
-                    // content is currently empty, we need to add a fake cell
-                    // for measuring
-                    noCells = true;
-                    VScrollTableRow next = (VScrollTableRow) iterator().next();
-                    boolean sorted = tHead.getHeaderCell(0) != null ? tHead
-                            .getHeaderCell(0).isSorted() : false;
-                    next.addCell(null, "", ALIGN_LEFT, "", true, sorted);
-                    firstTD = item.getCells().getItem(0);
-                }
-                com.google.gwt.dom.client.Element wrapper = firstTD
-                        .getFirstChildElement();
-                cellExtraWidth = firstTD.getOffsetWidth()
-                        - wrapper.getOffsetWidth();
-                if (noCells) {
-                    firstTD.getParentElement().removeChild(firstTD);
-                }
-            }
-        }
-
-        private void reLayoutComponents() {
-            for (Widget w : this) {
-                VScrollTableRow r = (VScrollTableRow) w;
-                for (Widget widget : r) {
-                    client.handleComponentRelativeSize(widget);
-                }
-            }
-        }
-
-        public int getLastRendered() {
-            return lastRendered;
-        }
-
-        public int getFirstRendered() {
-            return firstRendered;
-        }
-
-        public void moveCol(int oldIndex, int newIndex) {
-
-            // loop all rows and move given index to its new place
-            final Iterator<?> rows = iterator();
-            while (rows.hasNext()) {
-                final VScrollTableRow row = (VScrollTableRow) rows.next();
-
-                final Element td = DOM.getChild(row.getElement(), oldIndex);
-                if (td != null) {
-                    DOM.removeChild(row.getElement(), td);
-
-                    DOM.insertChild(row.getElement(), td, newIndex);
-                }
-            }
-
-        }
-
-        /**
-         * Restore row visibility which is set to "none" when the row is
-         * rendered (due a performance optimization).
-         */
-        private void restoreRowVisibility() {
-            for (Widget row : renderedRows) {
-                row.getElement().getStyle().setProperty("visibility", "");
-            }
-        }
-
-        public class VScrollTableRow extends Panel implements ActionOwner {
-
-            private static final int TOUCHSCROLL_TIMEOUT = 100;
-            private static final int DRAGMODE_MULTIROW = 2;
-            protected ArrayList<Widget> childWidgets = new ArrayList<Widget>();
-            private boolean selected = false;
-            protected final int rowKey;
-
-            private String[] actionKeys = null;
-            private final TableRowElement rowElement;
-            private int index;
-            private Event touchStart;
-
-            private static final int TOUCH_CONTEXT_MENU_TIMEOUT = 500;
-            private Timer contextTouchTimeout;
-            private Timer dragTouchTimeout;
-            private int touchStartY;
-            private int touchStartX;
-            private TooltipInfo tooltipInfo = null;
-            private Map<TableCellElement, TooltipInfo> cellToolTips = new HashMap<TableCellElement, TooltipInfo>();
-            private boolean isDragging = false;
-            private String rowStyle = null;
-
-            private VScrollTableRow(int rowKey) {
-                this.rowKey = rowKey;
-                rowElement = Document.get().createTRElement();
-                setElement(rowElement);
-                DOM.sinkEvents(getElement(), Event.MOUSEEVENTS
-                        | Event.TOUCHEVENTS | Event.ONDBLCLICK
-                        | Event.ONCONTEXTMENU | VTooltip.TOOLTIP_EVENTS);
-            }
-
-            public VScrollTableRow(UIDL uidl, char[] aligns) {
-                this(uidl.getIntAttribute("key"));
-
-                /*
-                 * Rendering the rows as hidden improves Firefox and Safari
-                 * performance drastically.
-                 */
-                getElement().getStyle().setProperty("visibility", "hidden");
-
-                rowStyle = uidl.getStringAttribute("rowstyle");
-                updateStyleNames(VScrollTable.this.getStylePrimaryName());
-
-                String rowDescription = uidl.getStringAttribute("rowdescr");
-                if (rowDescription != null && !rowDescription.equals("")) {
-                    tooltipInfo = new TooltipInfo(rowDescription);
-                } else {
-                    tooltipInfo = null;
-                }
-
-                tHead.getColumnAlignments();
-                int col = 0;
-                int visibleColumnIndex = -1;
-
-                // row header
-                if (showRowHeaders) {
-                    boolean sorted = tHead.getHeaderCell(col).isSorted();
-                    addCell(uidl, buildCaptionHtmlSnippet(uidl), aligns[col++],
-                            "rowheader", true, sorted);
-                    visibleColumnIndex++;
-                }
-
-                if (uidl.hasAttribute("al")) {
-                    actionKeys = uidl.getStringArrayAttribute("al");
-                }
-
-                addCellsFromUIDL(uidl, aligns, col, visibleColumnIndex);
-
-                if (uidl.hasAttribute("selected") && !isSelected()) {
-                    toggleSelection();
-                }
-            }
-
-            protected void updateStyleNames(String primaryStyleName) {
-
-                if (getStylePrimaryName().contains("odd")) {
-                    setStyleName(primaryStyleName + "-row-odd");
-                } else {
-                    setStyleName(primaryStyleName + "-row");
-                }
-
-                if (rowStyle != null) {
-                    addStyleName(primaryStyleName + "-row-" + rowStyle);
-                }
-
-                for (int i = 0; i < rowElement.getChildCount(); i++) {
-                    TableCellElement cell = (TableCellElement) rowElement
-                            .getChild(i);
-                    updateCellStyleNames(cell, primaryStyleName);
-                }
-            }
-
-            public TooltipInfo getTooltipInfo() {
-                return tooltipInfo;
-            }
-
-            /**
-             * Add a dummy row, used for measurements if Table is empty.
-             */
-            public VScrollTableRow() {
-                this(0);
-                addCell(null, "_", 'b', "", true, false);
-            }
-
-            protected void initCellWidths() {
-                final int cells = tHead.getVisibleCellCount();
-                for (int i = 0; i < cells; i++) {
-                    int w = VScrollTable.this.getColWidth(getColKeyByIndex(i));
-                    if (w < 0) {
-                        w = 0;
-                    }
-                    setCellWidth(i, w);
-                }
-            }
-
-            protected void setCellWidth(int cellIx, int width) {
-                final Element cell = DOM.getChild(getElement(), cellIx);
-                cell.getFirstChildElement().getStyle()
-                        .setPropertyPx("width", width);
-                cell.getStyle().setPropertyPx("width", width);
-            }
-
-            protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col,
-                    int visibleColumnIndex) {
-                final Iterator<?> cells = uidl.getChildIterator();
-                while (cells.hasNext()) {
-                    final Object cell = cells.next();
-                    visibleColumnIndex++;
-
-                    String columnId = visibleColOrder[visibleColumnIndex];
-
-                    String style = "";
-                    if (uidl.hasAttribute("style-" + columnId)) {
-                        style = uidl.getStringAttribute("style-" + columnId);
-                    }
-
-                    String description = null;
-                    if (uidl.hasAttribute("descr-" + columnId)) {
-                        description = uidl.getStringAttribute("descr-"
-                                + columnId);
-                    }
-
-                    boolean sorted = tHead.getHeaderCell(col).isSorted();
-                    if (cell instanceof String) {
-                        addCell(uidl, cell.toString(), aligns[col++], style,
-                                isRenderHtmlInCells(), sorted, description);
-                    } else {
-                        final ComponentConnector cellContent = client
-                                .getPaintable((UIDL) cell);
-
-                        addCell(uidl, cellContent.getWidget(), aligns[col++],
-                                style, sorted);
-                    }
-                }
-            }
-
-            /**
-             * Overriding this and returning true causes all text cells to be
-             * rendered as HTML.
-             * 
-             * @return always returns false in the default implementation
-             */
-            protected boolean isRenderHtmlInCells() {
-                return false;
-            }
-
-            /**
-             * Detects whether row is visible in tables viewport.
-             * 
-             * @return
-             */
-            public boolean isInViewPort() {
-                int absoluteTop = getAbsoluteTop();
-                int scrollPosition = scrollBodyPanel.getScrollPosition();
-                if (absoluteTop < scrollPosition) {
-                    return false;
-                }
-                int maxVisible = scrollPosition
-                        + scrollBodyPanel.getOffsetHeight() - getOffsetHeight();
-                if (absoluteTop > maxVisible) {
-                    return false;
-                }
-                return true;
-            }
-
-            /**
-             * Makes a check based on indexes whether the row is before the
-             * compared row.
-             * 
-             * @param row1
-             * @return true if this rows index is smaller than in the row1
-             */
-            public boolean isBefore(VScrollTableRow row1) {
-                return getIndex() < row1.getIndex();
-            }
-
-            /**
-             * Sets the index of the row in the whole table. Currently used just
-             * to set even/odd classname
-             * 
-             * @param indexInWholeTable
-             */
-            private void setIndex(int indexInWholeTable) {
-                index = indexInWholeTable;
-                boolean isOdd = indexInWholeTable % 2 == 0;
-                // Inverted logic to be backwards compatible with earlier 6.4.
-                // It is very strange because rows 1,3,5 are considered "even"
-                // and 2,4,6 "odd".
-                //
-                // First remove any old styles so that both styles aren't
-                // applied when indexes are updated.
-                String primaryStyleName = getStylePrimaryName();
-                if (primaryStyleName != null && !primaryStyleName.equals("")) {
-                    removeStyleName(getStylePrimaryName());
-                }
-                if (!isOdd) {
-                    addStyleName(VScrollTable.this.getStylePrimaryName()
-                            + "-row-odd");
-                } else {
-                    addStyleName(VScrollTable.this.getStylePrimaryName()
-                            + "-row");
-                }
-            }
-
-            public int getIndex() {
-                return index;
-            }
-
-            @Override
-            protected void onDetach() {
-                super.onDetach();
-                client.getContextMenu().ensureHidden(this);
-            }
-
-            public String getKey() {
-                return String.valueOf(rowKey);
-            }
-
-            public void addCell(UIDL rowUidl, String text, char align,
-                    String style, boolean textIsHTML, boolean sorted) {
-                addCell(rowUidl, text, align, style, textIsHTML, sorted, null);
-            }
-
-            public void addCell(UIDL rowUidl, String text, char align,
-                    String style, boolean textIsHTML, boolean sorted,
-                    String description) {
-                // String only content is optimized by not using Label widget
-                final TableCellElement td = DOM.createTD().cast();
-                initCellWithText(text, align, style, textIsHTML, sorted,
-                        description, td);
-            }
-
-            protected void initCellWithText(String text, char align,
-                    String style, boolean textIsHTML, boolean sorted,
-                    String description, final TableCellElement td) {
-                final Element container = DOM.createDiv();
-                container.setClassName(VScrollTable.this.getStylePrimaryName()
-                        + "-cell-wrapper");
-
-                td.setClassName(VScrollTable.this.getStylePrimaryName()
-                        + "-cell-content");
-
-                if (style != null && !style.equals("")) {
-                    td.addClassName(VScrollTable.this.getStylePrimaryName()
-                            + "-cell-content-" + style);
-                }
-
-                if (sorted) {
-                    td.addClassName(VScrollTable.this.getStylePrimaryName()
-                            + "-cell-content-sorted");
-                }
-
-                if (textIsHTML) {
-                    container.setInnerHTML(text);
-                } else {
-                    container.setInnerText(text);
-                }
-                if (align != ALIGN_LEFT) {
-                    switch (align) {
-                    case ALIGN_CENTER:
-                        container.getStyle().setProperty("textAlign", "center");
-                        break;
-                    case ALIGN_RIGHT:
-                    default:
-                        container.getStyle().setProperty("textAlign", "right");
-                        break;
-                    }
-                }
-
-                if (description != null && !description.equals("")) {
-                    TooltipInfo info = new TooltipInfo(description);
-                    cellToolTips.put(td, info);
-                } else {
-                    cellToolTips.remove(td);
-                }
-
-                td.appendChild(container);
-                getElement().appendChild(td);
-            }
-
-            protected void updateCellStyleNames(TableCellElement td,
-                    String primaryStyleName) {
-                Element container = td.getFirstChild().cast();
-                container.setClassName(primaryStyleName + "-cell-wrapper");
-
-                /*
-                 * Replace old primary style name with new one
-                 */
-                String className = td.getClassName();
-                String oldPrimaryName = className.split("-cell-content")[0];
-                td.setClassName(className.replaceAll(oldPrimaryName,
-                        primaryStyleName));
-            }
-
-            public void addCell(UIDL rowUidl, Widget w, char align,
-                    String style, boolean sorted) {
-                final TableCellElement td = DOM.createTD().cast();
-                initCellWithWidget(w, align, style, sorted, td);
-            }
-
-            protected void initCellWithWidget(Widget w, char align,
-                    String style, boolean sorted, final TableCellElement td) {
-                final Element container = DOM.createDiv();
-                String className = VScrollTable.this.getStylePrimaryName()
-                        + "-cell-content";
-                if (style != null && !style.equals("")) {
-                    className += " " + VScrollTable.this.getStylePrimaryName()
-                            + "-cell-content-" + style;
-                }
-                if (sorted) {
-                    className += " " + VScrollTable.this.getStylePrimaryName()
-                            + "-cell-content-sorted";
-                }
-                td.setClassName(className);
-                container.setClassName(VScrollTable.this.getStylePrimaryName()
-                        + "-cell-wrapper");
-                // TODO most components work with this, but not all (e.g.
-                // Select)
-                // Old comment: make widget cells respect align.
-                // text-align:center for IE, margin: auto for others
-                if (align != ALIGN_LEFT) {
-                    switch (align) {
-                    case ALIGN_CENTER:
-                        container.getStyle().setProperty("textAlign", "center");
-                        break;
-                    case ALIGN_RIGHT:
-                    default:
-                        container.getStyle().setProperty("textAlign", "right");
-                        break;
-                    }
-                }
-                td.appendChild(container);
-                getElement().appendChild(td);
-                // ensure widget not attached to another element (possible tBody
-                // change)
-                w.removeFromParent();
-                container.appendChild(w.getElement());
-                adopt(w);
-                childWidgets.add(w);
-            }
-
-            @Override
-            public Iterator<Widget> iterator() {
-                return childWidgets.iterator();
-            }
-
-            @Override
-            public boolean remove(Widget w) {
-                if (childWidgets.contains(w)) {
-                    orphan(w);
-                    DOM.removeChild(DOM.getParent(w.getElement()),
-                            w.getElement());
-                    childWidgets.remove(w);
-                    return true;
-                } else {
-                    return false;
-                }
-            }
-
-            /**
-             * If there are registered click listeners, sends a click event and
-             * returns true. Otherwise, does nothing and returns false.
-             * 
-             * @param event
-             * @param targetTdOrTr
-             * @param immediate
-             *            Whether the event is sent immediately
-             * @return Whether a click event was sent
-             */
-            private boolean handleClickEvent(Event event, Element targetTdOrTr,
-                    boolean immediate) {
-                if (!client.hasEventListeners(VScrollTable.this,
-                        TableConstants.ITEM_CLICK_EVENT_ID)) {
-                    // Don't send an event if nobody is listening
-                    return false;
-                }
-
-                // This row was clicked
-                client.updateVariable(paintableId, "clickedKey", "" + rowKey,
-                        false);
-
-                if (getElement() == targetTdOrTr.getParentElement()) {
-                    // A specific column was clicked
-                    int childIndex = DOM.getChildIndex(getElement(),
-                            targetTdOrTr);
-                    String colKey = null;
-                    colKey = tHead.getHeaderCell(childIndex).getColKey();
-                    client.updateVariable(paintableId, "clickedColKey", colKey,
-                            false);
-                }
-
-                MouseEventDetails details = MouseEventDetailsBuilder
-                        .buildMouseEventDetails(event);
-
-                client.updateVariable(paintableId, "clickEvent",
-                        details.toString(), immediate);
-
-                return true;
-            }
-
-            public TooltipInfo getTooltip(
-                    com.google.gwt.dom.client.Element target) {
-
-                TooltipInfo info = null;
-
-                if (target.hasTagName("TD")) {
-
-                    TableCellElement td = (TableCellElement) target.cast();
-                    info = cellToolTips.get(td);
-                }
-
-                if (info == null) {
-                    info = tooltipInfo;
-                }
-
-                return info;
-            }
-
-            /**
-             * Special handler for touch devices that support native scrolling
-             * 
-             * @return Whether the event was handled by this method.
-             */
-            private boolean handleTouchEvent(final Event event) {
-
-                boolean touchEventHandled = false;
-
-                if (enabled && hasNativeTouchScrolling) {
-                    final Element targetTdOrTr = getEventTargetTdOrTr(event);
-                    final int type = event.getTypeInt();
-
-                    switch (type) {
-                    case Event.ONTOUCHSTART:
-                        touchEventHandled = true;
-                        touchStart = event;
-                        isDragging = false;
-                        Touch touch = event.getChangedTouches().get(0);
-                        // save position to fields, touches in events are same
-                        // instance during the operation.
-                        touchStartX = touch.getClientX();
-                        touchStartY = touch.getClientY();
-
-                        if (dragmode != 0) {
-                            if (dragTouchTimeout == null) {
-                                dragTouchTimeout = new Timer() {
-
-                                    @Override
-                                    public void run() {
-                                        if (touchStart != null) {
-                                            // Start a drag if a finger is held
-                                            // in place long enough, then moved
-                                            isDragging = true;
-                                        }
-                                    }
-                                };
-                            }
-                            dragTouchTimeout.schedule(TOUCHSCROLL_TIMEOUT);
-                        }
-
-                        if (actionKeys != null) {
-                            if (contextTouchTimeout == null) {
-                                contextTouchTimeout = new Timer() {
-
-                                    @Override
-                                    public void run() {
-                                        if (touchStart != null) {
-                                            // Open the context menu if finger
-                                            // is held in place long enough.
-                                            showContextMenu(touchStart);
-                                            event.preventDefault();
-                                            touchStart = null;
-                                        }
-                                    }
-                                };
-                            }
-                            contextTouchTimeout
-                                    .schedule(TOUCH_CONTEXT_MENU_TIMEOUT);
-                        }
-                        break;
-                    case Event.ONTOUCHMOVE:
-                        touchEventHandled = true;
-                        if (isSignificantMove(event)) {
-                            if (contextTouchTimeout != null) {
-                                // Moved finger before the context menu timer
-                                // expired, so let the browser handle this as a
-                                // scroll.
-                                contextTouchTimeout.cancel();
-                                contextTouchTimeout = null;
-                            }
-                            if (!isDragging && dragTouchTimeout != null) {
-                                // Moved finger before the drag timer expired,
-                                // so let the browser handle this as a scroll.
-                                dragTouchTimeout.cancel();
-                                dragTouchTimeout = null;
-                            }
-
-                            if (dragmode != 0 && touchStart != null
-                                    && isDragging) {
-                                event.preventDefault();
-                                event.stopPropagation();
-                                startRowDrag(touchStart, type, targetTdOrTr);
-                            }
-                            touchStart = null;
-                        }
-                        break;
-                    case Event.ONTOUCHEND:
-                    case Event.ONTOUCHCANCEL:
-                        touchEventHandled = true;
-                        if (contextTouchTimeout != null) {
-                            contextTouchTimeout.cancel();
-                        }
-                        if (dragTouchTimeout != null) {
-                            dragTouchTimeout.cancel();
-                        }
-                        if (touchStart != null) {
-                            event.preventDefault();
-                            event.stopPropagation();
-                            if (!BrowserInfo.get().isAndroid()) {
-                                Util.simulateClickFromTouchEvent(touchStart,
-                                        this);
-                            }
-                            touchStart = null;
-                        }
-                        isDragging = false;
-                        break;
-                    }
-                }
-                return touchEventHandled;
-            }
-
-            /*
-             * React on click that occur on content cells only
-             */
-
-            @Override
-            public void onBrowserEvent(final Event event) {
-
-                final boolean touchEventHandled = handleTouchEvent(event);
-
-                if (enabled && !touchEventHandled) {
-                    final int type = event.getTypeInt();
-                    final Element targetTdOrTr = getEventTargetTdOrTr(event);
-                    if (type == Event.ONCONTEXTMENU) {
-                        showContextMenu(event);
-                        if (enabled
-                                && (actionKeys != null || client
-                                        .hasEventListeners(
-                                                VScrollTable.this,
-                                                TableConstants.ITEM_CLICK_EVENT_ID))) {
-                            /*
-                             * Prevent browser context menu only if there are
-                             * action handlers or item click listeners
-                             * registered
-                             */
-                            event.stopPropagation();
-                            event.preventDefault();
-                        }
-                        return;
-                    }
-
-                    boolean targetCellOrRowFound = targetTdOrTr != null;
-
-                    switch (type) {
-                    case Event.ONDBLCLICK:
-                        if (targetCellOrRowFound) {
-                            handleClickEvent(event, targetTdOrTr, true);
-                        }
-                        break;
-                    case Event.ONMOUSEUP:
-                        if (targetCellOrRowFound) {
-                            /*
-                             * Queue here, send at the same time as the
-                             * corresponding value change event - see #7127
-                             */
-                            boolean clickEventSent = handleClickEvent(event,
-                                    targetTdOrTr, false);
-
-                            if (event.getButton() == Event.BUTTON_LEFT
-                                    && isSelectable()) {
-
-                                // Ctrl+Shift click
-                                if ((event.getCtrlKey() || event.getMetaKey())
-                                        && event.getShiftKey()
-                                        && isMultiSelectModeDefault()) {
-                                    toggleShiftSelection(false);
-                                    setRowFocus(this);
-
-                                    // Ctrl click
-                                } else if ((event.getCtrlKey() || event
-                                        .getMetaKey())
-                                        && isMultiSelectModeDefault()) {
-                                    boolean wasSelected = isSelected();
-                                    toggleSelection();
-                                    setRowFocus(this);
-                                    /*
-                                     * next possible range select must start on
-                                     * this row
-                                     */
-                                    selectionRangeStart = this;
-                                    if (wasSelected) {
-                                        removeRowFromUnsentSelectionRanges(this);
-                                    }
-
-                                } else if ((event.getCtrlKey() || event
-                                        .getMetaKey()) && isSingleSelectMode()) {
-                                    // Ctrl (or meta) click (Single selection)
-                                    if (!isSelected()
-                                            || (isSelected() && nullSelectionAllowed)) {
-
-                                        if (!isSelected()) {
-                                            deselectAll();
-                                        }
-
-                                        toggleSelection();
-                                        setRowFocus(this);
-                                    }
-
-                                } else if (event.getShiftKey()
-                                        && isMultiSelectModeDefault()) {
-                                    // Shift click
-                                    toggleShiftSelection(true);
-
-                                } else {
-                                    // click
-                                    boolean currentlyJustThisRowSelected = selectedRowKeys
-                                            .size() == 1
-                                            && selectedRowKeys
-                                                    .contains(getKey());
-
-                                    if (!currentlyJustThisRowSelected) {
-                                        if (isSingleSelectMode()
-                                                || isMultiSelectModeDefault()) {
-                                            /*
-                                             * For default multi select mode
-                                             * (ctrl/shift) and for single
-                                             * select mode we need to clear the
-                                             * previous selection before
-                                             * selecting a new one when the user
-                                             * clicks on a row. Only in
-                                             * multiselect/simple mode the old
-                                             * selection should remain after a
-                                             * normal click.
-                                             */
-                                            deselectAll();
-                                        }
-                                        toggleSelection();
-                                    } else if ((isSingleSelectMode() || isMultiSelectModeSimple())
-                                            && nullSelectionAllowed) {
-                                        toggleSelection();
-                                    }/*
-                                      * else NOP to avoid excessive server
-                                      * visits (selection is removed with
-                                      * CTRL/META click)
-                                      */
-
-                                    selectionRangeStart = this;
-                                    setRowFocus(this);
-                                }
-
-                                // Remove IE text selection hack
-                                if (BrowserInfo.get().isIE()) {
-                                    ((Element) event.getEventTarget().cast())
-                                            .setPropertyJSO("onselectstart",
-                                                    null);
-                                }
-                                // Queue value change
-                                sendSelectedRows(false);
-                            }
-                            /*
-                             * Send queued click and value change events if any
-                             * If a click event is sent, send value change with
-                             * it regardless of the immediate flag, see #7127
-                             */
-                            if (immediate || clickEventSent) {
-                                client.sendPendingVariableChanges();
-                            }
-                        }
-                        break;
-                    case Event.ONTOUCHEND:
-                    case Event.ONTOUCHCANCEL:
-                        if (touchStart != null) {
-                            /*
-                             * Touch has not been handled as neither context or
-                             * drag start, handle it as a click.
-                             */
-                            Util.simulateClickFromTouchEvent(touchStart, this);
-                            touchStart = null;
-                        }
-                        if (contextTouchTimeout != null) {
-                            contextTouchTimeout.cancel();
-                        }
-                        break;
-                    case Event.ONTOUCHMOVE:
-                        if (isSignificantMove(event)) {
-                            /*
-                             * TODO figure out scroll delegate don't eat events
-                             * if row is selected. Null check for active
-                             * delegate is as a workaround.
-                             */
-                            if (dragmode != 0
-                                    && touchStart != null
-                                    && (TouchScrollDelegate
-                                            .getActiveScrollDelegate() == null)) {
-                                startRowDrag(touchStart, type, targetTdOrTr);
-                            }
-                            if (contextTouchTimeout != null) {
-                                contextTouchTimeout.cancel();
-                            }
-                            /*
-                             * Avoid clicks and drags by clearing touch start
-                             * flag.
-                             */
-                            touchStart = null;
-                        }
-
-                        break;
-                    case Event.ONTOUCHSTART:
-                        touchStart = event;
-                        Touch touch = event.getChangedTouches().get(0);
-                        // save position to fields, touches in events are same
-                        // isntance during the operation.
-                        touchStartX = touch.getClientX();
-                        touchStartY = touch.getClientY();
-                        /*
-                         * Prevent simulated mouse events.
-                         */
-                        touchStart.preventDefault();
-                        if (dragmode != 0 || actionKeys != null) {
-                            new Timer() {
-
-                                @Override
-                                public void run() {
-                                    TouchScrollDelegate activeScrollDelegate = TouchScrollDelegate
-                                            .getActiveScrollDelegate();
-                                    /*
-                                     * If there's a scroll delegate, check if
-                                     * we're actually scrolling and handle it.
-                                     * If no delegate, do nothing here and let
-                                     * the row handle potential drag'n'drop or
-                                     * context menu.
-                                     */
-                                    if (activeScrollDelegate != null) {
-                                        if (activeScrollDelegate.isMoved()) {
-                                            /*
-                                             * Prevent the row from handling
-                                             * touch move/end events (the
-                                             * delegate handles those) and from
-                                             * doing drag'n'drop or opening a
-                                             * context menu.
-                                             */
-                                            touchStart = null;
-                                        } else {
-                                            /*
-                                             * Scrolling hasn't started, so
-                                             * cancel delegate and let the row
-                                             * handle potential drag'n'drop or
-                                             * context menu.
-                                             */
-                                            activeScrollDelegate
-                                                    .stopScrolling();
-                                        }
-                                    }
-                                }
-                            }.schedule(TOUCHSCROLL_TIMEOUT);
-
-                            if (contextTouchTimeout == null
-                                    && actionKeys != null) {
-                                contextTouchTimeout = new Timer() {
-
-                                    @Override
-                                    public void run() {
-                                        if (touchStart != null) {
-                                            showContextMenu(touchStart);
-                                            touchStart = null;
-                                        }
-                                    }
-                                };
-                            }
-                            if (contextTouchTimeout != null) {
-                                contextTouchTimeout.cancel();
-                                contextTouchTimeout
-                                        .schedule(TOUCH_CONTEXT_MENU_TIMEOUT);
-                            }
-                        }
-                        break;
-                    case Event.ONMOUSEDOWN:
-                        if (targetCellOrRowFound) {
-                            setRowFocus(this);
-                            ensureFocus();
-                            if (dragmode != 0
-                                    && (event.getButton() == NativeEvent.BUTTON_LEFT)) {
-                                startRowDrag(event, type, targetTdOrTr);
-
-                            } else if (event.getCtrlKey()
-                                    || event.getShiftKey()
-                                    || event.getMetaKey()
-                                    && isMultiSelectModeDefault()) {
-
-                                // Prevent default text selection in Firefox
-                                event.preventDefault();
-
-                                // Prevent default text selection in IE
-                                if (BrowserInfo.get().isIE()) {
-                                    ((Element) event.getEventTarget().cast())
-                                            .setPropertyJSO(
-                                                    "onselectstart",
-                                                    getPreventTextSelectionIEHack());
-                                }
-
-                                event.stopPropagation();
-                            }
-                        }
-                        break;
-                    case Event.ONMOUSEOUT:
-                        break;
-                    default:
-                        break;
-                    }
-                }
-                super.onBrowserEvent(event);
-            }
-
-            private boolean isSignificantMove(Event event) {
-                if (touchStart == null) {
-                    // no touch start
-                    return false;
-                }
-                /*
-                 * TODO calculate based on real distance instead of separate
-                 * axis checks
-                 */
-                Touch touch = event.getChangedTouches().get(0);
-                if (Math.abs(touch.getClientX() - touchStartX) > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) {
-                    return true;
-                }
-                if (Math.abs(touch.getClientY() - touchStartY) > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) {
-                    return true;
-                }
-                return false;
-            }
-
-            /**
-             * Checks if the row represented by the row key has been selected
-             * 
-             * @param key
-             *            The generated row key
-             */
-            private boolean rowKeyIsSelected(int rowKey) {
-                // Check single selections
-                if (selectedRowKeys.contains("" + rowKey)) {
-                    return true;
-                }
-
-                // Check range selections
-                for (SelectionRange r : selectedRowRanges) {
-                    if (r.inRange(getRenderedRowByKey("" + rowKey))) {
-                        return true;
-                    }
-                }
-                return false;
-            }
-
-            protected void startRowDrag(Event event, final int type,
-                    Element targetTdOrTr) {
-                VTransferable transferable = new VTransferable();
-                transferable.setDragSource(ConnectorMap.get(client)
-                        .getConnector(VScrollTable.this));
-                transferable.setData("itemId", "" + rowKey);
-                NodeList<TableCellElement> cells = rowElement.getCells();
-                for (int i = 0; i < cells.getLength(); i++) {
-                    if (cells.getItem(i).isOrHasChild(targetTdOrTr)) {
-                        HeaderCell headerCell = tHead.getHeaderCell(i);
-                        transferable.setData("propertyId", headerCell.cid);
-                        break;
-                    }
-                }
-
-                VDragEvent ev = VDragAndDropManager.get().startDrag(
-                        transferable, event, true);
-                if (dragmode == DRAGMODE_MULTIROW && isMultiSelectModeAny()
-                        && rowKeyIsSelected(rowKey)) {
-
-                    // Create a drag image of ALL rows
-                    ev.createDragImage(
-                            (Element) scrollBody.tBodyElement.cast(), true);
-
-                    // Hide rows which are not selected
-                    Element dragImage = ev.getDragImage();
-                    int i = 0;
-                    for (Iterator<Widget> iterator = scrollBody.iterator(); iterator
-                            .hasNext();) {
-                        VScrollTableRow next = (VScrollTableRow) iterator
-                                .next();
-
-                        Element child = (Element) dragImage.getChild(i++);
-
-                        if (!rowKeyIsSelected(next.rowKey)) {
-                            child.getStyle().setVisibility(Visibility.HIDDEN);
-                        }
-                    }
-                } else {
-                    ev.createDragImage(getElement(), true);
-                }
-                if (type == Event.ONMOUSEDOWN) {
-                    event.preventDefault();
-                }
-                event.stopPropagation();
-            }
-
-            /**
-             * Finds the TD that the event interacts with. Returns null if the
-             * target of the event should not be handled. If the event target is
-             * the row directly this method returns the TR element instead of
-             * the TD.
-             * 
-             * @param event
-             * @return TD or TR element that the event targets (the actual event
-             *         target is this element or a child of it)
-             */
-            private Element getEventTargetTdOrTr(Event event) {
-                final Element eventTarget = event.getEventTarget().cast();
-                Widget widget = Util.findWidget(eventTarget, null);
-                final Element thisTrElement = getElement();
-
-                if (widget != this) {
-                    /*
-                     * This is a workaround to make Labels, read only TextFields
-                     * and Embedded in a Table clickable (see #2688). It is
-                     * really not a fix as it does not work with a custom read
-                     * only components (not extending VLabel/VEmbedded).
-                     */
-                    while (widget != null && widget.getParent() != this) {
-                        widget = widget.getParent();
-                    }
-
-                    if (!(widget instanceof VLabel)
-                            && !(widget instanceof VEmbedded)
-                            && !(widget instanceof VTextField && ((VTextField) widget)
-                                    .isReadOnly())) {
-                        return null;
-                    }
-                }
-                if (eventTarget == thisTrElement) {
-                    // This was a click on the TR element
-                    return thisTrElement;
-                }
-
-                // Iterate upwards until we find the TR element
-                Element element = eventTarget;
-                while (element != null
-                        && element.getParentElement().cast() != thisTrElement) {
-                    element = element.getParentElement().cast();
-                }
-                return element;
-            }
-
-            public void showContextMenu(Event event) {
-                if (enabled && actionKeys != null) {
-                    // Show context menu if there are registered action handlers
-                    int left = Util.getTouchOrMouseClientX(event);
-                    int top = Util.getTouchOrMouseClientY(event);
-                    top += Window.getScrollTop();
-                    left += Window.getScrollLeft();
-                    contextMenu = new ContextMenuDetails(getKey(), left, top);
-                    client.getContextMenu().showAt(this, left, top);
-                }
-            }
-
-            /**
-             * Has the row been selected?
-             * 
-             * @return Returns true if selected, else false
-             */
-            public boolean isSelected() {
-                return selected;
-            }
-
-            /**
-             * Toggle the selection of the row
-             */
-            public void toggleSelection() {
-                selected = !selected;
-                selectionChanged = true;
-                if (selected) {
-                    selectedRowKeys.add(String.valueOf(rowKey));
-                    addStyleName("v-selected");
-                } else {
-                    removeStyleName("v-selected");
-                    selectedRowKeys.remove(String.valueOf(rowKey));
-                }
-            }
-
-            /**
-             * Is called when a user clicks an item when holding SHIFT key down.
-             * This will select a new range from the last focused row
-             * 
-             * @param deselectPrevious
-             *            Should the previous selected range be deselected
-             */
-            private void toggleShiftSelection(boolean deselectPrevious) {
-
-                /*
-                 * Ensures that we are in multiselect mode and that we have a
-                 * previous selection which was not a deselection
-                 */
-                if (isSingleSelectMode()) {
-                    // No previous selection found
-                    deselectAll();
-                    toggleSelection();
-                    return;
-                }
-
-                // Set the selectable range
-                VScrollTableRow endRow = this;
-                VScrollTableRow startRow = selectionRangeStart;
-                if (startRow == null) {
-                    startRow = focusedRow;
-                    // If start row is null then we have a multipage selection
-                    // from
-                    // above
-                    if (startRow == null) {
-                        startRow = (VScrollTableRow) scrollBody.iterator()
-                                .next();
-                        setRowFocus(endRow);
-                    }
-                }
-                // Deselect previous items if so desired
-                if (deselectPrevious) {
-                    deselectAll();
-                }
-
-                // we'll ensure GUI state from top down even though selection
-                // was the opposite way
-                if (!startRow.isBefore(endRow)) {
-                    VScrollTableRow tmp = startRow;
-                    startRow = endRow;
-                    endRow = tmp;
-                }
-                SelectionRange range = new SelectionRange(startRow, endRow);
-
-                for (Widget w : scrollBody) {
-                    VScrollTableRow row = (VScrollTableRow) w;
-                    if (range.inRange(row)) {
-                        if (!row.isSelected()) {
-                            row.toggleSelection();
-                        }
-                        selectedRowKeys.add(row.getKey());
-                    }
-                }
-
-                // Add range
-                if (startRow != endRow) {
-                    selectedRowRanges.add(range);
-                }
-            }
-
-            /*
-             * (non-Javadoc)
-             * 
-             * @see com.vaadin.client.ui.IActionOwner#getActions ()
-             */
-
-            @Override
-            public Action[] getActions() {
-                if (actionKeys == null) {
-                    return new Action[] {};
-                }
-                final Action[] actions = new Action[actionKeys.length];
-                for (int i = 0; i < actions.length; i++) {
-                    final String actionKey = actionKeys[i];
-                    final TreeAction a = new TreeAction(this,
-                            String.valueOf(rowKey), actionKey) {
-
-                        @Override
-                        public void execute() {
-                            super.execute();
-                            lazyRevertFocusToRow(VScrollTableRow.this);
-                        }
-                    };
-                    a.setCaption(getActionCaption(actionKey));
-                    a.setIconUrl(getActionIcon(actionKey));
-                    actions[i] = a;
-                }
-                return actions;
-            }
-
-            @Override
-            public ApplicationConnection getClient() {
-                return client;
-            }
-
-            @Override
-            public String getPaintableId() {
-                return paintableId;
-            }
-
-            private int getColIndexOf(Widget child) {
-                com.google.gwt.dom.client.Element widgetCell = child
-                        .getElement().getParentElement().getParentElement();
-                NodeList<TableCellElement> cells = rowElement.getCells();
-                for (int i = 0; i < cells.getLength(); i++) {
-                    if (cells.getItem(i) == widgetCell) {
-                        return i;
-                    }
-                }
-                return -1;
-            }
-
-            public Widget getWidgetForPaintable() {
-                return this;
-            }
-        }
-
-        protected class VScrollTableGeneratedRow extends VScrollTableRow {
-
-            private boolean spanColumns;
-            private boolean htmlContentAllowed;
-
-            public VScrollTableGeneratedRow(UIDL uidl, char[] aligns) {
-                super(uidl, aligns);
-                addStyleName("v-table-generated-row");
-            }
-
-            public boolean isSpanColumns() {
-                return spanColumns;
-            }
-
-            @Override
-            protected void initCellWidths() {
-                if (spanColumns) {
-                    setSpannedColumnWidthAfterDOMFullyInited();
-                } else {
-                    super.initCellWidths();
-                }
-            }
-
-            private void setSpannedColumnWidthAfterDOMFullyInited() {
-                // Defer setting width on spanned columns to make sure that
-                // they are added to the DOM before trying to calculate
-                // widths.
-                Scheduler.get().scheduleDeferred(new ScheduledCommand() {
-
-                    @Override
-                    public void execute() {
-                        if (showRowHeaders) {
-                            setCellWidth(0, tHead.getHeaderCell(0).getWidth());
-                            calcAndSetSpanWidthOnCell(1);
-                        } else {
-                            calcAndSetSpanWidthOnCell(0);
-                        }
-                    }
-                });
-            }
-
-            @Override
-            protected boolean isRenderHtmlInCells() {
-                return htmlContentAllowed;
-            }
-
-            @Override
-            protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col,
-                    int visibleColumnIndex) {
-                htmlContentAllowed = uidl.getBooleanAttribute("gen_html");
-                spanColumns = uidl.getBooleanAttribute("gen_span");
-
-                final Iterator<?> cells = uidl.getChildIterator();
-                if (spanColumns) {
-                    int colCount = uidl.getChildCount();
-                    if (cells.hasNext()) {
-                        final Object cell = cells.next();
-                        if (cell instanceof String) {
-                            addSpannedCell(uidl, cell.toString(), aligns[0],
-                                    "", htmlContentAllowed, false, null,
-                                    colCount);
-                        } else {
-                            addSpannedCell(uidl, (Widget) cell, aligns[0], "",
-                                    false, colCount);
-                        }
-                    }
-                } else {
-                    super.addCellsFromUIDL(uidl, aligns, col,
-                            visibleColumnIndex);
-                }
-            }
-
-            private void addSpannedCell(UIDL rowUidl, Widget w, char align,
-                    String style, boolean sorted, int colCount) {
-                TableCellElement td = DOM.createTD().cast();
-                td.setColSpan(colCount);
-                initCellWithWidget(w, align, style, sorted, td);
-            }
-
-            private void addSpannedCell(UIDL rowUidl, String text, char align,
-                    String style, boolean textIsHTML, boolean sorted,
-                    String description, int colCount) {
-                // String only content is optimized by not using Label widget
-                final TableCellElement td = DOM.createTD().cast();
-                td.setColSpan(colCount);
-                initCellWithText(text, align, style, textIsHTML, sorted,
-                        description, td);
-            }
-
-            @Override
-            protected void setCellWidth(int cellIx, int width) {
-                if (isSpanColumns()) {
-                    if (showRowHeaders) {
-                        if (cellIx == 0) {
-                            super.setCellWidth(0, width);
-                        } else {
-                            // We need to recalculate the spanning TDs width for
-                            // every cellIx in order to support column resizing.
-                            calcAndSetSpanWidthOnCell(1);
-                        }
-                    } else {
-                        // Same as above.
-                        calcAndSetSpanWidthOnCell(0);
-                    }
-                } else {
-                    super.setCellWidth(cellIx, width);
-                }
-            }
-
-            private void calcAndSetSpanWidthOnCell(final int cellIx) {
-                int spanWidth = 0;
-                for (int ix = (showRowHeaders ? 1 : 0); ix < tHead
-                        .getVisibleCellCount(); ix++) {
-                    spanWidth += tHead.getHeaderCell(ix).getOffsetWidth();
-                }
-                Util.setWidthExcludingPaddingAndBorder((Element) getElement()
-                        .getChild(cellIx), spanWidth, 13, false);
-            }
-        }
-
-        /**
-         * Ensure the component has a focus.
-         * 
-         * TODO the current implementation simply always calls focus for the
-         * component. In case the Table at some point implements focus/blur
-         * listeners, this method needs to be evolved to conditionally call
-         * focus only if not currently focused.
-         */
-        protected void ensureFocus() {
-            if (!hasFocus) {
-                scrollBodyPanel.setFocus(true);
-            }
-
-        }
-
-    }
-
-    /**
-     * Deselects all items
-     */
-    public void deselectAll() {
-        for (Widget w : scrollBody) {
-            VScrollTableRow row = (VScrollTableRow) w;
-            if (row.isSelected()) {
-                row.toggleSelection();
-            }
-        }
-        // still ensure all selects are removed from (not necessary rendered)
-        selectedRowKeys.clear();
-        selectedRowRanges.clear();
-        // also notify server that it clears all previous selections (the client
-        // side does not know about the invisible ones)
-        instructServerToForgetPreviousSelections();
-    }
-
-    /**
-     * Used in multiselect mode when the client side knows that all selections
-     * are in the next request.
-     */
-    private void instructServerToForgetPreviousSelections() {
-        client.updateVariable(paintableId, "clearSelections", true, false);
-    }
-
-    /**
-     * Determines the pagelength when the table height is fixed.
-     */
-    public void updatePageLength() {
-        // Only update if visible and enabled
-        if (!isVisible() || !enabled) {
-            return;
-        }
-
-        if (scrollBody == null) {
-            return;
-        }
-
-        if (isDynamicHeight()) {
-            return;
-        }
-
-        int rowHeight = (int) Math.round(scrollBody.getRowHeight());
-        int bodyH = scrollBodyPanel.getOffsetHeight();
-        int rowsAtOnce = bodyH / rowHeight;
-        boolean anotherPartlyVisible = ((bodyH % rowHeight) != 0);
-        if (anotherPartlyVisible) {
-            rowsAtOnce++;
-        }
-        if (pageLength != rowsAtOnce) {
-            pageLength = rowsAtOnce;
-            client.updateVariable(paintableId, "pagelength", pageLength, false);
-
-            if (!rendering) {
-                int currentlyVisible = scrollBody.lastRendered
-                        - scrollBody.firstRendered;
-                if (currentlyVisible < pageLength
-                        && currentlyVisible < totalRows) {
-                    // shake scrollpanel to fill empty space
-                    scrollBodyPanel.setScrollPosition(scrollTop + 1);
-                    scrollBodyPanel.setScrollPosition(scrollTop - 1);
-                }
-
-                sizeNeedsInit = true;
-            }
-        }
-
-    }
-
-    void updateWidth() {
-        if (!isVisible()) {
-            /*
-             * Do not update size when the table is hidden as all column widths
-             * will be set to zero and they won't be recalculated when the table
-             * is set visible again (until the size changes again)
-             */
-            return;
-        }
-
-        if (!isDynamicWidth()) {
-            int innerPixels = getOffsetWidth() - getBorderWidth();
-            if (innerPixels < 0) {
-                innerPixels = 0;
-            }
-            setContentWidth(innerPixels);
-
-            // readjust undefined width columns
-            triggerLazyColumnAdjustment(false);
-
-        } else {
-
-            sizeNeedsInit = true;
-
-            // readjust undefined width columns
-            triggerLazyColumnAdjustment(false);
-        }
-
-        /*
-         * setting width may affect wheter the component has scrollbars -> needs
-         * scrolling or not
-         */
-        setProperTabIndex();
-    }
-
-    private static final int LAZY_COLUMN_ADJUST_TIMEOUT = 300;
-
-    private final Timer lazyAdjustColumnWidths = new Timer() {
-        /**
-         * Check for column widths, and available width, to see if we can fix
-         * column widths "optimally". Doing this lazily to avoid expensive
-         * calculation when resizing is not yet finished.
-         */
-
-        @Override
-        public void run() {
-            if (scrollBody == null) {
-                // Try again later if we get here before scrollBody has been
-                // initalized
-                triggerLazyColumnAdjustment(false);
-                return;
-            }
-
-            Iterator<Widget> headCells = tHead.iterator();
-            int usedMinimumWidth = 0;
-            int totalExplicitColumnsWidths = 0;
-            float expandRatioDivider = 0;
-            int colIndex = 0;
-            while (headCells.hasNext()) {
-                final HeaderCell hCell = (HeaderCell) headCells.next();
-                if (hCell.isDefinedWidth()) {
-                    totalExplicitColumnsWidths += hCell.getWidth();
-                    usedMinimumWidth += hCell.getWidth();
-                } else {
-                    usedMinimumWidth += hCell.getNaturalColumnWidth(colIndex);
-                    expandRatioDivider += hCell.getExpandRatio();
-                }
-                colIndex++;
-            }
-
-            int availW = scrollBody.getAvailableWidth();
-            // Hey IE, are you really sure about this?
-            availW = scrollBody.getAvailableWidth();
-            int visibleCellCount = tHead.getVisibleCellCount();
-            int totalExtraWidth = scrollBody.getCellExtraWidth()
-                    * visibleCellCount;
-            if (willHaveScrollbars()) {
-                totalExtraWidth += Util.getNativeScrollbarSize();
-            }
-            availW -= totalExtraWidth;
-            int forceScrollBodyWidth = -1;
-
-            int extraSpace = availW - usedMinimumWidth;
-            if (extraSpace < 0) {
-                if (getTotalRows() == 0) {
-                    /*
-                     * Too wide header combined with no rows in the table.
-                     * 
-                     * No horizontal scrollbars would be displayed because
-                     * there's no rows that grows too wide causing the
-                     * scrollBody container div to overflow. Must explicitely
-                     * force a width to a scrollbar. (see #9187)
-                     */
-                    forceScrollBodyWidth = usedMinimumWidth + totalExtraWidth;
-                }
-                extraSpace = 0;
-            }
-
-            if (forceScrollBodyWidth > 0) {
-                scrollBody.container.getStyle().setWidth(forceScrollBodyWidth,
-                        Unit.PX);
-            } else {
-                // Clear width that might have been set to force horizontal
-                // scrolling if there are no rows
-                scrollBody.container.getStyle().clearWidth();
-            }
-
-            int totalUndefinedNaturalWidths = usedMinimumWidth
-                    - totalExplicitColumnsWidths;
-
-            // we have some space that can be divided optimally
-            HeaderCell hCell;
-            colIndex = 0;
-            headCells = tHead.iterator();
-            int checksum = 0;
-            while (headCells.hasNext()) {
-                hCell = (HeaderCell) headCells.next();
-                if (!hCell.isDefinedWidth()) {
-                    int w = hCell.getNaturalColumnWidth(colIndex);
-                    int newSpace;
-                    if (expandRatioDivider > 0) {
-                        // divide excess space by expand ratios
-                        newSpace = Math.round((w + extraSpace
-                                * hCell.getExpandRatio() / expandRatioDivider));
-                    } else {
-                        if (totalUndefinedNaturalWidths != 0) {
-                            // divide relatively to natural column widths
-                            newSpace = Math.round(w + (float) extraSpace
-                                    * (float) w / totalUndefinedNaturalWidths);
-                        } else {
-                            newSpace = w;
-                        }
-                    }
-                    checksum += newSpace;
-                    setColWidth(colIndex, newSpace, false);
-                } else {
-                    checksum += hCell.getWidth();
-                }
-                colIndex++;
-            }
-
-            if (extraSpace > 0 && checksum != availW) {
-                /*
-                 * There might be in some cases a rounding error of 1px when
-                 * extra space is divided so if there is one then we give the
-                 * first undefined column 1 more pixel
-                 */
-                headCells = tHead.iterator();
-                colIndex = 0;
-                while (headCells.hasNext()) {
-                    HeaderCell hc = (HeaderCell) headCells.next();
-                    if (!hc.isDefinedWidth()) {
-                        setColWidth(colIndex,
-                                hc.getWidth() + availW - checksum, false);
-                        break;
-                    }
-                    colIndex++;
-                }
-            }
-
-            if (isDynamicHeight() && totalRows == pageLength) {
-                // fix body height (may vary if lazy loading is offhorizontal
-                // scrollbar appears/disappears)
-                int bodyHeight = scrollBody.getRequiredHeight();
-                boolean needsSpaceForHorizontalScrollbar = (availW < usedMinimumWidth);
-                if (needsSpaceForHorizontalScrollbar) {
-                    bodyHeight += Util.getNativeScrollbarSize();
-                }
-                int heightBefore = getOffsetHeight();
-                scrollBodyPanel.setHeight(bodyHeight + "px");
-                if (heightBefore != getOffsetHeight()) {
-                    Util.notifyParentOfSizeChange(VScrollTable.this, false);
-                }
-            }
-            scrollBody.reLayoutComponents();
-            Scheduler.get().scheduleDeferred(new Command() {
-
-                @Override
-                public void execute() {
-                    Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
-                }
-            });
-
-            forceRealignColumnHeaders();
-        }
-
-    };
-
-    private void forceRealignColumnHeaders() {
-        if (BrowserInfo.get().isIE()) {
-            /*
-             * IE does not fire onscroll event if scroll position is reverted to
-             * 0 due to the content element size growth. Ensure headers are in
-             * sync with content manually. Safe to use null event as we don't
-             * actually use the event object in listener.
-             */
-            onScroll(null);
-        }
-    }
-
-    /**
-     * helper to set pixel size of head and body part
-     * 
-     * @param pixels
-     */
-    private void setContentWidth(int pixels) {
-        tHead.setWidth(pixels + "px");
-        scrollBodyPanel.setWidth(pixels + "px");
-        tFoot.setWidth(pixels + "px");
-    }
-
-    private int borderWidth = -1;
-
-    /**
-     * @return border left + border right
-     */
-    private int getBorderWidth() {
-        if (borderWidth < 0) {
-            borderWidth = Util.measureHorizontalPaddingAndBorder(
-                    scrollBodyPanel.getElement(), 2);
-            if (borderWidth < 0) {
-                borderWidth = 0;
-            }
-        }
-        return borderWidth;
-    }
-
-    /**
-     * Ensures scrollable area is properly sized. This method is used when fixed
-     * size is used.
-     */
-    private int containerHeight;
-
-    private void setContainerHeight() {
-        if (!isDynamicHeight()) {
-            containerHeight = getOffsetHeight();
-            containerHeight -= showColHeaders ? tHead.getOffsetHeight() : 0;
-            containerHeight -= tFoot.getOffsetHeight();
-            containerHeight -= getContentAreaBorderHeight();
-            if (containerHeight < 0) {
-                containerHeight = 0;
-            }
-            scrollBodyPanel.setHeight(containerHeight + "px");
-        }
-    }
-
-    private int contentAreaBorderHeight = -1;
-    private int scrollLeft;
-    private int scrollTop;
-    VScrollTableDropHandler dropHandler;
-    private boolean navKeyDown;
-    boolean multiselectPending;
-
-    /**
-     * @return border top + border bottom of the scrollable area of table
-     */
-    private int getContentAreaBorderHeight() {
-        if (contentAreaBorderHeight < 0) {
-
-            DOM.setStyleAttribute(scrollBodyPanel.getElement(), "overflow",
-                    "hidden");
-            int oh = scrollBodyPanel.getOffsetHeight();
-            int ch = scrollBodyPanel.getElement()
-                    .getPropertyInt("clientHeight");
-            contentAreaBorderHeight = oh - ch;
-            DOM.setStyleAttribute(scrollBodyPanel.getElement(), "overflow",
-                    "auto");
-        }
-        return contentAreaBorderHeight;
-    }
-
-    @Override
-    public void setHeight(String height) {
-        if (height.length() == 0
-                && getElement().getStyle().getHeight().length() != 0) {
-            /*
-             * Changing from defined to undefined size -> should do a size init
-             * to take page length into account again
-             */
-            sizeNeedsInit = true;
-        }
-        super.setHeight(height);
-    }
-
-    void updateHeight() {
-        setContainerHeight();
-
-        if (initializedAndAttached) {
-            updatePageLength();
-        }
-        if (!rendering) {
-            // Webkit may sometimes get an odd rendering bug (white space
-            // between header and body), see bug #3875. Running
-            // overflow hack here to shake body element a bit.
-            // We must run the fix as a deferred command to prevent it from
-            // overwriting the scroll position with an outdated value, see
-            // #7607.
-            Scheduler.get().scheduleDeferred(new Command() {
-
-                @Override
-                public void execute() {
-                    Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
-                }
-            });
-        }
-
-        triggerLazyColumnAdjustment(false);
-
-        /*
-         * setting height may affect wheter the component has scrollbars ->
-         * needs scrolling or not
-         */
-        setProperTabIndex();
-
-    }
-
-    /*
-     * Overridden due Table might not survive of visibility change (scroll pos
-     * lost). Example ITabPanel just set contained components invisible and back
-     * when changing tabs.
-     */
-
-    @Override
-    public void setVisible(boolean visible) {
-        if (isVisible() != visible) {
-            super.setVisible(visible);
-            if (initializedAndAttached) {
-                if (visible) {
-                    Scheduler.get().scheduleDeferred(new Command() {
-
-                        @Override
-                        public void execute() {
-                            scrollBodyPanel
-                                    .setScrollPosition(measureRowHeightOffset(firstRowInViewPort));
-                        }
-                    });
-                }
-            }
-        }
-    }
-
-    /**
-     * Helper function to build html snippet for column or row headers
-     * 
-     * @param uidl
-     *            possibly with values caption and icon
-     * @return html snippet containing possibly an icon + caption text
-     */
-    protected String buildCaptionHtmlSnippet(UIDL uidl) {
-        String s = uidl.hasAttribute("caption") ? uidl
-                .getStringAttribute("caption") : "";
-        if (uidl.hasAttribute("icon")) {
-            s = "<img src=\""
-                    + Util.escapeAttribute(client.translateVaadinUri(uidl
-                            .getStringAttribute("icon")))
-                    + "\" alt=\"icon\" class=\"v-icon\">" + s;
-        }
-        return s;
-    }
-
-    /**
-     * This method has logic which rows needs to be requested from server when
-     * user scrolls
-     */
-
-    @Override
-    public void onScroll(ScrollEvent event) {
-        scrollLeft = scrollBodyPanel.getElement().getScrollLeft();
-        scrollTop = scrollBodyPanel.getScrollPosition();
-        /*
-         * #6970 - IE sometimes fires scroll events for a detached table.
-         * 
-         * FIXME initializedAndAttached should probably be renamed - its name
-         * doesn't seem to reflect its semantics. onDetach() doesn't set it to
-         * false, and changing that might break something else, so we need to
-         * check isAttached() separately.
-         */
-        if (!initializedAndAttached || !isAttached()) {
-            return;
-        }
-        if (!enabled) {
-            scrollBodyPanel
-                    .setScrollPosition(measureRowHeightOffset(firstRowInViewPort));
-            return;
-        }
-
-        rowRequestHandler.cancel();
-
-        if (BrowserInfo.get().isSafari() && event != null && scrollTop == 0) {
-            // due to the webkitoverflowworkaround, top may sometimes report 0
-            // for webkit, although it really is not. Expecting to have the
-            // correct
-            // value available soon.
-            Scheduler.get().scheduleDeferred(new Command() {
-
-                @Override
-                public void execute() {
-                    onScroll(null);
-                }
-            });
-            return;
-        }
-
-        // fix headers horizontal scrolling
-        tHead.setHorizontalScrollPosition(scrollLeft);
-
-        // fix footers horizontal scrolling
-        tFoot.setHorizontalScrollPosition(scrollLeft);
-
-        firstRowInViewPort = calcFirstRowInViewPort();
-        if (firstRowInViewPort > totalRows - pageLength) {
-            firstRowInViewPort = totalRows - pageLength;
-        }
-
-        int postLimit = (int) (firstRowInViewPort + (pageLength - 1) + pageLength
-                * cache_react_rate);
-        if (postLimit > totalRows - 1) {
-            postLimit = totalRows - 1;
-        }
-        int preLimit = (int) (firstRowInViewPort - pageLength
-                * cache_react_rate);
-        if (preLimit < 0) {
-            preLimit = 0;
-        }
-        final int lastRendered = scrollBody.getLastRendered();
-        final int firstRendered = scrollBody.getFirstRendered();
-
-        if (postLimit <= lastRendered && preLimit >= firstRendered) {
-            // we're within no-react area, no need to request more rows
-            // remember which firstvisible we requested, in case the server has
-            // a differing opinion
-            lastRequestedFirstvisible = firstRowInViewPort;
-            client.updateVariable(paintableId, "firstvisible",
-                    firstRowInViewPort, false);
-            return;
-        }
-
-        if (firstRowInViewPort - pageLength * cache_rate > lastRendered
-                || firstRowInViewPort + pageLength + pageLength * cache_rate < firstRendered) {
-            // need a totally new set of rows
-            rowRequestHandler
-                    .setReqFirstRow((firstRowInViewPort - (int) (pageLength * cache_rate)));
-            int last = firstRowInViewPort + (int) (cache_rate * pageLength)
-                    + pageLength - 1;
-            if (last >= totalRows) {
-                last = totalRows - 1;
-            }
-            rowRequestHandler.setReqRows(last
-                    - rowRequestHandler.getReqFirstRow() + 1);
-            rowRequestHandler.deferRowFetch();
-            return;
-        }
-        if (preLimit < firstRendered) {
-            // need some rows to the beginning of the rendered area
-            rowRequestHandler
-                    .setReqFirstRow((int) (firstRowInViewPort - pageLength
-                            * cache_rate));
-            rowRequestHandler.setReqRows(firstRendered
-                    - rowRequestHandler.getReqFirstRow());
-            rowRequestHandler.deferRowFetch();
-
-            return;
-        }
-        if (postLimit > lastRendered) {
-            // need some rows to the end of the rendered area
-            rowRequestHandler.setReqFirstRow(lastRendered + 1);
-            rowRequestHandler.setReqRows((int) ((firstRowInViewPort
-                    + pageLength + pageLength * cache_rate) - lastRendered));
-            rowRequestHandler.deferRowFetch();
-        }
-    }
-
-    protected int calcFirstRowInViewPort() {
-        return (int) Math.ceil(scrollTop / scrollBody.getRowHeight());
-    }
-
-    @Override
-    public VScrollTableDropHandler getDropHandler() {
-        return dropHandler;
-    }
-
-    private static class TableDDDetails {
-        int overkey = -1;
-        VerticalDropLocation dropLocation;
-        String colkey;
-
-        @Override
-        public boolean equals(Object obj) {
-            if (obj instanceof TableDDDetails) {
-                TableDDDetails other = (TableDDDetails) obj;
-                return dropLocation == other.dropLocation
-                        && overkey == other.overkey
-                        && ((colkey != null && colkey.equals(other.colkey)) || (colkey == null && other.colkey == null));
-            }
-            return false;
-        }
-
-        //
-        // public int hashCode() {
-        // return overkey;
-        // }
-    }
-
-    public class VScrollTableDropHandler extends VAbstractDropHandler {
-
-        private static final String ROWSTYLEBASE = "v-table-row-drag-";
-        private TableDDDetails dropDetails;
-        private TableDDDetails lastEmphasized;
-
-        @Override
-        public void dragEnter(VDragEvent drag) {
-            updateDropDetails(drag);
-            super.dragEnter(drag);
-        }
-
-        private void updateDropDetails(VDragEvent drag) {
-            dropDetails = new TableDDDetails();
-            Element elementOver = drag.getElementOver();
-
-            VScrollTableRow row = Util.findWidget(elementOver, getRowClass());
-            if (row != null) {
-                dropDetails.overkey = row.rowKey;
-                Element tr = row.getElement();
-                Element element = elementOver;
-                while (element != null && element.getParentElement() != tr) {
-                    element = (Element) element.getParentElement();
-                }
-                int childIndex = DOM.getChildIndex(tr, element);
-                dropDetails.colkey = tHead.getHeaderCell(childIndex)
-                        .getColKey();
-                dropDetails.dropLocation = DDUtil.getVerticalDropLocation(
-                        row.getElement(), drag.getCurrentGwtEvent(), 0.2);
-            }
-
-            drag.getDropDetails().put("itemIdOver", dropDetails.overkey + "");
-            drag.getDropDetails().put(
-                    "detail",
-                    dropDetails.dropLocation != null ? dropDetails.dropLocation
-                            .toString() : null);
-
-        }
-
-        private Class<? extends Widget> getRowClass() {
-            // get the row type this way to make dd work in derived
-            // implementations
-            return scrollBody.iterator().next().getClass();
-        }
-
-        @Override
-        public void dragOver(VDragEvent drag) {
-            TableDDDetails oldDetails = dropDetails;
-            updateDropDetails(drag);
-            if (!oldDetails.equals(dropDetails)) {
-                deEmphasis();
-                final TableDDDetails newDetails = dropDetails;
-                VAcceptCallback cb = new VAcceptCallback() {
-
-                    @Override
-                    public void accepted(VDragEvent event) {
-                        if (newDetails.equals(dropDetails)) {
-                            dragAccepted(event);
-                        }
-                        /*
-                         * Else new target slot already defined, ignore
-                         */
-                    }
-                };
-                validate(cb, drag);
-            }
-        }
-
-        @Override
-        public void dragLeave(VDragEvent drag) {
-            deEmphasis();
-            super.dragLeave(drag);
-        }
-
-        @Override
-        public boolean drop(VDragEvent drag) {
-            deEmphasis();
-            return super.drop(drag);
-        }
-
-        private void deEmphasis() {
-            UIObject.setStyleName(getElement(),
-                    VScrollTable.this.getStylePrimaryName() + "-drag", false);
-            if (lastEmphasized == null) {
-                return;
-            }
-            for (Widget w : scrollBody.renderedRows) {
-                VScrollTableRow row = (VScrollTableRow) w;
-                if (lastEmphasized != null
-                        && row.rowKey == lastEmphasized.overkey) {
-                    String stylename = ROWSTYLEBASE
-                            + lastEmphasized.dropLocation.toString()
-                                    .toLowerCase();
-                    VScrollTableRow.setStyleName(row.getElement(), stylename,
-                            false);
-                    lastEmphasized = null;
-                    return;
-                }
-            }
-        }
-
-        /**
-         * TODO needs different drop modes ?? (on cells, on rows), now only
-         * supports rows
-         */
-        private void emphasis(TableDDDetails details) {
-            deEmphasis();
-            UIObject.setStyleName(getElement(),
-                    VScrollTable.this.getStylePrimaryName() + "-drag", true);
-            // iterate old and new emphasized row
-            for (Widget w : scrollBody.renderedRows) {
-                VScrollTableRow row = (VScrollTableRow) w;
-                if (details != null && details.overkey == row.rowKey) {
-                    String stylename = ROWSTYLEBASE
-                            + details.dropLocation.toString().toLowerCase();
-                    VScrollTableRow.setStyleName(row.getElement(), stylename,
-                            true);
-                    lastEmphasized = details;
-                    return;
-                }
-            }
-        }
-
-        @Override
-        protected void dragAccepted(VDragEvent drag) {
-            emphasis(dropDetails);
-        }
-
-        @Override
-        public ComponentConnector getConnector() {
-            return ConnectorMap.get(client).getConnector(VScrollTable.this);
-        }
-
-        @Override
-        public ApplicationConnection getApplicationConnection() {
-            return client;
-        }
-
-    }
-
-    protected VScrollTableRow getFocusedRow() {
-        return focusedRow;
-    }
-
-    /**
-     * Moves the selection head to a specific row
-     * 
-     * @param row
-     *            The row to where the selection head should move
-     * @return Returns true if focus was moved successfully, else false
-     */
-    public boolean setRowFocus(VScrollTableRow row) {
-
-        if (!isSelectable()) {
-            return false;
-        }
-
-        // Remove previous selection
-        if (focusedRow != null && focusedRow != row) {
-            focusedRow.removeStyleName(getStylePrimaryName() + "-focus");
-        }
-
-        if (row != null) {
-
-            // Apply focus style to new selection
-            row.addStyleName(getStylePrimaryName() + "-focus");
-
-            /*
-             * Trying to set focus on already focused row
-             */
-            if (row == focusedRow) {
-                return false;
-            }
-
-            // Set new focused row
-            focusedRow = row;
-
-            ensureRowIsVisible(row);
-
-            return true;
-        }
-
-        return false;
-    }
-
-    /**
-     * Ensures that the row is visible
-     * 
-     * @param row
-     *            The row to ensure is visible
-     */
-    private void ensureRowIsVisible(VScrollTableRow row) {
-        if (BrowserInfo.get().isTouchDevice()) {
-            // Skip due to android devices that have broken scrolltop will may
-            // get odd scrolling here.
-            return;
-        }
-        Util.scrollIntoViewVertically(row.getElement());
-    }
-
-    /**
-     * Handles the keyboard events handled by the table
-     * 
-     * @param event
-     *            The keyboard event received
-     * @return true iff the navigation event was handled
-     */
-    protected boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
-        if (keycode == KeyCodes.KEY_TAB || keycode == KeyCodes.KEY_SHIFT) {
-            // Do not handle tab key
-            return false;
-        }
-
-        // Down navigation
-        if (!isSelectable() && keycode == getNavigationDownKey()) {
-            scrollBodyPanel.setScrollPosition(scrollBodyPanel
-                    .getScrollPosition() + scrollingVelocity);
-            return true;
-        } else if (keycode == getNavigationDownKey()) {
-            if (isMultiSelectModeAny() && moveFocusDown()) {
-                selectFocusedRow(ctrl, shift);
-
-            } else if (isSingleSelectMode() && !shift && moveFocusDown()) {
-                selectFocusedRow(ctrl, shift);
-            }
-            return true;
-        }
-
-        // Up navigation
-        if (!isSelectable() && keycode == getNavigationUpKey()) {
-            scrollBodyPanel.setScrollPosition(scrollBodyPanel
-                    .getScrollPosition() - scrollingVelocity);
-            return true;
-        } else if (keycode == getNavigationUpKey()) {
-            if (isMultiSelectModeAny() && moveFocusUp()) {
-                selectFocusedRow(ctrl, shift);
-            } else if (isSingleSelectMode() && !shift && moveFocusUp()) {
-                selectFocusedRow(ctrl, shift);
-            }
-            return true;
-        }
-
-        if (keycode == getNavigationLeftKey()) {
-            // Left navigation
-            scrollBodyPanel.setHorizontalScrollPosition(scrollBodyPanel
-                    .getHorizontalScrollPosition() - scrollingVelocity);
-            return true;
-
-        } else if (keycode == getNavigationRightKey()) {
-            // Right navigation
-            scrollBodyPanel.setHorizontalScrollPosition(scrollBodyPanel
-                    .getHorizontalScrollPosition() + scrollingVelocity);
-            return true;
-        }
-
-        // Select navigation
-        if (isSelectable() && keycode == getNavigationSelectKey()) {
-            if (isSingleSelectMode()) {
-                boolean wasSelected = focusedRow.isSelected();
-                deselectAll();
-                if (!wasSelected || !nullSelectionAllowed) {
-                    focusedRow.toggleSelection();
-                }
-            } else {
-                focusedRow.toggleSelection();
-                removeRowFromUnsentSelectionRanges(focusedRow);
-            }
-
-            sendSelectedRows();
-            return true;
-        }
-
-        // Page Down navigation
-        if (keycode == getNavigationPageDownKey()) {
-            if (isSelectable()) {
-                /*
-                 * If selectable we plagiate MSW behaviour: first scroll to the
-                 * end of current view. If at the end, scroll down one page
-                 * length and keep the selected row in the bottom part of
-                 * visible area.
-                 */
-                if (!isFocusAtTheEndOfTable()) {
-                    VScrollTableRow lastVisibleRowInViewPort = scrollBody
-                            .getRowByRowIndex(firstRowInViewPort
-                                    + getFullyVisibleRowCount() - 1);
-                    if (lastVisibleRowInViewPort != null
-                            && lastVisibleRowInViewPort != focusedRow) {
-                        // focused row is not at the end of the table, move
-                        // focus and select the last visible row
-                        setRowFocus(lastVisibleRowInViewPort);
-                        selectFocusedRow(ctrl, shift);
-                        sendSelectedRows();
-                    } else {
-                        int indexOfToBeFocused = focusedRow.getIndex()
-                                + getFullyVisibleRowCount();
-                        if (indexOfToBeFocused >= totalRows) {
-                            indexOfToBeFocused = totalRows - 1;
-                        }
-                        VScrollTableRow toBeFocusedRow = scrollBody
-                                .getRowByRowIndex(indexOfToBeFocused);
-
-                        if (toBeFocusedRow != null) {
-                            /*
-                             * if the next focused row is rendered
-                             */
-                            setRowFocus(toBeFocusedRow);
-                            selectFocusedRow(ctrl, shift);
-                            // TODO needs scrollintoview ?
-                            sendSelectedRows();
-                        } else {
-                            // scroll down by pixels and return, to wait for
-                            // new rows, then select the last item in the
-                            // viewport
-                            selectLastItemInNextRender = true;
-                            multiselectPending = shift;
-                            scrollByPagelenght(1);
-                        }
-                    }
-                }
-            } else {
-                /* No selections, go page down by scrolling */
-                scrollByPagelenght(1);
-            }
-            return true;
-        }
-
-        // Page Up navigation
-        if (keycode == getNavigationPageUpKey()) {
-            if (isSelectable()) {
-                /*
-                 * If selectable we plagiate MSW behaviour: first scroll to the
-                 * end of current view. If at the end, scroll down one page
-                 * length and keep the selected row in the bottom part of
-                 * visible area.
-                 */
-                if (!isFocusAtTheBeginningOfTable()) {
-                    VScrollTableRow firstVisibleRowInViewPort = scrollBody
-                            .getRowByRowIndex(firstRowInViewPort);
-                    if (firstVisibleRowInViewPort != null
-                            && firstVisibleRowInViewPort != focusedRow) {
-                        // focus is not at the beginning of the table, move
-                        // focus and select the first visible row
-                        setRowFocus(firstVisibleRowInViewPort);
-                        selectFocusedRow(ctrl, shift);
-                        sendSelectedRows();
-                    } else {
-                        int indexOfToBeFocused = focusedRow.getIndex()
-                                - getFullyVisibleRowCount();
-                        if (indexOfToBeFocused < 0) {
-                            indexOfToBeFocused = 0;
-                        }
-                        VScrollTableRow toBeFocusedRow = scrollBody
-                                .getRowByRowIndex(indexOfToBeFocused);
-
-                        if (toBeFocusedRow != null) { // if the next focused row
-                                                      // is rendered
-                            setRowFocus(toBeFocusedRow);
-                            selectFocusedRow(ctrl, shift);
-                            // TODO needs scrollintoview ?
-                            sendSelectedRows();
-                        } else {
-                            // unless waiting for the next rowset already
-                            // scroll down by pixels and return, to wait for
-                            // new rows, then select the last item in the
-                            // viewport
-                            selectFirstItemInNextRender = true;
-                            multiselectPending = shift;
-                            scrollByPagelenght(-1);
-                        }
-                    }
-                }
-            } else {
-                /* No selections, go page up by scrolling */
-                scrollByPagelenght(-1);
-            }
-
-            return true;
-        }
-
-        // Goto start navigation
-        if (keycode == getNavigationStartKey()) {
-            scrollBodyPanel.setScrollPosition(0);
-            if (isSelectable()) {
-                if (focusedRow != null && focusedRow.getIndex() == 0) {
-                    return false;
-                } else {
-                    VScrollTableRow rowByRowIndex = (VScrollTableRow) scrollBody
-                            .iterator().next();
-                    if (rowByRowIndex.getIndex() == 0) {
-                        setRowFocus(rowByRowIndex);
-                        selectFocusedRow(ctrl, shift);
-                        sendSelectedRows();
-                    } else {
-                        // first row of table will come in next row fetch
-                        if (ctrl) {
-                            focusFirstItemInNextRender = true;
-                        } else {
-                            selectFirstItemInNextRender = true;
-                            multiselectPending = shift;
-                        }
-                    }
-                }
-            }
-            return true;
-        }
-
-        // Goto end navigation
-        if (keycode == getNavigationEndKey()) {
-            scrollBodyPanel.setScrollPosition(scrollBody.getOffsetHeight());
-            if (isSelectable()) {
-                final int lastRendered = scrollBody.getLastRendered();
-                if (lastRendered + 1 == totalRows) {
-                    VScrollTableRow rowByRowIndex = scrollBody
-                            .getRowByRowIndex(lastRendered);
-                    if (focusedRow != rowByRowIndex) {
-                        setRowFocus(rowByRowIndex);
-                        selectFocusedRow(ctrl, shift);
-                        sendSelectedRows();
-                    }
-                } else {
-                    if (ctrl) {
-                        focusLastItemInNextRender = true;
-                    } else {
-                        selectLastItemInNextRender = true;
-                        multiselectPending = shift;
-                    }
-                }
-            }
-            return true;
-        }
-
-        return false;
-    }
-
-    private boolean isFocusAtTheBeginningOfTable() {
-        return focusedRow.getIndex() == 0;
-    }
-
-    private boolean isFocusAtTheEndOfTable() {
-        return focusedRow.getIndex() + 1 >= totalRows;
-    }
-
-    private int getFullyVisibleRowCount() {
-        return (int) (scrollBodyPanel.getOffsetHeight() / scrollBody
-                .getRowHeight());
-    }
-
-    private void scrollByPagelenght(int i) {
-        int pixels = i * scrollBodyPanel.getOffsetHeight();
-        int newPixels = scrollBodyPanel.getScrollPosition() + pixels;
-        if (newPixels < 0) {
-            newPixels = 0;
-        } // else if too high, NOP (all know browsers accept illegally big
-          // values here)
-        scrollBodyPanel.setScrollPosition(newPixels);
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see
-     * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
-     * .dom.client.FocusEvent)
-     */
-
-    @Override
-    public void onFocus(FocusEvent event) {
-        if (isFocusable()) {
-            hasFocus = true;
-
-            // Focus a row if no row is in focus
-            if (focusedRow == null) {
-                focusRowFromBody();
-            } else {
-                setRowFocus(focusedRow);
-            }
-        }
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see
-     * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event
-     * .dom.client.BlurEvent)
-     */
-
-    @Override
-    public void onBlur(BlurEvent event) {
-        hasFocus = false;
-        navKeyDown = false;
-
-        if (BrowserInfo.get().isIE()) {
-            // IE sometimes moves focus to a clicked table cell...
-            Element focusedElement = Util.getIEFocusedElement();
-            if (Util.getConnectorForElement(client, getParent(), focusedElement) == this) {
-                // ..in that case, steal the focus back to the focus handler
-                // but not if focus is in a child component instead (#7965)
-                focus();
-                return;
-            }
-        }
-
-        if (isFocusable()) {
-            // Unfocus any row
-            setRowFocus(null);
-        }
-    }
-
-    /**
-     * Removes a key from a range if the key is found in a selected range
-     * 
-     * @param key
-     *            The key to remove
-     */
-    private void removeRowFromUnsentSelectionRanges(VScrollTableRow row) {
-        Collection<SelectionRange> newRanges = null;
-        for (Iterator<SelectionRange> iterator = selectedRowRanges.iterator(); iterator
-                .hasNext();) {
-            SelectionRange range = iterator.next();
-            if (range.inRange(row)) {
-                // Split the range if given row is in range
-                Collection<SelectionRange> splitranges = range.split(row);
-                if (newRanges == null) {
-                    newRanges = new ArrayList<SelectionRange>();
-                }
-                newRanges.addAll(splitranges);
-                iterator.remove();
-            }
-        }
-        if (newRanges != null) {
-            selectedRowRanges.addAll(newRanges);
-        }
-    }
-
-    /**
-     * Can the Table be focused?
-     * 
-     * @return True if the table can be focused, else false
-     */
-    public boolean isFocusable() {
-        if (scrollBody != null && enabled) {
-            return !(!hasHorizontalScrollbar() && !hasVerticalScrollbar() && !isSelectable());
-        }
-        return false;
-    }
-
-    private boolean hasHorizontalScrollbar() {
-        return scrollBody.getOffsetWidth() > scrollBodyPanel.getOffsetWidth();
-    }
-
-    private boolean hasVerticalScrollbar() {
-        return scrollBody.getOffsetHeight() > scrollBodyPanel.getOffsetHeight();
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see com.vaadin.client.Focusable#focus()
-     */
-
-    @Override
-    public void focus() {
-        if (isFocusable()) {
-            scrollBodyPanel.focus();
-        }
-    }
-
-    /**
-     * Sets the proper tabIndex for scrollBodyPanel (the focusable elemen in the
-     * component).
-     * 
-     * If the component has no explicit tabIndex a zero is given (default
-     * tabbing order based on dom hierarchy) or -1 if the component does not
-     * need to gain focus. The component needs no focus if it has no scrollabars
-     * (not scrollable) and not selectable. Note that in the future shortcut
-     * actions may need focus.
-     * 
-     */
-    void setProperTabIndex() {
-        int storedScrollTop = 0;
-        int storedScrollLeft = 0;
-
-        if (BrowserInfo.get().getOperaVersion() >= 11) {
-            // Workaround for Opera scroll bug when changing tabIndex (#6222)
-            storedScrollTop = scrollBodyPanel.getScrollPosition();
-            storedScrollLeft = scrollBodyPanel.getHorizontalScrollPosition();
-        }
-
-        if (tabIndex == 0 && !isFocusable()) {
-            scrollBodyPanel.setTabIndex(-1);
-        } else {
-            scrollBodyPanel.setTabIndex(tabIndex);
-        }
-
-        if (BrowserInfo.get().getOperaVersion() >= 11) {
-            // Workaround for Opera scroll bug when changing tabIndex (#6222)
-            scrollBodyPanel.setScrollPosition(storedScrollTop);
-            scrollBodyPanel.setHorizontalScrollPosition(storedScrollLeft);
-        }
-    }
-
-    public void startScrollingVelocityTimer() {
-        if (scrollingVelocityTimer == null) {
-            scrollingVelocityTimer = new Timer() {
-
-                @Override
-                public void run() {
-                    scrollingVelocity++;
-                }
-            };
-            scrollingVelocityTimer.scheduleRepeating(100);
-        }
-    }
-
-    public void cancelScrollingVelocityTimer() {
-        if (scrollingVelocityTimer != null) {
-            // Remove velocityTimer if it exists and the Table is disabled
-            scrollingVelocityTimer.cancel();
-            scrollingVelocityTimer = null;
-            scrollingVelocity = 10;
-        }
-    }
-
-    /**
-     * 
-     * @param keyCode
-     * @return true if the given keyCode is used by the table for navigation
-     */
-    private boolean isNavigationKey(int keyCode) {
-        return keyCode == getNavigationUpKey()
-                || keyCode == getNavigationLeftKey()
-                || keyCode == getNavigationRightKey()
-                || keyCode == getNavigationDownKey()
-                || keyCode == getNavigationPageUpKey()
-                || keyCode == getNavigationPageDownKey()
-                || keyCode == getNavigationEndKey()
-                || keyCode == getNavigationStartKey();
-    }
-
-    public void lazyRevertFocusToRow(final VScrollTableRow currentlyFocusedRow) {
-        Scheduler.get().scheduleFinally(new ScheduledCommand() {
-
-            @Override
-            public void execute() {
-                if (currentlyFocusedRow != null) {
-                    setRowFocus(currentlyFocusedRow);
-                } else {
-                    VConsole.log("no row?");
-                    focusRowFromBody();
-                }
-                scrollBody.ensureFocus();
-            }
-        });
-    }
-
-    @Override
-    public Action[] getActions() {
-        if (bodyActionKeys == null) {
-            return new Action[] {};
-        }
-        final Action[] actions = new Action[bodyActionKeys.length];
-        for (int i = 0; i < actions.length; i++) {
-            final String actionKey = bodyActionKeys[i];
-            Action bodyAction = new TreeAction(this, null, actionKey);
-            bodyAction.setCaption(getActionCaption(actionKey));
-            bodyAction.setIconUrl(getActionIcon(actionKey));
-            actions[i] = bodyAction;
-        }
-        return actions;
-    }
-
-    @Override
-    public ApplicationConnection getClient() {
-        return client;
-    }
-
-    @Override
-    public String getPaintableId() {
-        return paintableId;
-    }
-
-    /**
-     * Add this to the element mouse down event by using element.setPropertyJSO
-     * ("onselectstart",applyDisableTextSelectionIEHack()); Remove it then again
-     * when the mouse is depressed in the mouse up event.
-     * 
-     * @return Returns the JSO preventing text selection
-     */
-    private static native JavaScriptObject getPreventTextSelectionIEHack()
-    /*-{
-            return function(){ return false; };
-    }-*/;
-
-    public void triggerLazyColumnAdjustment(boolean now) {
-        lazyAdjustColumnWidths.cancel();
-        if (now) {
-            lazyAdjustColumnWidths.run();
-        } else {
-            lazyAdjustColumnWidths.schedule(LAZY_COLUMN_ADJUST_TIMEOUT);
-        }
-    }
-
-    private boolean isDynamicWidth() {
-        ComponentConnector paintable = ConnectorMap.get(client).getConnector(
-                this);
-        return paintable.isUndefinedWidth();
-    }
-
-    private boolean isDynamicHeight() {
-        ComponentConnector paintable = ConnectorMap.get(client).getConnector(
-                this);
-        if (paintable == null) {
-            // This should be refactored. As isDynamicHeight can be called from
-            // a timer it is possible that the connector has been unregistered
-            // when this method is called, causing getConnector to return null.
-            return false;
-        }
-        return paintable.isUndefinedHeight();
-    }
-
-    private void debug(String msg) {
-        if (enableDebug) {
-            VConsole.error(msg);
-        }
-    }
-
-    public Widget getWidgetForPaintable() {
-        return this;
-    }
-}
index 160ee5e62907ba76d5ba251592d5af0a50b59deb..8e0ccaaa79cb28f667531c071f2374e62123731d 100644 (file)
@@ -24,6 +24,7 @@ import com.vaadin.client.ComponentConnector;
 import com.vaadin.client.Paintable;
 import com.vaadin.client.UIDL;
 import com.vaadin.client.ui.AbstractComponentContainerConnector;
+import com.vaadin.client.ui.VTabsheetBase;
 import com.vaadin.shared.ui.tabsheet.TabsheetBaseConstants;
 
 public abstract class TabsheetBaseConnector extends
index fd09b0316056f17f62345c5709f023dd851ea70c..edf257ad52f8cc5163d54a2b0af7ba92c5bf1916 100644 (file)
@@ -24,6 +24,7 @@ import com.vaadin.client.TooltipInfo;
 import com.vaadin.client.UIDL;
 import com.vaadin.client.Util;
 import com.vaadin.client.ui.SimpleManagedLayout;
+import com.vaadin.client.ui.VTabsheet;
 import com.vaadin.client.ui.layout.MayScrollChildren;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.tabsheet.TabsheetState;
diff --git a/client/src/com/vaadin/client/ui/tabsheet/VTabsheet.java b/client/src/com/vaadin/client/ui/tabsheet/VTabsheet.java
deleted file mode 100644 (file)
index 6ab5f4b..0000000
+++ /dev/null
@@ -1,1252 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.tabsheet;
-
-import java.util.Iterator;
-import java.util.List;
-
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.dom.client.DivElement;
-import com.google.gwt.dom.client.Style;
-import com.google.gwt.dom.client.Style.Visibility;
-import com.google.gwt.dom.client.TableCellElement;
-import com.google.gwt.dom.client.TableElement;
-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.HasBlurHandlers;
-import com.google.gwt.event.dom.client.HasFocusHandlers;
-import com.google.gwt.event.dom.client.HasKeyDownHandlers;
-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.shared.HandlerRegistration;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.ComplexPanel;
-import com.google.gwt.user.client.ui.SimplePanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.google.gwt.user.client.ui.impl.FocusImpl;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.ComponentConnector;
-import com.vaadin.client.ConnectorMap;
-import com.vaadin.client.Focusable;
-import com.vaadin.client.TooltipInfo;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.Util;
-import com.vaadin.client.VCaption;
-import com.vaadin.client.ui.label.VLabel;
-import com.vaadin.shared.ComponentState;
-import com.vaadin.shared.EventId;
-import com.vaadin.shared.ui.ComponentStateUtil;
-import com.vaadin.shared.ui.tabsheet.TabsheetBaseConstants;
-import com.vaadin.shared.ui.tabsheet.TabsheetConstants;
-
-public class VTabsheet extends VTabsheetBase implements Focusable,
-        FocusHandler, BlurHandler, KeyDownHandler {
-
-    private static class VCloseEvent {
-        private Tab tab;
-
-        VCloseEvent(Tab tab) {
-            this.tab = tab;
-        }
-
-        public Tab getTab() {
-            return tab;
-        }
-
-    }
-
-    private interface VCloseHandler {
-        public void onClose(VCloseEvent event);
-    }
-
-    /**
-     * Representation of a single "tab" shown in the TabBar
-     * 
-     */
-    private static class Tab extends SimplePanel implements HasFocusHandlers,
-            HasBlurHandlers, HasKeyDownHandlers {
-        private static final String TD_CLASSNAME = CLASSNAME + "-tabitemcell";
-        private static final String TD_FIRST_CLASSNAME = TD_CLASSNAME
-                + "-first";
-        private static final String TD_SELECTED_CLASSNAME = TD_CLASSNAME
-                + "-selected";
-        private static final String TD_SELECTED_FIRST_CLASSNAME = TD_SELECTED_CLASSNAME
-                + "-first";
-        private static final String TD_DISABLED_CLASSNAME = TD_CLASSNAME
-                + "-disabled";
-
-        private static final String DIV_CLASSNAME = CLASSNAME + "-tabitem";
-        private static final String DIV_SELECTED_CLASSNAME = DIV_CLASSNAME
-                + "-selected";
-
-        private TabCaption tabCaption;
-        Element td = getElement();
-        private VCloseHandler closeHandler;
-
-        private boolean enabledOnServer = true;
-        private Element div;
-        private TabBar tabBar;
-        private boolean hiddenOnServer = false;
-
-        private String styleName;
-
-        private Tab(TabBar tabBar) {
-            super(DOM.createTD());
-            this.tabBar = tabBar;
-            setStyleName(td, TD_CLASSNAME);
-
-            div = DOM.createDiv();
-            focusImpl.setTabIndex(td, -1);
-            setStyleName(div, DIV_CLASSNAME);
-
-            DOM.appendChild(td, div);
-
-            tabCaption = new TabCaption(this, getTabsheet()
-                    .getApplicationConnection());
-            add(tabCaption);
-
-            addFocusHandler(getTabsheet());
-            addBlurHandler(getTabsheet());
-            addKeyDownHandler(getTabsheet());
-        }
-
-        public boolean isHiddenOnServer() {
-            return hiddenOnServer;
-        }
-
-        public void setHiddenOnServer(boolean hiddenOnServer) {
-            this.hiddenOnServer = hiddenOnServer;
-        }
-
-        @Override
-        protected Element getContainerElement() {
-            // Attach caption element to div, not td
-            return div;
-        }
-
-        public boolean isEnabledOnServer() {
-            return enabledOnServer;
-        }
-
-        public void setEnabledOnServer(boolean enabled) {
-            enabledOnServer = enabled;
-            setStyleName(td, TD_DISABLED_CLASSNAME, !enabled);
-            if (!enabled) {
-                focusImpl.setTabIndex(td, -1);
-            }
-        }
-
-        public void addClickHandler(ClickHandler handler) {
-            tabCaption.addClickHandler(handler);
-        }
-
-        public void setCloseHandler(VCloseHandler closeHandler) {
-            this.closeHandler = closeHandler;
-        }
-
-        /**
-         * Toggles the style names for the Tab
-         * 
-         * @param selected
-         *            true if the Tab is selected
-         * @param first
-         *            true if the Tab is the first visible Tab
-         */
-        public void setStyleNames(boolean selected, boolean first) {
-            setStyleName(td, TD_FIRST_CLASSNAME, first);
-            setStyleName(td, TD_SELECTED_CLASSNAME, selected);
-            setStyleName(td, TD_SELECTED_FIRST_CLASSNAME, selected && first);
-            setStyleName(div, DIV_SELECTED_CLASSNAME, selected);
-        }
-
-        public void setTabulatorIndex(int tabIndex) {
-            focusImpl.setTabIndex(td, tabIndex);
-        }
-
-        public boolean isClosable() {
-            return tabCaption.isClosable();
-        }
-
-        public void onClose() {
-            closeHandler.onClose(new VCloseEvent(this));
-        }
-
-        public VTabsheet getTabsheet() {
-            return tabBar.getTabsheet();
-        }
-
-        public void updateFromUIDL(UIDL tabUidl) {
-            tabCaption.updateCaption(tabUidl);
-
-            // Apply the styleName set for the tab
-            String newStyleName = tabUidl
-                    .getStringAttribute(TabsheetConstants.TAB_STYLE_NAME);
-            // Find the nth td element
-            if (newStyleName != null && newStyleName.length() != 0) {
-                if (!newStyleName.equals(styleName)) {
-                    // If we have a new style name
-                    if (styleName != null && styleName.length() != 0) {
-                        // Remove old style name if present
-                        td.removeClassName(TD_CLASSNAME + "-" + styleName);
-                    }
-                    // Set new style name
-                    td.addClassName(TD_CLASSNAME + "-" + newStyleName);
-                    styleName = newStyleName;
-                }
-            } else if (styleName != null) {
-                // Remove the set stylename if no stylename is present in the
-                // uidl
-                td.removeClassName(TD_CLASSNAME + "-" + styleName);
-                styleName = null;
-            }
-        }
-
-        public void recalculateCaptionWidth() {
-            tabCaption.setWidth(tabCaption.getRequiredWidth() + "px");
-        }
-
-        @Override
-        public HandlerRegistration addFocusHandler(FocusHandler handler) {
-            return addDomHandler(handler, FocusEvent.getType());
-        }
-
-        @Override
-        public HandlerRegistration addBlurHandler(BlurHandler handler) {
-            return addDomHandler(handler, BlurEvent.getType());
-        }
-
-        @Override
-        public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) {
-            return addDomHandler(handler, KeyDownEvent.getType());
-        }
-
-        public void focus() {
-            focusImpl.focus(td);
-        }
-
-        public void blur() {
-            focusImpl.blur(td);
-        }
-    }
-
-    public static class TabCaption extends VCaption {
-
-        private boolean closable = false;
-        private Element closeButton;
-        private Tab tab;
-        private ApplicationConnection client;
-
-        TabCaption(Tab tab, ApplicationConnection client) {
-            super(client);
-            this.client = client;
-            this.tab = tab;
-        }
-
-        public boolean updateCaption(UIDL uidl) {
-            if (uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION)) {
-                setTooltipInfo(new TooltipInfo(
-                        uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION),
-                        uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ERROR_MESSAGE)));
-            } else {
-                setTooltipInfo(null);
-            }
-
-            // TODO need to call this instead of super because the caption does
-            // not have an owner
-            boolean ret = updateCaptionWithoutOwner(
-                    uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_CAPTION),
-                    uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DISABLED),
-                    uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION),
-                    uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ERROR_MESSAGE),
-                    uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ICON));
-
-            setClosable(uidl.hasAttribute("closable"));
-
-            return ret;
-        }
-
-        private VTabsheet getTabsheet() {
-            return tab.getTabsheet();
-        }
-
-        @Override
-        public void onBrowserEvent(Event event) {
-            if (closable && event.getTypeInt() == Event.ONCLICK
-                    && event.getEventTarget().cast() == closeButton) {
-                tab.onClose();
-                event.stopPropagation();
-                event.preventDefault();
-            }
-
-            super.onBrowserEvent(event);
-
-            if (event.getTypeInt() == Event.ONLOAD) {
-                getTabsheet().tabSizeMightHaveChanged(getTab());
-            }
-        }
-
-        public Tab getTab() {
-            return tab;
-        }
-
-        public void setClosable(boolean closable) {
-            this.closable = closable;
-            if (closable && closeButton == null) {
-                closeButton = DOM.createSpan();
-                closeButton.setInnerHTML("&times;");
-                closeButton
-                        .setClassName(VTabsheet.CLASSNAME + "-caption-close");
-                getElement().insertBefore(closeButton,
-                        getElement().getLastChild());
-            } else if (!closable && closeButton != null) {
-                getElement().removeChild(closeButton);
-                closeButton = null;
-            }
-            if (closable) {
-                addStyleDependentName("closable");
-            } else {
-                removeStyleDependentName("closable");
-            }
-        }
-
-        public boolean isClosable() {
-            return closable;
-        }
-
-        @Override
-        public int getRequiredWidth() {
-            int width = super.getRequiredWidth();
-            if (closeButton != null) {
-                width += Util.getRequiredWidth(closeButton);
-            }
-            return width;
-        }
-
-        public Element getCloseButton() {
-            return closeButton;
-        }
-
-    }
-
-    static class TabBar extends ComplexPanel implements ClickHandler,
-            VCloseHandler {
-
-        private final Element tr = DOM.createTR();
-
-        private final Element spacerTd = DOM.createTD();
-
-        private Tab selected;
-
-        private VTabsheet tabsheet;
-
-        TabBar(VTabsheet tabsheet) {
-            this.tabsheet = tabsheet;
-
-            Element el = DOM.createTable();
-            Element tbody = DOM.createTBody();
-            DOM.appendChild(el, tbody);
-            DOM.appendChild(tbody, tr);
-            setStyleName(spacerTd, CLASSNAME + "-spacertd");
-            DOM.appendChild(tr, spacerTd);
-            DOM.appendChild(spacerTd, DOM.createDiv());
-            setElement(el);
-        }
-
-        @Override
-        public void onClose(VCloseEvent event) {
-            Tab tab = event.getTab();
-            if (!tab.isEnabledOnServer()) {
-                return;
-            }
-            int tabIndex = getWidgetIndex(tab);
-            getTabsheet().sendTabClosedEvent(tabIndex);
-        }
-
-        protected Element getContainerElement() {
-            return tr;
-        }
-
-        public int getTabCount() {
-            return getWidgetCount();
-        }
-
-        public Tab addTab() {
-            Tab t = new Tab(this);
-            int tabIndex = getTabCount();
-
-            // Logical attach
-            insert(t, tr, tabIndex, true);
-
-            if (tabIndex == 0) {
-                // Set the "first" style
-                t.setStyleNames(false, true);
-            }
-
-            t.addClickHandler(this);
-            t.setCloseHandler(this);
-
-            return t;
-        }
-
-        @Override
-        public void onClick(ClickEvent event) {
-            TabCaption caption = (TabCaption) event.getSource();
-            Element targetElement = event.getNativeEvent().getEventTarget()
-                    .cast();
-            // the tab should not be focused if the close button was clicked
-            if (targetElement == caption.getCloseButton()) {
-                return;
-            }
-
-            int index = getWidgetIndex(caption.getParent());
-            // IE needs explicit focus()
-            if (BrowserInfo.get().isIE()) {
-                getTabsheet().focus();
-            }
-            getTabsheet().onTabSelected(index);
-        }
-
-        public VTabsheet getTabsheet() {
-            return tabsheet;
-        }
-
-        public Tab getTab(int index) {
-            if (index < 0 || index >= getTabCount()) {
-                return null;
-            }
-            return (Tab) super.getWidget(index);
-        }
-
-        public void selectTab(int index) {
-            final Tab newSelected = getTab(index);
-            final Tab oldSelected = selected;
-
-            newSelected.setStyleNames(true, isFirstVisibleTab(index));
-            newSelected.setTabulatorIndex(getTabsheet().tabulatorIndex);
-
-            if (oldSelected != null && oldSelected != newSelected) {
-                oldSelected.setStyleNames(false,
-                        isFirstVisibleTab(getWidgetIndex(oldSelected)));
-                oldSelected.setTabulatorIndex(-1);
-            }
-
-            // Update the field holding the currently selected tab
-            selected = newSelected;
-
-            // The selected tab might need more (or less) space
-            newSelected.recalculateCaptionWidth();
-            getTab(tabsheet.activeTabIndex).recalculateCaptionWidth();
-        }
-
-        public void removeTab(int i) {
-            Tab tab = getTab(i);
-            if (tab == null) {
-                return;
-            }
-
-            remove(tab);
-
-            /*
-             * If this widget was selected we need to unmark it as the last
-             * selected
-             */
-            if (tab == selected) {
-                selected = null;
-            }
-
-            // FIXME: Shouldn't something be selected instead?
-        }
-
-        private boolean isFirstVisibleTab(int index) {
-            return getFirstVisibleTab() == index;
-        }
-
-        /**
-         * Returns the index of the first visible tab
-         * 
-         * @return
-         */
-        private int getFirstVisibleTab() {
-            return getNextVisibleTab(-1);
-        }
-
-        /**
-         * Find the next visible tab. Returns -1 if none is found.
-         * 
-         * @param i
-         * @return
-         */
-        private int getNextVisibleTab(int i) {
-            int tabs = getTabCount();
-            do {
-                i++;
-            } while (i < tabs && getTab(i).isHiddenOnServer());
-
-            if (i == tabs) {
-                return -1;
-            } else {
-                return i;
-            }
-        }
-
-        /**
-         * Find the previous visible tab. Returns -1 if none is found.
-         * 
-         * @param i
-         * @return
-         */
-        private int getPreviousVisibleTab(int i) {
-            do {
-                i--;
-            } while (i >= 0 && getTab(i).isHiddenOnServer());
-
-            return i;
-
-        }
-
-        public int scrollLeft(int currentFirstVisible) {
-            int prevVisible = getPreviousVisibleTab(currentFirstVisible);
-            if (prevVisible == -1) {
-                return -1;
-            }
-
-            Tab newFirst = getTab(prevVisible);
-            newFirst.setVisible(true);
-            newFirst.recalculateCaptionWidth();
-
-            return prevVisible;
-        }
-
-        public int scrollRight(int currentFirstVisible) {
-            int nextVisible = getNextVisibleTab(currentFirstVisible);
-            if (nextVisible == -1) {
-                return -1;
-            }
-            Tab currentFirst = getTab(currentFirstVisible);
-            currentFirst.setVisible(false);
-            currentFirst.recalculateCaptionWidth();
-            return nextVisible;
-        }
-    }
-
-    public static final String CLASSNAME = "v-tabsheet";
-
-    public static final String TABS_CLASSNAME = "v-tabsheet-tabcontainer";
-    public static final String SCROLLER_CLASSNAME = "v-tabsheet-scroller";
-
-    final Element tabs; // tabbar and 'scroller' container
-    Tab focusedTab;
-    /**
-     * The tabindex property (position in the browser's focus cycle.) Named like
-     * this to avoid confusion with activeTabIndex.
-     */
-    int tabulatorIndex = 0;
-
-    private static final FocusImpl focusImpl = FocusImpl.getFocusImplForPanel();
-
-    private final Element scroller; // tab-scroller element
-    private final Element scrollerNext; // tab-scroller next button element
-    private final Element scrollerPrev; // tab-scroller prev button element
-
-    /**
-     * The index of the first visible tab (when scrolled)
-     */
-    private int scrollerIndex = 0;
-
-    final TabBar tb = new TabBar(this);
-    final VTabsheetPanel tp = new VTabsheetPanel();
-    final Element contentNode;
-
-    private final Element deco;
-
-    boolean waitingForResponse;
-
-    private String currentStyle;
-
-    /**
-     * @return Whether the tab could be selected or not.
-     */
-    private boolean onTabSelected(final int tabIndex) {
-        Tab tab = tb.getTab(tabIndex);
-        if (client == null || disabled || waitingForResponse) {
-            return false;
-        }
-        if (!tab.isEnabledOnServer() || tab.isHiddenOnServer()) {
-            return false;
-        }
-        if (activeTabIndex != tabIndex) {
-            tb.selectTab(tabIndex);
-
-            // If this TabSheet already has focus, set the new selected tab
-            // as focused.
-            if (focusedTab != null) {
-                focusedTab = tab;
-            }
-
-            addStyleDependentName("loading");
-            // Hide the current contents so a loading indicator can be shown
-            // instead
-            Widget currentlyDisplayedWidget = tp.getWidget(tp
-                    .getVisibleWidget());
-            currentlyDisplayedWidget.getElement().getParentElement().getStyle()
-                    .setVisibility(Visibility.HIDDEN);
-            client.updateVariable(id, "selected", tabKeys.get(tabIndex)
-                    .toString(), true);
-            waitingForResponse = true;
-        }
-        // Note that we return true when tabIndex == activeTabIndex; the active
-        // tab could be selected, it's just a no-op.
-        return true;
-    }
-
-    public ApplicationConnection getApplicationConnection() {
-        return client;
-    }
-
-    public void tabSizeMightHaveChanged(Tab tab) {
-        // icon onloads may change total width of tabsheet
-        if (isDynamicWidth()) {
-            updateDynamicWidth();
-        }
-        updateTabScroller();
-
-    }
-
-    void sendTabClosedEvent(int tabIndex) {
-        client.updateVariable(id, "close", tabKeys.get(tabIndex), true);
-    }
-
-    boolean isDynamicWidth() {
-        ComponentConnector paintable = ConnectorMap.get(client).getConnector(
-                this);
-        return paintable.isUndefinedWidth();
-    }
-
-    boolean isDynamicHeight() {
-        ComponentConnector paintable = ConnectorMap.get(client).getConnector(
-                this);
-        return paintable.isUndefinedHeight();
-    }
-
-    public VTabsheet() {
-        super(CLASSNAME);
-
-        addHandler(this, FocusEvent.getType());
-        addHandler(this, BlurEvent.getType());
-
-        // Tab scrolling
-        DOM.setStyleAttribute(getElement(), "overflow", "hidden");
-        tabs = DOM.createDiv();
-        DOM.setElementProperty(tabs, "className", TABS_CLASSNAME);
-        scroller = DOM.createDiv();
-
-        DOM.setElementProperty(scroller, "className", SCROLLER_CLASSNAME);
-        scrollerPrev = DOM.createButton();
-        DOM.setElementProperty(scrollerPrev, "className", SCROLLER_CLASSNAME
-                + "Prev");
-        DOM.sinkEvents(scrollerPrev, Event.ONCLICK);
-        scrollerNext = DOM.createButton();
-        DOM.setElementProperty(scrollerNext, "className", SCROLLER_CLASSNAME
-                + "Next");
-        DOM.sinkEvents(scrollerNext, Event.ONCLICK);
-        DOM.appendChild(getElement(), tabs);
-
-        // Tabs
-        tp.setStyleName(CLASSNAME + "-tabsheetpanel");
-        contentNode = DOM.createDiv();
-
-        deco = DOM.createDiv();
-
-        addStyleDependentName("loading"); // Indicate initial progress
-        tb.setStyleName(CLASSNAME + "-tabs");
-        DOM.setElementProperty(contentNode, "className", CLASSNAME + "-content");
-        DOM.setElementProperty(deco, "className", CLASSNAME + "-deco");
-
-        add(tb, tabs);
-        DOM.appendChild(scroller, scrollerPrev);
-        DOM.appendChild(scroller, scrollerNext);
-
-        DOM.appendChild(getElement(), contentNode);
-        add(tp, contentNode);
-        DOM.appendChild(getElement(), deco);
-
-        DOM.appendChild(tabs, scroller);
-
-        // TODO Use for Safari only. Fix annoying 1px first cell in TabBar.
-        // DOM.setStyleAttribute(DOM.getFirstChild(DOM.getFirstChild(DOM
-        // .getFirstChild(tb.getElement()))), "display", "none");
-
-    }
-
-    @Override
-    public void onBrowserEvent(Event event) {
-        if (event.getTypeInt() == Event.ONCLICK) {
-            // Tab scrolling
-            if (isScrolledTabs() && DOM.eventGetTarget(event) == scrollerPrev) {
-                int newFirstIndex = tb.scrollLeft(scrollerIndex);
-                if (newFirstIndex != -1) {
-                    scrollerIndex = newFirstIndex;
-                    updateTabScroller();
-                }
-                event.stopPropagation();
-                return;
-            } else if (isClippedTabs()
-                    && DOM.eventGetTarget(event) == scrollerNext) {
-                int newFirstIndex = tb.scrollRight(scrollerIndex);
-
-                if (newFirstIndex != -1) {
-                    scrollerIndex = newFirstIndex;
-                    updateTabScroller();
-                }
-                event.stopPropagation();
-                return;
-            }
-        }
-        super.onBrowserEvent(event);
-    }
-
-    /**
-     * Checks if the tab with the selected index has been scrolled out of the
-     * view (on the left side).
-     * 
-     * @param index
-     * @return
-     */
-    private boolean scrolledOutOfView(int index) {
-        return scrollerIndex > index;
-    }
-
-    void handleStyleNames(UIDL uidl, ComponentState state) {
-        // Add proper stylenames for all elements (easier to prevent unwanted
-        // style inheritance)
-        if (ComponentStateUtil.hasStyles(state)) {
-            final List<String> styles = state.styles;
-            if (!currentStyle.equals(styles.toString())) {
-                currentStyle = styles.toString();
-                final String tabsBaseClass = TABS_CLASSNAME;
-                String tabsClass = tabsBaseClass;
-                final String contentBaseClass = CLASSNAME + "-content";
-                String contentClass = contentBaseClass;
-                final String decoBaseClass = CLASSNAME + "-deco";
-                String decoClass = decoBaseClass;
-                for (String style : styles) {
-                    tb.addStyleDependentName(style);
-                    tabsClass += " " + tabsBaseClass + "-" + style;
-                    contentClass += " " + contentBaseClass + "-" + style;
-                    decoClass += " " + decoBaseClass + "-" + style;
-                }
-                DOM.setElementProperty(tabs, "className", tabsClass);
-                DOM.setElementProperty(contentNode, "className", contentClass);
-                DOM.setElementProperty(deco, "className", decoClass);
-                borderW = -1;
-            }
-        } else {
-            tb.setStyleName(CLASSNAME + "-tabs");
-            DOM.setElementProperty(tabs, "className", TABS_CLASSNAME);
-            DOM.setElementProperty(contentNode, "className", CLASSNAME
-                    + "-content");
-            DOM.setElementProperty(deco, "className", CLASSNAME + "-deco");
-        }
-
-        if (uidl.hasAttribute("hidetabs")) {
-            tb.setVisible(false);
-            addStyleName(CLASSNAME + "-hidetabs");
-        } else {
-            tb.setVisible(true);
-            removeStyleName(CLASSNAME + "-hidetabs");
-        }
-    }
-
-    void updateDynamicWidth() {
-        // Find width consumed by tabs
-        TableCellElement spacerCell = ((TableElement) tb.getElement().cast())
-                .getRows().getItem(0).getCells().getItem(tb.getTabCount());
-
-        int spacerWidth = spacerCell.getOffsetWidth();
-        DivElement div = (DivElement) spacerCell.getFirstChildElement();
-
-        int spacerMinWidth = spacerCell.getOffsetWidth() - div.getOffsetWidth();
-
-        int tabsWidth = tb.getOffsetWidth() - spacerWidth + spacerMinWidth;
-
-        // Find content width
-        Style style = tp.getElement().getStyle();
-        String overflow = style.getProperty("overflow");
-        style.setProperty("overflow", "hidden");
-        style.setPropertyPx("width", tabsWidth);
-
-        boolean hasTabs = tp.getWidgetCount() > 0;
-
-        Style wrapperstyle = null;
-        if (hasTabs) {
-            wrapperstyle = tp.getWidget(tp.getVisibleWidget()).getElement()
-                    .getParentElement().getStyle();
-            wrapperstyle.setPropertyPx("width", tabsWidth);
-        }
-        // Get content width from actual widget
-
-        int contentWidth = 0;
-        if (hasTabs) {
-            contentWidth = tp.getWidget(tp.getVisibleWidget()).getOffsetWidth();
-        }
-        style.setProperty("overflow", overflow);
-
-        // Set widths to max(tabs,content)
-        if (tabsWidth < contentWidth) {
-            tabsWidth = contentWidth;
-        }
-
-        int outerWidth = tabsWidth + getContentAreaBorderWidth();
-
-        tabs.getStyle().setPropertyPx("width", outerWidth);
-        style.setPropertyPx("width", tabsWidth);
-        if (hasTabs) {
-            wrapperstyle.setPropertyPx("width", tabsWidth);
-        }
-
-        contentNode.getStyle().setPropertyPx("width", tabsWidth);
-        super.setWidth(outerWidth + "px");
-        updateOpenTabSize();
-    }
-
-    @Override
-    protected void renderTab(final UIDL tabUidl, int index, boolean selected,
-            boolean hidden) {
-        Tab tab = tb.getTab(index);
-        if (tab == null) {
-            tab = tb.addTab();
-        }
-        tab.updateFromUIDL(tabUidl);
-        tab.setEnabledOnServer((!disabledTabKeys.contains(tabKeys.get(index))));
-        tab.setHiddenOnServer(hidden);
-
-        if (scrolledOutOfView(index)) {
-            // Should not set tabs visible if they are scrolled out of view
-            hidden = true;
-        }
-        // Set the current visibility of the tab (in the browser)
-        tab.setVisible(!hidden);
-
-        /*
-         * Force the width of the caption container so the content will not wrap
-         * and tabs won't be too narrow in certain browsers
-         */
-        tab.recalculateCaptionWidth();
-
-        UIDL tabContentUIDL = null;
-        ComponentConnector tabContentPaintable = null;
-        Widget tabContentWidget = null;
-        if (tabUidl.getChildCount() > 0) {
-            tabContentUIDL = tabUidl.getChildUIDL(0);
-            tabContentPaintable = client.getPaintable(tabContentUIDL);
-            tabContentWidget = tabContentPaintable.getWidget();
-        }
-
-        if (tabContentPaintable != null) {
-            /* This is a tab with content information */
-
-            int oldIndex = tp.getWidgetIndex(tabContentWidget);
-            if (oldIndex != -1 && oldIndex != index) {
-                /*
-                 * The tab has previously been rendered in another position so
-                 * we must move the cached content to correct position
-                 */
-                tp.insert(tabContentWidget, index);
-            }
-        } else {
-            /* A tab whose content has not yet been loaded */
-
-            /*
-             * Make sure there is a corresponding empty tab in tp. The same
-             * operation as the moving above but for not-loaded tabs.
-             */
-            if (index < tp.getWidgetCount()) {
-                Widget oldWidget = tp.getWidget(index);
-                if (!(oldWidget instanceof PlaceHolder)) {
-                    tp.insert(new PlaceHolder(), index);
-                }
-            }
-
-        }
-
-        if (selected) {
-            renderContent(tabContentUIDL);
-            tb.selectTab(index);
-        } else {
-            if (tabContentUIDL != null) {
-                // updating a drawn child on hidden tab
-                if (tp.getWidgetIndex(tabContentWidget) < 0) {
-                    tp.insert(tabContentWidget, index);
-                }
-            } else if (tp.getWidgetCount() <= index) {
-                tp.add(new PlaceHolder());
-            }
-        }
-    }
-
-    public class PlaceHolder extends VLabel {
-        public PlaceHolder() {
-            super("");
-        }
-    }
-
-    @Override
-    protected void selectTab(int index, final UIDL contentUidl) {
-        if (index != activeTabIndex) {
-            activeTabIndex = index;
-            tb.selectTab(activeTabIndex);
-        }
-        renderContent(contentUidl);
-    }
-
-    private void renderContent(final UIDL contentUIDL) {
-        final ComponentConnector content = client.getPaintable(contentUIDL);
-        Widget newWidget = content.getWidget();
-        if (tp.getWidgetCount() > activeTabIndex) {
-            Widget old = tp.getWidget(activeTabIndex);
-            if (old != newWidget) {
-                tp.remove(activeTabIndex);
-                ConnectorMap paintableMap = ConnectorMap.get(client);
-                if (paintableMap.isConnector(old)) {
-                    paintableMap.unregisterConnector(paintableMap
-                            .getConnector(old));
-                }
-                tp.insert(content.getWidget(), activeTabIndex);
-            }
-        } else {
-            tp.add(content.getWidget());
-        }
-
-        tp.showWidget(activeTabIndex);
-
-        VTabsheet.this.iLayout();
-        /*
-         * The size of a cached, relative sized component must be updated to
-         * report correct size to updateOpenTabSize().
-         */
-        if (contentUIDL.getBooleanAttribute("cached")) {
-            client.handleComponentRelativeSize(content.getWidget());
-        }
-        updateOpenTabSize();
-        VTabsheet.this.removeStyleDependentName("loading");
-    }
-
-    void updateContentNodeHeight() {
-        if (!isDynamicHeight()) {
-            int contentHeight = getOffsetHeight();
-            contentHeight -= DOM.getElementPropertyInt(deco, "offsetHeight");
-            contentHeight -= tb.getOffsetHeight();
-            if (contentHeight < 0) {
-                contentHeight = 0;
-            }
-
-            // Set proper values for content element
-            DOM.setStyleAttribute(contentNode, "height", contentHeight + "px");
-        } else {
-            DOM.setStyleAttribute(contentNode, "height", "");
-        }
-    }
-
-    public void iLayout() {
-        updateTabScroller();
-    }
-
-    /**
-     * Sets the size of the visible tab (component). As the tab is set to
-     * position: absolute (to work around a firefox flickering bug) we must keep
-     * this up-to-date by hand.
-     */
-    void updateOpenTabSize() {
-        /*
-         * The overflow=auto element must have a height specified, otherwise it
-         * will be just as high as the contents and no scrollbars will appear
-         */
-        int height = -1;
-        int width = -1;
-        int minWidth = 0;
-
-        if (!isDynamicHeight()) {
-            height = contentNode.getOffsetHeight();
-        }
-        if (!isDynamicWidth()) {
-            width = contentNode.getOffsetWidth() - getContentAreaBorderWidth();
-        } else {
-            /*
-             * If the tabbar is wider than the content we need to use the tabbar
-             * width as minimum width so scrollbars get placed correctly (at the
-             * right edge).
-             */
-            minWidth = tb.getOffsetWidth() - getContentAreaBorderWidth();
-        }
-        tp.fixVisibleTabSize(width, height, minWidth);
-
-    }
-
-    /**
-     * Layouts the tab-scroller elements, and applies styles.
-     */
-    private void updateTabScroller() {
-        if (!isDynamicWidth()) {
-            ComponentConnector paintable = ConnectorMap.get(client)
-                    .getConnector(this);
-            DOM.setStyleAttribute(tabs, "width", paintable.getState().width);
-        }
-
-        // Make sure scrollerIndex is valid
-        if (scrollerIndex < 0 || scrollerIndex > tb.getTabCount()) {
-            scrollerIndex = tb.getFirstVisibleTab();
-        } else if (tb.getTabCount() > 0
-                && tb.getTab(scrollerIndex).isHiddenOnServer()) {
-            scrollerIndex = tb.getNextVisibleTab(scrollerIndex);
-        }
-
-        boolean scrolled = isScrolledTabs();
-        boolean clipped = isClippedTabs();
-        if (tb.getTabCount() > 0 && tb.isVisible() && (scrolled || clipped)) {
-            DOM.setStyleAttribute(scroller, "display", "");
-            DOM.setElementProperty(scrollerPrev, "className",
-                    SCROLLER_CLASSNAME + (scrolled ? "Prev" : "Prev-disabled"));
-            DOM.setElementProperty(scrollerNext, "className",
-                    SCROLLER_CLASSNAME + (clipped ? "Next" : "Next-disabled"));
-        } else {
-            DOM.setStyleAttribute(scroller, "display", "none");
-        }
-
-        if (BrowserInfo.get().isSafari()) {
-            // fix tab height for safari, bugs sometimes if tabs contain icons
-            String property = tabs.getStyle().getProperty("height");
-            if (property == null || property.equals("")) {
-                tabs.getStyle().setPropertyPx("height", tb.getOffsetHeight());
-            }
-            /*
-             * another hack for webkits. tabscroller sometimes drops without
-             * "shaking it" reproducable in
-             * com.vaadin.tests.components.tabsheet.TabSheetIcons
-             */
-            final Style style = scroller.getStyle();
-            style.setProperty("whiteSpace", "normal");
-            Scheduler.get().scheduleDeferred(new Command() {
-
-                @Override
-                public void execute() {
-                    style.setProperty("whiteSpace", "");
-                }
-            });
-        }
-
-    }
-
-    void showAllTabs() {
-        scrollerIndex = tb.getFirstVisibleTab();
-        for (int i = 0; i < tb.getTabCount(); i++) {
-            Tab t = tb.getTab(i);
-            if (!t.isHiddenOnServer()) {
-                t.setVisible(true);
-            }
-        }
-    }
-
-    private boolean isScrolledTabs() {
-        return scrollerIndex > tb.getFirstVisibleTab();
-    }
-
-    private boolean isClippedTabs() {
-        return (tb.getOffsetWidth() - DOM.getElementPropertyInt((Element) tb
-                .getContainerElement().getLastChild().cast(), "offsetWidth")) > getOffsetWidth()
-                - (isScrolledTabs() ? scroller.getOffsetWidth() : 0);
-    }
-
-    private boolean isClipped(Tab tab) {
-        return tab.getAbsoluteLeft() + tab.getOffsetWidth() > getAbsoluteLeft()
-                + getOffsetWidth() - scroller.getOffsetWidth();
-    }
-
-    @Override
-    protected void clearPaintables() {
-
-        int i = tb.getTabCount();
-        while (i > 0) {
-            tb.removeTab(--i);
-        }
-        tp.clear();
-
-    }
-
-    @Override
-    protected Iterator<Widget> getWidgetIterator() {
-        return tp.iterator();
-    }
-
-    private int borderW = -1;
-
-    int getContentAreaBorderWidth() {
-        if (borderW < 0) {
-            borderW = Util.measureHorizontalBorder(contentNode);
-        }
-        return borderW;
-    }
-
-    @Override
-    protected int getTabCount() {
-        return tb.getTabCount();
-    }
-
-    @Override
-    protected ComponentConnector getTab(int index) {
-        if (tp.getWidgetCount() > index) {
-            Widget widget = tp.getWidget(index);
-            return ConnectorMap.get(client).getConnector(widget);
-        }
-        return null;
-    }
-
-    @Override
-    protected void removeTab(int index) {
-        tb.removeTab(index);
-        /*
-         * This must be checked because renderTab automatically removes the
-         * active tab content when it changes
-         */
-        if (tp.getWidgetCount() > index) {
-            tp.remove(index);
-        }
-    }
-
-    @Override
-    public void onBlur(BlurEvent event) {
-        if (focusedTab != null && event.getSource() instanceof Tab) {
-            focusedTab = null;
-            if (client.hasEventListeners(this, EventId.BLUR)) {
-                client.updateVariable(id, EventId.BLUR, "", true);
-            }
-        }
-    }
-
-    @Override
-    public void onFocus(FocusEvent event) {
-        if (focusedTab == null && event.getSource() instanceof Tab) {
-            focusedTab = (Tab) event.getSource();
-            if (client.hasEventListeners(this, EventId.FOCUS)) {
-                client.updateVariable(id, EventId.FOCUS, "", true);
-            }
-        }
-    }
-
-    @Override
-    public void focus() {
-        tb.getTab(activeTabIndex).focus();
-    }
-
-    public void blur() {
-        tb.getTab(activeTabIndex).blur();
-    }
-
-    @Override
-    public void onKeyDown(KeyDownEvent event) {
-        if (event.getSource() instanceof Tab) {
-            int keycode = event.getNativeEvent().getKeyCode();
-
-            if (keycode == getPreviousTabKey()) {
-                selectPreviousTab();
-            } else if (keycode == getNextTabKey()) {
-                selectNextTab();
-            } else if (keycode == getCloseTabKey()) {
-                Tab tab = tb.getTab(activeTabIndex);
-                if (tab.isClosable()) {
-                    tab.onClose();
-                }
-            }
-        }
-    }
-
-    /**
-     * @return The key code of the keyboard shortcut that selects the previous
-     *         tab in a focused tabsheet.
-     */
-    protected int getPreviousTabKey() {
-        return KeyCodes.KEY_LEFT;
-    }
-
-    /**
-     * @return The key code of the keyboard shortcut that selects the next tab
-     *         in a focused tabsheet.
-     */
-    protected int getNextTabKey() {
-        return KeyCodes.KEY_RIGHT;
-    }
-
-    /**
-     * @return The key code of the keyboard shortcut that closes the currently
-     *         selected tab in a focused tabsheet.
-     */
-    protected int getCloseTabKey() {
-        return KeyCodes.KEY_DELETE;
-    }
-
-    private void selectPreviousTab() {
-        int newTabIndex = activeTabIndex;
-        // Find the previous visible and enabled tab if any.
-        do {
-            newTabIndex--;
-        } while (newTabIndex >= 0 && !onTabSelected(newTabIndex));
-
-        if (newTabIndex >= 0) {
-            activeTabIndex = newTabIndex;
-            if (isScrolledTabs()) {
-                // Scroll until the new active tab is visible
-                int newScrollerIndex = scrollerIndex;
-                while (tb.getTab(activeTabIndex).getAbsoluteLeft() < getAbsoluteLeft()
-                        && newScrollerIndex != -1) {
-                    newScrollerIndex = tb.scrollLeft(newScrollerIndex);
-                }
-                scrollerIndex = newScrollerIndex;
-                updateTabScroller();
-            }
-        }
-    }
-
-    private void selectNextTab() {
-        int newTabIndex = activeTabIndex;
-        // Find the next visible and enabled tab if any.
-        do {
-            newTabIndex++;
-        } while (newTabIndex < getTabCount() && !onTabSelected(newTabIndex));
-
-        if (newTabIndex < getTabCount()) {
-            activeTabIndex = newTabIndex;
-            if (isClippedTabs()) {
-                // Scroll until the new active tab is completely visible
-                int newScrollerIndex = scrollerIndex;
-                while (isClipped(tb.getTab(activeTabIndex))
-                        && newScrollerIndex != -1) {
-                    newScrollerIndex = tb.scrollRight(newScrollerIndex);
-                }
-                scrollerIndex = newScrollerIndex;
-                updateTabScroller();
-            }
-        }
-    }
-}
diff --git a/client/src/com/vaadin/client/ui/tabsheet/VTabsheetBase.java b/client/src/com/vaadin/client/ui/tabsheet/VTabsheetBase.java
deleted file mode 100644 (file)
index cde90cd..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.vaadin.client.ui.tabsheet;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.ui.ComplexPanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.ComponentConnector;
-import com.vaadin.client.UIDL;
-
-public abstract class VTabsheetBase extends ComplexPanel {
-
-    protected String id;
-    protected ApplicationConnection client;
-
-    protected final ArrayList<String> tabKeys = new ArrayList<String>();
-    protected int activeTabIndex = 0;
-    protected boolean disabled;
-    protected boolean readonly;
-    protected Set<String> disabledTabKeys = new HashSet<String>();
-
-    public VTabsheetBase(String classname) {
-        setElement(DOM.createDiv());
-        setStyleName(classname);
-    }
-
-    /**
-     * @return a list of currently shown Widgets
-     */
-    abstract protected Iterator<Widget> getWidgetIterator();
-
-    /**
-     * Clears current tabs and contents
-     */
-    abstract protected void clearPaintables();
-
-    /**
-     * Implement in extending classes. This method should render needed elements
-     * and set the visibility of the tab according to the 'selected' parameter.
-     */
-    protected abstract void renderTab(final UIDL tabUidl, int index,
-            boolean selected, boolean hidden);
-
-    /**
-     * Implement in extending classes. This method should render any previously
-     * non-cached content and set the activeTabIndex property to the specified
-     * index.
-     */
-    protected abstract void selectTab(int index, final UIDL contentUidl);
-
-    /**
-     * Implement in extending classes. This method should return the number of
-     * tabs currently rendered.
-     */
-    protected abstract int getTabCount();
-
-    /**
-     * Implement in extending classes. This method should return the Paintable
-     * corresponding to the given index.
-     */
-    protected abstract ComponentConnector getTab(int index);
-
-    /**
-     * Implement in extending classes. This method should remove the rendered
-     * tab with the specified index.
-     */
-    protected abstract void removeTab(int index);
-}
diff --git a/client/src/com/vaadin/client/ui/tabsheet/VTabsheetPanel.java b/client/src/com/vaadin/client/ui/tabsheet/VTabsheetPanel.java
deleted file mode 100644 (file)
index 6c1b30d..0000000
+++ /dev/null
@@ -1,201 +0,0 @@
-/* 
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.tabsheet;
-
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.ui.ComplexPanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ui.TouchScrollDelegate;
-import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
-
-/**
- * A panel that displays all of its child widgets in a 'deck', where only one
- * can be visible at a time. It is used by
- * {@link com.vaadin.client.ui.tabsheet.VTabsheet}.
- * 
- * This class has the same basic functionality as the GWT DeckPanel
- * {@link com.google.gwt.user.client.ui.DeckPanel}, with the exception that it
- * doesn't manipulate the child widgets' width and height attributes.
- */
-public class VTabsheetPanel extends ComplexPanel {
-
-    private Widget visibleWidget;
-
-    private final TouchScrollHandler touchScrollHandler;
-
-    /**
-     * Creates an empty tabsheet panel.
-     */
-    public VTabsheetPanel() {
-        setElement(DOM.createDiv());
-        touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this);
-    }
-
-    /**
-     * Adds the specified widget to the deck.
-     * 
-     * @param w
-     *            the widget to be added
-     */
-    @Override
-    public void add(Widget w) {
-        Element el = createContainerElement();
-        DOM.appendChild(getElement(), el);
-        super.add(w, el);
-    }
-
-    private Element createContainerElement() {
-        Element el = DOM.createDiv();
-        DOM.setStyleAttribute(el, "position", "absolute");
-        hide(el);
-        touchScrollHandler.addElement(el);
-        return el;
-    }
-
-    /**
-     * Gets the index of the currently-visible widget.
-     * 
-     * @return the visible widget's index
-     */
-    public int getVisibleWidget() {
-        return getWidgetIndex(visibleWidget);
-    }
-
-    /**
-     * Inserts a widget before the specified index.
-     * 
-     * @param w
-     *            the widget to be inserted
-     * @param beforeIndex
-     *            the index before which it will be inserted
-     * @throws IndexOutOfBoundsException
-     *             if <code>beforeIndex</code> is out of range
-     */
-    public void insert(Widget w, int beforeIndex) {
-        Element el = createContainerElement();
-        DOM.insertChild(getElement(), el, beforeIndex);
-        super.insert(w, el, beforeIndex, false);
-    }
-
-    @Override
-    public boolean remove(Widget w) {
-        Element child = w.getElement();
-        Element parent = null;
-        if (child != null) {
-            parent = DOM.getParent(child);
-        }
-        final boolean removed = super.remove(w);
-        if (removed) {
-            if (visibleWidget == w) {
-                visibleWidget = null;
-            }
-            if (parent != null) {
-                DOM.removeChild(getElement(), parent);
-            }
-            touchScrollHandler.removeElement(parent);
-        }
-        return removed;
-    }
-
-    /**
-     * Shows the widget at the specified index. This causes the currently-
-     * visible widget to be hidden.
-     * 
-     * @param index
-     *            the index of the widget to be shown
-     */
-    public void showWidget(int index) {
-        checkIndexBoundsForAccess(index);
-        Widget newVisible = getWidget(index);
-        if (visibleWidget != newVisible) {
-            if (visibleWidget != null) {
-                hide(DOM.getParent(visibleWidget.getElement()));
-            }
-            visibleWidget = newVisible;
-            touchScrollHandler.setElements(visibleWidget.getElement()
-                    .getParentElement());
-        }
-        // Always ensure the selected tab is visible. If server prevents a tab
-        // change we might end up here with visibleWidget == newVisible but its
-        // parent is still hidden.
-        unHide(DOM.getParent(visibleWidget.getElement()));
-    }
-
-    private void hide(Element e) {
-        DOM.setStyleAttribute(e, "visibility", "hidden");
-        DOM.setStyleAttribute(e, "top", "-100000px");
-        DOM.setStyleAttribute(e, "left", "-100000px");
-    }
-
-    private void unHide(Element e) {
-        DOM.setStyleAttribute(e, "top", "0px");
-        DOM.setStyleAttribute(e, "left", "0px");
-        DOM.setStyleAttribute(e, "visibility", "");
-    }
-
-    public void fixVisibleTabSize(int width, int height, int minWidth) {
-        if (visibleWidget == null) {
-            return;
-        }
-
-        boolean dynamicHeight = false;
-
-        if (height < 0) {
-            height = visibleWidget.getOffsetHeight();
-            dynamicHeight = true;
-        }
-        if (width < 0) {
-            width = visibleWidget.getOffsetWidth();
-        }
-        if (width < minWidth) {
-            width = minWidth;
-        }
-
-        Element wrapperDiv = (Element) visibleWidget.getElement()
-                .getParentElement();
-
-        // width first
-        getElement().getStyle().setPropertyPx("width", width);
-        wrapperDiv.getStyle().setPropertyPx("width", width);
-
-        if (dynamicHeight) {
-            // height of widget might have changed due wrapping
-            height = visibleWidget.getOffsetHeight();
-        }
-        // v-tabsheet-tabsheetpanel height
-        getElement().getStyle().setPropertyPx("height", height);
-
-        // widget wrapper height
-        if (dynamicHeight) {
-            wrapperDiv.getStyle().clearHeight();
-        } else {
-            // widget wrapper height
-            wrapperDiv.getStyle().setPropertyPx("height", height);
-        }
-    }
-
-    public void replaceComponent(Widget oldComponent, Widget newComponent) {
-        boolean isVisible = (visibleWidget == oldComponent);
-        int widgetIndex = getWidgetIndex(oldComponent);
-        remove(oldComponent);
-        insert(newComponent, widgetIndex);
-        if (isVisible) {
-            showWidget(widgetIndex);
-        }
-    }
-}
index bda667e501bd10852e063a28fa2f81463e71b5e9..cc00425a5f18568296e7e7c62f71e4359b3df73d 100644 (file)
@@ -16,6 +16,7 @@
 
 package com.vaadin.client.ui.textarea;
 
+import com.vaadin.client.ui.VTextArea;
 import com.vaadin.client.ui.textfield.TextFieldConnector;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.textarea.TextAreaState;
diff --git a/client/src/com/vaadin/client/ui/textarea/VTextArea.java b/client/src/com/vaadin/client/ui/textarea/VTextArea.java
deleted file mode 100644 (file)
index c4b1c2d..0000000
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.textarea;
-
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.dom.client.Style.Overflow;
-import com.google.gwt.dom.client.TextAreaElement;
-import com.google.gwt.event.dom.client.ChangeEvent;
-import com.google.gwt.event.dom.client.ChangeHandler;
-import com.google.gwt.event.dom.client.KeyUpEvent;
-import com.google.gwt.event.dom.client.KeyUpHandler;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Event;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.textfield.VTextField;
-
-/**
- * This class represents a multiline textfield (textarea).
- * 
- * TODO consider replacing this with a RichTextArea based implementation. IE
- * does not support CSS height for textareas in Strict mode :-(
- * 
- * @author Vaadin Ltd.
- * 
- */
-public class VTextArea extends VTextField {
-    public static final String CLASSNAME = "v-textarea";
-    private boolean wordwrap = true;
-    private MaxLengthHandler maxLengthHandler = new MaxLengthHandler();
-    private boolean browserSupportsMaxLengthAttribute = browserSupportsMaxLengthAttribute();
-
-    public VTextArea() {
-        super(DOM.createTextArea());
-        setStyleName(CLASSNAME);
-        if (!browserSupportsMaxLengthAttribute) {
-            addKeyUpHandler(maxLengthHandler);
-            addChangeHandler(maxLengthHandler);
-            sinkEvents(Event.ONPASTE);
-        }
-    }
-
-    public TextAreaElement getTextAreaElement() {
-        return super.getElement().cast();
-    }
-
-    public void setRows(int rows) {
-        getTextAreaElement().setRows(rows);
-    }
-
-    private class MaxLengthHandler implements KeyUpHandler, ChangeHandler {
-
-        @Override
-        public void onKeyUp(KeyUpEvent event) {
-            enforceMaxLength();
-        }
-
-        public void onPaste(Event event) {
-            enforceMaxLength();
-        }
-
-        @Override
-        public void onChange(ChangeEvent event) {
-            // Opera does not support paste events so this enforces max length
-            // for Opera.
-            enforceMaxLength();
-        }
-
-    }
-
-    protected void enforceMaxLength() {
-        if (getMaxLength() >= 0) {
-            Scheduler.get().scheduleDeferred(new Command() {
-                @Override
-                public void execute() {
-                    if (getText().length() > getMaxLength()) {
-                        setText(getText().substring(0, getMaxLength()));
-                    }
-                }
-            });
-        }
-    }
-
-    protected boolean browserSupportsMaxLengthAttribute() {
-        BrowserInfo info = BrowserInfo.get();
-        if (info.isFirefox() && info.isBrowserVersionNewerOrEqual(4, 0)) {
-            return true;
-        }
-        if (info.isSafari() && info.isBrowserVersionNewerOrEqual(5, 0)) {
-            return true;
-        }
-        if (info.isIE() && info.isBrowserVersionNewerOrEqual(10, 0)) {
-            return true;
-        }
-        if (info.isAndroid() && info.isBrowserVersionNewerOrEqual(2, 3)) {
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    protected void updateMaxLength(int maxLength) {
-        if (browserSupportsMaxLengthAttribute) {
-            super.updateMaxLength(maxLength);
-        } else {
-            // Events handled by MaxLengthHandler. This call enforces max length
-            // when the max length value has changed
-            enforceMaxLength();
-        }
-    }
-
-    @Override
-    public void onBrowserEvent(Event event) {
-        super.onBrowserEvent(event);
-        if (event.getTypeInt() == Event.ONPASTE) {
-            maxLengthHandler.onPaste(event);
-        }
-    }
-
-    @Override
-    public int getCursorPos() {
-        // This is needed so that TextBoxImplIE6 is used to return the correct
-        // position for old Internet Explorer versions where it has to be
-        // detected in a different way.
-        return getImpl().getTextAreaCursorPos(getElement());
-    }
-
-    @Override
-    protected void setMaxLengthToElement(int newMaxLength) {
-        // There is no maxlength property for textarea. The maximum length is
-        // enforced by the KEYUP handler
-
-    }
-
-    public void setWordwrap(boolean wordwrap) {
-        if (wordwrap == this.wordwrap) {
-            return; // No change
-        }
-
-        if (wordwrap) {
-            getElement().removeAttribute("wrap");
-            getElement().getStyle().clearOverflow();
-        } else {
-            getElement().setAttribute("wrap", "off");
-            getElement().getStyle().setOverflow(Overflow.AUTO);
-        }
-        if (BrowserInfo.get().isOpera()) {
-            // Opera fails to dynamically update the wrap attribute so we detach
-            // and reattach the whole TextArea.
-            Util.detachAttach(getElement());
-        }
-        this.wordwrap = wordwrap;
-    }
-}
index 7fa68f2bc651b8b2f68e16746abd160ffbfbc1a6..25d6ab836917cf81beab92806198eceb402c3a28 100644 (file)
@@ -23,6 +23,7 @@ import com.vaadin.client.ApplicationConnection;
 import com.vaadin.client.Paintable;
 import com.vaadin.client.UIDL;
 import com.vaadin.client.ui.AbstractFieldConnector;
+import com.vaadin.client.ui.VTextField;
 import com.vaadin.client.ui.ShortcutActionHandler.BeforeShortcutActionListener;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.Connect.LoadStyle;
diff --git a/client/src/com/vaadin/client/ui/textfield/VTextField.java b/client/src/com/vaadin/client/ui/textfield/VTextField.java
deleted file mode 100644 (file)
index 4b7e620..0000000
+++ /dev/null
@@ -1,441 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.textfield;
-
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.event.dom.client.ChangeEvent;
-import com.google.gwt.event.dom.client.ChangeHandler;
-import com.google.gwt.event.dom.client.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.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.Timer;
-import com.google.gwt.user.client.ui.TextBoxBase;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.Field;
-import com.vaadin.shared.EventId;
-import com.vaadin.shared.ui.textfield.TextFieldConstants;
-
-/**
- * This class represents a basic text input field with one row.
- * 
- * @author Vaadin Ltd.
- * 
- */
-public class VTextField extends TextBoxBase implements Field, ChangeHandler,
-        FocusHandler, BlurHandler, KeyDownHandler {
-
-    /**
-     * The input node CSS classname.
-     */
-    public static final String CLASSNAME = "v-textfield";
-    /**
-     * This CSS classname is added to the input node on hover.
-     */
-    public static final String CLASSNAME_FOCUS = "focus";
-
-    protected String paintableId;
-
-    protected ApplicationConnection client;
-
-    protected String valueBeforeEdit = null;
-
-    /**
-     * Set to false if a text change event has been sent since the last value
-     * change event. This means that {@link #valueBeforeEdit} should not be
-     * trusted when determining whether a text change even should be sent.
-     */
-    private boolean valueBeforeEditIsSynced = true;
-
-    private boolean immediate = false;
-    private int maxLength = -1;
-
-    private static final String CLASSNAME_PROMPT = "prompt";
-    private static final String TEXTCHANGE_MODE_TIMEOUT = "TIMEOUT";
-
-    private String inputPrompt = null;
-    private boolean prompting = false;
-    private int lastCursorPos = -1;
-
-    public VTextField() {
-        this(DOM.createInputText());
-    }
-
-    protected VTextField(Element node) {
-        super(node);
-        setStyleName(CLASSNAME);
-        addChangeHandler(this);
-        if (BrowserInfo.get().isIE()) {
-            // IE does not send change events when pressing enter in a text
-            // input so we handle it using a key listener instead
-            addKeyDownHandler(this);
-        }
-        addFocusHandler(this);
-        addBlurHandler(this);
-    }
-
-    /*
-     * TODO When GWT adds ONCUT, add it there and remove workaround. See
-     * http://code.google.com/p/google-web-toolkit/issues/detail?id=4030
-     * 
-     * Also note that the cut/paste are not totally crossbrowsers compatible.
-     * E.g. in Opera mac works via context menu, but on via File->Paste/Cut.
-     * Opera might need the polling method for 100% working textchanceevents.
-     * Eager polling for a change is bit dum and heavy operation, so I guess we
-     * should first try to survive without.
-     */
-    protected static final int TEXTCHANGE_EVENTS = Event.ONPASTE
-            | Event.KEYEVENTS | Event.ONMOUSEUP;
-
-    @Override
-    public void onBrowserEvent(Event event) {
-        super.onBrowserEvent(event);
-
-        if (listenTextChangeEvents
-                && (event.getTypeInt() & TEXTCHANGE_EVENTS) == event
-                        .getTypeInt()) {
-            deferTextChangeEvent();
-        }
-
-    }
-
-    /*
-     * TODO optimize this so that only changes are sent + make the value change
-     * event just a flag that moves the current text to value
-     */
-    private String lastTextChangeString = null;
-
-    private String getLastCommunicatedString() {
-        return lastTextChangeString;
-    }
-
-    private void communicateTextValueToServer() {
-        String text = getText();
-        if (prompting) {
-            // Input prompt visible, text is actually ""
-            text = "";
-        }
-        if (!text.equals(getLastCommunicatedString())) {
-            if (valueBeforeEditIsSynced && text.equals(valueBeforeEdit)) {
-                /*
-                 * Value change for the current text has been enqueued since the
-                 * last text change event was sent, but we can't know that it
-                 * has been sent to the server. Ensure that all pending changes
-                 * are sent now. Sending a value change without a text change
-                 * will simulate a TextChangeEvent on the server.
-                 */
-                client.sendPendingVariableChanges();
-            } else {
-                // Default case - just send an immediate text change message
-                client.updateVariable(paintableId,
-                        TextFieldConstants.VAR_CUR_TEXT, text, true);
-
-                // Shouldn't investigate valueBeforeEdit to avoid duplicate text
-                // change events as the states are not in sync any more
-                valueBeforeEditIsSynced = false;
-            }
-            lastTextChangeString = text;
-        }
-    }
-
-    private Timer textChangeEventTrigger = new Timer() {
-
-        @Override
-        public void run() {
-            if (isAttached()) {
-                updateCursorPosition();
-                communicateTextValueToServer();
-                scheduled = false;
-            }
-        }
-    };
-    private boolean scheduled = false;
-    protected boolean listenTextChangeEvents;
-    protected String textChangeEventMode;
-    protected int textChangeEventTimeout;
-
-    private void deferTextChangeEvent() {
-        if (textChangeEventMode.equals(TEXTCHANGE_MODE_TIMEOUT) && scheduled) {
-            return;
-        } else {
-            textChangeEventTrigger.cancel();
-        }
-        textChangeEventTrigger.schedule(getTextChangeEventTimeout());
-        scheduled = true;
-    }
-
-    private int getTextChangeEventTimeout() {
-        return textChangeEventTimeout;
-    }
-
-    @Override
-    public void setReadOnly(boolean readOnly) {
-        boolean wasReadOnly = isReadOnly();
-
-        if (readOnly) {
-            setTabIndex(-1);
-        } else if (wasReadOnly && !readOnly && getTabIndex() == -1) {
-            /*
-             * Need to manually set tab index to 0 since server will not send
-             * the tab index if it is 0.
-             */
-            setTabIndex(0);
-        }
-
-        super.setReadOnly(readOnly);
-    }
-
-    protected void updateFieldContent(final String text) {
-        setPrompting(inputPrompt != null && focusedTextField != this
-                && (text.equals("")));
-
-        String fieldValue;
-        if (prompting) {
-            fieldValue = isReadOnly() ? "" : inputPrompt;
-            addStyleDependentName(CLASSNAME_PROMPT);
-        } else {
-            fieldValue = text;
-            removeStyleDependentName(CLASSNAME_PROMPT);
-        }
-        setText(fieldValue);
-
-        lastTextChangeString = valueBeforeEdit = text;
-        valueBeforeEditIsSynced = true;
-    }
-
-    protected void onCut() {
-        if (listenTextChangeEvents) {
-            deferTextChangeEvent();
-        }
-    }
-
-    protected native void attachCutEventListener(Element el)
-    /*-{
-        var me = this;
-        el.oncut = $entry(function() {
-            me.@com.vaadin.client.ui.textfield.VTextField::onCut()();
-        });
-    }-*/;
-
-    protected native void detachCutEventListener(Element el)
-    /*-{
-        el.oncut = null;
-    }-*/;
-
-    @Override
-    protected void onDetach() {
-        super.onDetach();
-        detachCutEventListener(getElement());
-        if (focusedTextField == this) {
-            focusedTextField = null;
-        }
-    }
-
-    @Override
-    protected void onAttach() {
-        super.onAttach();
-        if (listenTextChangeEvents) {
-            detachCutEventListener(getElement());
-        }
-    }
-
-    protected void setMaxLength(int newMaxLength) {
-        if (newMaxLength >= 0 && newMaxLength != maxLength) {
-            maxLength = newMaxLength;
-            updateMaxLength(maxLength);
-        } else if (maxLength != -1) {
-            maxLength = -1;
-            updateMaxLength(maxLength);
-        }
-
-    }
-
-    /**
-     * This method is reponsible for updating the DOM or otherwise ensuring that
-     * the given max length is enforced. Called when the max length for the
-     * field has changed.
-     * 
-     * @param maxLength
-     *            The new max length
-     */
-    protected void updateMaxLength(int maxLength) {
-        if (maxLength >= 0) {
-            getElement().setPropertyInt("maxLength", maxLength);
-        } else {
-            getElement().removeAttribute("maxLength");
-
-        }
-        setMaxLengthToElement(maxLength);
-    }
-
-    protected void setMaxLengthToElement(int newMaxLength) {
-        if (newMaxLength >= 0) {
-            getElement().setPropertyInt("maxLength", newMaxLength);
-        } else {
-            getElement().removeAttribute("maxLength");
-        }
-    }
-
-    public int getMaxLength() {
-        return maxLength;
-    }
-
-    @Override
-    public void onChange(ChangeEvent event) {
-        valueChange(false);
-    }
-
-    /**
-     * Called when the field value might have changed and/or the field was
-     * blurred. These are combined so the blur event is sent in the same batch
-     * as a possible value change event (these are often connected).
-     * 
-     * @param blurred
-     *            true if the field was blurred
-     */
-    public void valueChange(boolean blurred) {
-        if (client != null && paintableId != null) {
-            boolean sendBlurEvent = false;
-            boolean sendValueChange = false;
-
-            if (blurred && client.hasEventListeners(this, EventId.BLUR)) {
-                sendBlurEvent = true;
-                client.updateVariable(paintableId, EventId.BLUR, "", false);
-            }
-
-            String newText = getText();
-            if (!prompting && newText != null
-                    && !newText.equals(valueBeforeEdit)) {
-                sendValueChange = immediate;
-                client.updateVariable(paintableId, "text", newText, false);
-                valueBeforeEdit = newText;
-                valueBeforeEditIsSynced = true;
-            }
-
-            /*
-             * also send cursor position, no public api yet but for easier
-             * extension
-             */
-            updateCursorPosition();
-
-            if (sendBlurEvent || sendValueChange) {
-                /*
-                 * Avoid sending text change event as we will simulate it on the
-                 * server side before value change events.
-                 */
-                textChangeEventTrigger.cancel();
-                scheduled = false;
-                client.sendPendingVariableChanges();
-            }
-        }
-    }
-
-    /**
-     * Updates the cursor position variable if it has changed since the last
-     * update.
-     * 
-     * @return true iff the value was updated
-     */
-    protected boolean updateCursorPosition() {
-        if (Util.isAttachedAndDisplayed(this)) {
-            int cursorPos = getCursorPos();
-            if (lastCursorPos != cursorPos) {
-                client.updateVariable(paintableId,
-                        TextFieldConstants.VAR_CURSOR, cursorPos, false);
-                lastCursorPos = cursorPos;
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private static VTextField focusedTextField;
-
-    public static void flushChangesFromFocusedTextField() {
-        if (focusedTextField != null) {
-            focusedTextField.onChange(null);
-        }
-    }
-
-    @Override
-    public void onFocus(FocusEvent event) {
-        addStyleDependentName(CLASSNAME_FOCUS);
-        if (prompting) {
-            setText("");
-            removeStyleDependentName(CLASSNAME_PROMPT);
-            setPrompting(false);
-        }
-        focusedTextField = this;
-        if (client.hasEventListeners(this, EventId.FOCUS)) {
-            client.updateVariable(paintableId, EventId.FOCUS, "", true);
-        }
-    }
-
-    @Override
-    public void onBlur(BlurEvent event) {
-        // this is called twice on Chrome when e.g. changing tab while prompting
-        // field focused - do not change settings on the second time
-        if (focusedTextField != this) {
-            return;
-        }
-        removeStyleDependentName(CLASSNAME_FOCUS);
-        focusedTextField = null;
-        String text = getText();
-        setPrompting(inputPrompt != null && (text == null || "".equals(text)));
-        if (prompting) {
-            setText(isReadOnly() ? "" : inputPrompt);
-            addStyleDependentName(CLASSNAME_PROMPT);
-        }
-
-        valueChange(true);
-    }
-
-    private void setPrompting(boolean prompting) {
-        this.prompting = prompting;
-    }
-
-    public void setColumns(int columns) {
-        if (columns <= 0) {
-            return;
-        }
-
-        setWidth(columns + "em");
-    }
-
-    @Override
-    public void onKeyDown(KeyDownEvent event) {
-        if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
-            valueChange(false);
-        }
-    }
-
-    public void setImmediate(boolean immediate) {
-        this.immediate = immediate;
-    }
-
-    public void setInputPrompt(String inputPrompt) {
-        this.inputPrompt = inputPrompt;
-    }
-
-}
index b7491f8d92a335cef52e7019939c501932b25749..0ddbc7e96c9e5a89d004f44fbf1f62c24a59fb41 100644 (file)
@@ -27,7 +27,8 @@ import com.vaadin.client.TooltipInfo;
 import com.vaadin.client.UIDL;
 import com.vaadin.client.Util;
 import com.vaadin.client.ui.AbstractComponentConnector;
-import com.vaadin.client.ui.tree.VTree.TreeNode;
+import com.vaadin.client.ui.VTree;
+import com.vaadin.client.ui.VTree.TreeNode;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.MultiSelectMode;
 import com.vaadin.shared.ui.tree.TreeConstants;
diff --git a/client/src/com/vaadin/client/ui/tree/VTree.java b/client/src/com/vaadin/client/ui/tree/VTree.java
deleted file mode 100644 (file)
index ca021b6..0000000
+++ /dev/null
@@ -1,2139 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.tree;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
-
-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.NativeEvent;
-import com.google.gwt.dom.client.Node;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.event.dom.client.ContextMenuEvent;
-import com.google.gwt.event.dom.client.ContextMenuHandler;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.dom.client.KeyDownEvent;
-import com.google.gwt.event.dom.client.KeyDownHandler;
-import com.google.gwt.event.dom.client.KeyPressEvent;
-import com.google.gwt.event.dom.client.KeyPressHandler;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.SimplePanel;
-import com.google.gwt.user.client.ui.UIObject;
-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.ConnectorMap;
-import com.vaadin.client.MouseEventDetailsBuilder;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.Action;
-import com.vaadin.client.ui.ActionOwner;
-import com.vaadin.client.ui.FocusElementPanel;
-import com.vaadin.client.ui.Icon;
-import com.vaadin.client.ui.SubPartAware;
-import com.vaadin.client.ui.TreeAction;
-import com.vaadin.client.ui.VLazyExecutor;
-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.VTransferable;
-import com.vaadin.shared.MouseEventDetails;
-import com.vaadin.shared.MouseEventDetails.MouseButton;
-import com.vaadin.shared.ui.MultiSelectMode;
-import com.vaadin.shared.ui.dd.VerticalDropLocation;
-import com.vaadin.shared.ui.tree.TreeConstants;
-
-/**
- * 
- */
-public class VTree extends FocusElementPanel implements VHasDropHandler,
-        FocusHandler, BlurHandler, KeyPressHandler, KeyDownHandler,
-        SubPartAware, ActionOwner {
-
-    public static final String CLASSNAME = "v-tree";
-
-    /**
-     * @deprecated from 7.0, use {@link MultiSelectMode#DEFAULT} instead.
-     */
-    @Deprecated
-    public static final MultiSelectMode MULTISELECT_MODE_DEFAULT = MultiSelectMode.DEFAULT;
-
-    /**
-     * @deprecated from 7.0, use {@link MultiSelectMode#SIMPLE} instead.
-     */
-    @Deprecated
-    public static final MultiSelectMode MULTISELECT_MODE_SIMPLE = MultiSelectMode.SIMPLE;
-
-    private static final int CHARCODE_SPACE = 32;
-
-    final FlowPanel body = new FlowPanel();
-
-    Set<String> selectedIds = new HashSet<String>();
-    ApplicationConnection client;
-    String paintableId;
-    boolean selectable;
-    boolean isMultiselect;
-    private String currentMouseOverKey;
-    TreeNode lastSelection;
-    TreeNode focusedNode;
-    MultiSelectMode multiSelectMode = MultiSelectMode.DEFAULT;
-
-    private final HashMap<String, TreeNode> keyToNode = new HashMap<String, TreeNode>();
-
-    /**
-     * This map contains captions and icon urls for actions like: * "33_c" ->
-     * "Edit" * "33_i" -> "http://dom.com/edit.png"
-     */
-    private final HashMap<String, String> actionMap = new HashMap<String, String>();
-
-    boolean immediate;
-
-    boolean isNullSelectionAllowed = true;
-
-    boolean disabled = false;
-
-    boolean readonly;
-
-    boolean rendering;
-
-    private VAbstractDropHandler dropHandler;
-
-    int dragMode;
-
-    private boolean selectionHasChanged = false;
-
-    String[] bodyActionKeys;
-
-    public VLazyExecutor iconLoaded = new VLazyExecutor(50,
-            new ScheduledCommand() {
-
-                @Override
-                public void execute() {
-                    Util.notifyParentOfSizeChange(VTree.this, true);
-                }
-
-            });
-
-    public VTree() {
-        super();
-        setStyleName(CLASSNAME);
-        add(body);
-
-        addFocusHandler(this);
-        addBlurHandler(this);
-
-        /*
-         * Listen to context menu events on the empty space in the tree
-         */
-        sinkEvents(Event.ONCONTEXTMENU);
-        addDomHandler(new ContextMenuHandler() {
-            @Override
-            public void onContextMenu(ContextMenuEvent event) {
-                handleBodyContextMenu(event);
-            }
-        }, ContextMenuEvent.getType());
-
-        /*
-         * Firefox auto-repeat works correctly only if we use a key press
-         * handler, other browsers handle it correctly when using a key down
-         * handler
-         */
-        if (BrowserInfo.get().isGecko() || BrowserInfo.get().isOpera()) {
-            addKeyPressHandler(this);
-        } else {
-            addKeyDownHandler(this);
-        }
-
-        /*
-         * We need to use the sinkEvents method to catch the keyUp events so we
-         * can cache a single shift. KeyUpHandler cannot do this. At the same
-         * time we catch the mouse down and up events so we can apply the text
-         * selection patch in IE
-         */
-        sinkEvents(Event.ONMOUSEDOWN | Event.ONMOUSEUP | Event.ONKEYUP);
-
-        /*
-         * Re-set the tab index to make sure that the FocusElementPanel's
-         * (super) focus element gets the tab index and not the element
-         * containing the tree.
-         */
-        setTabIndex(0);
-    }
-
-    /*
-     * (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);
-        if (event.getTypeInt() == Event.ONMOUSEDOWN) {
-            // Prevent default text selection in IE
-            if (BrowserInfo.get().isIE()) {
-                ((Element) event.getEventTarget().cast()).setPropertyJSO(
-                        "onselectstart", applyDisableTextSelectionIEHack());
-            }
-        } else if (event.getTypeInt() == Event.ONMOUSEUP) {
-            // Remove IE text selection hack
-            if (BrowserInfo.get().isIE()) {
-                ((Element) event.getEventTarget().cast()).setPropertyJSO(
-                        "onselectstart", null);
-            }
-        } else if (event.getTypeInt() == Event.ONKEYUP) {
-            if (selectionHasChanged) {
-                if (event.getKeyCode() == getNavigationDownKey()
-                        && !event.getShiftKey()) {
-                    sendSelectionToServer();
-                    event.preventDefault();
-                } else if (event.getKeyCode() == getNavigationUpKey()
-                        && !event.getShiftKey()) {
-                    sendSelectionToServer();
-                    event.preventDefault();
-                } else if (event.getKeyCode() == KeyCodes.KEY_SHIFT) {
-                    sendSelectionToServer();
-                    event.preventDefault();
-                } else if (event.getKeyCode() == getNavigationSelectKey()) {
-                    sendSelectionToServer();
-                    event.preventDefault();
-                }
-            }
-        }
-    }
-
-    public String getActionCaption(String actionKey) {
-        return actionMap.get(actionKey + "_c");
-    }
-
-    public String getActionIcon(String actionKey) {
-        return actionMap.get(actionKey + "_i");
-    }
-
-    /**
-     * Returns the first root node of the tree or null if there are no root
-     * nodes.
-     * 
-     * @return The first root {@link TreeNode}
-     */
-    protected TreeNode getFirstRootNode() {
-        if (body.getWidgetCount() == 0) {
-            return null;
-        }
-        return (TreeNode) body.getWidget(0);
-    }
-
-    /**
-     * Returns the last root node of the tree or null if there are no root
-     * nodes.
-     * 
-     * @return The last root {@link TreeNode}
-     */
-    protected TreeNode getLastRootNode() {
-        if (body.getWidgetCount() == 0) {
-            return null;
-        }
-        return (TreeNode) body.getWidget(body.getWidgetCount() - 1);
-    }
-
-    /**
-     * Returns a list of all root nodes in the Tree in the order they appear in
-     * the tree.
-     * 
-     * @return A list of all root {@link TreeNode}s.
-     */
-    protected List<TreeNode> getRootNodes() {
-        ArrayList<TreeNode> rootNodes = new ArrayList<TreeNode>();
-        for (int i = 0; i < body.getWidgetCount(); i++) {
-            rootNodes.add((TreeNode) body.getWidget(i));
-        }
-        return rootNodes;
-    }
-
-    private void updateTreeRelatedDragData(VDragEvent drag) {
-
-        currentMouseOverKey = findCurrentMouseOverKey(drag.getElementOver());
-
-        drag.getDropDetails().put("itemIdOver", currentMouseOverKey);
-        if (currentMouseOverKey != null) {
-            TreeNode treeNode = getNodeByKey(currentMouseOverKey);
-            VerticalDropLocation detail = treeNode.getDropDetail(drag
-                    .getCurrentGwtEvent());
-            Boolean overTreeNode = null;
-            if (treeNode != null && !treeNode.isLeaf()
-                    && detail == VerticalDropLocation.MIDDLE) {
-                overTreeNode = true;
-            }
-            drag.getDropDetails().put("itemIdOverIsNode", overTreeNode);
-            drag.getDropDetails().put("detail", detail);
-        } else {
-            drag.getDropDetails().put("itemIdOverIsNode", null);
-            drag.getDropDetails().put("detail", null);
-        }
-
-    }
-
-    private String findCurrentMouseOverKey(Element elementOver) {
-        TreeNode treeNode = Util.findWidget(elementOver, TreeNode.class);
-        return treeNode == null ? null : treeNode.key;
-    }
-
-    void updateDropHandler(UIDL childUidl) {
-        if (dropHandler == null) {
-            dropHandler = new VAbstractDropHandler() {
-
-                @Override
-                public void dragEnter(VDragEvent drag) {
-                }
-
-                @Override
-                protected void dragAccepted(final VDragEvent drag) {
-
-                }
-
-                @Override
-                public void dragOver(final VDragEvent currentDrag) {
-                    final Object oldIdOver = currentDrag.getDropDetails().get(
-                            "itemIdOver");
-                    final VerticalDropLocation oldDetail = (VerticalDropLocation) currentDrag
-                            .getDropDetails().get("detail");
-
-                    updateTreeRelatedDragData(currentDrag);
-                    final VerticalDropLocation detail = (VerticalDropLocation) currentDrag
-                            .getDropDetails().get("detail");
-                    boolean nodeHasChanged = (currentMouseOverKey != null && currentMouseOverKey != oldIdOver)
-                            || (currentMouseOverKey == null && oldIdOver != null);
-                    boolean detailHasChanded = (detail != null && detail != oldDetail)
-                            || (detail == null && oldDetail != null);
-
-                    if (nodeHasChanged || detailHasChanded) {
-                        final String newKey = currentMouseOverKey;
-                        TreeNode treeNode = keyToNode.get(oldIdOver);
-                        if (treeNode != null) {
-                            // clear old styles
-                            treeNode.emphasis(null);
-                        }
-                        if (newKey != null) {
-                            validate(new VAcceptCallback() {
-                                @Override
-                                public void accepted(VDragEvent event) {
-                                    VerticalDropLocation curDetail = (VerticalDropLocation) event
-                                            .getDropDetails().get("detail");
-                                    if (curDetail == detail
-                                            && newKey.equals(currentMouseOverKey)) {
-                                        getNodeByKey(newKey).emphasis(detail);
-                                    }
-                                    /*
-                                     * Else drag is already on a different
-                                     * node-detail pair, new criteria check is
-                                     * going on
-                                     */
-                                }
-                            }, currentDrag);
-
-                        }
-                    }
-
-                }
-
-                @Override
-                public void dragLeave(VDragEvent drag) {
-                    cleanUp();
-                }
-
-                private void cleanUp() {
-                    if (currentMouseOverKey != null) {
-                        getNodeByKey(currentMouseOverKey).emphasis(null);
-                        currentMouseOverKey = null;
-                    }
-                }
-
-                @Override
-                public boolean drop(VDragEvent drag) {
-                    cleanUp();
-                    return super.drop(drag);
-                }
-
-                @Override
-                public ComponentConnector getConnector() {
-                    return ConnectorMap.get(client).getConnector(VTree.this);
-                }
-
-                @Override
-                public ApplicationConnection getApplicationConnection() {
-                    return client;
-                }
-
-            };
-        }
-        dropHandler.updateAcceptRules(childUidl);
-    }
-
-    public void setSelected(TreeNode treeNode, boolean selected) {
-        if (selected) {
-            if (!isMultiselect) {
-                while (selectedIds.size() > 0) {
-                    final String id = selectedIds.iterator().next();
-                    final TreeNode oldSelection = getNodeByKey(id);
-                    if (oldSelection != null) {
-                        // can be null if the node is not visible (parent
-                        // collapsed)
-                        oldSelection.setSelected(false);
-                    }
-                    selectedIds.remove(id);
-                }
-            }
-            treeNode.setSelected(true);
-            selectedIds.add(treeNode.key);
-        } else {
-            if (!isNullSelectionAllowed) {
-                if (!isMultiselect || selectedIds.size() == 1) {
-                    return;
-                }
-            }
-            selectedIds.remove(treeNode.key);
-            treeNode.setSelected(false);
-        }
-
-        sendSelectionToServer();
-    }
-
-    /**
-     * Sends the selection to the server
-     */
-    private void sendSelectionToServer() {
-        Command command = new Command() {
-            @Override
-            public void execute() {
-                client.updateVariable(paintableId, "selected",
-                        selectedIds.toArray(new String[selectedIds.size()]),
-                        immediate);
-                selectionHasChanged = false;
-            }
-        };
-
-        /*
-         * Delaying the sending of the selection in webkit to ensure the
-         * selection is always sent when the tree has focus and after click
-         * events have been processed. This is due to the focusing
-         * implementation in FocusImplSafari which uses timeouts when focusing
-         * and blurring.
-         */
-        if (BrowserInfo.get().isWebkit()) {
-            Scheduler.get().scheduleDeferred(command);
-        } else {
-            command.execute();
-        }
-    }
-
-    /**
-     * Is a node selected in the tree
-     * 
-     * @param treeNode
-     *            The node to check
-     * @return
-     */
-    public boolean isSelected(TreeNode treeNode) {
-        return selectedIds.contains(treeNode.key);
-    }
-
-    public class TreeNode extends SimplePanel implements ActionOwner {
-
-        public static final String CLASSNAME = "v-tree-node";
-        public static final String CLASSNAME_FOCUSED = CLASSNAME + "-focused";
-
-        public String key;
-
-        String[] actionKeys = null;
-
-        boolean childrenLoaded;
-
-        Element nodeCaptionDiv;
-
-        protected Element nodeCaptionSpan;
-
-        FlowPanel childNodeContainer;
-
-        private boolean open;
-
-        private Icon icon;
-
-        private Event mouseDownEvent;
-
-        private int cachedHeight = -1;
-
-        private boolean focused = false;
-
-        public TreeNode() {
-            constructDom();
-            sinkEvents(Event.ONCLICK | Event.ONDBLCLICK | Event.MOUSEEVENTS
-                    | Event.TOUCHEVENTS | Event.ONCONTEXTMENU);
-        }
-
-        public VerticalDropLocation getDropDetail(NativeEvent currentGwtEvent) {
-            if (cachedHeight < 0) {
-                /*
-                 * Height is cached to avoid flickering (drop hints may change
-                 * the reported offsetheight -> would change the drop detail)
-                 */
-                cachedHeight = nodeCaptionDiv.getOffsetHeight();
-            }
-            VerticalDropLocation verticalDropLocation = DDUtil
-                    .getVerticalDropLocation(nodeCaptionDiv, cachedHeight,
-                            currentGwtEvent, 0.15);
-            return verticalDropLocation;
-        }
-
-        protected void emphasis(VerticalDropLocation detail) {
-            String base = "v-tree-node-drag-";
-            UIObject.setStyleName(getElement(), base + "top",
-                    VerticalDropLocation.TOP == detail);
-            UIObject.setStyleName(getElement(), base + "bottom",
-                    VerticalDropLocation.BOTTOM == detail);
-            UIObject.setStyleName(getElement(), base + "center",
-                    VerticalDropLocation.MIDDLE == detail);
-            base = "v-tree-node-caption-drag-";
-            UIObject.setStyleName(nodeCaptionDiv, base + "top",
-                    VerticalDropLocation.TOP == detail);
-            UIObject.setStyleName(nodeCaptionDiv, base + "bottom",
-                    VerticalDropLocation.BOTTOM == detail);
-            UIObject.setStyleName(nodeCaptionDiv, base + "center",
-                    VerticalDropLocation.MIDDLE == detail);
-
-            // also add classname to "folder node" into which the drag is
-            // targeted
-
-            TreeNode folder = null;
-            /* Possible parent of this TreeNode will be stored here */
-            TreeNode parentFolder = getParentNode();
-
-            // TODO fix my bugs
-            if (isLeaf()) {
-                folder = parentFolder;
-                // note, parent folder may be null if this is root node => no
-                // folder target exists
-            } else {
-                if (detail == VerticalDropLocation.TOP) {
-                    folder = parentFolder;
-                } else {
-                    folder = this;
-                }
-                // ensure we remove the dragfolder classname from the previous
-                // folder node
-                setDragFolderStyleName(this, false);
-                setDragFolderStyleName(parentFolder, false);
-            }
-            if (folder != null) {
-                setDragFolderStyleName(folder, detail != null);
-            }
-
-        }
-
-        private TreeNode getParentNode() {
-            Widget parent2 = getParent().getParent();
-            if (parent2 instanceof TreeNode) {
-                return (TreeNode) parent2;
-            }
-            return null;
-        }
-
-        private void setDragFolderStyleName(TreeNode folder, boolean add) {
-            if (folder != null) {
-                UIObject.setStyleName(folder.getElement(),
-                        "v-tree-node-dragfolder", add);
-                UIObject.setStyleName(folder.nodeCaptionDiv,
-                        "v-tree-node-caption-dragfolder", add);
-            }
-        }
-
-        /**
-         * Handles mouse selection
-         * 
-         * @param ctrl
-         *            Was the ctrl-key pressed
-         * @param shift
-         *            Was the shift-key pressed
-         * @return Returns true if event was handled, else false
-         */
-        private boolean handleClickSelection(final boolean ctrl,
-                final boolean shift) {
-
-            // always when clicking an item, focus it
-            setFocusedNode(this, false);
-
-            if (!BrowserInfo.get().isOpera()) {
-                /*
-                 * Ensure that the tree's focus element also gains focus
-                 * (TreeNodes focus is faked using FocusElementPanel in browsers
-                 * other than Opera).
-                 */
-                focus();
-            }
-
-            executeEventCommand(new ScheduledCommand() {
-
-                @Override
-                public void execute() {
-
-                    if (multiSelectMode == MultiSelectMode.SIMPLE
-                            || !isMultiselect) {
-                        toggleSelection();
-                        lastSelection = TreeNode.this;
-                    } else if (multiSelectMode == MultiSelectMode.DEFAULT) {
-                        // Handle ctrl+click
-                        if (isMultiselect && ctrl && !shift) {
-                            toggleSelection();
-                            lastSelection = TreeNode.this;
-
-                            // Handle shift+click
-                        } else if (isMultiselect && !ctrl && shift) {
-                            deselectAll();
-                            selectNodeRange(lastSelection.key, key);
-                            sendSelectionToServer();
-
-                            // Handle ctrl+shift click
-                        } else if (isMultiselect && ctrl && shift) {
-                            selectNodeRange(lastSelection.key, key);
-
-                            // Handle click
-                        } else {
-                            // TODO should happen only if this alone not yet
-                            // selected,
-                            // now sending excess server calls
-                            deselectAll();
-                            toggleSelection();
-                            lastSelection = TreeNode.this;
-                        }
-                    }
-                }
-            });
-
-            return true;
-        }
-
-        /*
-         * (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);
-            final int type = DOM.eventGetType(event);
-            final Element target = DOM.eventGetTarget(event);
-
-            if (type == Event.ONLOAD && target == icon.getElement()) {
-                iconLoaded.trigger();
-            }
-
-            if (disabled) {
-                return;
-            }
-
-            final boolean inCaption = isCaptionElement(target);
-            if (inCaption
-                    && client.hasEventListeners(VTree.this,
-                            TreeConstants.ITEM_CLICK_EVENT_ID)
-
-                    && (type == Event.ONDBLCLICK || type == Event.ONMOUSEUP)) {
-                fireClick(event);
-            }
-            if (type == Event.ONCLICK) {
-                if (getElement() == target) {
-                    // state change
-                    toggleState();
-                } else if (!readonly && inCaption) {
-                    if (selectable) {
-                        // caption click = selection change && possible click
-                        // event
-                        if (handleClickSelection(
-                                event.getCtrlKey() || event.getMetaKey(),
-                                event.getShiftKey())) {
-                            event.preventDefault();
-                        }
-                    } else {
-                        // Not selectable, only focus the node.
-                        setFocusedNode(this);
-                    }
-                }
-                event.stopPropagation();
-            } else if (type == Event.ONCONTEXTMENU) {
-                showContextMenu(event);
-            }
-
-            if (dragMode != 0 || dropHandler != null) {
-                if (type == Event.ONMOUSEDOWN || type == Event.ONTOUCHSTART) {
-                    if (nodeCaptionDiv.isOrHasChild((Node) event
-                            .getEventTarget().cast())) {
-                        if (dragMode > 0
-                                && (type == Event.ONTOUCHSTART || event
-                                        .getButton() == NativeEvent.BUTTON_LEFT)) {
-                            mouseDownEvent = event; // save event for possible
-                            // dd operation
-                            if (type == Event.ONMOUSEDOWN) {
-                                event.preventDefault(); // prevent text
-                                // selection
-                            } else {
-                                /*
-                                 * FIXME We prevent touch start event to be used
-                                 * as a scroll start event. Note that we cannot
-                                 * easily distinguish whether the user wants to
-                                 * drag or scroll. The same issue is in table
-                                 * that has scrollable area and has drag and
-                                 * drop enable. Some kind of timer might be used
-                                 * to resolve the issue.
-                                 */
-                                event.stopPropagation();
-                            }
-                        }
-                    }
-                } else if (type == Event.ONMOUSEMOVE
-                        || type == Event.ONMOUSEOUT
-                        || type == Event.ONTOUCHMOVE) {
-
-                    if (mouseDownEvent != null) {
-                        // start actual drag on slight move when mouse is down
-                        VTransferable t = new VTransferable();
-                        t.setDragSource(ConnectorMap.get(client).getConnector(
-                                VTree.this));
-                        t.setData("itemId", key);
-                        VDragEvent drag = VDragAndDropManager.get().startDrag(
-                                t, mouseDownEvent, true);
-
-                        drag.createDragImage(nodeCaptionDiv, true);
-                        event.stopPropagation();
-
-                        mouseDownEvent = null;
-                    }
-                } else if (type == Event.ONMOUSEUP) {
-                    mouseDownEvent = null;
-                }
-                if (type == Event.ONMOUSEOVER) {
-                    mouseDownEvent = null;
-                    currentMouseOverKey = key;
-                    event.stopPropagation();
-                }
-
-            } else if (type == Event.ONMOUSEDOWN
-                    && event.getButton() == NativeEvent.BUTTON_LEFT) {
-                event.preventDefault(); // text selection
-            }
-        }
-
-        /**
-         * Checks if the given element is the caption or the icon.
-         * 
-         * @param target
-         *            The element to check
-         * @return true if the element is the caption or the icon
-         */
-        public boolean isCaptionElement(com.google.gwt.dom.client.Element target) {
-            return (target == nodeCaptionSpan || (icon != null && target == icon
-                    .getElement()));
-        }
-
-        private void fireClick(final Event evt) {
-            /*
-             * Ensure we have focus in tree before sending variables. Otherwise
-             * previously modified field may contain dirty variables.
-             */
-            if (!treeHasFocus) {
-                if (BrowserInfo.get().isOpera()) {
-                    if (focusedNode == null) {
-                        getNodeByKey(key).setFocused(true);
-                    } else {
-                        focusedNode.setFocused(true);
-                    }
-                } else {
-                    focus();
-                }
-            }
-
-            final MouseEventDetails details = MouseEventDetailsBuilder
-                    .buildMouseEventDetails(evt);
-
-            executeEventCommand(new ScheduledCommand() {
-
-                @Override
-                public void execute() {
-                    // Determine if we should send the event immediately to the
-                    // server. We do not want to send the event if there is a
-                    // selection event happening after this. In all other cases
-                    // we want to send it immediately.
-                    boolean sendClickEventNow = true;
-
-                    if (details.getButton() == MouseButton.LEFT && immediate
-                            && selectable) {
-                        // Probably a selection that will cause a value change
-                        // event to be sent
-                        sendClickEventNow = false;
-
-                        // The exception is that user clicked on the
-                        // currently selected row and null selection is not
-                        // allowed == no selection event
-                        if (isSelected() && selectedIds.size() == 1
-                                && !isNullSelectionAllowed) {
-                            sendClickEventNow = true;
-                        }
-                    }
-
-                    client.updateVariable(paintableId, "clickedKey", key, false);
-                    client.updateVariable(paintableId, "clickEvent",
-                            details.toString(), sendClickEventNow);
-                }
-            });
-        }
-
-        /*
-         * Must wait for Safari to focus before sending click and value change
-         * events (see #6373, #6374)
-         */
-        private void executeEventCommand(ScheduledCommand command) {
-            if (BrowserInfo.get().isWebkit() && !treeHasFocus) {
-                Scheduler.get().scheduleDeferred(command);
-            } else {
-                command.execute();
-            }
-        }
-
-        private void toggleSelection() {
-            if (selectable) {
-                VTree.this.setSelected(this, !isSelected());
-            }
-        }
-
-        private void toggleState() {
-            setState(!getState(), true);
-        }
-
-        protected void constructDom() {
-            addStyleName(CLASSNAME);
-
-            nodeCaptionDiv = DOM.createDiv();
-            DOM.setElementProperty(nodeCaptionDiv, "className", CLASSNAME
-                    + "-caption");
-            Element wrapper = DOM.createDiv();
-            nodeCaptionSpan = DOM.createSpan();
-            DOM.appendChild(getElement(), nodeCaptionDiv);
-            DOM.appendChild(nodeCaptionDiv, wrapper);
-            DOM.appendChild(wrapper, nodeCaptionSpan);
-
-            if (BrowserInfo.get().isOpera()) {
-                /*
-                 * Focus the caption div of the node to get keyboard navigation
-                 * to work without scrolling up or down when focusing a node.
-                 */
-                nodeCaptionDiv.setTabIndex(-1);
-            }
-
-            childNodeContainer = new FlowPanel();
-            childNodeContainer.setStyleName(CLASSNAME + "-children");
-            setWidget(childNodeContainer);
-        }
-
-        public boolean isLeaf() {
-            String[] styleNames = getStyleName().split(" ");
-            for (String styleName : styleNames) {
-                if (styleName.equals(CLASSNAME + "-leaf")) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        void setState(boolean state, boolean notifyServer) {
-            if (open == state) {
-                return;
-            }
-            if (state) {
-                if (!childrenLoaded && notifyServer) {
-                    client.updateVariable(paintableId, "requestChildTree",
-                            true, false);
-                }
-                if (notifyServer) {
-                    client.updateVariable(paintableId, "expand",
-                            new String[] { key }, true);
-                }
-                addStyleName(CLASSNAME + "-expanded");
-                childNodeContainer.setVisible(true);
-
-            } else {
-                removeStyleName(CLASSNAME + "-expanded");
-                childNodeContainer.setVisible(false);
-                if (notifyServer) {
-                    client.updateVariable(paintableId, "collapse",
-                            new String[] { key }, true);
-                }
-            }
-            open = state;
-
-            if (!rendering) {
-                Util.notifyParentOfSizeChange(VTree.this, false);
-            }
-        }
-
-        boolean getState() {
-            return open;
-        }
-
-        void setText(String text) {
-            DOM.setInnerText(nodeCaptionSpan, text);
-        }
-
-        public boolean isChildrenLoaded() {
-            return childrenLoaded;
-        }
-
-        /**
-         * Returns the children of the node
-         * 
-         * @return A set of tree nodes
-         */
-        public List<TreeNode> getChildren() {
-            List<TreeNode> nodes = new LinkedList<TreeNode>();
-
-            if (!isLeaf() && isChildrenLoaded()) {
-                Iterator<Widget> iter = childNodeContainer.iterator();
-                while (iter.hasNext()) {
-                    TreeNode node = (TreeNode) iter.next();
-                    nodes.add(node);
-                }
-            }
-            return nodes;
-        }
-
-        @Override
-        public Action[] getActions() {
-            if (actionKeys == null) {
-                return new Action[] {};
-            }
-            final Action[] actions = new Action[actionKeys.length];
-            for (int i = 0; i < actions.length; i++) {
-                final String actionKey = actionKeys[i];
-                final TreeAction a = new TreeAction(this, String.valueOf(key),
-                        actionKey);
-                a.setCaption(getActionCaption(actionKey));
-                a.setIconUrl(getActionIcon(actionKey));
-                actions[i] = a;
-            }
-            return actions;
-        }
-
-        @Override
-        public ApplicationConnection getClient() {
-            return client;
-        }
-
-        @Override
-        public String getPaintableId() {
-            return paintableId;
-        }
-
-        /**
-         * Adds/removes Vaadin specific style name. This method ought to be
-         * called only from VTree.
-         * 
-         * @param selected
-         */
-        protected void setSelected(boolean selected) {
-            // add style name to caption dom structure only, not to subtree
-            setStyleName(nodeCaptionDiv, "v-tree-node-selected", selected);
-        }
-
-        protected boolean isSelected() {
-            return VTree.this.isSelected(this);
-        }
-
-        /**
-         * Travels up the hierarchy looking for this node
-         * 
-         * @param child
-         *            The child which grandparent this is or is not
-         * @return True if this is a grandparent of the child node
-         */
-        public boolean isGrandParentOf(TreeNode child) {
-            TreeNode currentNode = child;
-            boolean isGrandParent = false;
-            while (currentNode != null) {
-                currentNode = currentNode.getParentNode();
-                if (currentNode == this) {
-                    isGrandParent = true;
-                    break;
-                }
-            }
-            return isGrandParent;
-        }
-
-        public boolean isSibling(TreeNode node) {
-            return node.getParentNode() == getParentNode();
-        }
-
-        public void showContextMenu(Event event) {
-            if (!readonly && !disabled) {
-                if (actionKeys != null) {
-                    int left = event.getClientX();
-                    int top = event.getClientY();
-                    top += Window.getScrollTop();
-                    left += Window.getScrollLeft();
-                    client.getContextMenu().showAt(this, left, top);
-                }
-                event.stopPropagation();
-                event.preventDefault();
-            }
-        }
-
-        /*
-         * (non-Javadoc)
-         * 
-         * @see com.google.gwt.user.client.ui.Widget#onDetach()
-         */
-        @Override
-        protected void onDetach() {
-            super.onDetach();
-            client.getContextMenu().ensureHidden(this);
-        }
-
-        /*
-         * (non-Javadoc)
-         * 
-         * @see com.google.gwt.user.client.ui.UIObject#toString()
-         */
-        @Override
-        public String toString() {
-            return nodeCaptionSpan.getInnerText();
-        }
-
-        /**
-         * Is the node focused?
-         * 
-         * @param focused
-         *            True if focused, false if not
-         */
-        public void setFocused(boolean focused) {
-            if (!this.focused && focused) {
-                nodeCaptionDiv.addClassName(CLASSNAME_FOCUSED);
-
-                this.focused = focused;
-                if (BrowserInfo.get().isOpera()) {
-                    nodeCaptionDiv.focus();
-                }
-                treeHasFocus = true;
-            } else if (this.focused && !focused) {
-                nodeCaptionDiv.removeClassName(CLASSNAME_FOCUSED);
-                this.focused = focused;
-                treeHasFocus = false;
-            }
-        }
-
-        /**
-         * Scrolls the caption into view
-         */
-        public void scrollIntoView() {
-            Util.scrollIntoViewVertically(nodeCaptionDiv);
-        }
-
-        public void setIcon(String iconUrl) {
-            if (iconUrl != null) {
-                // Add icon if not present
-                if (icon == null) {
-                    icon = new Icon(client);
-                    DOM.insertBefore(DOM.getFirstChild(nodeCaptionDiv),
-                            icon.getElement(), nodeCaptionSpan);
-                }
-                icon.setUri(iconUrl);
-            } else {
-                // Remove icon if present
-                if (icon != null) {
-                    DOM.removeChild(DOM.getFirstChild(nodeCaptionDiv),
-                            icon.getElement());
-                    icon = null;
-                }
-            }
-        }
-
-        public void setNodeStyleName(String styleName) {
-            addStyleName(TreeNode.CLASSNAME + "-" + styleName);
-            setStyleName(nodeCaptionDiv, TreeNode.CLASSNAME + "-caption-"
-                    + styleName, true);
-            childNodeContainer.addStyleName(TreeNode.CLASSNAME + "-children-"
-                    + styleName);
-
-        }
-
-    }
-
-    @Override
-    public VDropHandler getDropHandler() {
-        return dropHandler;
-    }
-
-    public TreeNode getNodeByKey(String key) {
-        return keyToNode.get(key);
-    }
-
-    /**
-     * Deselects all items in the tree
-     */
-    public void deselectAll() {
-        for (String key : selectedIds) {
-            TreeNode node = keyToNode.get(key);
-            if (node != null) {
-                node.setSelected(false);
-            }
-        }
-        selectedIds.clear();
-        selectionHasChanged = true;
-    }
-
-    /**
-     * Selects a range of nodes
-     * 
-     * @param startNodeKey
-     *            The start node key
-     * @param endNodeKey
-     *            The end node key
-     */
-    private void selectNodeRange(String startNodeKey, String endNodeKey) {
-
-        TreeNode startNode = keyToNode.get(startNodeKey);
-        TreeNode endNode = keyToNode.get(endNodeKey);
-
-        // The nodes have the same parent
-        if (startNode.getParent() == endNode.getParent()) {
-            doSiblingSelection(startNode, endNode);
-
-            // The start node is a grandparent of the end node
-        } else if (startNode.isGrandParentOf(endNode)) {
-            doRelationSelection(startNode, endNode);
-
-            // The end node is a grandparent of the start node
-        } else if (endNode.isGrandParentOf(startNode)) {
-            doRelationSelection(endNode, startNode);
-
-        } else {
-            doNoRelationSelection(startNode, endNode);
-        }
-    }
-
-    /**
-     * Selects a node and deselect all other nodes
-     * 
-     * @param node
-     *            The node to select
-     */
-    private void selectNode(TreeNode node, boolean deselectPrevious) {
-        if (deselectPrevious) {
-            deselectAll();
-        }
-
-        if (node != null) {
-            node.setSelected(true);
-            selectedIds.add(node.key);
-            lastSelection = node;
-        }
-        selectionHasChanged = true;
-    }
-
-    /**
-     * Deselects a node
-     * 
-     * @param node
-     *            The node to deselect
-     */
-    private void deselectNode(TreeNode node) {
-        node.setSelected(false);
-        selectedIds.remove(node.key);
-        selectionHasChanged = true;
-    }
-
-    /**
-     * Selects all the open children to a node
-     * 
-     * @param node
-     *            The parent node
-     */
-    private void selectAllChildren(TreeNode node, boolean includeRootNode) {
-        if (includeRootNode) {
-            node.setSelected(true);
-            selectedIds.add(node.key);
-        }
-
-        for (TreeNode child : node.getChildren()) {
-            if (!child.isLeaf() && child.getState()) {
-                selectAllChildren(child, true);
-            } else {
-                child.setSelected(true);
-                selectedIds.add(child.key);
-            }
-        }
-        selectionHasChanged = true;
-    }
-
-    /**
-     * Selects all children until a stop child is reached
-     * 
-     * @param root
-     *            The root not to start from
-     * @param stopNode
-     *            The node to finish with
-     * @param includeRootNode
-     *            Should the root node be selected
-     * @param includeStopNode
-     *            Should the stop node be selected
-     * 
-     * @return Returns false if the stop child was found, else true if all
-     *         children was selected
-     */
-    private boolean selectAllChildrenUntil(TreeNode root, TreeNode stopNode,
-            boolean includeRootNode, boolean includeStopNode) {
-        if (includeRootNode) {
-            root.setSelected(true);
-            selectedIds.add(root.key);
-        }
-        if (root.getState() && root != stopNode) {
-            for (TreeNode child : root.getChildren()) {
-                if (!child.isLeaf() && child.getState() && child != stopNode) {
-                    if (!selectAllChildrenUntil(child, stopNode, true,
-                            includeStopNode)) {
-                        return false;
-                    }
-                } else if (child == stopNode) {
-                    if (includeStopNode) {
-                        child.setSelected(true);
-                        selectedIds.add(child.key);
-                    }
-                    return false;
-                } else {
-                    child.setSelected(true);
-                    selectedIds.add(child.key);
-                }
-            }
-        }
-        selectionHasChanged = true;
-
-        return true;
-    }
-
-    /**
-     * Select a range between two nodes which have no relation to each other
-     * 
-     * @param startNode
-     *            The start node to start the selection from
-     * @param endNode
-     *            The end node to end the selection to
-     */
-    private void doNoRelationSelection(TreeNode startNode, TreeNode endNode) {
-
-        TreeNode commonParent = getCommonGrandParent(startNode, endNode);
-        TreeNode startBranch = null, endBranch = null;
-
-        // Find the children of the common parent
-        List<TreeNode> children;
-        if (commonParent != null) {
-            children = commonParent.getChildren();
-        } else {
-            children = getRootNodes();
-        }
-
-        // Find the start and end branches
-        for (TreeNode node : children) {
-            if (nodeIsInBranch(startNode, node)) {
-                startBranch = node;
-            }
-            if (nodeIsInBranch(endNode, node)) {
-                endBranch = node;
-            }
-        }
-
-        // Swap nodes if necessary
-        if (children.indexOf(startBranch) > children.indexOf(endBranch)) {
-            TreeNode temp = startBranch;
-            startBranch = endBranch;
-            endBranch = temp;
-
-            temp = startNode;
-            startNode = endNode;
-            endNode = temp;
-        }
-
-        // Select all children under the start node
-        selectAllChildren(startNode, true);
-        TreeNode startParent = startNode.getParentNode();
-        TreeNode currentNode = startNode;
-        while (startParent != null && startParent != commonParent) {
-            List<TreeNode> startChildren = startParent.getChildren();
-            for (int i = startChildren.indexOf(currentNode) + 1; i < startChildren
-                    .size(); i++) {
-                selectAllChildren(startChildren.get(i), true);
-            }
-
-            currentNode = startParent;
-            startParent = startParent.getParentNode();
-        }
-
-        // Select nodes until the end node is reached
-        for (int i = children.indexOf(startBranch) + 1; i <= children
-                .indexOf(endBranch); i++) {
-            selectAllChildrenUntil(children.get(i), endNode, true, true);
-        }
-
-        // Ensure end node was selected
-        endNode.setSelected(true);
-        selectedIds.add(endNode.key);
-        selectionHasChanged = true;
-    }
-
-    /**
-     * Examines the children of the branch node and returns true if a node is in
-     * that branch
-     * 
-     * @param node
-     *            The node to search for
-     * @param branch
-     *            The branch to search in
-     * @return True if found, false if not found
-     */
-    private boolean nodeIsInBranch(TreeNode node, TreeNode branch) {
-        if (node == branch) {
-            return true;
-        }
-        for (TreeNode child : branch.getChildren()) {
-            if (child == node) {
-                return true;
-            }
-            if (!child.isLeaf() && child.getState()) {
-                if (nodeIsInBranch(node, child)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Selects a range of items which are in direct relation with each other.<br/>
-     * NOTE: The start node <b>MUST</b> be before the end node!
-     * 
-     * @param startNode
-     * 
-     * @param endNode
-     */
-    private void doRelationSelection(TreeNode startNode, TreeNode endNode) {
-        TreeNode currentNode = endNode;
-        while (currentNode != startNode) {
-            currentNode.setSelected(true);
-            selectedIds.add(currentNode.key);
-
-            // Traverse children above the selection
-            List<TreeNode> subChildren = currentNode.getParentNode()
-                    .getChildren();
-            if (subChildren.size() > 1) {
-                selectNodeRange(subChildren.iterator().next().key,
-                        currentNode.key);
-            } else if (subChildren.size() == 1) {
-                TreeNode n = subChildren.get(0);
-                n.setSelected(true);
-                selectedIds.add(n.key);
-            }
-
-            currentNode = currentNode.getParentNode();
-        }
-        startNode.setSelected(true);
-        selectedIds.add(startNode.key);
-        selectionHasChanged = true;
-    }
-
-    /**
-     * Selects a range of items which have the same parent.
-     * 
-     * @param startNode
-     *            The start node
-     * @param endNode
-     *            The end node
-     */
-    private void doSiblingSelection(TreeNode startNode, TreeNode endNode) {
-        TreeNode parent = startNode.getParentNode();
-
-        List<TreeNode> children;
-        if (parent == null) {
-            // Topmost parent
-            children = getRootNodes();
-        } else {
-            children = parent.getChildren();
-        }
-
-        // Swap start and end point if needed
-        if (children.indexOf(startNode) > children.indexOf(endNode)) {
-            TreeNode temp = startNode;
-            startNode = endNode;
-            endNode = temp;
-        }
-
-        Iterator<TreeNode> childIter = children.iterator();
-        boolean startFound = false;
-        while (childIter.hasNext()) {
-            TreeNode node = childIter.next();
-            if (node == startNode) {
-                startFound = true;
-            }
-
-            if (startFound && node != endNode && node.getState()) {
-                selectAllChildren(node, true);
-            } else if (startFound && node != endNode) {
-                node.setSelected(true);
-                selectedIds.add(node.key);
-            }
-
-            if (node == endNode) {
-                node.setSelected(true);
-                selectedIds.add(node.key);
-                break;
-            }
-        }
-        selectionHasChanged = true;
-    }
-
-    /**
-     * Returns the first common parent of two nodes
-     * 
-     * @param node1
-     *            The first node
-     * @param node2
-     *            The second node
-     * @return The common parent or null
-     */
-    public TreeNode getCommonGrandParent(TreeNode node1, TreeNode node2) {
-        // If either one does not have a grand parent then return null
-        if (node1.getParentNode() == null || node2.getParentNode() == null) {
-            return null;
-        }
-
-        // If the nodes are parents of each other then return null
-        if (node1.isGrandParentOf(node2) || node2.isGrandParentOf(node1)) {
-            return null;
-        }
-
-        // Get parents of node1
-        List<TreeNode> parents1 = new ArrayList<TreeNode>();
-        TreeNode parent1 = node1.getParentNode();
-        while (parent1 != null) {
-            parents1.add(parent1);
-            parent1 = parent1.getParentNode();
-        }
-
-        // Get parents of node2
-        List<TreeNode> parents2 = new ArrayList<TreeNode>();
-        TreeNode parent2 = node2.getParentNode();
-        while (parent2 != null) {
-            parents2.add(parent2);
-            parent2 = parent2.getParentNode();
-        }
-
-        // Search the parents for the first common parent
-        for (int i = 0; i < parents1.size(); i++) {
-            parent1 = parents1.get(i);
-            for (int j = 0; j < parents2.size(); j++) {
-                parent2 = parents2.get(j);
-                if (parent1 == parent2) {
-                    return parent1;
-                }
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * Sets the node currently in focus
-     * 
-     * @param node
-     *            The node to focus or null to remove the focus completely
-     * @param scrollIntoView
-     *            Scroll the node into view
-     */
-    public void setFocusedNode(TreeNode node, boolean scrollIntoView) {
-        // Unfocus previously focused node
-        if (focusedNode != null) {
-            focusedNode.setFocused(false);
-        }
-
-        if (node != null) {
-            node.setFocused(true);
-        }
-
-        focusedNode = node;
-
-        if (node != null && scrollIntoView) {
-            /*
-             * Delay scrolling the focused node into view if we are still
-             * rendering. #5396
-             */
-            if (!rendering) {
-                node.scrollIntoView();
-            } else {
-                Scheduler.get().scheduleDeferred(new Command() {
-                    @Override
-                    public void execute() {
-                        focusedNode.scrollIntoView();
-                    }
-                });
-            }
-        }
-    }
-
-    /**
-     * Focuses a node and scrolls it into view
-     * 
-     * @param node
-     *            The node to focus
-     */
-    public void setFocusedNode(TreeNode node) {
-        setFocusedNode(node, true);
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see
-     * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
-     * .dom.client.FocusEvent)
-     */
-    @Override
-    public void onFocus(FocusEvent event) {
-        treeHasFocus = true;
-        // If no node has focus, focus the first item in the tree
-        if (focusedNode == null && lastSelection == null && selectable) {
-            setFocusedNode(getFirstRootNode(), false);
-        } else if (focusedNode != null && selectable) {
-            setFocusedNode(focusedNode, false);
-        } else if (lastSelection != null && selectable) {
-            setFocusedNode(lastSelection, 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) {
-        treeHasFocus = false;
-        if (focusedNode != null) {
-            focusedNode.setFocused(false);
-        }
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see
-     * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google
-     * .gwt.event.dom.client.KeyPressEvent)
-     */
-    @Override
-    public void onKeyPress(KeyPressEvent event) {
-        NativeEvent nativeEvent = event.getNativeEvent();
-        int keyCode = nativeEvent.getKeyCode();
-        if (keyCode == 0 && nativeEvent.getCharCode() == ' ') {
-            // Provide a keyCode for space to be compatible with FireFox
-            // keypress event
-            keyCode = CHARCODE_SPACE;
-        }
-        if (handleKeyNavigation(keyCode,
-                event.isControlKeyDown() || event.isMetaKeyDown(),
-                event.isShiftKeyDown())) {
-            event.preventDefault();
-            event.stopPropagation();
-        }
-    }
-
-    /*
-     * (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 (handleKeyNavigation(event.getNativeEvent().getKeyCode(),
-                event.isControlKeyDown() || event.isMetaKeyDown(),
-                event.isShiftKeyDown())) {
-            event.preventDefault();
-            event.stopPropagation();
-        }
-    }
-
-    /**
-     * Handles the keyboard navigation
-     * 
-     * @param keycode
-     *            The keycode of the pressed key
-     * @param ctrl
-     *            Was ctrl pressed
-     * @param shift
-     *            Was shift pressed
-     * @return Returns true if the key was handled, else false
-     */
-    protected boolean handleKeyNavigation(int keycode, boolean ctrl,
-            boolean shift) {
-        // Navigate down
-        if (keycode == getNavigationDownKey()) {
-            TreeNode node = null;
-            // If node is open and has children then move in to the children
-            if (!focusedNode.isLeaf() && focusedNode.getState()
-                    && focusedNode.getChildren().size() > 0) {
-                node = focusedNode.getChildren().get(0);
-            }
-
-            // Else move down to the next sibling
-            else {
-                node = getNextSibling(focusedNode);
-                if (node == null) {
-                    // Else jump to the parent and try to select the next
-                    // sibling there
-                    TreeNode current = focusedNode;
-                    while (node == null && current.getParentNode() != null) {
-                        node = getNextSibling(current.getParentNode());
-                        current = current.getParentNode();
-                    }
-                }
-            }
-
-            if (node != null) {
-                setFocusedNode(node);
-                if (selectable) {
-                    if (!ctrl && !shift) {
-                        selectNode(node, true);
-                    } else if (shift && isMultiselect) {
-                        deselectAll();
-                        selectNodeRange(lastSelection.key, node.key);
-                    } else if (shift) {
-                        selectNode(node, true);
-                    }
-                }
-            }
-            return true;
-        }
-
-        // Navigate up
-        if (keycode == getNavigationUpKey()) {
-            TreeNode prev = getPreviousSibling(focusedNode);
-            TreeNode node = null;
-            if (prev != null) {
-                node = getLastVisibleChildInTree(prev);
-            } else if (focusedNode.getParentNode() != null) {
-                node = focusedNode.getParentNode();
-            }
-            if (node != null) {
-                setFocusedNode(node);
-                if (selectable) {
-                    if (!ctrl && !shift) {
-                        selectNode(node, true);
-                    } else if (shift && isMultiselect) {
-                        deselectAll();
-                        selectNodeRange(lastSelection.key, node.key);
-                    } else if (shift) {
-                        selectNode(node, true);
-                    }
-                }
-            }
-            return true;
-        }
-
-        // Navigate left (close branch)
-        if (keycode == getNavigationLeftKey()) {
-            if (!focusedNode.isLeaf() && focusedNode.getState()) {
-                focusedNode.setState(false, true);
-            } else if (focusedNode.getParentNode() != null
-                    && (focusedNode.isLeaf() || !focusedNode.getState())) {
-
-                if (ctrl || !selectable) {
-                    setFocusedNode(focusedNode.getParentNode());
-                } else if (shift) {
-                    doRelationSelection(focusedNode.getParentNode(),
-                            focusedNode);
-                    setFocusedNode(focusedNode.getParentNode());
-                } else {
-                    focusAndSelectNode(focusedNode.getParentNode());
-                }
-            }
-            return true;
-        }
-
-        // Navigate right (open branch)
-        if (keycode == getNavigationRightKey()) {
-            if (!focusedNode.isLeaf() && !focusedNode.getState()) {
-                focusedNode.setState(true, true);
-            } else if (!focusedNode.isLeaf()) {
-                if (ctrl || !selectable) {
-                    setFocusedNode(focusedNode.getChildren().get(0));
-                } else if (shift) {
-                    setSelected(focusedNode, true);
-                    setFocusedNode(focusedNode.getChildren().get(0));
-                    setSelected(focusedNode, true);
-                } else {
-                    focusAndSelectNode(focusedNode.getChildren().get(0));
-                }
-            }
-            return true;
-        }
-
-        // Selection
-        if (keycode == getNavigationSelectKey()) {
-            if (!focusedNode.isSelected()) {
-                selectNode(
-                        focusedNode,
-                        (!isMultiselect || multiSelectMode == MULTISELECT_MODE_SIMPLE)
-                                && selectable);
-            } else {
-                deselectNode(focusedNode);
-            }
-            return true;
-        }
-
-        // Home selection
-        if (keycode == getNavigationStartKey()) {
-            TreeNode node = getFirstRootNode();
-            if (ctrl || !selectable) {
-                setFocusedNode(node);
-            } else if (shift) {
-                deselectAll();
-                selectNodeRange(focusedNode.key, node.key);
-            } else {
-                selectNode(node, true);
-            }
-            sendSelectionToServer();
-            return true;
-        }
-
-        // End selection
-        if (keycode == getNavigationEndKey()) {
-            TreeNode lastNode = getLastRootNode();
-            TreeNode node = getLastVisibleChildInTree(lastNode);
-            if (ctrl || !selectable) {
-                setFocusedNode(node);
-            } else if (shift) {
-                deselectAll();
-                selectNodeRange(focusedNode.key, node.key);
-            } else {
-                selectNode(node, true);
-            }
-            sendSelectionToServer();
-            return true;
-        }
-
-        return false;
-    }
-
-    private void focusAndSelectNode(TreeNode node) {
-        /*
-         * Keyboard navigation doesn't work reliably if the tree is in
-         * multiselect mode as well as isNullSelectionAllowed = false. It first
-         * tries to deselect the old focused node, which fails since there must
-         * be at least one selection. After this the newly focused node is
-         * selected and we've ended up with two selected nodes even though we
-         * only navigated with the arrow keys.
-         * 
-         * Because of this, we first select the next node and later de-select
-         * the old one.
-         */
-        TreeNode oldFocusedNode = focusedNode;
-        setFocusedNode(node);
-        setSelected(focusedNode, true);
-        setSelected(oldFocusedNode, false);
-    }
-
-    /**
-     * Traverses the tree to the bottom most child
-     * 
-     * @param root
-     *            The root of the tree
-     * @return The bottom most child
-     */
-    private TreeNode getLastVisibleChildInTree(TreeNode root) {
-        if (root.isLeaf() || !root.getState() || root.getChildren().size() == 0) {
-            return root;
-        }
-        List<TreeNode> children = root.getChildren();
-        return getLastVisibleChildInTree(children.get(children.size() - 1));
-    }
-
-    /**
-     * Gets the next sibling in the tree
-     * 
-     * @param node
-     *            The node to get the sibling for
-     * @return The sibling node or null if the node is the last sibling
-     */
-    private TreeNode getNextSibling(TreeNode node) {
-        TreeNode parent = node.getParentNode();
-        List<TreeNode> children;
-        if (parent == null) {
-            children = getRootNodes();
-        } else {
-            children = parent.getChildren();
-        }
-
-        int idx = children.indexOf(node);
-        if (idx < children.size() - 1) {
-            return children.get(idx + 1);
-        }
-
-        return null;
-    }
-
-    /**
-     * Returns the previous sibling in the tree
-     * 
-     * @param node
-     *            The node to get the sibling for
-     * @return The sibling node or null if the node is the first sibling
-     */
-    private TreeNode getPreviousSibling(TreeNode node) {
-        TreeNode parent = node.getParentNode();
-        List<TreeNode> children;
-        if (parent == null) {
-            children = getRootNodes();
-        } else {
-            children = parent.getChildren();
-        }
-
-        int idx = children.indexOf(node);
-        if (idx > 0) {
-            return children.get(idx - 1);
-        }
-
-        return null;
-    }
-
-    /**
-     * Add this to the element mouse down event by using element.setPropertyJSO
-     * ("onselectstart",applyDisableTextSelectionIEHack()); Remove it then again
-     * when the mouse is depressed in the mouse up event.
-     * 
-     * @return Returns the JSO preventing text selection
-     */
-    private native JavaScriptObject applyDisableTextSelectionIEHack()
-    /*-{
-            return function(){ return false; };
-    }-*/;
-
-    /**
-     * Get the key that moves the selection head upwards. By default it is the
-     * up arrow key but by overriding this you can change the key to whatever
-     * you want.
-     * 
-     * @return The keycode of the key
-     */
-    protected int getNavigationUpKey() {
-        return KeyCodes.KEY_UP;
-    }
-
-    /**
-     * Get the key that moves the selection head downwards. By default it is the
-     * down arrow key but by overriding this you can change the key to whatever
-     * you want.
-     * 
-     * @return The keycode of the key
-     */
-    protected int getNavigationDownKey() {
-        return KeyCodes.KEY_DOWN;
-    }
-
-    /**
-     * Get the key that scrolls to the left in the table. By default it is the
-     * left arrow key but by overriding this you can change the key to whatever
-     * you want.
-     * 
-     * @return The keycode of the key
-     */
-    protected int getNavigationLeftKey() {
-        return KeyCodes.KEY_LEFT;
-    }
-
-    /**
-     * Get the key that scroll to the right on the table. By default it is the
-     * right arrow key but by overriding this you can change the key to whatever
-     * you want.
-     * 
-     * @return The keycode of the key
-     */
-    protected int getNavigationRightKey() {
-        return KeyCodes.KEY_RIGHT;
-    }
-
-    /**
-     * Get the key that selects an item in the table. By default it is the space
-     * bar key but by overriding this you can change the key to whatever you
-     * want.
-     * 
-     * @return
-     */
-    protected int getNavigationSelectKey() {
-        return CHARCODE_SPACE;
-    }
-
-    /**
-     * Get the key the moves the selection one page up in the table. By default
-     * this is the Page Up key but by overriding this you can change the key to
-     * whatever you want.
-     * 
-     * @return
-     */
-    protected int getNavigationPageUpKey() {
-        return KeyCodes.KEY_PAGEUP;
-    }
-
-    /**
-     * Get the key the moves the selection one page down in the table. By
-     * default this is the Page Down key but by overriding this you can change
-     * the key to whatever you want.
-     * 
-     * @return
-     */
-    protected int getNavigationPageDownKey() {
-        return KeyCodes.KEY_PAGEDOWN;
-    }
-
-    /**
-     * Get the key the moves the selection to the beginning of the table. By
-     * default this is the Home key but by overriding this you can change the
-     * key to whatever you want.
-     * 
-     * @return
-     */
-    protected int getNavigationStartKey() {
-        return KeyCodes.KEY_HOME;
-    }
-
-    /**
-     * Get the key the moves the selection to the end of the table. By default
-     * this is the End key but by overriding this you can change the key to
-     * whatever you want.
-     * 
-     * @return
-     */
-    protected int getNavigationEndKey() {
-        return KeyCodes.KEY_END;
-    }
-
-    private final String SUBPART_NODE_PREFIX = "n";
-    private final String EXPAND_IDENTIFIER = "expand";
-
-    /*
-     * In webkit, focus may have been requested for this component but not yet
-     * gained. Use this to trac if tree has gained the focus on webkit. See
-     * FocusImplSafari and #6373
-     */
-    private boolean treeHasFocus;
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see com.vaadin.client.ui.SubPartAware#getSubPartElement(java
-     * .lang.String)
-     */
-    @Override
-    public Element getSubPartElement(String subPart) {
-        if ("fe".equals(subPart)) {
-            if (BrowserInfo.get().isOpera() && focusedNode != null) {
-                return focusedNode.getElement();
-            }
-            return getFocusElement();
-        }
-
-        if (subPart.startsWith(SUBPART_NODE_PREFIX + "[")) {
-            boolean expandCollapse = false;
-
-            // Node
-            String[] nodes = subPart.split("/");
-            TreeNode treeNode = null;
-            try {
-                for (String node : nodes) {
-                    if (node.startsWith(SUBPART_NODE_PREFIX)) {
-
-                        // skip SUBPART_NODE_PREFIX"["
-                        node = node.substring(SUBPART_NODE_PREFIX.length() + 1);
-                        // skip "]"
-                        node = node.substring(0, node.length() - 1);
-                        int position = Integer.parseInt(node);
-                        if (treeNode == null) {
-                            treeNode = getRootNodes().get(position);
-                        } else {
-                            treeNode = treeNode.getChildren().get(position);
-                        }
-                    } else if (node.startsWith(EXPAND_IDENTIFIER)) {
-                        expandCollapse = true;
-                    }
-                }
-
-                if (expandCollapse) {
-                    return treeNode.getElement();
-                } else {
-                    return treeNode.nodeCaptionSpan;
-                }
-            } catch (Exception e) {
-                // Invalid locator string or node could not be found
-                return null;
-            }
-        }
-        return null;
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see com.vaadin.client.ui.SubPartAware#getSubPartName(com.google
-     * .gwt.user.client.Element)
-     */
-    @Override
-    public String getSubPartName(Element subElement) {
-        // Supported identifiers:
-        //
-        // n[index]/n[index]/n[index]{/expand}
-        //
-        // Ends with "/expand" if the target is expand/collapse indicator,
-        // otherwise ends with the node
-
-        boolean isExpandCollapse = false;
-
-        if (!getElement().isOrHasChild(subElement)) {
-            return null;
-        }
-
-        if (subElement == getFocusElement()) {
-            return "fe";
-        }
-
-        TreeNode treeNode = Util.findWidget(subElement, TreeNode.class);
-        if (treeNode == null) {
-            // Did not click on a node, let somebody else take care of the
-            // locator string
-            return null;
-        }
-
-        if (subElement == treeNode.getElement()) {
-            // Targets expand/collapse arrow
-            isExpandCollapse = true;
-        }
-
-        ArrayList<Integer> positions = new ArrayList<Integer>();
-        while (treeNode.getParentNode() != null) {
-            positions.add(0,
-                    treeNode.getParentNode().getChildren().indexOf(treeNode));
-            treeNode = treeNode.getParentNode();
-        }
-        positions.add(0, getRootNodes().indexOf(treeNode));
-
-        String locator = "";
-        for (Integer i : positions) {
-            locator += SUBPART_NODE_PREFIX + "[" + i + "]/";
-        }
-
-        locator = locator.substring(0, locator.length() - 1);
-        if (isExpandCollapse) {
-            locator += "/" + EXPAND_IDENTIFIER;
-        }
-        return locator;
-    }
-
-    @Override
-    public Action[] getActions() {
-        if (bodyActionKeys == null) {
-            return new Action[] {};
-        }
-        final Action[] actions = new Action[bodyActionKeys.length];
-        for (int i = 0; i < actions.length; i++) {
-            final String actionKey = bodyActionKeys[i];
-            final TreeAction a = new TreeAction(this, null, actionKey);
-            a.setCaption(getActionCaption(actionKey));
-            a.setIconUrl(getActionIcon(actionKey));
-            actions[i] = a;
-        }
-        return actions;
-    }
-
-    @Override
-    public ApplicationConnection getClient() {
-        return client;
-    }
-
-    @Override
-    public String getPaintableId() {
-        return paintableId;
-    }
-
-    private void handleBodyContextMenu(ContextMenuEvent event) {
-        if (!readonly && !disabled) {
-            if (bodyActionKeys != null) {
-                int left = event.getNativeEvent().getClientX();
-                int top = event.getNativeEvent().getClientY();
-                top += Window.getScrollTop();
-                left += Window.getScrollLeft();
-                client.getContextMenu().showAt(this, left, top);
-            }
-            event.stopPropagation();
-            event.preventDefault();
-        }
-    }
-
-    public void registerAction(String key, String caption, String iconUrl) {
-        actionMap.put(key + "_c", caption);
-        if (iconUrl != null) {
-            actionMap.put(key + "_i", iconUrl);
-        } else {
-            actionMap.remove(key + "_i");
-        }
-
-    }
-
-    public void registerNode(TreeNode treeNode) {
-        keyToNode.put(treeNode.key, treeNode);
-    }
-
-    public void clearNodeToKeyMap() {
-        keyToNode.clear();
-    }
-
-}
index 4539fbd12a246b941bcebfdc254afd388a74c8ad..11200f28b7e436f8b4ad7458cad61e18a2482cad 100644 (file)
@@ -18,9 +18,10 @@ package com.vaadin.client.ui.treetable;
 import com.vaadin.client.ApplicationConnection;
 import com.vaadin.client.UIDL;
 import com.vaadin.client.ui.FocusableScrollPanel;
+import com.vaadin.client.ui.VTreeTable;
+import com.vaadin.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow;
+import com.vaadin.client.ui.VTreeTable.PendingNavigationEvent;
 import com.vaadin.client.ui.table.TableConnector;
-import com.vaadin.client.ui.table.VScrollTable.VScrollTableBody.VScrollTableRow;
-import com.vaadin.client.ui.treetable.VTreeTable.PendingNavigationEvent;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.treetable.TreeTableConstants;
 import com.vaadin.shared.ui.treetable.TreeTableState;
diff --git a/client/src/com/vaadin/client/ui/treetable/VTreeTable.java b/client/src/com/vaadin/client/ui/treetable/VTreeTable.java
deleted file mode 100644 (file)
index 807c1f4..0000000
+++ /dev/null
@@ -1,865 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.treetable;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-
-import com.google.gwt.animation.client.Animation;
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.dom.client.ImageElement;
-import com.google.gwt.dom.client.SpanElement;
-import com.google.gwt.dom.client.Style.Display;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.dom.client.Style.Visibility;
-import com.google.gwt.dom.client.TableCellElement;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ComputedStyle;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.table.VScrollTable;
-import com.vaadin.client.ui.treetable.VTreeTable.VTreeTableScrollBody.VTreeTableRow;
-
-public class VTreeTable extends VScrollTable {
-
-    static class PendingNavigationEvent {
-        final int keycode;
-        final boolean ctrl;
-        final boolean shift;
-
-        public PendingNavigationEvent(int keycode, boolean ctrl, boolean shift) {
-            this.keycode = keycode;
-            this.ctrl = ctrl;
-            this.shift = shift;
-        }
-
-        @Override
-        public String toString() {
-            String string = "Keyboard event: " + keycode;
-            if (ctrl) {
-                string += " + ctrl";
-            }
-            if (shift) {
-                string += " + shift";
-            }
-            return string;
-        }
-    }
-
-    boolean collapseRequest;
-    private boolean selectionPending;
-    int colIndexOfHierarchy;
-    String collapsedRowKey;
-    VTreeTableScrollBody scrollBody;
-    boolean animationsEnabled;
-    LinkedList<PendingNavigationEvent> pendingNavigationEvents = new LinkedList<VTreeTable.PendingNavigationEvent>();
-    boolean focusParentResponsePending;
-
-    @Override
-    protected VScrollTableBody createScrollBody() {
-        scrollBody = new VTreeTableScrollBody();
-        return scrollBody;
-    }
-
-    /*
-     * Overridden to allow animation of expands and collapses of nodes.
-     */
-    @Override
-    protected void addAndRemoveRows(UIDL partialRowAdditions) {
-        if (partialRowAdditions == null) {
-            return;
-        }
-
-        if (animationsEnabled) {
-            if (partialRowAdditions.hasAttribute("hide")) {
-                scrollBody.unlinkRowsAnimatedAndUpdateCacheWhenFinished(
-                        partialRowAdditions.getIntAttribute("firstprowix"),
-                        partialRowAdditions.getIntAttribute("numprows"));
-            } else {
-                scrollBody.insertRowsAnimated(partialRowAdditions,
-                        partialRowAdditions.getIntAttribute("firstprowix"),
-                        partialRowAdditions.getIntAttribute("numprows"));
-                discardRowsOutsideCacheWindow();
-            }
-        } else {
-            super.addAndRemoveRows(partialRowAdditions);
-        }
-    }
-
-    class VTreeTableScrollBody extends VScrollTable.VScrollTableBody {
-        private int indentWidth = -1;
-
-        VTreeTableScrollBody() {
-            super();
-        }
-
-        @Override
-        protected VScrollTableRow createRow(UIDL uidl, char[] aligns2) {
-            if (uidl.hasAttribute("gen_html")) {
-                // This is a generated row.
-                return new VTreeTableGeneratedRow(uidl, aligns2);
-            }
-            return new VTreeTableRow(uidl, aligns2);
-        }
-
-        class VTreeTableRow extends
-                VScrollTable.VScrollTableBody.VScrollTableRow {
-
-            private boolean isTreeCellAdded = false;
-            private SpanElement treeSpacer;
-            private boolean open;
-            private int depth;
-            private boolean canHaveChildren;
-            protected Widget widgetInHierarchyColumn;
-
-            public VTreeTableRow(UIDL uidl, char[] aligns2) {
-                super(uidl, aligns2);
-            }
-
-            @Override
-            public void addCell(UIDL rowUidl, String text, char align,
-                    String style, boolean textIsHTML, boolean isSorted,
-                    String description) {
-                super.addCell(rowUidl, text, align, style, textIsHTML,
-                        isSorted, description);
-
-                addTreeSpacer(rowUidl);
-            }
-
-            protected boolean addTreeSpacer(UIDL rowUidl) {
-                if (cellShowsTreeHierarchy(getElement().getChildCount() - 1)) {
-                    Element container = (Element) getElement().getLastChild()
-                            .getFirstChild();
-
-                    if (rowUidl.hasAttribute("icon")) {
-                        // icons are in first content cell in TreeTable
-                        ImageElement icon = Document.get().createImageElement();
-                        icon.setClassName("v-icon");
-                        icon.setAlt("icon");
-                        icon.setSrc(client.translateVaadinUri(rowUidl
-                                .getStringAttribute("icon")));
-                        container.insertFirst(icon);
-                    }
-
-                    String classname = "v-treetable-treespacer";
-                    if (rowUidl.getBooleanAttribute("ca")) {
-                        canHaveChildren = true;
-                        open = rowUidl.getBooleanAttribute("open");
-                        classname += open ? " v-treetable-node-open"
-                                : " v-treetable-node-closed";
-                    }
-
-                    treeSpacer = Document.get().createSpanElement();
-
-                    treeSpacer.setClassName(classname);
-                    container.insertFirst(treeSpacer);
-                    depth = rowUidl.hasAttribute("depth") ? rowUidl
-                            .getIntAttribute("depth") : 0;
-                    setIndent();
-                    isTreeCellAdded = true;
-                    return true;
-                }
-                return false;
-            }
-
-            private boolean cellShowsTreeHierarchy(int curColIndex) {
-                if (isTreeCellAdded) {
-                    return false;
-                }
-                return curColIndex == getHierarchyColumnIndex();
-            }
-
-            @Override
-            public void onBrowserEvent(Event event) {
-                if (event.getEventTarget().cast() == treeSpacer
-                        && treeSpacer.getClassName().contains("node")) {
-                    if (event.getTypeInt() == Event.ONMOUSEUP) {
-                        sendToggleCollapsedUpdate(getKey());
-                    }
-                    return;
-                }
-                super.onBrowserEvent(event);
-            }
-
-            @Override
-            public void addCell(UIDL rowUidl, Widget w, char align,
-                    String style, boolean isSorted) {
-                super.addCell(rowUidl, w, align, style, isSorted);
-                if (addTreeSpacer(rowUidl)) {
-                    widgetInHierarchyColumn = w;
-                }
-
-            }
-
-            private void setIndent() {
-                if (getIndentWidth() > 0) {
-                    treeSpacer.getParentElement().getStyle()
-                            .setPaddingLeft(getIndent(), Unit.PX);
-                    treeSpacer.getStyle().setWidth(getIndent(), Unit.PX);
-                }
-            }
-
-            @Override
-            protected void onAttach() {
-                super.onAttach();
-                if (getIndentWidth() < 0) {
-                    detectIndent(this);
-                    // If we detect indent here then the size of the hierarchy
-                    // column is still wrong as it has been set when the indent
-                    // was not known.
-                    int w = getCellWidthFromDom(getHierarchyColumnIndex());
-                    if (w >= 0) {
-                        setColWidth(getHierarchyColumnIndex(), w);
-                    }
-                }
-            }
-
-            private int getCellWidthFromDom(int cellIndex) {
-                final Element cell = DOM.getChild(getElement(), cellIndex);
-                String w = cell.getStyle().getProperty("width");
-                if (w == null || "".equals(w) || !w.endsWith("px")) {
-                    return -1;
-                } else {
-                    return Integer.parseInt(w.substring(0, w.length() - 2));
-                }
-            }
-
-            private int getHierarchyAndIconWidth() {
-                int consumedSpace = treeSpacer.getOffsetWidth();
-                if (treeSpacer.getParentElement().getChildCount() > 2) {
-                    // icon next to tree spacer
-                    consumedSpace += ((com.google.gwt.dom.client.Element) treeSpacer
-                            .getNextSibling()).getOffsetWidth();
-                }
-                return consumedSpace;
-            }
-
-            @Override
-            protected void setCellWidth(int cellIx, int width) {
-                if (cellIx == getHierarchyColumnIndex()) {
-                    // take indentation padding into account if this is the
-                    // hierarchy column
-                    int indent = getIndent();
-                    if (indent != -1) {
-                        width = Math.max(width - getIndent(), 0);
-                    }
-                }
-                super.setCellWidth(cellIx, width);
-            }
-
-            private int getHierarchyColumnIndex() {
-                return colIndexOfHierarchy + (showRowHeaders ? 1 : 0);
-            }
-
-            private int getIndent() {
-                return (depth + 1) * getIndentWidth();
-            }
-        }
-
-        protected class VTreeTableGeneratedRow extends VTreeTableRow {
-            private boolean spanColumns;
-            private boolean htmlContentAllowed;
-
-            public VTreeTableGeneratedRow(UIDL uidl, char[] aligns) {
-                super(uidl, aligns);
-                addStyleName("v-table-generated-row");
-            }
-
-            public boolean isSpanColumns() {
-                return spanColumns;
-            }
-
-            @Override
-            protected void initCellWidths() {
-                if (spanColumns) {
-                    setSpannedColumnWidthAfterDOMFullyInited();
-                } else {
-                    super.initCellWidths();
-                }
-            }
-
-            private void setSpannedColumnWidthAfterDOMFullyInited() {
-                // Defer setting width on spanned columns to make sure that
-                // they are added to the DOM before trying to calculate
-                // widths.
-                Scheduler.get().scheduleDeferred(new ScheduledCommand() {
-
-                    @Override
-                    public void execute() {
-                        if (showRowHeaders) {
-                            setCellWidth(0, tHead.getHeaderCell(0).getWidth());
-                            calcAndSetSpanWidthOnCell(1);
-                        } else {
-                            calcAndSetSpanWidthOnCell(0);
-                        }
-                    }
-                });
-            }
-
-            @Override
-            protected boolean isRenderHtmlInCells() {
-                return htmlContentAllowed;
-            }
-
-            @Override
-            protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col,
-                    int visibleColumnIndex) {
-                htmlContentAllowed = uidl.getBooleanAttribute("gen_html");
-                spanColumns = uidl.getBooleanAttribute("gen_span");
-
-                final Iterator<?> cells = uidl.getChildIterator();
-                if (spanColumns) {
-                    int colCount = uidl.getChildCount();
-                    if (cells.hasNext()) {
-                        final Object cell = cells.next();
-                        if (cell instanceof String) {
-                            addSpannedCell(uidl, cell.toString(), aligns[0],
-                                    "", htmlContentAllowed, false, null,
-                                    colCount);
-                        } else {
-                            addSpannedCell(uidl, (Widget) cell, aligns[0], "",
-                                    false, colCount);
-                        }
-                    }
-                } else {
-                    super.addCellsFromUIDL(uidl, aligns, col,
-                            visibleColumnIndex);
-                }
-            }
-
-            private void addSpannedCell(UIDL rowUidl, Widget w, char align,
-                    String style, boolean sorted, int colCount) {
-                TableCellElement td = DOM.createTD().cast();
-                td.setColSpan(colCount);
-                initCellWithWidget(w, align, style, sorted, td);
-                td.getStyle().setHeight(getRowHeight(), Unit.PX);
-                if (addTreeSpacer(rowUidl)) {
-                    widgetInHierarchyColumn = w;
-                }
-            }
-
-            private void addSpannedCell(UIDL rowUidl, String text, char align,
-                    String style, boolean textIsHTML, boolean sorted,
-                    String description, int colCount) {
-                // String only content is optimized by not using Label widget
-                final TableCellElement td = DOM.createTD().cast();
-                td.setColSpan(colCount);
-                initCellWithText(text, align, style, textIsHTML, sorted,
-                        description, td);
-                td.getStyle().setHeight(getRowHeight(), Unit.PX);
-                addTreeSpacer(rowUidl);
-            }
-
-            @Override
-            protected void setCellWidth(int cellIx, int width) {
-                if (isSpanColumns()) {
-                    if (showRowHeaders) {
-                        if (cellIx == 0) {
-                            super.setCellWidth(0, width);
-                        } else {
-                            // We need to recalculate the spanning TDs width for
-                            // every cellIx in order to support column resizing.
-                            calcAndSetSpanWidthOnCell(1);
-                        }
-                    } else {
-                        // Same as above.
-                        calcAndSetSpanWidthOnCell(0);
-                    }
-                } else {
-                    super.setCellWidth(cellIx, width);
-                }
-            }
-
-            private void calcAndSetSpanWidthOnCell(final int cellIx) {
-                int spanWidth = 0;
-                for (int ix = (showRowHeaders ? 1 : 0); ix < tHead
-                        .getVisibleCellCount(); ix++) {
-                    spanWidth += tHead.getHeaderCell(ix).getOffsetWidth();
-                }
-                Util.setWidthExcludingPaddingAndBorder((Element) getElement()
-                        .getChild(cellIx), spanWidth, 13, false);
-            }
-        }
-
-        private int getIndentWidth() {
-            return indentWidth;
-        }
-
-        private void detectIndent(VTreeTableRow vTreeTableRow) {
-            indentWidth = vTreeTableRow.treeSpacer.getOffsetWidth();
-            if (indentWidth == 0) {
-                indentWidth = -1;
-                return;
-            }
-            Iterator<Widget> iterator = iterator();
-            while (iterator.hasNext()) {
-                VTreeTableRow next = (VTreeTableRow) iterator.next();
-                next.setIndent();
-            }
-        }
-
-        protected void unlinkRowsAnimatedAndUpdateCacheWhenFinished(
-                final int firstIndex, final int rows) {
-            List<VScrollTableRow> rowsToDelete = new ArrayList<VScrollTableRow>();
-            for (int ix = firstIndex; ix < firstIndex + rows; ix++) {
-                VScrollTableRow row = getRowByRowIndex(ix);
-                if (row != null) {
-                    rowsToDelete.add(row);
-                }
-            }
-            if (!rowsToDelete.isEmpty()) {
-                // #8810 Only animate if there's something to animate
-                RowCollapseAnimation anim = new RowCollapseAnimation(
-                        rowsToDelete) {
-                    @Override
-                    protected void onComplete() {
-                        super.onComplete();
-                        // Actually unlink the rows and update the cache after
-                        // the
-                        // animation is done.
-                        unlinkAndReindexRows(firstIndex, rows);
-                        discardRowsOutsideCacheWindow();
-                        ensureCacheFilled();
-                    }
-                };
-                anim.run(150);
-            }
-        }
-
-        protected List<VScrollTableRow> insertRowsAnimated(UIDL rowData,
-                int firstIndex, int rows) {
-            List<VScrollTableRow> insertedRows = insertAndReindexRows(rowData,
-                    firstIndex, rows);
-            if (!insertedRows.isEmpty()) {
-                // Only animate if there's something to animate (#8810)
-                RowExpandAnimation anim = new RowExpandAnimation(insertedRows);
-                anim.run(150);
-            }
-            return insertedRows;
-        }
-
-        /**
-         * Prepares the table for animation by copying the background colors of
-         * all TR elements to their respective TD elements if the TD element is
-         * transparent. This is needed, since if TDs have transparent
-         * backgrounds, the rows sliding behind them are visible.
-         */
-        private class AnimationPreparator {
-            private final int lastItemIx;
-
-            public AnimationPreparator(int lastItemIx) {
-                this.lastItemIx = lastItemIx;
-            }
-
-            public void prepareTableForAnimation() {
-                int ix = lastItemIx;
-                VScrollTableRow row = null;
-                while ((row = getRowByRowIndex(ix)) != null) {
-                    copyTRBackgroundsToTDs(row);
-                    --ix;
-                }
-            }
-
-            private void copyTRBackgroundsToTDs(VScrollTableRow row) {
-                Element tr = row.getElement();
-                ComputedStyle cs = new ComputedStyle(tr);
-                String backgroundAttachment = cs
-                        .getProperty("backgroundAttachment");
-                String backgroundClip = cs.getProperty("backgroundClip");
-                String backgroundColor = cs.getProperty("backgroundColor");
-                String backgroundImage = cs.getProperty("backgroundImage");
-                String backgroundOrigin = cs.getProperty("backgroundOrigin");
-                for (int ix = 0; ix < tr.getChildCount(); ix++) {
-                    Element td = tr.getChild(ix).cast();
-                    if (!elementHasBackground(td)) {
-                        td.getStyle().setProperty("backgroundAttachment",
-                                backgroundAttachment);
-                        td.getStyle().setProperty("backgroundClip",
-                                backgroundClip);
-                        td.getStyle().setProperty("backgroundColor",
-                                backgroundColor);
-                        td.getStyle().setProperty("backgroundImage",
-                                backgroundImage);
-                        td.getStyle().setProperty("backgroundOrigin",
-                                backgroundOrigin);
-                    }
-                }
-            }
-
-            private boolean elementHasBackground(Element element) {
-                ComputedStyle cs = new ComputedStyle(element);
-                String clr = cs.getProperty("backgroundColor");
-                String img = cs.getProperty("backgroundImage");
-                return !("rgba(0, 0, 0, 0)".equals(clr.trim())
-                        || "transparent".equals(clr.trim()) || img == null);
-            }
-
-            public void restoreTableAfterAnimation() {
-                int ix = lastItemIx;
-                VScrollTableRow row = null;
-                while ((row = getRowByRowIndex(ix)) != null) {
-                    restoreStyleForTDsInRow(row);
-
-                    --ix;
-                }
-            }
-
-            private void restoreStyleForTDsInRow(VScrollTableRow row) {
-                Element tr = row.getElement();
-                for (int ix = 0; ix < tr.getChildCount(); ix++) {
-                    Element td = tr.getChild(ix).cast();
-                    td.getStyle().clearProperty("backgroundAttachment");
-                    td.getStyle().clearProperty("backgroundClip");
-                    td.getStyle().clearProperty("backgroundColor");
-                    td.getStyle().clearProperty("backgroundImage");
-                    td.getStyle().clearProperty("backgroundOrigin");
-                }
-            }
-        }
-
-        /**
-         * Animates row expansion using the GWT animation framework.
-         * 
-         * The idea is as follows:
-         * 
-         * 1. Insert all rows normally
-         * 
-         * 2. Insert a newly created DIV containing a new TABLE element below
-         * the DIV containing the actual scroll table body.
-         * 
-         * 3. Clone the rows that were inserted in step 1 and attach the clones
-         * to the new TABLE element created in step 2.
-         * 
-         * 4. The new DIV from step 2 is absolutely positioned so that the last
-         * inserted row is just behind the row that was expanded.
-         * 
-         * 5. Hide the contents of the originally inserted rows by setting the
-         * DIV.v-table-cell-wrapper to display:none;.
-         * 
-         * 6. Set the height of the originally inserted rows to 0.
-         * 
-         * 7. The animation loop slides the DIV from step 2 downwards, while at
-         * the same pace growing the height of each of the inserted rows from 0
-         * to full height. The first inserted row grows from 0 to full and after
-         * this the second row grows from 0 to full, etc until all rows are full
-         * height.
-         * 
-         * 8. Remove the DIV from step 2
-         * 
-         * 9. Restore display:block; to the DIV.v-table-cell-wrapper elements.
-         * 
-         * 10. DONE
-         */
-        private class RowExpandAnimation extends Animation {
-
-            private final List<VScrollTableRow> rows;
-            private Element cloneDiv;
-            private Element cloneTable;
-            private AnimationPreparator preparator;
-
-            /**
-             * @param rows
-             *            List of rows to animate. Must not be empty.
-             */
-            public RowExpandAnimation(List<VScrollTableRow> rows) {
-                this.rows = rows;
-                buildAndInsertAnimatingDiv();
-                preparator = new AnimationPreparator(rows.get(0).getIndex() - 1);
-                preparator.prepareTableForAnimation();
-                for (VScrollTableRow row : rows) {
-                    cloneAndAppendRow(row);
-                    row.addStyleName("v-table-row-animating");
-                    setCellWrapperDivsToDisplayNone(row);
-                    row.setHeight(getInitialHeight());
-                }
-            }
-
-            protected String getInitialHeight() {
-                return "0px";
-            }
-
-            private void cloneAndAppendRow(VScrollTableRow row) {
-                Element clonedTR = null;
-                clonedTR = row.getElement().cloneNode(true).cast();
-                clonedTR.getStyle().setVisibility(Visibility.VISIBLE);
-                cloneTable.appendChild(clonedTR);
-            }
-
-            protected double getBaseOffset() {
-                return rows.get(0).getAbsoluteTop()
-                        - rows.get(0).getParent().getAbsoluteTop()
-                        - rows.size() * getRowHeight();
-            }
-
-            private void buildAndInsertAnimatingDiv() {
-                cloneDiv = DOM.createDiv();
-                cloneDiv.addClassName("v-treetable-animation-clone-wrapper");
-                cloneTable = DOM.createTable();
-                cloneTable.addClassName("v-treetable-animation-clone");
-                cloneDiv.appendChild(cloneTable);
-                insertAnimatingDiv();
-            }
-
-            private void insertAnimatingDiv() {
-                Element tableBody = getElement().cast();
-                Element tableBodyParent = tableBody.getParentElement().cast();
-                tableBodyParent.insertAfter(cloneDiv, tableBody);
-            }
-
-            @Override
-            protected void onUpdate(double progress) {
-                animateDiv(progress);
-                animateRowHeights(progress);
-            }
-
-            private void animateDiv(double progress) {
-                double offset = calculateDivOffset(progress, getRowHeight());
-
-                cloneDiv.getStyle().setTop(getBaseOffset() + offset, Unit.PX);
-            }
-
-            private void animateRowHeights(double progress) {
-                double rh = getRowHeight();
-                double vlh = calculateHeightOfAllVisibleLines(progress, rh);
-                int ix = 0;
-
-                while (ix < rows.size()) {
-                    double height = vlh < rh ? vlh : rh;
-                    rows.get(ix).setHeight(height + "px");
-                    vlh -= height;
-                    ix++;
-                }
-            }
-
-            protected double calculateHeightOfAllVisibleLines(double progress,
-                    double rh) {
-                return rows.size() * rh * progress;
-            }
-
-            protected double calculateDivOffset(double progress, double rh) {
-                return progress * rows.size() * rh;
-            }
-
-            @Override
-            protected void onComplete() {
-                preparator.restoreTableAfterAnimation();
-                for (VScrollTableRow row : rows) {
-                    resetCellWrapperDivsDisplayProperty(row);
-                    row.removeStyleName("v-table-row-animating");
-                }
-                Element tableBodyParent = (Element) getElement()
-                        .getParentElement();
-                tableBodyParent.removeChild(cloneDiv);
-            }
-
-            private void setCellWrapperDivsToDisplayNone(VScrollTableRow row) {
-                Element tr = row.getElement();
-                for (int ix = 0; ix < tr.getChildCount(); ix++) {
-                    getWrapperDiv(tr, ix).getStyle().setDisplay(Display.NONE);
-                }
-            }
-
-            private Element getWrapperDiv(Element tr, int tdIx) {
-                Element td = tr.getChild(tdIx).cast();
-                return td.getChild(0).cast();
-            }
-
-            private void resetCellWrapperDivsDisplayProperty(VScrollTableRow row) {
-                Element tr = row.getElement();
-                for (int ix = 0; ix < tr.getChildCount(); ix++) {
-                    getWrapperDiv(tr, ix).getStyle().clearProperty("display");
-                }
-            }
-
-        }
-
-        /**
-         * This is the inverse of the RowExpandAnimation and is implemented by
-         * extending it and overriding the calculation of offsets and heights.
-         */
-        private class RowCollapseAnimation extends RowExpandAnimation {
-
-            private final List<VScrollTableRow> rows;
-
-            /**
-             * @param rows
-             *            List of rows to animate. Must not be empty.
-             */
-            public RowCollapseAnimation(List<VScrollTableRow> rows) {
-                super(rows);
-                this.rows = rows;
-            }
-
-            @Override
-            protected String getInitialHeight() {
-                return getRowHeight() + "px";
-            }
-
-            @Override
-            protected double getBaseOffset() {
-                return getRowHeight();
-            }
-
-            @Override
-            protected double calculateHeightOfAllVisibleLines(double progress,
-                    double rh) {
-                return rows.size() * rh * (1 - progress);
-            }
-
-            @Override
-            protected double calculateDivOffset(double progress, double rh) {
-                return -super.calculateDivOffset(progress, rh);
-            }
-        }
-    }
-
-    /**
-     * Icons rendered into first actual column in TreeTable, not to row header
-     * cell
-     */
-    @Override
-    protected String buildCaptionHtmlSnippet(UIDL uidl) {
-        if (uidl.getTag().equals("column")) {
-            return super.buildCaptionHtmlSnippet(uidl);
-        } else {
-            String s = uidl.getStringAttribute("caption");
-            return s;
-        }
-    }
-
-    @Override
-    protected boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
-        if (collapseRequest || focusParentResponsePending) {
-            // Enqueue the event if there might be pending content changes from
-            // the server
-            if (pendingNavigationEvents.size() < 10) {
-                // Only keep 10 keyboard events in the queue
-                PendingNavigationEvent pendingNavigationEvent = new PendingNavigationEvent(
-                        keycode, ctrl, shift);
-                pendingNavigationEvents.add(pendingNavigationEvent);
-            }
-            return true;
-        }
-
-        VTreeTableRow focusedRow = (VTreeTableRow) getFocusedRow();
-        if (focusedRow != null) {
-            if (focusedRow.canHaveChildren
-                    && ((keycode == KeyCodes.KEY_RIGHT && !focusedRow.open) || (keycode == KeyCodes.KEY_LEFT && focusedRow.open))) {
-                if (!ctrl) {
-                    client.updateVariable(paintableId, "selectCollapsed", true,
-                            false);
-                }
-                sendSelectedRows(false);
-                sendToggleCollapsedUpdate(focusedRow.getKey());
-                return true;
-            } else if (keycode == KeyCodes.KEY_RIGHT && focusedRow.open) {
-                // already expanded, move selection down if next is on a deeper
-                // level (is-a-child)
-                VTreeTableScrollBody body = (VTreeTableScrollBody) focusedRow
-                        .getParent();
-                Iterator<Widget> iterator = body.iterator();
-                VTreeTableRow next = null;
-                while (iterator.hasNext()) {
-                    next = (VTreeTableRow) iterator.next();
-                    if (next == focusedRow) {
-                        next = (VTreeTableRow) iterator.next();
-                        break;
-                    }
-                }
-                if (next != null) {
-                    if (next.depth > focusedRow.depth) {
-                        selectionPending = true;
-                        return super.handleNavigation(getNavigationDownKey(),
-                                ctrl, shift);
-                    }
-                } else {
-                    // Note, a minor change here for a bit false behavior if
-                    // cache rows is disabled + last visible row + no childs for
-                    // the node
-                    selectionPending = true;
-                    return super.handleNavigation(getNavigationDownKey(), ctrl,
-                            shift);
-                }
-            } else if (keycode == KeyCodes.KEY_LEFT) {
-                // already collapsed move selection up to parent node
-                // do on the server side as the parent is not necessary
-                // rendered on the client, could check if parent is visible if
-                // a performance issue arises
-
-                client.updateVariable(paintableId, "focusParent",
-                        focusedRow.getKey(), true);
-
-                // Set flag that we should enqueue navigation events until we
-                // get a response to this request
-                focusParentResponsePending = true;
-
-                return true;
-            }
-        }
-        return super.handleNavigation(keycode, ctrl, shift);
-    }
-
-    private void sendToggleCollapsedUpdate(String rowKey) {
-        collapsedRowKey = rowKey;
-        collapseRequest = true;
-        client.updateVariable(paintableId, "toggleCollapsed", rowKey, true);
-    }
-
-    @Override
-    public void onBrowserEvent(Event event) {
-        super.onBrowserEvent(event);
-        if (event.getTypeInt() == Event.ONKEYUP && selectionPending) {
-            sendSelectedRows();
-        }
-    }
-
-    @Override
-    protected void sendSelectedRows(boolean immediately) {
-        super.sendSelectedRows(immediately);
-        selectionPending = false;
-    }
-
-    @Override
-    protected void reOrderColumn(String columnKey, int newIndex) {
-        super.reOrderColumn(columnKey, newIndex);
-        // current impl not intelligent enough to survive without visiting the
-        // server to redraw content
-        client.sendPendingVariableChanges();
-    }
-
-    @Override
-    public void setStyleName(String style) {
-        super.setStyleName(style + " v-treetable");
-    }
-
-    @Override
-    protected void updateTotalRows(UIDL uidl) {
-        // Make sure that initializedAndAttached & al are not reset when the
-        // totalrows are updated on expand/collapse requests.
-        int newTotalRows = uidl.getIntAttribute("totalrows");
-        setTotalRows(newTotalRows);
-    }
-
-}
index f9767bb727da387ed6e087742c8161114d087bf3..45b7a130550ab8bec0b25bfb997cf4ca3f239b6a 100644 (file)
@@ -19,6 +19,7 @@ package com.vaadin.client.ui.twincolselect;
 import com.vaadin.client.ApplicationConnection;
 import com.vaadin.client.DirectionalManagedLayout;
 import com.vaadin.client.UIDL;
+import com.vaadin.client.ui.VTwinColSelect;
 import com.vaadin.client.ui.optiongroup.OptionGroupBaseConnector;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.twincolselect.TwinColSelectState;
diff --git a/client/src/com/vaadin/client/ui/twincolselect/VTwinColSelect.java b/client/src/com/vaadin/client/ui/twincolselect/VTwinColSelect.java
deleted file mode 100644 (file)
index 494db81..0000000
+++ /dev/null
@@ -1,623 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.twincolselect;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
-import com.google.gwt.dom.client.Style.Overflow;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.DoubleClickEvent;
-import com.google.gwt.event.dom.client.DoubleClickHandler;
-import com.google.gwt.event.dom.client.HasDoubleClickHandlers;
-import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.dom.client.KeyDownEvent;
-import com.google.gwt.event.dom.client.KeyDownHandler;
-import com.google.gwt.event.dom.client.MouseDownEvent;
-import com.google.gwt.event.dom.client.MouseDownHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.ListBox;
-import com.google.gwt.user.client.ui.Panel;
-import com.vaadin.client.UIDL;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.SubPartAware;
-import com.vaadin.client.ui.button.VButton;
-import com.vaadin.client.ui.optiongroup.VOptionGroupBase;
-import com.vaadin.shared.ui.twincolselect.TwinColSelectConstants;
-
-public class VTwinColSelect extends VOptionGroupBase implements KeyDownHandler,
-        MouseDownHandler, DoubleClickHandler, SubPartAware {
-
-    public static final String CLASSNAME = "v-select-twincol";
-
-    private static final int VISIBLE_COUNT = 10;
-
-    private static final int DEFAULT_COLUMN_COUNT = 10;
-
-    private final DoubleClickListBox options;
-
-    private final DoubleClickListBox selections;
-
-    FlowPanel captionWrapper;
-
-    private HTML optionsCaption = null;
-
-    private HTML selectionsCaption = null;
-
-    private final VButton add;
-
-    private final VButton remove;
-
-    private final FlowPanel buttons;
-
-    private final Panel panel;
-
-    /**
-     * A ListBox which catches double clicks
-     * 
-     */
-    public class DoubleClickListBox extends ListBox implements
-            HasDoubleClickHandlers {
-        public DoubleClickListBox(boolean isMultipleSelect) {
-            super(isMultipleSelect);
-        }
-
-        public DoubleClickListBox() {
-            super();
-        }
-
-        @Override
-        public HandlerRegistration addDoubleClickHandler(
-                DoubleClickHandler handler) {
-            return addDomHandler(handler, DoubleClickEvent.getType());
-        }
-    }
-
-    public VTwinColSelect() {
-        super(CLASSNAME);
-
-        captionWrapper = new FlowPanel();
-
-        options = new DoubleClickListBox();
-        options.addClickHandler(this);
-        options.addDoubleClickHandler(this);
-        options.setVisibleItemCount(VISIBLE_COUNT);
-        options.setStyleName(CLASSNAME + "-options");
-
-        selections = new DoubleClickListBox();
-        selections.addClickHandler(this);
-        selections.addDoubleClickHandler(this);
-        selections.setVisibleItemCount(VISIBLE_COUNT);
-        selections.setStyleName(CLASSNAME + "-selections");
-
-        buttons = new FlowPanel();
-        buttons.setStyleName(CLASSNAME + "-buttons");
-        add = new VButton();
-        add.setText(">>");
-        add.addClickHandler(this);
-        remove = new VButton();
-        remove.setText("<<");
-        remove.addClickHandler(this);
-
-        panel = ((Panel) optionsContainer);
-
-        panel.add(captionWrapper);
-        captionWrapper.getElement().getStyle().setOverflow(Overflow.HIDDEN);
-        // Hide until there actually is a caption to prevent IE from rendering
-        // extra empty space
-        captionWrapper.setVisible(false);
-
-        panel.add(options);
-        buttons.add(add);
-        final HTML br = new HTML("<span/>");
-        br.setStyleName(CLASSNAME + "-deco");
-        buttons.add(br);
-        buttons.add(remove);
-        panel.add(buttons);
-        panel.add(selections);
-
-        options.addKeyDownHandler(this);
-        options.addMouseDownHandler(this);
-
-        selections.addMouseDownHandler(this);
-        selections.addKeyDownHandler(this);
-    }
-
-    public HTML getOptionsCaption() {
-        if (optionsCaption == null) {
-            optionsCaption = new HTML();
-            optionsCaption.setStyleName(CLASSNAME + "-caption-left");
-            optionsCaption.getElement().getStyle()
-                    .setFloat(com.google.gwt.dom.client.Style.Float.LEFT);
-            captionWrapper.add(optionsCaption);
-        }
-
-        return optionsCaption;
-    }
-
-    public HTML getSelectionsCaption() {
-        if (selectionsCaption == null) {
-            selectionsCaption = new HTML();
-            selectionsCaption.setStyleName(CLASSNAME + "-caption-right");
-            selectionsCaption.getElement().getStyle()
-                    .setFloat(com.google.gwt.dom.client.Style.Float.RIGHT);
-            captionWrapper.add(selectionsCaption);
-        }
-
-        return selectionsCaption;
-    }
-
-    protected void updateCaptions(UIDL uidl) {
-        String leftCaption = (uidl
-                .hasAttribute(TwinColSelectConstants.ATTRIBUTE_LEFT_CAPTION) ? uidl
-                .getStringAttribute(TwinColSelectConstants.ATTRIBUTE_LEFT_CAPTION)
-                : null);
-        String rightCaption = (uidl
-                .hasAttribute(TwinColSelectConstants.ATTRIBUTE_RIGHT_CAPTION) ? uidl
-                .getStringAttribute(TwinColSelectConstants.ATTRIBUTE_RIGHT_CAPTION)
-                : null);
-
-        boolean hasCaptions = (leftCaption != null || rightCaption != null);
-
-        if (leftCaption == null) {
-            removeOptionsCaption();
-        } else {
-            getOptionsCaption().setText(leftCaption);
-
-        }
-
-        if (rightCaption == null) {
-            removeSelectionsCaption();
-        } else {
-            getSelectionsCaption().setText(rightCaption);
-        }
-
-        captionWrapper.setVisible(hasCaptions);
-    }
-
-    private void removeOptionsCaption() {
-        if (optionsCaption == null) {
-            return;
-        }
-
-        if (optionsCaption.getParent() != null) {
-            captionWrapper.remove(optionsCaption);
-        }
-
-        optionsCaption = null;
-    }
-
-    private void removeSelectionsCaption() {
-        if (selectionsCaption == null) {
-            return;
-        }
-
-        if (selectionsCaption.getParent() != null) {
-            captionWrapper.remove(selectionsCaption);
-        }
-
-        selectionsCaption = null;
-    }
-
-    @Override
-    protected void buildOptions(UIDL uidl) {
-        final boolean enabled = !isDisabled() && !isReadonly();
-        options.setMultipleSelect(isMultiselect());
-        selections.setMultipleSelect(isMultiselect());
-        options.setEnabled(enabled);
-        selections.setEnabled(enabled);
-        add.setEnabled(enabled && !readonly);
-        remove.setEnabled(enabled && !readonly);
-        options.clear();
-        selections.clear();
-        for (final Iterator<?> i = uidl.getChildIterator(); i.hasNext();) {
-            final UIDL optionUidl = (UIDL) i.next();
-            if (optionUidl.hasAttribute("selected")) {
-                selections.addItem(optionUidl.getStringAttribute("caption"),
-                        optionUidl.getStringAttribute("key"));
-            } else {
-                options.addItem(optionUidl.getStringAttribute("caption"),
-                        optionUidl.getStringAttribute("key"));
-            }
-        }
-
-        if (getRows() > 0) {
-            options.setVisibleItemCount(getRows());
-            selections.setVisibleItemCount(getRows());
-
-        }
-
-        add.setStyleName("v-disabled", readonly);
-        remove.setStyleName("v-disabled", readonly);
-    }
-
-    @Override
-    protected String[] getSelectedItems() {
-        final ArrayList<String> selectedItemKeys = new ArrayList<String>();
-        for (int i = 0; i < selections.getItemCount(); i++) {
-            selectedItemKeys.add(selections.getValue(i));
-        }
-        return selectedItemKeys.toArray(new String[selectedItemKeys.size()]);
-    }
-
-    private boolean[] getSelectionBitmap(ListBox listBox) {
-        final boolean[] selectedIndexes = new boolean[listBox.getItemCount()];
-        for (int i = 0; i < listBox.getItemCount(); i++) {
-            if (listBox.isItemSelected(i)) {
-                selectedIndexes[i] = true;
-            } else {
-                selectedIndexes[i] = false;
-            }
-        }
-        return selectedIndexes;
-    }
-
-    private void addItem() {
-        Set<String> movedItems = moveSelectedItems(options, selections);
-        selectedKeys.addAll(movedItems);
-
-        client.updateVariable(paintableId, "selected",
-                selectedKeys.toArray(new String[selectedKeys.size()]),
-                isImmediate());
-    }
-
-    private void removeItem() {
-        Set<String> movedItems = moveSelectedItems(selections, options);
-        selectedKeys.removeAll(movedItems);
-
-        client.updateVariable(paintableId, "selected",
-                selectedKeys.toArray(new String[selectedKeys.size()]),
-                isImmediate());
-    }
-
-    private Set<String> moveSelectedItems(ListBox source, ListBox target) {
-        final boolean[] sel = getSelectionBitmap(source);
-        final Set<String> movedItems = new HashSet<String>();
-        int lastSelected = 0;
-        for (int i = 0; i < sel.length; i++) {
-            if (sel[i]) {
-                final int optionIndex = i
-                        - (sel.length - source.getItemCount());
-                movedItems.add(source.getValue(optionIndex));
-
-                // Move selection to another column
-                final String text = source.getItemText(optionIndex);
-                final String value = source.getValue(optionIndex);
-                target.addItem(text, value);
-                target.setItemSelected(target.getItemCount() - 1, true);
-                source.removeItem(optionIndex);
-
-                if (source.getItemCount() > 0) {
-                    lastSelected = optionIndex > 0 ? optionIndex - 1 : 0;
-                }
-            }
-        }
-
-        if (source.getItemCount() > 0) {
-            source.setSelectedIndex(lastSelected);
-        }
-
-        // If no items are left move the focus to the selections
-        if (source.getItemCount() == 0) {
-            target.setFocus(true);
-        } else {
-            source.setFocus(true);
-        }
-
-        return movedItems;
-    }
-
-    @Override
-    public void onClick(ClickEvent event) {
-        super.onClick(event);
-        if (event.getSource() == add) {
-            addItem();
-
-        } else if (event.getSource() == remove) {
-            removeItem();
-
-        } else if (event.getSource() == options) {
-            // unselect all in other list, to avoid mistakes (i.e wrong button)
-            final int c = selections.getItemCount();
-            for (int i = 0; i < c; i++) {
-                selections.setItemSelected(i, false);
-            }
-        } else if (event.getSource() == selections) {
-            // unselect all in other list, to avoid mistakes (i.e wrong button)
-            final int c = options.getItemCount();
-            for (int i = 0; i < c; i++) {
-                options.setItemSelected(i, false);
-            }
-        }
-    }
-
-    void clearInternalHeights() {
-        selections.setHeight("");
-        options.setHeight("");
-    }
-
-    void setInternalHeights() {
-        int captionHeight = Util.getRequiredHeight(captionWrapper);
-        int totalHeight = getOffsetHeight();
-
-        String selectHeight = (totalHeight - captionHeight) + "px";
-
-        selections.setHeight(selectHeight);
-        options.setHeight(selectHeight);
-
-    }
-
-    void clearInternalWidths() {
-        int cols = -1;
-        if (getColumns() > 0) {
-            cols = getColumns();
-        } else {
-            cols = DEFAULT_COLUMN_COUNT;
-        }
-
-        if (cols >= 0) {
-            String colWidth = cols + "em";
-            String containerWidth = (2 * cols + 4) + "em";
-            // Caption wrapper width == optionsSelect + buttons +
-            // selectionsSelect
-            String captionWrapperWidth = (2 * cols + 4 - 0.5) + "em";
-
-            options.setWidth(colWidth);
-            if (optionsCaption != null) {
-                optionsCaption.setWidth(colWidth);
-            }
-            selections.setWidth(colWidth);
-            if (selectionsCaption != null) {
-                selectionsCaption.setWidth(colWidth);
-            }
-            buttons.setWidth("3.5em");
-            optionsContainer.setWidth(containerWidth);
-            captionWrapper.setWidth(captionWrapperWidth);
-        }
-    }
-
-    void setInternalWidths() {
-        DOM.setStyleAttribute(getElement(), "position", "relative");
-        int bordersAndPaddings = Util.measureHorizontalPaddingAndBorder(
-                buttons.getElement(), 0);
-
-        int buttonWidth = Util.getRequiredWidth(buttons);
-        int totalWidth = getOffsetWidth();
-
-        int spaceForSelect = (totalWidth - buttonWidth - bordersAndPaddings) / 2;
-
-        options.setWidth(spaceForSelect + "px");
-        if (optionsCaption != null) {
-            optionsCaption.setWidth(spaceForSelect + "px");
-        }
-
-        selections.setWidth(spaceForSelect + "px");
-        if (selectionsCaption != null) {
-            selectionsCaption.setWidth(spaceForSelect + "px");
-        }
-        captionWrapper.setWidth("100%");
-    }
-
-    @Override
-    protected void setTabIndex(int tabIndex) {
-        options.setTabIndex(tabIndex);
-        selections.setTabIndex(tabIndex);
-        add.setTabIndex(tabIndex);
-        remove.setTabIndex(tabIndex);
-    }
-
-    @Override
-    public void focus() {
-        options.setFocus(true);
-    }
-
-    /**
-     * Get the key that selects an item in the table. By default it is the Enter
-     * key but by overriding this you can change the key to whatever you want.
-     * 
-     * @return
-     */
-    protected int getNavigationSelectKey() {
-        return 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) {
-        int keycode = event.getNativeKeyCode();
-
-        // Catch tab and move between select:s
-        if (keycode == KeyCodes.KEY_TAB && event.getSource() == options) {
-            // Prevent default behavior
-            event.preventDefault();
-
-            // Remove current selections
-            for (int i = 0; i < options.getItemCount(); i++) {
-                options.setItemSelected(i, false);
-            }
-
-            // Focus selections
-            selections.setFocus(true);
-        }
-
-        if (keycode == KeyCodes.KEY_TAB && event.isShiftKeyDown()
-                && event.getSource() == selections) {
-            // Prevent default behavior
-            event.preventDefault();
-
-            // Remove current selections
-            for (int i = 0; i < selections.getItemCount(); i++) {
-                selections.setItemSelected(i, false);
-            }
-
-            // Focus options
-            options.setFocus(true);
-        }
-
-        if (keycode == getNavigationSelectKey()) {
-            // Prevent default behavior
-            event.preventDefault();
-
-            // Decide which select the selection was made in
-            if (event.getSource() == options) {
-                // Prevents the selection to become a single selection when
-                // using Enter key
-                // as the selection key (default)
-                options.setFocus(false);
-
-                addItem();
-
-            } else if (event.getSource() == selections) {
-                // Prevents the selection to become a single selection when
-                // using Enter key
-                // as the selection key (default)
-                selections.setFocus(false);
-
-                removeItem();
-            }
-        }
-
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see
-     * com.google.gwt.event.dom.client.MouseDownHandler#onMouseDown(com.google
-     * .gwt.event.dom.client.MouseDownEvent)
-     */
-    @Override
-    public void onMouseDown(MouseDownEvent event) {
-        // Ensure that items are deselected when selecting
-        // from a different source. See #3699 for details.
-        if (event.getSource() == options) {
-            for (int i = 0; i < selections.getItemCount(); i++) {
-                selections.setItemSelected(i, false);
-            }
-        } else if (event.getSource() == selections) {
-            for (int i = 0; i < options.getItemCount(); i++) {
-                options.setItemSelected(i, false);
-            }
-        }
-
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see
-     * com.google.gwt.event.dom.client.DoubleClickHandler#onDoubleClick(com.
-     * google.gwt.event.dom.client.DoubleClickEvent)
-     */
-    @Override
-    public void onDoubleClick(DoubleClickEvent event) {
-        if (event.getSource() == options) {
-            addItem();
-            options.setSelectedIndex(-1);
-            options.setFocus(false);
-        } else if (event.getSource() == selections) {
-            removeItem();
-            selections.setSelectedIndex(-1);
-            selections.setFocus(false);
-        }
-
-    }
-
-    private static final String SUBPART_OPTION_SELECT = "leftSelect";
-    private static final String SUBPART_OPTION_SELECT_ITEM = SUBPART_OPTION_SELECT
-            + "-item";
-    private static final String SUBPART_SELECTION_SELECT = "rightSelect";
-    private static final String SUBPART_SELECTION_SELECT_ITEM = SUBPART_SELECTION_SELECT
-            + "-item";
-    private static final String SUBPART_LEFT_CAPTION = "leftCaption";
-    private static final String SUBPART_RIGHT_CAPTION = "rightCaption";
-    private static final String SUBPART_ADD_BUTTON = "add";
-    private static final String SUBPART_REMOVE_BUTTON = "remove";
-
-    @Override
-    public Element getSubPartElement(String subPart) {
-        if (SUBPART_OPTION_SELECT.equals(subPart)) {
-            return options.getElement();
-        } else if (subPart.startsWith(SUBPART_OPTION_SELECT_ITEM)) {
-            String idx = subPart.substring(SUBPART_OPTION_SELECT_ITEM.length());
-            return (Element) options.getElement().getChild(
-                    Integer.parseInt(idx));
-        } else if (SUBPART_SELECTION_SELECT.equals(subPart)) {
-            return selections.getElement();
-        } else if (subPart.startsWith(SUBPART_SELECTION_SELECT_ITEM)) {
-            String idx = subPart.substring(SUBPART_SELECTION_SELECT_ITEM
-                    .length());
-            return (Element) selections.getElement().getChild(
-                    Integer.parseInt(idx));
-        } else if (optionsCaption != null
-                && SUBPART_LEFT_CAPTION.equals(subPart)) {
-            return optionsCaption.getElement();
-        } else if (selectionsCaption != null
-                && SUBPART_RIGHT_CAPTION.equals(subPart)) {
-            return selectionsCaption.getElement();
-        } else if (SUBPART_ADD_BUTTON.equals(subPart)) {
-            return add.getElement();
-        } else if (SUBPART_REMOVE_BUTTON.equals(subPart)) {
-            return remove.getElement();
-        }
-
-        return null;
-    }
-
-    @Override
-    public String getSubPartName(Element subElement) {
-        if (optionsCaption != null
-                && optionsCaption.getElement().isOrHasChild(subElement)) {
-            return SUBPART_LEFT_CAPTION;
-        } else if (selectionsCaption != null
-                && selectionsCaption.getElement().isOrHasChild(subElement)) {
-            return SUBPART_RIGHT_CAPTION;
-        } else if (options.getElement().isOrHasChild(subElement)) {
-            if (options.getElement() == subElement) {
-                return SUBPART_OPTION_SELECT;
-            } else {
-                int idx = Util.getChildElementIndex(subElement);
-                return SUBPART_OPTION_SELECT_ITEM + idx;
-            }
-        } else if (selections.getElement().isOrHasChild(subElement)) {
-            if (selections.getElement() == subElement) {
-                return SUBPART_SELECTION_SELECT;
-            } else {
-                int idx = Util.getChildElementIndex(subElement);
-                return SUBPART_SELECTION_SELECT_ITEM + idx;
-            }
-        } else if (add.getElement().isOrHasChild(subElement)) {
-            return SUBPART_ADD_BUTTON;
-        } else if (remove.getElement().isOrHasChild(subElement)) {
-            return SUBPART_REMOVE_BUTTON;
-        }
-
-        return null;
-    }
-}
index e4da9afb241a426d7ec1ad67050de695d00e9312..c1083a2bb46e348061dafa6c1ca6a87aa07f9a1a 100644 (file)
@@ -47,8 +47,9 @@ import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler;
 import com.vaadin.client.ui.AbstractComponentContainerConnector;
 import com.vaadin.client.ui.ClickEventHandler;
 import com.vaadin.client.ui.ShortcutActionHandler;
+import com.vaadin.client.ui.VNotification;
+import com.vaadin.client.ui.VUI;
 import com.vaadin.client.ui.layout.MayScrollChildren;
-import com.vaadin.client.ui.notification.VNotification;
 import com.vaadin.client.ui.window.WindowConnector;
 import com.vaadin.shared.MouseEventDetails;
 import com.vaadin.shared.ui.ComponentStateUtil;
diff --git a/client/src/com/vaadin/client/ui/ui/VUI.java b/client/src/com/vaadin/client/ui/ui/VUI.java
deleted file mode 100644 (file)
index d650b17..0000000
+++ /dev/null
@@ -1,465 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.ui;
-
-import java.util.ArrayList;
-
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.event.logical.shared.HasResizeHandlers;
-import com.google.gwt.event.logical.shared.ResizeEvent;
-import com.google.gwt.event.logical.shared.ResizeHandler;
-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.user.client.DOM;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.History;
-import com.google.gwt.user.client.Timer;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.ui.SimplePanel;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.ComponentConnector;
-import com.vaadin.client.ConnectorMap;
-import com.vaadin.client.Focusable;
-import com.vaadin.client.LayoutManager;
-import com.vaadin.client.VConsole;
-import com.vaadin.client.ui.ShortcutActionHandler;
-import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
-import com.vaadin.client.ui.TouchScrollDelegate;
-import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler;
-import com.vaadin.client.ui.VLazyExecutor;
-import com.vaadin.client.ui.textfield.VTextField;
-import com.vaadin.shared.ApplicationConstants;
-import com.vaadin.shared.ui.ui.UIConstants;
-
-/**
- *
- */
-public class VUI extends SimplePanel implements ResizeHandler,
-        Window.ClosingHandler, ShortcutActionHandlerOwner, Focusable,
-        HasResizeHandlers {
-
-    private static final String CLASSNAME = "v-view";
-
-    private static int MONITOR_PARENT_TIMER_INTERVAL = 1000;
-
-    String theme;
-
-    String id;
-
-    ShortcutActionHandler actionHandler;
-
-    /*
-     * Last known window size used to detect whether VView should be layouted
-     * again. Detection must check window size, because the VView size might be
-     * fixed and thus not automatically adapt to changed window sizes.
-     */
-    private int windowWidth;
-    private int windowHeight;
-
-    /*
-     * Last know view size used to detect whether new dimensions should be sent
-     * to the server.
-     */
-    private int viewWidth;
-    private int viewHeight;
-
-    ApplicationConnection connection;
-
-    /**
-     * Keep track of possible parent size changes when an embedded application.
-     * 
-     * Uses {@link #parentWidth} and {@link #parentHeight} as an optimization to
-     * keep track of when there is a real change.
-     */
-    private Timer resizeTimer;
-
-    /** stored width of parent for embedded application auto-resize */
-    private int parentWidth;
-
-    /** stored height of parent for embedded application auto-resize */
-    private int parentHeight;
-
-    int scrollTop;
-
-    int scrollLeft;
-
-    boolean rendering;
-
-    boolean scrollable;
-
-    boolean immediate;
-
-    boolean resizeLazy = false;
-
-    private HandlerRegistration historyHandlerRegistration;
-
-    private TouchScrollHandler touchScrollHandler;
-
-    /**
-     * The current URI fragment, used to avoid sending updates if nothing has
-     * changed.
-     */
-    String currentFragment;
-
-    /**
-     * Listener for URI fragment changes. Notifies the server of the new value
-     * whenever the value changes.
-     */
-    private final ValueChangeHandler<String> historyChangeHandler = new ValueChangeHandler<String>() {
-
-        @Override
-        public void onValueChange(ValueChangeEvent<String> event) {
-            String newFragment = event.getValue();
-
-            // Send the location to the server if the fragment has changed
-            if (!newFragment.equals(currentFragment) && connection != null) {
-                currentFragment = newFragment;
-                connection.updateVariable(id, UIConstants.LOCATION_VARIABLE,
-                        Window.Location.getHref(), true);
-            }
-        }
-    };
-
-    private VLazyExecutor delayedResizeExecutor = new VLazyExecutor(200,
-            new ScheduledCommand() {
-
-                @Override
-                public void execute() {
-                    performSizeCheck();
-                }
-
-            });
-
-    public VUI() {
-        super();
-        setStyleName(CLASSNAME);
-
-        // Allow focusing the view by using the focus() method, the view
-        // should not be in the document focus flow
-        getElement().setTabIndex(-1);
-        makeScrollable();
-    }
-
-    /**
-     * Start to periodically monitor for parent element resizes if embedded
-     * application (e.g. portlet).
-     */
-    @Override
-    protected void onLoad() {
-        super.onLoad();
-        if (isMonitoringParentSize()) {
-            resizeTimer = new Timer() {
-
-                @Override
-                public void run() {
-                    // trigger check to see if parent size has changed,
-                    // recalculate layouts
-                    performSizeCheck();
-                    resizeTimer.schedule(MONITOR_PARENT_TIMER_INTERVAL);
-                }
-            };
-            resizeTimer.schedule(MONITOR_PARENT_TIMER_INTERVAL);
-        }
-    }
-
-    @Override
-    protected void onAttach() {
-        super.onAttach();
-        historyHandlerRegistration = History
-                .addValueChangeHandler(historyChangeHandler);
-        currentFragment = History.getToken();
-    }
-
-    @Override
-    protected void onDetach() {
-        super.onDetach();
-        historyHandlerRegistration.removeHandler();
-        historyHandlerRegistration = null;
-    }
-
-    /**
-     * Stop monitoring for parent element resizes.
-     */
-
-    @Override
-    protected void onUnload() {
-        if (resizeTimer != null) {
-            resizeTimer.cancel();
-            resizeTimer = null;
-        }
-        super.onUnload();
-    }
-
-    /**
-     * Called when the window or parent div might have been resized.
-     * 
-     * This immediately checks the sizes of the window and the parent div (if
-     * monitoring it) and triggers layout recalculation if they have changed.
-     */
-    protected void performSizeCheck() {
-        windowSizeMaybeChanged(Window.getClientWidth(),
-                Window.getClientHeight());
-    }
-
-    /**
-     * Called when the window or parent div might have been resized.
-     * 
-     * This immediately checks the sizes of the window and the parent div (if
-     * monitoring it) and triggers layout recalculation if they have changed.
-     * 
-     * @param newWindowWidth
-     *            The new width of the window
-     * @param newWindowHeight
-     *            The new height of the window
-     * 
-     * @deprecated use {@link #performSizeCheck()}
-     */
-    @Deprecated
-    protected void windowSizeMaybeChanged(int newWindowWidth,
-            int newWindowHeight) {
-        boolean changed = false;
-        ComponentConnector connector = ConnectorMap.get(connection)
-                .getConnector(this);
-        if (windowWidth != newWindowWidth) {
-            windowWidth = newWindowWidth;
-            changed = true;
-            connector.getLayoutManager().reportOuterWidth(connector,
-                    newWindowWidth);
-            VConsole.log("New window width: " + windowWidth);
-        }
-        if (windowHeight != newWindowHeight) {
-            windowHeight = newWindowHeight;
-            changed = true;
-            connector.getLayoutManager().reportOuterHeight(connector,
-                    newWindowHeight);
-            VConsole.log("New window height: " + windowHeight);
-        }
-        Element parentElement = getElement().getParentElement();
-        if (isMonitoringParentSize() && parentElement != null) {
-            // check also for parent size changes
-            int newParentWidth = parentElement.getClientWidth();
-            int newParentHeight = parentElement.getClientHeight();
-            if (parentWidth != newParentWidth) {
-                parentWidth = newParentWidth;
-                changed = true;
-                VConsole.log("New parent width: " + parentWidth);
-            }
-            if (parentHeight != newParentHeight) {
-                parentHeight = newParentHeight;
-                changed = true;
-                VConsole.log("New parent height: " + parentHeight);
-            }
-        }
-        if (changed) {
-            /*
-             * If the window size has changed, layout the VView again and send
-             * new size to the server if the size changed. (Just checking VView
-             * size would cause us to ignore cases when a relatively sized VView
-             * should shrink as the content's size is fixed and would thus not
-             * automatically shrink.)
-             */
-            VConsole.log("Running layout functions due to window or parent resize");
-
-            // update size to avoid (most) redundant re-layout passes
-            // there can still be an extra layout recalculation if webkit
-            // overflow fix updates the size in a deferred block
-            if (isMonitoringParentSize() && parentElement != null) {
-                parentWidth = parentElement.getClientWidth();
-                parentHeight = parentElement.getClientHeight();
-            }
-
-            sendClientResized();
-
-            LayoutManager layoutManager = connector.getLayoutManager();
-            if (layoutManager.isLayoutRunning()) {
-                layoutManager.layoutLater();
-            } else {
-                layoutManager.layoutNow();
-            }
-        }
-    }
-
-    public String getTheme() {
-        return theme;
-    }
-
-    /**
-     * Used to reload host page on theme changes.
-     */
-    static native void reloadHostPage()
-    /*-{
-         $wnd.location.reload();
-     }-*/;
-
-    /**
-     * Returns true if the body is NOT generated, i.e if someone else has made
-     * the page that we're running in. Otherwise we're in charge of the whole
-     * page.
-     * 
-     * @return true if we're running embedded
-     */
-    public boolean isEmbedded() {
-        return !getElement().getOwnerDocument().getBody().getClassName()
-                .contains(ApplicationConstants.GENERATED_BODY_CLASSNAME);
-    }
-
-    /**
-     * Returns true if the size of the parent should be checked periodically and
-     * the application should react to its changes.
-     * 
-     * @return true if size of parent should be tracked
-     */
-    protected boolean isMonitoringParentSize() {
-        // could also perform a more specific check (Liferay portlet)
-        return isEmbedded();
-    }
-
-    @Override
-    public void onBrowserEvent(Event event) {
-        super.onBrowserEvent(event);
-        int type = DOM.eventGetType(event);
-        if (type == Event.ONKEYDOWN && actionHandler != null) {
-            actionHandler.handleKeyboardEvent(event);
-            return;
-        } else if (scrollable && type == Event.ONSCROLL) {
-            updateScrollPosition();
-        }
-    }
-
-    /**
-     * Updates scroll position from DOM and saves variables to server.
-     */
-    private void updateScrollPosition() {
-        int oldTop = scrollTop;
-        int oldLeft = scrollLeft;
-        scrollTop = DOM.getElementPropertyInt(getElement(), "scrollTop");
-        scrollLeft = DOM.getElementPropertyInt(getElement(), "scrollLeft");
-        if (connection != null && !rendering) {
-            if (oldTop != scrollTop) {
-                connection.updateVariable(id, "scrollTop", scrollTop, false);
-            }
-            if (oldLeft != scrollLeft) {
-                connection.updateVariable(id, "scrollLeft", scrollLeft, false);
-            }
-        }
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see
-     * com.google.gwt.event.logical.shared.ResizeHandler#onResize(com.google
-     * .gwt.event.logical.shared.ResizeEvent)
-     */
-
-    @Override
-    public void onResize(ResizeEvent event) {
-        triggerSizeChangeCheck();
-    }
-
-    /**
-     * Called when a resize event is received.
-     * 
-     * This may trigger a lazy refresh or perform the size check immediately
-     * depending on the browser used and whether the server side requests
-     * resizes to be lazy.
-     */
-    private void triggerSizeChangeCheck() {
-        /*
-         * IE (pre IE9 at least) will give us some false resize events due to
-         * problems with scrollbars. Firefox 3 might also produce some extra
-         * events. We postpone both the re-layouting and the server side event
-         * for a while to deal with these issues.
-         * 
-         * We may also postpone these events to avoid slowness when resizing the
-         * browser window. Constantly recalculating the layout causes the resize
-         * operation to be really slow with complex layouts.
-         */
-        boolean lazy = resizeLazy || BrowserInfo.get().isIE8();
-
-        if (lazy) {
-            delayedResizeExecutor.trigger();
-        } else {
-            performSizeCheck();
-        }
-    }
-
-    /**
-     * Send new dimensions to the server.
-     */
-    void sendClientResized() {
-        Element parentElement = getElement().getParentElement();
-        int viewHeight = parentElement.getClientHeight();
-        int viewWidth = parentElement.getClientWidth();
-
-        ResizeEvent.fire(this, viewWidth, viewHeight);
-    }
-
-    public native static void goTo(String url)
-    /*-{
-       $wnd.location = url;
-     }-*/;
-
-    @Override
-    public void onWindowClosing(Window.ClosingEvent event) {
-        // Change focus on this window in order to ensure that all state is
-        // collected from textfields
-        // TODO this is a naive hack, that only works with text fields and may
-        // cause some odd issues. Should be replaced with a decent solution, see
-        // also related BeforeShortcutActionListener interface. Same interface
-        // might be usable here.
-        VTextField.flushChangesFromFocusedTextField();
-    }
-
-    private native static void loadAppIdListFromDOM(ArrayList<String> list)
-    /*-{
-         var j;
-         for(j in $wnd.vaadin.vaadinConfigurations) {
-            // $entry not needed as function is not exported
-            list.@java.util.Collection::add(Ljava/lang/Object;)(j);
-         }
-     }-*/;
-
-    @Override
-    public ShortcutActionHandler getShortcutActionHandler() {
-        return actionHandler;
-    }
-
-    @Override
-    public void focus() {
-        getElement().focus();
-    }
-
-    /**
-     * Ensures the root is scrollable eg. after style name changes.
-     */
-    void makeScrollable() {
-        if (touchScrollHandler == null) {
-            touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this);
-        }
-        touchScrollHandler.addElement(getElement());
-    }
-
-    @Override
-    public HandlerRegistration addResizeHandler(ResizeHandler resizeHandler) {
-        return addHandler(resizeHandler, ResizeEvent.getType());
-    }
-
-}
index 7a69138d215f0feeca394a65dca4626abfada656..fce35ed6e7705250fd8189a4bcc90be2134cd543 100644 (file)
@@ -20,6 +20,7 @@ import com.vaadin.client.ApplicationConnection;
 import com.vaadin.client.Paintable;
 import com.vaadin.client.UIDL;
 import com.vaadin.client.ui.AbstractComponentConnector;
+import com.vaadin.client.ui.VUpload;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.Connect.LoadStyle;
 import com.vaadin.ui.Upload;
index 8dfd30ed8e017a53bd5c0ec9839405ea9989284d..4ce50e71331891d2c22897bd5b578e85020c5382 100644 (file)
  */
 package com.vaadin.client.ui.upload;
 
+import com.vaadin.client.ui.VUpload;
+
 public class UploadIFrameOnloadStrategy {
 
-    native void hookEvents(com.google.gwt.dom.client.Element iframe,
+    public native void hookEvents(com.google.gwt.dom.client.Element iframe,
             VUpload upload)
     /*-{
         iframe.onload = $entry(function() {
@@ -29,7 +31,7 @@ public class UploadIFrameOnloadStrategy {
      * @param iframe
      *            the iframe whose onLoad event is to be cleaned
      */
-    native void unHookEvents(com.google.gwt.dom.client.Element iframe)
+    public native void unHookEvents(com.google.gwt.dom.client.Element iframe)
     /*-{
         iframe.onload = null;
     }-*/;
index 9ef1066d227640619a271c9221e8389a5b9541b5..cdbc41be9381d689adfc0edc60fead6ab43a3122 100644 (file)
@@ -16,6 +16,7 @@
 package com.vaadin.client.ui.upload;
 
 import com.google.gwt.dom.client.Element;
+import com.vaadin.client.ui.VUpload;
 
 /**
  * IE does not have onload, detect onload via readystatechange
@@ -23,7 +24,7 @@ import com.google.gwt.dom.client.Element;
  */
 public class UploadIFrameOnloadStrategyIE extends UploadIFrameOnloadStrategy {
     @Override
-    native void hookEvents(Element iframe, VUpload upload)
+    public native void hookEvents(Element iframe, VUpload upload)
     /*-{
       iframe.onreadystatechange = $entry(function() {
         if (iframe.readyState == 'complete') {
@@ -33,7 +34,7 @@ public class UploadIFrameOnloadStrategyIE extends UploadIFrameOnloadStrategy {
     }-*/;
 
     @Override
-    native void unHookEvents(Element iframe)
+    public native void unHookEvents(Element iframe)
     /*-{
       iframe.onreadystatechange = null;
     }-*/;
diff --git a/client/src/com/vaadin/client/ui/upload/VUpload.java b/client/src/com/vaadin/client/ui/upload/VUpload.java
deleted file mode 100644 (file)
index 42fd285..0000000
+++ /dev/null
@@ -1,319 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.upload;
-
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.dom.client.DivElement;
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.dom.client.FormElement;
-import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.Timer;
-import com.google.gwt.user.client.ui.FileUpload;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.FormPanel;
-import com.google.gwt.user.client.ui.Hidden;
-import com.google.gwt.user.client.ui.Panel;
-import com.google.gwt.user.client.ui.SimplePanel;
-import com.vaadin.client.ApplicationConnection;
-import com.vaadin.client.BrowserInfo;
-import com.vaadin.client.VConsole;
-import com.vaadin.client.ui.button.VButton;
-
-/**
- * 
- * Note, we are not using GWT FormPanel as we want to listen submitcomplete
- * events even though the upload component is already detached.
- * 
- */
-public class VUpload extends SimplePanel {
-
-    private final class MyFileUpload extends FileUpload {
-        @Override
-        public void onBrowserEvent(Event event) {
-            super.onBrowserEvent(event);
-            if (event.getTypeInt() == Event.ONCHANGE) {
-                if (immediate && fu.getFilename() != null
-                        && !"".equals(fu.getFilename())) {
-                    submit();
-                }
-            } else if (BrowserInfo.get().isIE()
-                    && event.getTypeInt() == Event.ONFOCUS) {
-                // IE and user has clicked on hidden textarea part of upload
-                // field. Manually open file selector, other browsers do it by
-                // default.
-                fireNativeClick(fu.getElement());
-                // also remove focus to enable hack if user presses cancel
-                // button
-                fireNativeBlur(fu.getElement());
-            }
-        }
-    }
-
-    public static final String CLASSNAME = "v-upload";
-
-    /**
-     * FileUpload component that opens native OS dialog to select file.
-     */
-    FileUpload fu = new MyFileUpload();
-
-    Panel panel = new FlowPanel();
-
-    UploadIFrameOnloadStrategy onloadstrategy = GWT
-            .create(UploadIFrameOnloadStrategy.class);
-
-    ApplicationConnection client;
-
-    protected String paintableId;
-
-    /**
-     * Button that initiates uploading
-     */
-    protected final VButton submitButton;
-
-    /**
-     * When expecting big files, programmer may initiate some UI changes when
-     * uploading the file starts. Bit after submitting file we'll visit the
-     * server to check possible changes.
-     */
-    protected Timer t;
-
-    /**
-     * some browsers tries to send form twice if submit is called in button
-     * click handler, some don't submit at all without it, so we need to track
-     * if form is already being submitted
-     */
-    private boolean submitted = false;
-
-    private boolean enabled = true;
-
-    private boolean immediate;
-
-    private Hidden maxfilesize = new Hidden();
-
-    protected FormElement element;
-
-    private com.google.gwt.dom.client.Element synthesizedFrame;
-
-    protected int nextUploadId;
-
-    public VUpload() {
-        super(com.google.gwt.dom.client.Document.get().createFormElement());
-
-        element = getElement().cast();
-        setEncoding(getElement(), FormPanel.ENCODING_MULTIPART);
-        element.setMethod(FormPanel.METHOD_POST);
-
-        setWidget(panel);
-        panel.add(maxfilesize);
-        panel.add(fu);
-        submitButton = new VButton();
-        submitButton.addClickHandler(new ClickHandler() {
-            @Override
-            public void onClick(ClickEvent event) {
-                if (immediate) {
-                    // fire click on upload (eg. focused button and hit space)
-                    fireNativeClick(fu.getElement());
-                } else {
-                    submit();
-                }
-            }
-        });
-        panel.add(submitButton);
-
-        setStyleName(CLASSNAME);
-    }
-
-    private static native void setEncoding(Element form, String encoding)
-    /*-{
-      form.enctype = encoding;
-    }-*/;
-
-    protected void setImmediate(boolean booleanAttribute) {
-        if (immediate != booleanAttribute) {
-            immediate = booleanAttribute;
-            if (immediate) {
-                fu.sinkEvents(Event.ONCHANGE);
-                fu.sinkEvents(Event.ONFOCUS);
-            }
-        }
-        setStyleName(getElement(), CLASSNAME + "-immediate", immediate);
-    }
-
-    private static native void fireNativeClick(Element element)
-    /*-{
-        element.click();
-    }-*/;
-
-    private static native void fireNativeBlur(Element element)
-    /*-{
-        element.blur();
-    }-*/;
-
-    protected void disableUpload() {
-        submitButton.setEnabled(false);
-        if (!submitted) {
-            // Cannot disable the fileupload while submitting or the file won't
-            // be submitted at all
-            fu.getElement().setPropertyBoolean("disabled", true);
-        }
-        enabled = false;
-    }
-
-    protected void enableUpload() {
-        submitButton.setEnabled(true);
-        fu.getElement().setPropertyBoolean("disabled", false);
-        enabled = true;
-        if (submitted) {
-            /*
-             * An old request is still in progress (most likely cancelled),
-             * ditching that target frame to make it possible to send a new
-             * file. A new target frame is created later."
-             */
-            cleanTargetFrame();
-            submitted = false;
-        }
-    }
-
-    /**
-     * Re-creates file input field and populates panel. This is needed as we
-     * want to clear existing values from our current file input field.
-     */
-    private void rebuildPanel() {
-        panel.remove(submitButton);
-        panel.remove(fu);
-        fu = new MyFileUpload();
-        fu.setName(paintableId + "_file");
-        fu.getElement().setPropertyBoolean("disabled", !enabled);
-        panel.add(fu);
-        panel.add(submitButton);
-        if (immediate) {
-            fu.sinkEvents(Event.ONCHANGE);
-        }
-    }
-
-    /**
-     * Called by JSNI (hooked via {@link #onloadstrategy})
-     */
-    private void onSubmitComplete() {
-        /* Needs to be run dereferred to avoid various browser issues. */
-        Scheduler.get().scheduleDeferred(new Command() {
-            @Override
-            public void execute() {
-                if (submitted) {
-                    if (client != null) {
-                        if (t != null) {
-                            t.cancel();
-                        }
-                        VConsole.log("VUpload:Submit complete");
-                        client.sendPendingVariableChanges();
-                    }
-
-                    rebuildPanel();
-
-                    submitted = false;
-                    enableUpload();
-                    if (!isAttached()) {
-                        /*
-                         * Upload is complete when upload is already abandoned.
-                         */
-                        cleanTargetFrame();
-                    }
-                }
-            }
-        });
-    }
-
-    protected void submit() {
-        if (fu.getFilename().length() == 0 || submitted || !enabled) {
-            VConsole.log("Submit cancelled (disabled, no file or already submitted)");
-            return;
-        }
-        // flush possibly pending variable changes, so they will be handled
-        // before upload
-        client.sendPendingVariableChanges();
-
-        element.submit();
-        submitted = true;
-        VConsole.log("Submitted form");
-
-        disableUpload();
-
-        /*
-         * Visit server a moment after upload has started to see possible
-         * changes from UploadStarted event. Will be cleared on complete.
-         */
-        t = new Timer() {
-            @Override
-            public void run() {
-                VConsole.log("Visiting server to see if upload started event changed UI.");
-                client.updateVariable(paintableId, "pollForStart",
-                        nextUploadId, true);
-            }
-        };
-        t.schedule(800);
-    }
-
-    @Override
-    protected void onAttach() {
-        super.onAttach();
-        if (client != null) {
-            ensureTargetFrame();
-        }
-    }
-
-    protected void ensureTargetFrame() {
-        if (synthesizedFrame == null) {
-            // Attach a hidden IFrame to the form. This is the target iframe to
-            // which the form will be submitted. We have to create the iframe
-            // using innerHTML, because setting an iframe's 'name' property
-            // dynamically doesn't work on most browsers.
-            DivElement dummy = Document.get().createDivElement();
-            dummy.setInnerHTML("<iframe src=\"javascript:''\" name='"
-                    + getFrameName()
-                    + "' style='position:absolute;width:0;height:0;border:0'>");
-            synthesizedFrame = dummy.getFirstChildElement();
-            Document.get().getBody().appendChild(synthesizedFrame);
-            element.setTarget(getFrameName());
-            onloadstrategy.hookEvents(synthesizedFrame, this);
-        }
-    }
-
-    private String getFrameName() {
-        return paintableId + "_TGT_FRAME";
-    }
-
-    @Override
-    protected void onDetach() {
-        super.onDetach();
-        if (!submitted) {
-            cleanTargetFrame();
-        }
-    }
-
-    private void cleanTargetFrame() {
-        if (synthesizedFrame != null) {
-            Document.get().getBody().removeChild(synthesizedFrame);
-            onloadstrategy.unHookEvents(synthesizedFrame);
-            synthesizedFrame = null;
-        }
-    }
-}
diff --git a/client/src/com/vaadin/client/ui/video/VVideo.java b/client/src/com/vaadin/client/ui/video/VVideo.java
deleted file mode 100644 (file)
index 2f9fbe4..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.video;
-
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.dom.client.VideoElement;
-import com.google.gwt.user.client.Element;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.VMediaBase;
-
-public class VVideo extends VMediaBase {
-
-    public static String CLASSNAME = "v-video";
-
-    private VideoElement video;
-
-    public VVideo() {
-        video = Document.get().createVideoElement();
-        setMediaElement(video);
-        setStyleName(CLASSNAME);
-
-        updateDimensionsWhenMetadataLoaded(getElement());
-    }
-
-    /**
-     * Registers a listener that updates the dimensions of the widget when the
-     * video metadata has been loaded.
-     * 
-     * @param el
-     */
-    private native void updateDimensionsWhenMetadataLoaded(Element el)
-    /*-{
-              var self = this;
-              el.addEventListener('loadedmetadata', $entry(function(e) {
-                  self.@com.vaadin.client.ui.video.VVideo::updateElementDynamicSize(II)(el.videoWidth, el.videoHeight);
-              }), false);
-
-    }-*/;
-
-    /**
-     * Updates the dimensions of the widget.
-     * 
-     * @param w
-     * @param h
-     */
-    private void updateElementDynamicSize(int w, int h) {
-        video.getStyle().setWidth(w, Unit.PX);
-        video.getStyle().setHeight(h, Unit.PX);
-        Util.notifyParentOfSizeChange(this, true);
-    }
-
-    public void setPoster(String poster) {
-        video.setPoster(poster);
-    }
-
-}
index d2d9804f66a96b931c4463dbc70aad22e8f0c0c7..052f9694d15965fd35cfbb70461a2b404cf30ba4 100644 (file)
@@ -17,6 +17,7 @@ package com.vaadin.client.ui.video;
 
 import com.vaadin.client.communication.StateChangeEvent;
 import com.vaadin.client.ui.MediaBaseConnector;
+import com.vaadin.client.ui.VVideo;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.shared.ui.video.VideoConstants;
 import com.vaadin.shared.ui.video.VideoState;
diff --git a/client/src/com/vaadin/client/ui/window/VWindow.java b/client/src/com/vaadin/client/ui/window/VWindow.java
deleted file mode 100644 (file)
index 056de6d..0000000
+++ /dev/null
@@ -1,935 +0,0 @@
-/*
- * Copyright 2011 Vaadin Ltd.
- * 
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client.ui.window;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.event.dom.client.FocusEvent;
-import com.google.gwt.event.dom.client.FocusHandler;
-import com.google.gwt.event.dom.client.KeyDownEvent;
-import com.google.gwt.event.dom.client.KeyDownHandler;
-import com.google.gwt.event.dom.client.ScrollEvent;
-import com.google.gwt.event.dom.client.ScrollHandler;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.ui.HasWidgets;
-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.ConnectorMap;
-import com.vaadin.client.Console;
-import com.vaadin.client.Focusable;
-import com.vaadin.client.LayoutManager;
-import com.vaadin.client.Util;
-import com.vaadin.client.ui.FocusableScrollPanel;
-import com.vaadin.client.ui.ShortcutActionHandler;
-import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
-import com.vaadin.client.ui.VLazyExecutor;
-import com.vaadin.client.ui.VOverlay;
-import com.vaadin.client.ui.notification.VNotification;
-import com.vaadin.shared.EventId;
-
-/**
- * "Sub window" component.
- * 
- * @author Vaadin Ltd
- */
-public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
-        ScrollHandler, KeyDownHandler, FocusHandler, BlurHandler, Focusable {
-
-    /**
-     * Minimum allowed height of a window. This refers to the content area, not
-     * the outer borders.
-     */
-    private static final int MIN_CONTENT_AREA_HEIGHT = 100;
-
-    /**
-     * Minimum allowed width of a window. This refers to the content area, not
-     * the outer borders.
-     */
-    private static final int MIN_CONTENT_AREA_WIDTH = 150;
-
-    private static ArrayList<VWindow> windowOrder = new ArrayList<VWindow>();
-
-    private static boolean orderingDefered;
-
-    public static final String CLASSNAME = "v-window";
-
-    private static final int STACKING_OFFSET_PIXELS = 15;
-
-    public static final int Z_INDEX = 10000;
-
-    ComponentConnector layout;
-
-    Element contents;
-
-    Element header;
-
-    Element footer;
-
-    private Element resizeBox;
-
-    final FocusableScrollPanel contentPanel = new FocusableScrollPanel();
-
-    private boolean dragging;
-
-    private int startX;
-
-    private int startY;
-
-    private int origX;
-
-    private int origY;
-
-    private boolean resizing;
-
-    private int origW;
-
-    private int origH;
-
-    Element closeBox;
-
-    protected ApplicationConnection client;
-
-    String id;
-
-    ShortcutActionHandler shortcutHandler;
-
-    /** Last known positionx read from UIDL or updated to application connection */
-    private int uidlPositionX = -1;
-
-    /** Last known positiony read from UIDL or updated to application connection */
-    private int uidlPositionY = -1;
-
-    boolean vaadinModality = false;
-
-    boolean resizable = true;
-
-    private boolean draggable = true;
-
-    boolean resizeLazy = false;
-
-    private Element modalityCurtain;
-    private Element draggingCurtain;
-    private Element resizingCurtain;
-
-    private Element headerText;
-
-    private boolean closable = true;
-
-    // If centered (via UIDL), the window should stay in the centered -mode
-    // until a position is received from the server, or the user moves or
-    // resizes the window.
-    boolean centered = false;
-
-    boolean immediate;
-
-    private Element wrapper;
-
-    boolean visibilityChangesDisabled;
-
-    int bringToFrontSequence = -1;
-
-    private VLazyExecutor delayedContentsSizeUpdater = new VLazyExecutor(200,
-            new ScheduledCommand() {
-
-                @Override
-                public void execute() {
-                    updateContentsSize();
-                }
-            });
-
-    public VWindow() {
-        super(false, false, true); // no autohide, not modal, shadow
-        // Different style of shadow for windows
-        setShadowStyle("window");
-
-        constructDOM();
-        contentPanel.addScrollHandler(this);
-        contentPanel.addKeyDownHandler(this);
-        contentPanel.addFocusHandler(this);
-        contentPanel.addBlurHandler(this);
-    }
-
-    public void bringToFront() {
-        int curIndex = windowOrder.indexOf(this);
-        if (curIndex + 1 < windowOrder.size()) {
-            windowOrder.remove(this);
-            windowOrder.add(this);
-            for (; curIndex < windowOrder.size(); curIndex++) {
-                windowOrder.get(curIndex).setWindowOrder(curIndex);
-            }
-        }
-    }
-
-    /**
-     * Returns true if this window is the topmost VWindow
-     * 
-     * @return
-     */
-    private boolean isActive() {
-        return equals(getTopmostWindow());
-    }
-
-    private static VWindow getTopmostWindow() {
-        return windowOrder.get(windowOrder.size() - 1);
-    }
-
-    void setWindowOrderAndPosition() {
-        // This cannot be done in the constructor as the widgets are created in
-        // a different order than on they should appear on screen
-        if (windowOrder.contains(this)) {
-            // Already set
-            return;
-        }
-        final int order = windowOrder.size();
-        setWindowOrder(order);
-        windowOrder.add(this);
-        setPopupPosition(order * STACKING_OFFSET_PIXELS, order
-                * STACKING_OFFSET_PIXELS);
-
-    }
-
-    private void setWindowOrder(int order) {
-        setZIndex(order + Z_INDEX);
-    }
-
-    @Override
-    protected void setZIndex(int zIndex) {
-        super.setZIndex(zIndex);
-        if (vaadinModality) {
-            DOM.setStyleAttribute(getModalityCurtain(), "zIndex", "" + zIndex);
-        }
-    }
-
-    protected Element getModalityCurtain() {
-        if (modalityCurtain == null) {
-            modalityCurtain = DOM.createDiv();
-            modalityCurtain.setClassName(CLASSNAME + "-modalitycurtain");
-        }
-        return modalityCurtain;
-    }
-
-    protected void constructDOM() {
-        setStyleName(CLASSNAME);
-
-        header = DOM.createDiv();
-        DOM.setElementProperty(header, "className", CLASSNAME + "-outerheader");
-        headerText = DOM.createDiv();
-        DOM.setElementProperty(headerText, "className", CLASSNAME + "-header");
-        contents = DOM.createDiv();
-        DOM.setElementProperty(contents, "className", CLASSNAME + "-contents");
-        footer = DOM.createDiv();
-        DOM.setElementProperty(footer, "className", CLASSNAME + "-footer");
-        resizeBox = DOM.createDiv();
-        DOM.setElementProperty(resizeBox, "className", CLASSNAME + "-resizebox");
-        closeBox = DOM.createDiv();
-        DOM.setElementProperty(closeBox, "className", CLASSNAME + "-closebox");
-        DOM.appendChild(footer, resizeBox);
-
-        wrapper = DOM.createDiv();
-        DOM.setElementProperty(wrapper, "className", CLASSNAME + "-wrap");
-
-        DOM.appendChild(wrapper, header);
-        DOM.appendChild(wrapper, closeBox);
-        DOM.appendChild(header, headerText);
-        DOM.appendChild(wrapper, contents);
-        DOM.appendChild(wrapper, footer);
-        DOM.appendChild(super.getContainerElement(), wrapper);
-
-        sinkEvents(Event.MOUSEEVENTS | Event.TOUCHEVENTS | Event.ONCLICK
-                | Event.ONLOSECAPTURE);
-
-        setWidget(contentPanel);
-
-    }
-
-    /**
-     * Calling this method will defer ordering algorithm, to order windows based
-     * on servers bringToFront and modality instructions. Non changed windows
-     * will be left intact.
-     */
-    static void deferOrdering() {
-        if (!orderingDefered) {
-            orderingDefered = true;
-            Scheduler.get().scheduleFinally(new Command() {
-
-                @Override
-                public void execute() {
-                    doServerSideOrdering();
-                    VNotification.bringNotificationsToFront();
-                }
-            });
-        }
-    }
-
-    private static void doServerSideOrdering() {
-        orderingDefered = false;
-        VWindow[] array = windowOrder.toArray(new VWindow[windowOrder.size()]);
-        Arrays.sort(array, new Comparator<VWindow>() {
-
-            @Override
-            public int compare(VWindow o1, VWindow o2) {
-                /*
-                 * Order by modality, then by bringtofront sequence.
-                 */
-
-                if (o1.vaadinModality && !o2.vaadinModality) {
-                    return 1;
-                } else if (!o1.vaadinModality && o2.vaadinModality) {
-                    return -1;
-                } else if (o1.bringToFrontSequence > o2.bringToFrontSequence) {
-                    return 1;
-                } else if (o1.bringToFrontSequence < o2.bringToFrontSequence) {
-                    return -1;
-                } else {
-                    return 0;
-                }
-            }
-        });
-        for (int i = 0; i < array.length; i++) {
-            VWindow w = array[i];
-            if (w.bringToFrontSequence != -1 || w.vaadinModality) {
-                w.bringToFront();
-                w.bringToFrontSequence = -1;
-            }
-        }
-    }
-
-    @Override
-    public void setVisible(boolean visible) {
-        /*
-         * Visibility with VWindow works differently than with other Paintables
-         * in Vaadin. Invisible VWindows are not attached to DOM at all. Flag is
-         * used to avoid visibility call from
-         * ApplicationConnection.updateComponent();
-         */
-        if (!visibilityChangesDisabled) {
-            super.setVisible(visible);
-        }
-    }
-
-    void setDraggable(boolean draggable) {
-        if (this.draggable == draggable) {
-            return;
-        }
-
-        this.draggable = draggable;
-
-        setCursorProperties();
-    }
-
-    private void setCursorProperties() {
-        if (!draggable) {
-            header.getStyle().setProperty("cursor", "default");
-            footer.getStyle().setProperty("cursor", "default");
-        } else {
-            header.getStyle().setProperty("cursor", "");
-            footer.getStyle().setProperty("cursor", "");
-        }
-    }
-
-    /**
-     * Sets the closable state of the window. Additionally hides/shows the close
-     * button according to the new state.
-     * 
-     * @param closable
-     *            true if the window can be closed by the user
-     */
-    protected void setClosable(boolean closable) {
-        if (this.closable == closable) {
-            return;
-        }
-
-        this.closable = closable;
-        if (closable) {
-            DOM.setStyleAttribute(closeBox, "display", "");
-        } else {
-            DOM.setStyleAttribute(closeBox, "display", "none");
-        }
-
-    }
-
-    /**
-     * Returns the closable state of the sub window. If the sub window is
-     * closable a decoration (typically an X) is shown to the user. By clicking
-     * on the X the user can close the window.
-     * 
-     * @return true if the sub window is closable
-     */
-    protected boolean isClosable() {
-        return closable;
-    }
-
-    @Override
-    public void show() {
-        if (!windowOrder.contains(this)) {
-            // This is needed if the window is hidden and then shown again.
-            // Otherwise this VWindow is added to windowOrder in the
-            // constructor.
-            windowOrder.add(this);
-        }
-
-        if (vaadinModality) {
-            showModalityCurtain();
-        }
-        super.show();
-    }
-
-    @Override
-    public void hide() {
-        if (vaadinModality) {
-            hideModalityCurtain();
-        }
-        super.hide();
-
-        // Remove window from windowOrder to avoid references being left
-        // hanging.
-        windowOrder.remove(this);
-    }
-
-    void setVaadinModality(boolean modality) {
-        vaadinModality = modality;
-        if (vaadinModality) {
-            if (isAttached()) {
-                showModalityCurtain();
-            }
-            deferOrdering();
-        } else {
-            if (modalityCurtain != null) {
-                if (isAttached()) {
-                    hideModalityCurtain();
-                }
-                modalityCurtain = null;
-            }
-        }
-    }
-
-    private void showModalityCurtain() {
-        DOM.setStyleAttribute(getModalityCurtain(), "zIndex",
-                "" + (windowOrder.indexOf(this) + Z_INDEX));
-
-        if (isShowing()) {
-            getOverlayContainer().insertBefore(getModalityCurtain(),
-                    getElement());
-        } else {
-            getOverlayContainer().appendChild(getModalityCurtain());
-        }
-
-    }
-
-    private void hideModalityCurtain() {
-        modalityCurtain.removeFromParent();
-    }
-
-    /*
-     * Shows an empty div on top of all other content; used when moving, so that
-     * iframes (etc) do not steal event.
-     */
-    private void showDraggingCurtain() {
-        getElement().getParentElement().insertBefore(getDraggingCurtain(),
-                getElement());
-    }
-
-    private void hideDraggingCurtain() {
-        if (draggingCurtain != null) {
-            draggingCurtain.removeFromParent();
-        }
-    }
-
-    /*
-     * Shows an empty div on top of all other content; used when resizing, so
-     * that iframes (etc) do not steal event.
-     */
-    private void showResizingCurtain() {
-        getElement().getParentElement().insertBefore(getResizingCurtain(),
-                getElement());
-    }
-
-    private void hideResizingCurtain() {
-        if (resizingCurtain != null) {
-            resizingCurtain.removeFromParent();
-        }
-    }
-
-    private Element getDraggingCurtain() {
-        if (draggingCurtain == null) {
-            draggingCurtain = createCurtain();
-            draggingCurtain.setClassName(CLASSNAME + "-draggingCurtain");
-        }
-
-        return draggingCurtain;
-    }
-
-    private Element getResizingCurtain() {
-        if (resizingCurtain == null) {
-            resizingCurtain = createCurtain();
-            resizingCurtain.setClassName(CLASSNAME + "-resizingCurtain");
-        }
-
-        return resizingCurtain;
-    }
-
-    private Element createCurtain() {
-        Element curtain = DOM.createDiv();
-
-        DOM.setStyleAttribute(curtain, "position", "absolute");
-        DOM.setStyleAttribute(curtain, "top", "0px");
-        DOM.setStyleAttribute(curtain, "left", "0px");
-        DOM.setStyleAttribute(curtain, "width", "100%");
-        DOM.setStyleAttribute(curtain, "height", "100%");
-        DOM.setStyleAttribute(curtain, "zIndex", "" + VOverlay.Z_INDEX);
-
-        return curtain;
-    }
-
-    void setResizable(boolean resizability) {
-        resizable = resizability;
-        if (resizability) {
-            DOM.setElementProperty(footer, "className", CLASSNAME + "-footer");
-            DOM.setElementProperty(resizeBox, "className", CLASSNAME
-                    + "-resizebox");
-        } else {
-            DOM.setElementProperty(footer, "className", CLASSNAME + "-footer "
-                    + CLASSNAME + "-footer-noresize");
-            DOM.setElementProperty(resizeBox, "className", CLASSNAME
-                    + "-resizebox " + CLASSNAME + "-resizebox-disabled");
-        }
-    }
-
-    @Override
-    public void setPopupPosition(int left, int top) {
-        if (top < 0) {
-            // ensure window is not moved out of browser window from top of the
-            // screen
-            top = 0;
-        }
-        super.setPopupPosition(left, top);
-        if (left != uidlPositionX && client != null) {
-            client.updateVariable(id, "positionx", left, false);
-            uidlPositionX = left;
-        }
-        if (top != uidlPositionY && client != null) {
-            client.updateVariable(id, "positiony", top, false);
-            uidlPositionY = top;
-        }
-    }
-
-    public void setCaption(String c) {
-        setCaption(c, null);
-    }
-
-    public void setCaption(String c, String icon) {
-        String html = Util.escapeHTML(c);
-        if (icon != null) {
-            icon = client.translateVaadinUri(icon);
-            html = "<img src=\"" + Util.escapeAttribute(icon)
-                    + "\" class=\"v-icon\" />" + html;
-        }
-        DOM.setInnerHTML(headerText, html);
-    }
-
-    @Override
-    protected Element getContainerElement() {
-        // in GWT 1.5 this method is used in PopupPanel constructor
-        if (contents == null) {
-            return super.getContainerElement();
-        }
-        return contents;
-    }
-
-    @Override
-    public void onBrowserEvent(final Event event) {
-        boolean bubble = true;
-
-        final int type = event.getTypeInt();
-
-        final Element target = DOM.eventGetTarget(event);
-
-        if (resizing || resizeBox == target) {
-            onResizeEvent(event);
-            bubble = false;
-        } else if (isClosable() && target == closeBox) {
-            if (type == Event.ONCLICK) {
-                onCloseClick();
-            }
-            bubble = false;
-        } else if (dragging || !contents.isOrHasChild(target)) {
-            onDragEvent(event);
-            bubble = false;
-        } else if (type == Event.ONCLICK) {
-            // clicked inside window, ensure to be on top
-            if (!isActive()) {
-                bringToFront();
-            }
-        }
-
-        /*
-         * If clicking on other than the content, move focus to the window.
-         * After that this windows e.g. gets all keyboard shortcuts.
-         */
-        if (type == Event.ONMOUSEDOWN
-                && !contentPanel.getElement().isOrHasChild(target)
-                && target != closeBox) {
-            contentPanel.focus();
-        }
-
-        if (!bubble) {
-            event.stopPropagation();
-        } else {
-            // Super.onBrowserEvent takes care of Handlers added by the
-            // ClickEventHandler
-            super.onBrowserEvent(event);
-        }
-    }
-
-    private void onCloseClick() {
-        client.updateVariable(id, "close", true, true);
-    }
-
-    private void onResizeEvent(Event event) {
-        if (resizable && Util.isTouchEventOrLeftMouseButton(event)) {
-            switch (event.getTypeInt()) {
-            case Event.ONMOUSEDOWN:
-            case Event.ONTOUCHSTART:
-                if (!isActive()) {
-                    bringToFront();
-                }
-                showResizingCurtain();
-                if (BrowserInfo.get().isIE()) {
-                    DOM.setStyleAttribute(resizeBox, "visibility", "hidden");
-                }
-                resizing = true;
-                startX = Util.getTouchOrMouseClientX(event);
-                startY = Util.getTouchOrMouseClientY(event);
-                origW = getElement().getOffsetWidth();
-                origH = getElement().getOffsetHeight();
-                DOM.setCapture(getElement());
-                event.preventDefault();
-                break;
-            case Event.ONMOUSEUP:
-            case Event.ONTOUCHEND:
-                setSize(event, true);
-            case Event.ONTOUCHCANCEL:
-                DOM.releaseCapture(getElement());
-            case Event.ONLOSECAPTURE:
-                hideResizingCurtain();
-                if (BrowserInfo.get().isIE()) {
-                    DOM.setStyleAttribute(resizeBox, "visibility", "");
-                }
-                resizing = false;
-                break;
-            case Event.ONMOUSEMOVE:
-            case Event.ONTOUCHMOVE:
-                if (resizing) {
-                    centered = false;
-                    setSize(event, false);
-                    event.preventDefault();
-                }
-                break;
-            default:
-                event.preventDefault();
-                break;
-            }
-        }
-    }
-
-    /**
-     * TODO check if we need to support this with touch based devices.
-     * 
-     * Checks if the cursor was inside the browser content area when the event
-     * happened.
-     * 
-     * @param event
-     *            The event to be checked
-     * @return true, if the cursor is inside the browser content area
-     * 
-     *         false, otherwise
-     */
-    private boolean cursorInsideBrowserContentArea(Event event) {
-        if (event.getClientX() < 0 || event.getClientY() < 0) {
-            // Outside to the left or above
-            return false;
-        }
-
-        if (event.getClientX() > Window.getClientWidth()
-                || event.getClientY() > Window.getClientHeight()) {
-            // Outside to the right or below
-            return false;
-        }
-
-        return true;
-    }
-
-    private void setSize(Event event, boolean updateVariables) {
-        if (!cursorInsideBrowserContentArea(event)) {
-            // Only drag while cursor is inside the browser client area
-            return;
-        }
-
-        int w = Util.getTouchOrMouseClientX(event) - startX + origW;
-        int minWidth = getMinWidth();
-        if (w < minWidth) {
-            w = minWidth;
-        }
-
-        int h = Util.getTouchOrMouseClientY(event) - startY + origH;
-        int minHeight = getMinHeight();
-        if (h < minHeight) {
-            h = minHeight;
-        }
-
-        setWidth(w + "px");
-        setHeight(h + "px");
-
-        if (updateVariables) {
-            // sending width back always as pixels, no need for unit
-            client.updateVariable(id, "width", w, false);
-            client.updateVariable(id, "height", h, immediate);
-        }
-
-        if (updateVariables || !resizeLazy) {
-            // Resize has finished or is not lazy
-            updateContentsSize();
-        } else {
-            // Lazy resize - wait for a while before re-rendering contents
-            delayedContentsSizeUpdater.trigger();
-        }
-    }
-
-    private void updateContentsSize() {
-        // Update child widget dimensions
-        if (client != null) {
-            client.handleComponentRelativeSize(layout.getWidget());
-            client.runDescendentsLayout((HasWidgets) layout.getWidget());
-        }
-
-        LayoutManager layoutManager = LayoutManager.get(client);
-        layoutManager.setNeedsMeasure(ConnectorMap.get(client).getConnector(
-                this));
-        layoutManager.layoutNow();
-    }
-
-    @Override
-    public void setWidth(String width) {
-        // Override PopupPanel which sets the width to the contents
-        getElement().getStyle().setProperty("width", width);
-        // Update v-has-width in case undefined window is resized
-        setStyleName("v-has-width", width != null && width.length() > 0);
-    }
-
-    @Override
-    public void setHeight(String height) {
-        // Override PopupPanel which sets the height to the contents
-        getElement().getStyle().setProperty("height", height);
-        // Update v-has-height in case undefined window is resized
-        setStyleName("v-has-height", height != null && height.length() > 0);
-    }
-
-    private void onDragEvent(Event event) {
-        if (!Util.isTouchEventOrLeftMouseButton(event)) {
-            return;
-        }
-
-        switch (DOM.eventGetType(event)) {
-        case Event.ONTOUCHSTART:
-            if (event.getTouches().length() > 1) {
-                return;
-            }
-        case Event.ONMOUSEDOWN:
-            if (!isActive()) {
-                bringToFront();
-            }
-            beginMovingWindow(event);
-            break;
-        case Event.ONMOUSEUP:
-        case Event.ONTOUCHEND:
-        case Event.ONTOUCHCANCEL:
-        case Event.ONLOSECAPTURE:
-            stopMovingWindow();
-            break;
-        case Event.ONMOUSEMOVE:
-        case Event.ONTOUCHMOVE:
-            moveWindow(event);
-            break;
-        default:
-            break;
-        }
-    }
-
-    private void moveWindow(Event event) {
-        if (dragging) {
-            centered = false;
-            if (cursorInsideBrowserContentArea(event)) {
-                // Only drag while cursor is inside the browser client area
-                final int x = Util.getTouchOrMouseClientX(event) - startX
-                        + origX;
-                final int y = Util.getTouchOrMouseClientY(event) - startY
-                        + origY;
-                setPopupPosition(x, y);
-            }
-            DOM.eventPreventDefault(event);
-        }
-    }
-
-    private void beginMovingWindow(Event event) {
-        if (draggable) {
-            showDraggingCurtain();
-            dragging = true;
-            startX = Util.getTouchOrMouseClientX(event);
-            startY = Util.getTouchOrMouseClientY(event);
-            origX = DOM.getAbsoluteLeft(getElement());
-            origY = DOM.getAbsoluteTop(getElement());
-            DOM.setCapture(getElement());
-            DOM.eventPreventDefault(event);
-        }
-    }
-
-    private void stopMovingWindow() {
-        dragging = false;
-        hideDraggingCurtain();
-        DOM.releaseCapture(getElement());
-    }
-
-    @Override
-    public boolean onEventPreview(Event event) {
-        if (dragging) {
-            onDragEvent(event);
-            return false;
-        } else if (resizing) {
-            onResizeEvent(event);
-            return false;
-        }
-
-        // TODO This is probably completely unnecessary as the modality curtain
-        // prevents events from reaching other windows and any security check
-        // must be done on the server side and not here.
-        // The code here is also run many times as each VWindow has an event
-        // preview but we cannot check only the current VWindow here (e.g.
-        // if(isTopMost) {...}) because PopupPanel will cause all events that
-        // are not cancelled here and target this window to be consume():d
-        // meaning the event won't be sent to the rest of the preview handlers.
-
-        if (getTopmostWindow().vaadinModality) {
-            // Topmost window is modal. Cancel the event if it targets something
-            // outside that window (except debug console...)
-            if (DOM.getCaptureElement() != null) {
-                // Allow events when capture is set
-                return true;
-            }
-
-            final Element target = event.getEventTarget().cast();
-            if (!DOM.isOrHasChild(getTopmostWindow().getElement(), target)) {
-                // not within the modal window, but let's see if it's in the
-                // debug window
-                Widget w = Util.findWidget(target, null);
-                while (w != null) {
-                    if (w instanceof Console) {
-                        return true; // allow debug-window clicks
-                    } else if (ConnectorMap.get(client).isConnector(w)) {
-                        return false;
-                    }
-                    w = w.getParent();
-                }
-                return false;
-            }
-        }
-        return true;
-    }
-
-    @Override
-    public void addStyleDependentName(String styleSuffix) {
-        // VWindow's getStyleElement() does not return the same element as
-        // getElement(), so we need to override this.
-        setStyleName(getElement(), getStylePrimaryName() + "-" + styleSuffix,
-                true);
-    }
-
-    @Override
-    public ShortcutActionHandler getShortcutActionHandler() {
-        return shortcutHandler;
-    }
-
-    @Override
-    public void onScroll(ScrollEvent event) {
-        client.updateVariable(id, "scrollTop",
-                contentPanel.getScrollPosition(), false);
-        client.updateVariable(id, "scrollLeft",
-                contentPanel.getHorizontalScrollPosition(), false);
-
-    }
-
-    @Override
-    public void onKeyDown(KeyDownEvent event) {
-        if (shortcutHandler != null) {
-            shortcutHandler
-                    .handleKeyboardEvent(Event.as(event.getNativeEvent()));
-            return;
-        }
-    }
-
-    @Override
-    public void onBlur(BlurEvent event) {
-        if (client.hasEventListeners(this, EventId.BLUR)) {
-            client.updateVariable(id, EventId.BLUR, "", true);
-        }
-    }
-
-    @Override
-    public void onFocus(FocusEvent event) {
-        if (client.hasEventListeners(this, EventId.FOCUS)) {
-            client.updateVariable(id, EventId.FOCUS, "", true);
-        }
-    }
-
-    @Override
-    public void focus() {
-        contentPanel.focus();
-    }
-
-    public int getMinHeight() {
-        return MIN_CONTENT_AREA_HEIGHT + getDecorationHeight();
-    }
-
-    private int getDecorationHeight() {
-        LayoutManager lm = layout.getLayoutManager();
-        int headerHeight = lm.getOuterHeight(header);
-        int footerHeight = lm.getOuterHeight(footer);
-        return headerHeight + footerHeight;
-    }
-
-    public int getMinWidth() {
-        return MIN_CONTENT_AREA_WIDTH + getDecorationWidth();
-    }
-
-    private int getDecorationWidth() {
-        LayoutManager layoutManager = layout.getLayoutManager();
-        return layoutManager.getOuterWidth(getElement())
-                - contentPanel.getElement().getOffsetWidth();
-    }
-
-}
index ebcdd24feef70545321f927eb078ee5b3ff965bc..8b4c5d9f375556b7132f1aeac29bd3405c125dbd 100644 (file)
@@ -35,6 +35,7 @@ import com.vaadin.client.ui.AbstractComponentContainerConnector;
 import com.vaadin.client.ui.ClickEventHandler;
 import com.vaadin.client.ui.PostLayoutListener;
 import com.vaadin.client.ui.ShortcutActionHandler;
+import com.vaadin.client.ui.VWindow;
 import com.vaadin.client.ui.ShortcutActionHandler.BeforeShortcutActionListener;
 import com.vaadin.client.ui.SimpleManagedLayout;
 import com.vaadin.client.ui.layout.MayScrollChildren;
index 601ec6db1f49792aecbbdbb1a83b6d9375960ca9..4b9938079fe54ef7bb3f4249cc61828541d31326 100644 (file)
@@ -18,7 +18,7 @@ package com.vaadin.tests.widgetset.client;
 
 import com.vaadin.client.communication.StateChangeEvent;
 import com.vaadin.client.ui.AbstractComponentConnector;
-import com.vaadin.client.ui.label.VLabel;
+import com.vaadin.client.ui.VLabel;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.tests.widgetset.server.DummyLabel;
 
index bc514047f96fe239219cbacfa29325b9df56e88b..a2f8ab6333c18f5cba32b9b77b20b6cd39db1b2d 100644 (file)
@@ -16,7 +16,7 @@
 package com.vaadin.tests.widgetset.client;
 
 import com.vaadin.client.ui.AbstractComponentConnector;
-import com.vaadin.client.ui.label.VLabel;
+import com.vaadin.client.ui.VLabel;
 import com.vaadin.shared.ui.Connect;
 import com.vaadin.tests.widgetset.server.MissingFromDefaultWidgetsetComponent;
 
index 704b9cc6d9efcba5481e98af89bae9d99407d2bf..abe1be12a94c1284845ce83a8d25cbab27691032 100644 (file)
@@ -16,7 +16,7 @@
 
 package com.vaadin.tests.widgetset.client;
 
-import com.vaadin.client.ui.textarea.VTextArea;
+import com.vaadin.client.ui.VTextArea;
 
 public class VExtendedTextArea extends VTextArea {