Adds addtional improvements to HorizontalSplitPanel and VerticalSplitPanel.
The changes include:

1 - Limited styling on container elements to work around Safari2 bug with
percentage heights.

2 - Added thumb images to each of the split panels (via ImageBundle).

3 - Reworked VerticalSplitPanel to be more like HorizontalSplitPanel.

4 - Refactored most of the utility methods into static methods inside of
SplitPanel.

5 - Added mouse move event coalescing to the IE6/7 impl to make repaints less
choppy.

Review by: jgw



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1319 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/kitchensink/src/com/google/gwt/sample/kitchensink/public/KitchenSink.css b/samples/kitchensink/src/com/google/gwt/sample/kitchensink/public/KitchenSink.css
index 79f8266..b8812c9 100644
--- a/samples/kitchensink/src/com/google/gwt/sample/kitchensink/public/KitchenSink.css
+++ b/samples/kitchensink/src/com/google/gwt/sample/kitchensink/public/KitchenSink.css
@@ -2,16 +2,16 @@
   background-color: white;
   color: black;
   font-family: Helvetica, Arial, sans-serif;
-	font-size: 10pt;
+  font-size: 10pt;
   margin: 0px 20px 20px 20px;
 }
 
 h2 {
-	font-weight: normal;
+  font-weight: normal;
 }
 
 table {
-	font-size: 100%;
+  font-size: 100%;
 }
 
 code {
@@ -114,7 +114,7 @@
 }
 
 .gwt-TabPanel {
-	margin-top: 4px;
+  margin-top: 4px;
 }
 
 .gwt-TabPanelBottom {
@@ -160,11 +160,11 @@
 }
 
 .gwt-TextBox-readonly {
-	color: #888;
+  color: #888;
 }
 
 .gwt-Tree {
-	background: white;
+  background: white;
 }
 
 .gwt-Tree .gwt-TreeItem {
@@ -319,12 +319,12 @@
 }
 
 .gwt-HorizontalSplitPanel {
-	border: 8px solid #C3D9FF;
+  border: 8px solid #C3D9FF;
 }
 
-.gwt-HorizontalSplitPanel .splitter {
-	background-color: #C3D9FF;
-	cursor: move;
+.gwt-HorizontalSplitPanel .hsplitter {
+  background-color: #C3D9FF;
+  cursor: move;
 }
 
 .gwt-HorizontalSplitPanel .left {
@@ -335,22 +335,22 @@
 }
 
 .gwt-VerticalSplitPanel .splitter {
-	background-color: #C3D9FF;
-	height: 8px;
-	cursor: move;
+  background-color: #C3D9FF;
+  height: 8px;
+  cursor: move;
 }
 
 .gwt-SuggestBoxPopup {
-	border: 2px solid #C3D9FF;
+  border: 2px solid #C3D9FF;
 }
 
 .gwt-SuggestBoxPopup .item {
-	padding: 2px;
+  padding: 2px;
 }
 
 .gwt-SuggestBoxPopup .item-selected {
-	background-color: #C3D9FF;
-	padding: 2px;
+  background-color: #C3D9FF;
+  padding: 2px;
 }
 
 /* -------------------------------------------------------------------------- */
@@ -360,7 +360,7 @@
 }
 
 .ks-Info {
-	color: white;
+  color: white;
   padding: 20px 10px 20px 40px;
   margin-bottom: 10px;
 }
@@ -369,32 +369,32 @@
 }
 
 .ks-List .gwt-Image {
-	position: relative;
-	top: 8px;
+  position: relative;
+  top: 8px;
 }
 
 .ks-List .ks-SinkItem a {
-	text-decoration: none;
-	color: white;
+  text-decoration: none;
+  color: white;
 }
 
 .ks-List .ks-SinkItem-selected a {
-	text-decoration: none;
-	color: white;
+  text-decoration: none;
+  color: white;
 }
 
 .ks-List .ks-FirstSinkItem a {
-	text-decoration: none;
-	color: white;
+  text-decoration: none;
+  color: white;
 }
 
 .ks-List .ks-FirstSinkItem-selected a {
-	text-decoration: none;
-	color: white;
+  text-decoration: none;
+  color: white;
 }
 
 .ks-List .ks-SinkItem {
-	background: #c6cab7;
+  background: #c6cab7;
   padding: 4px;
   padding-left: 16px;
   padding-right: 16px;
@@ -405,7 +405,7 @@
 }
 
 .ks-List .ks-SinkItem-selected {
-	background: #c6cab7;
+  background: #c6cab7;
   padding: 4px;
   padding-left: 16px;
   padding-right: 16px;
@@ -414,7 +414,7 @@
 }
 
 .ks-List .ks-FirstSinkItem {
-	background: #c6cab7 url(images/corner.gif) no-repeat top left;
+  background: #c6cab7 url(images/corner.gif) no-repeat top left;
   padding: 4px;
   padding-left: 16px;
   padding-right: 16px;
@@ -425,7 +425,7 @@
 }
 
 .ks-List .ks-FirstSinkItem-selected {
-	background: #c3d9ff url(images/corner.gif) no-repeat top left;
+  background: #c3d9ff url(images/corner.gif) no-repeat top left;
   padding: 4px;
   padding-left: 16px;
   padding-right: 16px;
diff --git a/user/src/com/google/gwt/user/SplitPanel.gwt.xml b/user/src/com/google/gwt/user/SplitPanel.gwt.xml
index 4ea6171..a0bb8db 100644
--- a/user/src/com/google/gwt/user/SplitPanel.gwt.xml
+++ b/user/src/com/google/gwt/user/SplitPanel.gwt.xml
@@ -14,14 +14,29 @@
   the License.
 -->
 <module>
-	<inherits name="com.google.gwt.user.User"/>
-	
-	<replace-with class="com.google.gwt.user.client.ui.HorizontalSplitPanel.Impl">
-		<when-type-is class="com.google.gwt.user.client.ui.HorizontalSplitPanel.Impl"/>
-	</replace-with>
+  <inherits name="com.google.gwt.user.User"/>
 
-	<replace-with class="com.google.gwt.user.client.ui.HorizontalSplitPanel.ImplIE6">
-		<when-type-is class="com.google.gwt.user.client.ui.HorizontalSplitPanel.Impl"/>
-		<when-property-is name="user.agent" value="ie6"/>
-	</replace-with>
+  <replace-with class="com.google.gwt.user.client.ui.HorizontalSplitPanel.Impl">
+    <when-type-is class="com.google.gwt.user.client.ui.HorizontalSplitPanel.Impl"/>
+  </replace-with>
+
+  <replace-with class="com.google.gwt.user.client.ui.HorizontalSplitPanel.ImplIE6">
+    <when-type-is class="com.google.gwt.user.client.ui.HorizontalSplitPanel.Impl"/>
+    <when-property-is name="user.agent" value="ie6"/>
+  </replace-with>
+
+  <replace-with class="com.google.gwt.user.client.ui.HorizontalSplitPanel.ImplSafari">
+    <when-type-is class="com.google.gwt.user.client.ui.HorizontalSplitPanel.Impl"/>
+    <when-property-is name="user.agent" value="safari"/>
+  </replace-with>
+  
+  <replace-with class="com.google.gwt.user.client.ui.VerticalSplitPanel.Impl">
+    <when-type-is class="com.google.gwt.user.client.ui.VerticalSplitPanel.Impl"/>
+  </replace-with>
+
+  <replace-with class="com.google.gwt.user.client.ui.VerticalSplitPanel.ImplIE6">
+    <when-type-is class="com.google.gwt.user.client.ui.VerticalSplitPanel.Impl"/>
+    <when-property-is name="user.agent" value="ie6"/>
+  </replace-with>
+
 </module>
diff --git a/user/src/com/google/gwt/user/client/ui/HorizontalSplitPanel.java b/user/src/com/google/gwt/user/client/ui/HorizontalSplitPanel.java
index 9c3f2a7..cd8a2b9 100644
--- a/user/src/com/google/gwt/user/client/ui/HorizontalSplitPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/HorizontalSplitPanel.java
@@ -20,6 +20,7 @@
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.DeferredCommand;
 import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Timer;
 
 /**
  * A panel that arranges two widgets in a single horizontal row and allows the
@@ -34,8 +35,6 @@
  * <h3>CSS Style Rules</h3>
  * <ul class='css'>
  * <li>.gwt-HorizontalSplitPanel { the panel itself }</li>
- * <li>.gwt-HorizontalSplitPanel left { the left container }</li>
- * <li>.gwt-HorizontalSplitPanel right { the right container }</li>
  * <li>.gwt-HorizontalSplitPanel hsplitter { the splitter }</li>
  * </ul>
  */
@@ -45,40 +44,84 @@
    * The standard implementation for horizontal split panels.
    */
   private static class Impl {
-    private static void snapToAllEdges(Element elem) {
-      enableAbsolutePositon(elem);
-      DOM.setStyleAttribute(elem, "left", "0");
-      DOM.setStyleAttribute(elem, "right", "0");
-      DOM.setStyleAttribute(elem, "top", "0");
-      DOM.setStyleAttribute(elem, "bottom", "0");
+    private static void expandToFitParentHorizontally(Element elem) {
+      addAbsolutePositoning(elem);
+      final String zeroSize = "0px";
+      setTop(elem, zeroSize);
+      setBottom(elem, zeroSize);
     }
 
-    private static void snapToTopAndBottomEdges(Element elem) {
-      enableAbsolutePositon(elem);
-      DOM.setStyleAttribute(elem, "top", "0");
-      DOM.setStyleAttribute(elem, "bottom", "0");
-    }
+    protected HorizontalSplitPanel panel;
 
     public void init(HorizontalSplitPanel panel) {
+      this.panel = panel;
+
       DOM.setStyleAttribute(panel.getElement(), "position", "relative");
-      snapToAllEdges(panel.rootWrapper);
-      snapToAllEdges(panel.getElement(LEFT));
-      snapToAllEdges(panel.getElement(RIGHT));
 
-      snapToTopAndBottomEdges(panel.leftWrapper);
-      snapToTopAndBottomEdges(panel.rightWrapper);
-      snapToTopAndBottomEdges(panel.getSplitElement());
+      final Element rightElem = panel.getElement(RIGHT);
 
-      // This ensures that any overflow is hidden on the right side of the
-      // panel. This can happen when the right side panel is smaller than
-      // the borders on its child.
-      addElementClipping(panel.rightWrapper);
+      expandToFitParentHorizontally(panel.getElement(LEFT));
+      expandToFitParentHorizontally(rightElem);
+      expandToFitParentHorizontally(panel.getSplitElement());
+
+      expandToFitParentUsingCssOffsets(panel.container);
+
+      // Snap the right wrapper to the right side.
+      setRight(rightElem, "0px");
     }
 
-    public void onAttach(HorizontalSplitPanel panel) {
+    public void onAttach() {
     }
 
-    public void onDetach(HorizontalSplitPanel panel) {
+    public void onDetach() {
+    }
+
+    public void onSplitResize(int px) {
+      setSplitPosition(px);
+    }
+
+    public void setSplitPosition(int px) {
+      final Element splitElem = panel.getSplitElement();
+
+      final int rootElemWidth = getOffsetWidth(panel.container);
+      final int splitElemWidth = getOffsetWidth(splitElem);
+
+      // This represents an invalid state where layout is incomplete. This
+      // typically happens before DOM attachment, but I leave it here as a
+      // precaution because negative width/height style attributes produce
+      // errors on IE.
+      if (rootElemWidth < splitElemWidth) {
+        return;
+      }
+
+      // Compute the new right side width.
+      int newRightWidth = rootElemWidth - px - splitElemWidth;
+
+      // Constrain the dragging to the physical size of the panel.
+      if (px < 0) {
+        px = 0;
+        newRightWidth = rootElemWidth - splitElemWidth;
+      } else if (newRightWidth < 0) {
+        px = rootElemWidth - splitElemWidth;
+        newRightWidth = 0;
+      }
+
+      final Element rightElem = panel.getElement(RIGHT);
+
+      // Set the width of the left side.
+      setWidth(panel.getElement(LEFT), px + "px");
+
+      // Move the splitter to the right edge of the left element.
+      setLeft(splitElem, px + "px");
+
+      // Move the right element to the right of the splitter.
+      setLeft(rightElem, (px + splitElemWidth) + "px");
+
+      updateRightWidth(rightElem, newRightWidth);
+    }
+
+    public void updateRightWidth(Element rightElem, int newRightWidth) {
+      // Update is handled by CSS.
     }
   }
 
@@ -86,112 +129,110 @@
    * The IE6 implementation for horizontal split panels.
    */
   private static class ImplIE6 extends Impl {
-    private static final String FULLSIZE = "100%";
 
-    private static native void addResizeListener(HorizontalSplitPanel panel) /*-{
-      var root = panel.@com.google.gwt.user.client.ui.HorizontalSplitPanel::rootWrapper;
-      root.onresize = function() {
-        @com.google.gwt.user.client.ui.HorizontalSplitPanel$ImplIE6::onResize(Lcom/google/gwt/user/client/ui/HorizontalSplitPanel;)(panel);
-      };
-    }-*/;
+    private boolean isResizeInProgress = false;
 
-    private static void onResize(HorizontalSplitPanel panel) {
-      final String height = getOffsetHeight(panel.rootWrapper) + "px";
-      setHeight(panel.rightWrapper, height);
-      setHeight(panel.getSplitElement(), height);
-      setHeight(panel.leftWrapper, height);
-    }
-
-    private static void snapToAllEdges(Element elem) {
-      enableAbsolutePositon(elem);
-      setWidth(elem, FULLSIZE);
-      setHeight(elem, FULLSIZE);
-    }
-
-    private static void snapToTopAndBottomEdges(Element elem) {
-      enableAbsolutePositon(elem);
-      setHeight(elem, FULLSIZE);
-    }
+    private int splitPosition = 0;
 
     public void init(HorizontalSplitPanel panel) {
+      this.panel = panel;
+
       final Element elem = panel.getElement();
       // Prevents inherited text-align settings from interfering with the
       // panel's layout.
       DOM.setStyleAttribute(elem, "textAlign", "left");
       DOM.setStyleAttribute(elem, "position", "relative");
 
-      enableAbsolutePositon(panel.rightWrapper);
-      enableAbsolutePositon(panel.leftWrapper);
+      /*
+       * Technically, these are snapped to the top and bottom, but IE doesn't
+       * provide a reliable way to make that happen, so a resize listener is
+       * wired up to control the height of these elements.
+       */
+      addAbsolutePositoning(panel.getElement(LEFT));
+      addAbsolutePositoning(panel.getElement(RIGHT));
+      addAbsolutePositoning(panel.getSplitElement());
 
-      snapToAllEdges(panel.rootWrapper);
-      snapToAllEdges(panel.getElement(LEFT));
-      snapToAllEdges(panel.getElement(RIGHT));
-      snapToTopAndBottomEdges(panel.getSplitElement());
+      expandToFitParentUsingPercentages(panel.container);
     }
 
-    public void onAttach(HorizontalSplitPanel panel) {
-      addResizeListener(panel);
-      onResize(panel);
+    public void onAttach() {
+      addResizeListener(panel.container);
+      onResize();
     }
 
-    public void onDetach(HorizontalSplitPanel panel) {
-      DOM.setElementAttribute(panel.rootWrapper, "onresize", null);
+    public void onDetach() {
+      DOM.setElementAttribute(panel.container, "onresize", null);
+    }
+
+    public void onSplitResize(int px) {
+      final int resizeUpdatePeriod = 20; // ms
+      if (!isResizeInProgress) {
+        isResizeInProgress = true;
+        new Timer() {
+          public void run() {
+            setSplitPosition(splitPosition);
+            isResizeInProgress = false;
+          }
+        }.schedule(resizeUpdatePeriod);
+      }
+      splitPosition = px;
+    }
+
+    public void updateRightWidth(Element rightElem, int newRightWidth) {
+      setWidth(rightElem, newRightWidth + "px");
+    }
+
+    private native void addResizeListener(Element container) /*-{
+      var self = this;
+      container.onresize = function() {
+        self.@com.google.gwt.user.client.ui.HorizontalSplitPanel$ImplIE6::onResize()();
+      };
+    }-*/;
+
+    private void onResize() {
+      final Element leftElem = panel.getElement(LEFT);
+      final Element rightElem = panel.getElement(RIGHT);
+
+      final String height = getOffsetHeight(panel.container) + "px";
+      setHeight(rightElem, height);
+      setHeight(panel.getSplitElement(), height);
+      setHeight(leftElem, height);
+      setSplitPosition(getOffsetWidth(leftElem));
     }
   }
 
   /**
-   * Constants to provide more readable calls to {@link #getElement()} and
+   * The Safari implementation which owes its existence entirely to a single
+   * WebKit bug: http://bugs.webkit.org/show_bug.cgi?id=9137.
+   */
+  private static class ImplSafari extends Impl {
+    public void init(HorizontalSplitPanel panel) {
+      this.panel = panel;
+      final String fullSize = "100%";
+      super.init(panel);
+      setHeight(panel.container, fullSize);
+      setHeight(panel.getElement(LEFT), fullSize);
+      setHeight(panel.getElement(RIGHT), fullSize);
+      setHeight(panel.getSplitElement(), fullSize);
+    }
+  }
+
+  /**
+   * Constant makes for readable calls to {@link #getElement(int)} and
    * {@link #getWidget(int)}.
    */
   private static final int LEFT = 0;
 
+  /**
+   * Constant makes for readable calls to {@link #getElement(int)} and
+   * {@link #getWidget(int)}.
+   */
   private static final int RIGHT = 1;
 
-  private static final Impl impl = (Impl) GWT.create(Impl.class);
+  // A style-free element to serve as the root container.
+  private final Element container;
 
-  private static void enableAbsolutePositon(Element elem) {
-    DOM.setStyleAttribute(elem, "position", "absolute");
-  }
-
-  private static final int getClientWidth(final Element elem) {
-    return DOM.getElementPropertyInt(elem, "clientWidth");
-  }
-
-  private static final int getOffsetHeight(Element elem) {
-    return DOM.getElementPropertyInt(elem, "offsetHeight");
-  }
-
-  private static final int getOffsetWidth(final Element elem) {
-    return DOM.getElementPropertyInt(elem, "offsetWidth");
-  }
-
-  private static final void setHeight(Element elem, String size) {
-    DOM.setStyleAttribute(elem, "height", size);
-  }
-
-  private static final void setLeft(Element elem, String size) {
-    DOM.setStyleAttribute(elem, "left", size);
-  }
-
-  private static final void setWidth(Element elem, String size) {
-    DOM.setStyleAttribute(elem, "width", size);
-  }
-
-  /**
-   * DOM elements needed to support splitter dragging. The underlying DOM
-   * structure is:
-   * 
-   * <pre>
-   *   div
-   *     div (rootWrapper)
-   *       div (leftWrapper)
-   *         div (getElement(LEFT))
-   *       div (getSplitElement())
-   *       div (rightWrapper)
-   *         div (getElement(RIGHT))
-   * </pre>
-   */
-  private final Element rootWrapper, leftWrapper, rightWrapper;
+  private final Impl impl = (Impl) GWT.create(Impl.class);
 
   /**
    * If the split position is set while the split panel is not attached, save it
@@ -203,17 +244,21 @@
 
   private int initialLeftWidth;
 
+  public HorizontalSplitPanel() {
+    this(
+        (HorizontalSplitPanelImages) GWT.create(HorizontalSplitPanelImages.class));
+  }
+
   /**
    * Creates an empty horizontal split panel.
    */
-  public HorizontalSplitPanel() {
-    super(DOM.createDiv(), DOM.createDiv(), DOM.createDiv(), DOM.createDiv());
+  public HorizontalSplitPanel(HorizontalSplitPanelImages images) {
+    super(DOM.createDiv(), DOM.createDiv(), preventBoxStyles(DOM.createDiv()),
+        preventBoxStyles(DOM.createDiv()));
 
-    rootWrapper = preventElementBoxStyles(DOM.createDiv());
-    leftWrapper = preventElementBoxStyles(DOM.createDiv());
-    rightWrapper = preventElementBoxStyles(DOM.createDiv());
+    container = preventBoxStyles(DOM.createDiv());
 
-    buildDOM();
+    buildDOM(images.horizontalSplitPanelThumb());
 
     setStyleName("gwt-HorizontalSplitPanel");
 
@@ -263,21 +308,16 @@
 
   public final void setSplitPosition(String pos) {
     lastSplitPosition = pos;
-    final Element leftElem = leftWrapper;
+    final Element leftElem = getElement(LEFT);
     setWidth(leftElem, pos);
-    setSplitPosition(getOffsetWidth(leftElem));
-  }
-
-  public void setWidth(String width) {
-    super.setWidth(width);
-    setSplitPosition(getOffsetWidth(leftWrapper));
+    impl.setSplitPosition(getOffsetWidth(leftElem));
   }
 
   protected void onLoad() {
-    // If the split position has been changed while detached, apply the change
-    impl.onAttach(this);
+    impl.onAttach();
 
     /*
+     * If the split position has been changed while detached, apply the change.
      * Set the position realizing that it might not work until after layout
      * runs. This first call is simply to try to avoid a jitter effect if
      * possible.
@@ -291,81 +331,40 @@
   }
 
   protected void onUnload() {
-    impl.onDetach(this);
+    impl.onDetach();
   }
 
   final void onSplitterResize(int x, int y) {
-    // Move the split position by the appropriate offset.
-    setSplitPosition(initialLeftWidth + x - initialThumbPos);
+    impl.onSplitResize(initialLeftWidth + x - initialThumbPos);
   }
 
   final void onSplitterResizeStarted(int x, int y) {
     initialThumbPos = x;
-    initialLeftWidth = getClientWidth(leftWrapper);
+    initialLeftWidth = getOffsetWidth(getElement(LEFT));
   }
 
-  private void buildDOM() {
+  private void buildDOM(AbstractImagePrototype thumbImage) {
     final Element leftDiv = getElement(LEFT);
     final Element rightDiv = getElement(RIGHT);
     final Element splitDiv = getSplitElement();
 
-    DOM.appendChild(getElement(), rootWrapper);
+    DOM.appendChild(getElement(), container);
 
-    DOM.appendChild(rootWrapper, leftWrapper);
-    DOM.appendChild(rootWrapper, splitDiv);
-    DOM.appendChild(rootWrapper, rightWrapper);
+    DOM.appendChild(container, leftDiv);
+    DOM.appendChild(container, splitDiv);
+    DOM.appendChild(container, rightDiv);
 
-    DOM.appendChild(leftWrapper, leftDiv);
-    DOM.appendChild(rightWrapper, rightDiv);
+    /*
+     * Sadly, this is the only way I've found to get vertical centering in this
+     * case. The usually CSS hacks (display: table-cell, vertical-align: middle)
+     * don't work in an absolute positioned DIV.
+     */
+    DOM.setInnerHTML(splitDiv,
+        "<table class='hsplitter' height='100%' cellpadding='0' "
+            + "cellspacing='0'><tr><td align='center' valign='middle'>"
+            + thumbImage.getHTML());
 
-    DOM.setInnerHTML(splitDiv, "&nbsp;");
-
-    addElementScrolling(leftDiv);
-    addElementScrolling(rightDiv);
-
-    setElementClassname(leftDiv, "left");
-    setElementClassname(splitDiv, "hsplitter");
-    setElementClassname(rightDiv, "right");
-  }
-
-  private final void setSplitPosition(int px) {
-    final Element splitElem = getSplitElement();
-
-    final int rootElemWidth = getOffsetWidth(rootWrapper);
-    final int splitElemWidth = getOffsetWidth(splitElem);
-
-    // This represents an invalid state where layout is incomplete. This
-    // typically happens before DOM attachment, but I leave it here as a
-    // precaution because negative width/height style attributes produce
-    // errors on IE.
-    if (rootElemWidth < splitElemWidth) {
-      return;
-    }
-
-    // Compute the new right side width.
-    int newRightWidth = rootElemWidth - px - splitElemWidth;
-
-    // Constrain the dragging to the physical size of the panel.
-    if (px < 0) {
-      px = 0;
-      newRightWidth = rootElemWidth - splitElemWidth;
-    } else if (newRightWidth < 0) {
-      px = rootElemWidth - splitElemWidth;
-      newRightWidth = 0;
-    }
-
-    final Element rightElem = rightWrapper;
-
-    // Set the width of the left side.
-    setWidth(leftWrapper, px + "px");
-
-    // Move the splitter to the right edge of the left element.
-    setLeft(splitElem, px + "px");
-
-    // Move the right element to the right of the splitter.
-    setLeft(rightElem, (px + splitElemWidth) + "px");
-
-    // Update the right element's width.
-    setWidth(rightElem, newRightWidth + "px");
+    addScrolling(leftDiv);
+    addScrolling(rightDiv);
   }
 }
diff --git a/user/src/com/google/gwt/user/client/ui/HorizontalSplitPanelImages.java b/user/src/com/google/gwt/user/client/ui/HorizontalSplitPanelImages.java
new file mode 100644
index 0000000..dd2b959
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/HorizontalSplitPanelImages.java
@@ -0,0 +1,30 @@
+/*
+ * 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.google.gwt.user.client.ui;
+
+/**
+ * An {@link ImageBundle} that provides images for
+ * {@link com.google.gwt.user.client.ui.HorizontalSplitPanel}.
+ */
+public interface HorizontalSplitPanelImages extends ImageBundle {
+
+  /**
+   * An image representing the drag thumb.
+   * 
+   * @gwt.resource splitPanelThumb.png
+   */
+  AbstractImagePrototype horizontalSplitPanelThumb();
+}
diff --git a/user/src/com/google/gwt/user/client/ui/SplitPanel.java b/user/src/com/google/gwt/user/client/ui/SplitPanel.java
index 99f954f..bc30e92 100644
--- a/user/src/com/google/gwt/user/client/ui/SplitPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/SplitPanel.java
@@ -28,11 +28,20 @@
 abstract class SplitPanel extends Panel {
 
   /**
+   * Sets an elements positioning to absolute.
+   * 
+   * @param elem the element
+   */
+  static void addAbsolutePositoning(Element elem) {
+    DOM.setStyleAttribute(elem, "position", "absolute");
+  }
+
+  /**
    * Adds clipping to an element.
    * 
    * @param elem the element
    */
-  static final void addElementClipping(final Element elem) {
+  static final void addClipping(final Element elem) {
     DOM.setStyleAttribute(elem, "overflow", "hidden");
   }
 
@@ -41,19 +50,74 @@
    * 
    * @param elem the element
    */
-  static final void addElementScrolling(final Element elem) {
+  static final void addScrolling(final Element elem) {
     DOM.setStyleAttribute(elem, "overflow", "auto");
   }
 
   /**
-   * Adds zero or none css values for padding, margin and border to prevent
-   * stylesheet overrides. Returns the element for convienence to support
+   * Sizes and element to consume the full area of its parent using the CSS
+   * properties left, right, top, and bottom. This method is used for all
+   * browsers except IE6/7.
+   * 
+   * @param elem the element
+   */
+  static final void expandToFitParentUsingCssOffsets(Element elem) {
+    final String zeroSize = "0px";
+
+    addAbsolutePositoning(elem);
+    setLeft(elem, zeroSize);
+    setRight(elem, zeroSize);
+    setTop(elem, zeroSize);
+    setBottom(elem, zeroSize);
+  }
+
+  /**
+   * Sizes an element to consume the full areas of its parent using 100% width
+   * and height. This method is used on IE6/7 where CSS offsets don't work
+   * reliably.
+   * 
+   * @param elem the element
+   */
+  static final void expandToFitParentUsingPercentages(Element elem) {
+    final String zeroSize = "0px";
+    final String fullSize = "100%";
+
+    addAbsolutePositoning(elem);
+    setTop(elem, zeroSize);
+    setLeft(elem, zeroSize);
+    setWidth(elem, fullSize);
+    setHeight(elem, fullSize);
+  }
+
+  /**
+   * Returns the offsetHeight element property.
+   * 
+   * @param elem the element
+   * @return the offsetHeight property
+   */
+  static final int getOffsetHeight(Element elem) {
+    return DOM.getElementPropertyInt(elem, "offsetHeight");
+  }
+
+  /**
+   * Returns the offsetWidth element property.
+   * 
+   * @param elem the element
+   * @return the offsetWidth property
+   */
+  static final int getOffsetWidth(Element elem) {
+    return DOM.getElementPropertyInt(elem, "offsetWidth");
+  }
+
+  /**
+   * Adds zero or none CSS values for padding, margin and border to prevent
+   * stylesheet overrides. Returns the element for convenience to support
    * builder pattern.
    * 
    * @param elem the element
    * @return the element
    */
-  static final Element preventElementBoxStyles(final Element elem) {
+  static final Element preventBoxStyles(final Element elem) {
     DOM.setIntStyleAttribute(elem, "padding", 0);
     DOM.setIntStyleAttribute(elem, "margin", 0);
     DOM.setStyleAttribute(elem, "border", "none");
@@ -61,12 +125,13 @@
   }
 
   /**
-   * Adds zero size padding to an element.
+   * Convenience method to set bottom offset of an element.
    * 
-   * @param elem the element.
+   * @param elem the element
+   * @param size a CSS length value for bottom
    */
-  static final void preventElementPadding(final Element elem) {
-    DOM.setStyleAttribute(elem, "padding", "0");
+  static void setBottom(Element elem, String size) {
+    DOM.setStyleAttribute(elem, "bottom", size);
   }
 
   /**
@@ -75,11 +140,60 @@
    * @param elem the element
    * @param className the class name
    */
-  static final void setElementClassname(final Element elem,
-      final String className) {
+  static final void setClassname(final Element elem, final String className) {
     DOM.setElementProperty(elem, "className", className);
   }
 
+  /**
+   * Convenience method to set the height of an element.
+   * 
+   * @param elem the element
+   * @param height a CSS length value for the height
+   */
+  static final void setHeight(Element elem, String height) {
+    DOM.setStyleAttribute(elem, "height", height);
+  }
+
+  /**
+   * Convenience method to set the left offset of an element.
+   * 
+   * @param elem the element
+   * @param height a CSS length value for left
+   */
+  static final void setLeft(Element elem, String left) {
+    DOM.setStyleAttribute(elem, "left", left);
+  }
+
+  /**
+   * Convenience method to set the right offset of an element.
+   * 
+   * @param elem the element
+   * @param height a CSS length value for right
+   */
+  static final void setRight(Element elem, String right) {
+    DOM.setStyleAttribute(elem, "right", right);
+  }
+
+  /**
+   * Convenience method to set the top offset of an element.
+   * 
+   * @param elem the element
+   * @param height a CSS length value for top
+   */
+  static final void setTop(Element elem, String top) {
+    DOM.setStyleAttribute(elem, "top", top);
+  }
+
+  /**
+   * Convenience method to set the width of an element.
+   * 
+   * @param elem the element
+   * @param height a CSS length value for the width
+   */
+  static final void setWidth(Element elem, String width) {
+    DOM.setStyleAttribute(elem, "width", width);
+  }
+
   // The enclosed widgets.
   private final Widget[] widgets = new Widget[2];
 
@@ -181,7 +295,7 @@
    * Moves the position of the splitter.
    * 
    * @param size the new size of the left region in CSS units (e.g. "10px",
-   *          "1em")
+   *            "1em")
    */
   public abstract void setSplitPosition(String size);
 
diff --git a/user/src/com/google/gwt/user/client/ui/VerticalSplitPanel.java b/user/src/com/google/gwt/user/client/ui/VerticalSplitPanel.java
index c8679e6..0b037aa 100644
--- a/user/src/com/google/gwt/user/client/ui/VerticalSplitPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/VerticalSplitPanel.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -20,128 +20,260 @@
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.DeferredCommand;
 import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Timer;
 
 /**
  * A panel that arranges two widgets in a single vertical column and allows the
  * user to interactively change the proportion of the height dedicated to each
  * of the two widgets. Widgets contained within a
  * <code>VerticalSplitterPanel</code> will be automatically decorated with
- * scrollbars when neccessary.
- *
+ * scrollbars when necessary.
+ * 
  * <p>
  * <img class='gallery' src='VerticalSplitPanel.png'/>
  * </p>
- *
+ * 
  * <h3>CSS Style Rules</h3>
  * <ul class='css'>
  * <li>.gwt-VerticalSplitPanel { the panel itself }</li>
- * <li>.gwt-VerticalSplitPanel top { the top container }</li>
- * <li>.gwt-VerticalSplitPanel bottom { the bottom container }</li>
  * <li>.gwt-VerticalSplitPanel vsplitter { the splitter }</li>
  * </ul>
  */
 public final class VerticalSplitPanel extends SplitPanel {
 
   /**
-   * Provides different implementations for retrieving an element's height. The
-   * default binding is based on DOM1 clientHeight.
+   * Provides a base implementation for splitter layout that relies on CSS
+   * positioned layout.
    */
   private static class Impl {
-    /**
-     * Gets an element's height.
-     *
-     * @param elem an element
-     * @return the height of the element
-     */
-    protected int getElementHeight(Element elem) {
-      return DOM.getElementPropertyInt(elem, "clientHeight");
+    private static void expandToFitParentHorizontally(Element elem) {
+      addAbsolutePositoning(elem);
+      DOM.setStyleAttribute(elem, "left", "0");
+      DOM.setStyleAttribute(elem, "right", "0");
+    }
+
+    protected VerticalSplitPanel panel;
+
+    public void init(VerticalSplitPanel panel) {
+      this.panel = panel;
+
+      DOM.setStyleAttribute(panel.getElement(), "position", "relative");
+
+      final Element topElem = panel.getElement(TOP);
+      final Element bottomElem = panel.getElement(BOTTOM);
+
+      expandToFitParentHorizontally(topElem);
+      expandToFitParentHorizontally(bottomElem);
+      expandToFitParentHorizontally(panel.getSplitElement());
+
+      expandToFitParentUsingCssOffsets(panel.container);
+
+      // Snap the bottom wrapper to the bottom side.
+      DOM.setStyleAttribute(bottomElem, "bottom", "0");
+    }
+
+    public void onAttach() {
+    }
+
+    public void onDetach() {
+    }
+
+    public void onSplitterResize(int px) {
+      setSplitPosition(px);
+    }
+
+    public void setSplitPosition(int px) {
+      final Element splitElem = panel.getSplitElement();
+
+      final int rootElemHeight = getOffsetHeight(panel.container);
+      final int splitElemHeight = getOffsetHeight(splitElem);
+
+      if (rootElemHeight < splitElemHeight) {
+        return;
+      }
+
+      int newBottomHeight = rootElemHeight - px - splitElemHeight;
+      if (px < 0) {
+        px = 0;
+        newBottomHeight = rootElemHeight - splitElemHeight;
+      } else if (newBottomHeight < 0) {
+        px = rootElemHeight - splitElemHeight;
+        newBottomHeight = 0;
+      }
+
+      updateElements(panel.getElement(TOP), splitElem,
+          panel.getElement(BOTTOM), px, px + splitElemHeight, newBottomHeight);
+    }
+
+    protected void updateElements(Element topElem, Element splitElem,
+        Element bottomElem, int topHeight, int bottomTop, int bottomHeight) {
+      setHeight(topElem, topHeight + "px");
+
+      setTop(splitElem, topHeight + "px");
+
+      setTop(bottomElem, bottomTop + "px");
+
+      // bottom's height is handled by CSS.
     }
   }
 
   /**
-   * Provides an implementation for IE6 based on Element.getBoundingClientRect.
+   * Provides an implementation for IE6/7 that relies on 100% length in CSS.
    */
   private static class ImplIE6 extends Impl {
-    protected native int getElementHeight(Element elem) /*-{
-      var box = elem.getBoundingClientRect();
-      return box.bottom - box.top;
-    }-*/;
+
+    private static void expandToFitParentHorizontally(Element elem) {
+      addAbsolutePositoning(elem);
+      setLeft(elem, "0");
+      setWidth(elem, "100%");
+    }
+
+    private boolean isResizeInProgress = false;
+
+    private int splitPosition;
+
+    private boolean isTopHidden = false, isBottomHidden = false;
+
+    public void init(VerticalSplitPanel panel) {
+      this.panel = panel;
+
+      final Element elem = panel.getElement();
+
+      // Prevents inherited text-align settings from interfering with the
+      // panel's layout.
+      DOM.setStyleAttribute(elem, "textAlign", "left");
+      DOM.setStyleAttribute(elem, "position", "relative");
+
+      final Element topElem = panel.getElement(TOP);
+      final Element bottomElem = panel.getElement(BOTTOM);
+
+      expandToFitParentHorizontally(topElem);
+      expandToFitParentHorizontally(bottomElem);
+      expandToFitParentHorizontally(panel.getSplitElement());
+
+      expandToFitParentUsingPercentages(panel.container);
+    }
+
+    public void onAttach() {
+      addResizeListener(panel.container);
+      onResize();
+    }
+
+    public void onDetach() {
+      DOM.setElementProperty(panel.container, "onresize", null);
+    }
+
+    public void onSplitterResize(int px) {
+      /*
+       * IE6/7 has event priority issues that will prevent the repaints from
+       * happening quickly enough causing the interaction to seem unresponsive.
+       * The following is simply a poor man's mouse event coalescing.
+       */
+      final int resizeUpdatePeriod = 20; // ms
+      if (!isResizeInProgress) {
+        isResizeInProgress = true;
+        new Timer() {
+          public void run() {
+            setSplitPosition(splitPosition);
+            isResizeInProgress = false;
+          }
+        }.schedule(resizeUpdatePeriod);
+      }
+      splitPosition = px;
+    }
+
+    protected void updateElements(Element topElem, Element splitElem,
+        Element bottomElem, int topHeight, int bottomTop, int bottomHeight) {
+      /*
+       * IE6/7 has a quirk where a zero height element with non-zero height
+       * children will expand larger than 100%. To prevent this, the width is
+       * explicitly set to zero when height is zero.
+       */
+      if (topHeight == 0) {
+        setWidth(topElem, "0px");
+        isTopHidden = true;
+      } else if (isTopHidden) {
+        setWidth(topElem, "100%");
+        isTopHidden = false;
+      }
+
+      if (bottomHeight == 0) {
+        setWidth(bottomElem, "0px");
+        isBottomHidden = true;
+      } else if (isBottomHidden) {
+        setWidth(bottomElem, "100%");
+        isBottomHidden = false;
+      }
+
+      super.updateElements(topElem, splitElem, bottomElem, topHeight,
+          bottomTop, bottomHeight);
+
+      // IE6/7 cannot update properly with CSS alone.
+      setHeight(bottomElem, bottomHeight + "px");
+    }
+
+    private native void addResizeListener(Element container) /*-{
+         var self = this;
+         container.onresize = function() {
+           self.@com.google.gwt.user.client.ui.VerticalSplitPanel$ImplIE6::onResize()();
+         };
+      }-*/;
+
+    private void onResize() {
+      setSplitPosition(getOffsetHeight(panel.getElement(TOP)));
+    }
   }
 
+  /**
+   * Constant makes for readable calls to {@link #getElement(int)} and
+   * {@link #getWidget(int)}.
+   */
   private static final int TOP = 0;
 
+  /**
+   * Constant makes for readable calls to {@link #getElement(int)} and
+   * {@link #getWidget(int)}.
+   */
   private static final int BOTTOM = 1;
 
-  private static final Impl impl = (Impl) GWT.create(Impl.class);
-
-  private static int getOffsetTop(Element elem) {
-    return DOM.getElementPropertyInt(elem, "offsetTop");
-  }
-
-  private static Element lockStyles(final Element elem) {
-    DOM.setIntStyleAttribute(elem, "height", 0);
-    return preventElementBoxStyles(elem);
-  }
-
-  private static void setHeight(Element elem, int px) {
-    DOM.setStyleAttribute(elem, "height", Math.max(0, px) + "px");
-  }
-
-  // Element is added below bottom container element to make it possible to
-  // infer the bottom element's height.
-  private final Element probeElem;
-
   // Captures the height of the top container when drag resizing starts.
   private int initialTopHeight = 0;
 
   // Captures the offset of a user's mouse pointer during drag resizing.
   private int initialThumbPos = 0;
 
+  // A style-free element to serve as the root container.
+  private final Element container;
+
+  private final Impl impl = (Impl) GWT.create(Impl.class);
+
+  private String lastSplitPosition;
+
+  public VerticalSplitPanel() {
+    this((VerticalSplitPanelImages) GWT.create(VerticalSplitPanelImages.class));
+  }
+
   /**
    * Creates an empty vertical split panel.
    */
-  public VerticalSplitPanel() {
-    super(DOM.createDiv(), DOM.createDiv(), DOM.createDiv(), DOM.createDiv());
+  public VerticalSplitPanel(VerticalSplitPanelImages images) {
+    super(DOM.createDiv(), DOM.createDiv(), preventBoxStyles(DOM.createDiv()),
+        preventBoxStyles(DOM.createDiv()));
 
-    final Element thisElem = getElement();
-    final Element splitElem = getSplitElement();
-    final Element topElem = getElement(TOP);
-    final Element bottomElem = getElement(BOTTOM);
-    probeElem = lockStyles(DOM.createDiv());
+    container = preventBoxStyles(DOM.createDiv());
 
-    DOM.appendChild(thisElem, topElem);
-    DOM.appendChild(thisElem, splitElem);
-    DOM.appendChild(thisElem, bottomElem);
-    DOM.appendChild(thisElem, probeElem);
-
-    addElementClipping(thisElem);
-    addElementScrolling(topElem);
-    addElementScrolling(bottomElem);
-
-    // Prevent padding on container elements.
-    preventElementPadding(thisElem);
-    preventElementPadding(topElem);
-    preventElementPadding(bottomElem);
-
-    setElementClassname(topElem, "top");
-    setElementClassname(splitElem, "vsplitter");
-    setElementClassname(bottomElem, "bottom");
+    buildDOM(images.verticalSplitPanelThumb());
 
     setStyleName("gwt-VerticalSplitPanel");
 
-    // Must wait on layout to do the initial layout.
-    DeferredCommand.addCommand(new Command() {
-      public void execute() {
-        updateBottomHeight();
-      }
-    });
+    impl.init(this);
 
     setSplitPosition("50%");
   }
 
   /**
    * Gets the widget in the bottom of the panel.
-   *
+   * 
    * @return the widget, <code>null</code> if there is not one
    */
   public final Widget getBottomWidget() {
@@ -150,7 +282,7 @@
 
   /**
    * Gets the widget in the top of the panel.
-   *
+   * 
    * @return the widget, <code>null</code> if there is not one
    */
   public final Widget getTopWidget() {
@@ -159,7 +291,7 @@
 
   /**
    * Sets the widget in the bottom of the panel.
-   *
+   * 
    * @param w the widget
    */
   public final void setBottomWidget(Widget w) {
@@ -168,101 +300,71 @@
 
   public void setHeight(String height) {
     super.setHeight(height);
-    updateBottomHeight();
   }
 
-  public final void setSplitPosition(String size) {
-    DOM.setStyleAttribute(getElement(TOP), "height", size);
-    updateBottomHeight();
+  public final void setSplitPosition(String pos) {
+    lastSplitPosition = pos;
+    final Element topElem = getElement(TOP);
+    setHeight(topElem, pos);
+    impl.setSplitPosition(getOffsetHeight(topElem));
   }
 
   /**
    * Sets the widget in the top of the panel.
-   *
+   * 
    * @param w the widget
    */
   public final void setTopWidget(Widget w) {
     setWidget(TOP, w);
   }
 
+  protected void onLoad() {
+    impl.onAttach();
+
+    /*
+     * Set the position realizing it might not work until after layout runs.
+     * This first call is simply to try to avoid a jitter effect if possible.
+     */
+    setSplitPosition(lastSplitPosition);
+    DeferredCommand.addCommand(new Command() {
+      public void execute() {
+        setSplitPosition(lastSplitPosition);
+      }
+    });
+  }
+
+  protected void onUnload() {
+    impl.onDetach();
+  }
+
   final void onSplitterResize(int x, int y) {
-    /*
-     * When dragging starts we record the thumb position and the current height
-     * of the top div. On each subsequent resize event, we compute how far the
-     * thumb has moved and adjust the top and bottom div by that offset.
-     */
-    final Element topElem = getElement(TOP);
-    final Element botElem = getElement(BOTTOM);
-
-    // Compute what the new top height should be.
-    final int newTopHeight = initialTopHeight + (y - initialThumbPos);
-    final int newBotHeight = impl.getElementHeight(botElem)
-        + impl.getElementHeight(topElem) - newTopHeight;
-
-    /*
-     * NOTE: The bottom must be adjusted before the top due to FF bug which
-     * leaves scrollbar artifacts in the overflow region.
-     * https://bugzilla.mozilla.org/show_bug.cgi?id=368190
-     */
-    if (newBotHeight < 0) {
-      setHeight(botElem, 0);
-      setHeight(topElem, newTopHeight + newBotHeight);
-    } else {
-      setHeight(botElem, newBotHeight);
-      setHeight(topElem, newTopHeight);
-    }
-
-    updateBottomHeight();
+    impl.onSplitterResize(initialTopHeight + y - initialThumbPos);
   }
 
   final void onSplitterResizeStarted(int x, int y) {
     initialThumbPos = y;
-    initialTopHeight = impl.getElementHeight(getElement(TOP));
+    initialTopHeight = getOffsetHeight(getElement(TOP));
   }
 
-  /**
-   * Updates to the height on the bottom div so that it remains within the outer
-   * container.
-   */
-  private void updateBottomHeight() {
-    final Element thisElem = getElement();
-    final Element bottomElem = getElement(BOTTOM);
+  private void buildDOM(AbstractImagePrototype thumb) {
+    final Element topDiv = getElement(TOP);
+    final Element bottomDiv = getElement(BOTTOM);
+    final Element splitDiv = getSplitElement();
+
+    DOM.appendChild(getElement(), container);
+
+    DOM.appendChild(container, topDiv);
+    DOM.appendChild(container, splitDiv);
+    DOM.appendChild(container, bottomDiv);
 
     /*
-     * This is the definitive check that tells us how far (in pixels) the height
-     * of the bottom div must change. We do this by comparing the clientHeight
-     * of the root div with the offsetTop of a probe div under the bottom div.
+     * The style name is placed on the table rather than splitElem to allow the
+     * splitter to be styled without interfering with layout.
      */
-    final int adjust = impl.getElementHeight(thisElem)
-        - (getOffsetTop(probeElem) - getOffsetTop(thisElem));
+    DOM.setInnerHTML(splitDiv, "<div class='vsplitter' "
+        + "style='text-align:center;'>" + thumb.getHTML() + "</div>");
 
-    /*
-     * In the case where the user is dragging the splitter, resizeTopBy should
-     * generally guess the right adjustment based on how far the top div was
-     * adjusted. So for the most common case, we find we do not need adjustment
-     * and exit here.
-     */
-    if (adjust == 0) {
-      return;
-    }
-
-    /*
-     * We don't know what margins and borders are in play on the bottom div, so
-     * we naively guess they are all zero, which would mean that the CSS height
-     * property will be equal to the clientHeight attribute. After we set the
-     * height in css, we take the difference between what we set and the
-     * reported clientHeight. If that is non-zero, it tells us how much to
-     * accomodate for margin, border and what not.
-     */
-    final int curHeight = impl.getElementHeight(bottomElem);
-    final int newHeight = curHeight + adjust;
-    setHeight(bottomElem, newHeight);
-    final int error = impl.getElementHeight(bottomElem) - newHeight;
-
-    if (error == 0) {
-      return;
-    }
-
-    setHeight(bottomElem, newHeight - error);
+    addScrolling(topDiv);
+    addScrolling(bottomDiv);
   }
 }
diff --git a/user/src/com/google/gwt/user/client/ui/VerticalSplitPanelImages.java b/user/src/com/google/gwt/user/client/ui/VerticalSplitPanelImages.java
new file mode 100644
index 0000000..409b6d3
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/VerticalSplitPanelImages.java
@@ -0,0 +1,30 @@
+/*
+ * 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.google.gwt.user.client.ui;
+
+/**
+ * An {@link ImageBundle} that provides images for
+ * {@link com.google.gwt.user.client.ui.HorizontalSplitPanel}.
+ */
+public interface VerticalSplitPanelImages extends ImageBundle {
+
+  /**
+   * An image representing the drag thumb.
+   * 
+   * @gwt.resource splitPanelThumb.png
+   */
+  AbstractImagePrototype verticalSplitPanelThumb();
+}
diff --git a/user/src/com/google/gwt/user/client/ui/splitPanelThumb.png b/user/src/com/google/gwt/user/client/ui/splitPanelThumb.png
new file mode 100644
index 0000000..d2ef025
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/splitPanelThumb.png
Binary files differ