- Adds UiBinder parsers for DockLayoutPanel and StackLayoutPanel.
- Changes DockLayoutPanel interface to be more direct
  (e.g., addNorth(w) rather than add(w, NORTH)).
- Changes Mail sample to use UiBinder throughout.
Review: http://gwt-code-reviews.appspot.com/68805

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6192 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/mail/src/com/google/gwt/sample/mail/Mail.gwt.xml b/samples/mail/src/com/google/gwt/sample/mail/Mail.gwt.xml
index 6cca2a8..9225cee 100644
--- a/samples/mail/src/com/google/gwt/sample/mail/Mail.gwt.xml
+++ b/samples/mail/src/com/google/gwt/sample/mail/Mail.gwt.xml
@@ -14,5 +14,6 @@
 
 <module rename-to="mail">
 	<inherits name='com.google.gwt.user.User'/>
+  <inherits name="com.google.gwt.uibinder.UiBinder" />
 	<entry-point class='com.google.gwt.sample.mail.client.Mail'/>
 </module>
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/AboutDialog.java b/samples/mail/src/com/google/gwt/sample/mail/client/AboutDialog.java
index f371ee0..12d1e62 100644
--- a/samples/mail/src/com/google/gwt/sample/mail/client/AboutDialog.java
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/AboutDialog.java
@@ -15,57 +15,53 @@
  */
 package com.google.gwt.sample.mail.client;
 
+import com.google.gwt.core.client.GWT;
+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.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.DialogBox;
-import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
 
 /**
  * A simple example of an 'about' dialog box.
  */
 public class AboutDialog extends DialogBox {
 
+  interface Binder extends UiBinder<Widget, AboutDialog> { }
+  private static final Binder binder = GWT.create(Binder.class);
+
+  @UiField Button closeButton;
+
   public AboutDialog() {
     // Use this opportunity to set the dialog's caption.
     setText("About the Mail Sample");
-
-    // Create a VerticalPanel to contain the 'about' label and the 'OK' button.
-    VerticalPanel outer = new VerticalPanel();
-
-    // Create the 'about' text and set a style name so we can style it with CSS.
-
-    HTML text = new HTML("This sample application demonstrates the "
-        + "construction of a complex user interface using GWT's built-in "
-        + "widgets.  Have a look at the code to see how easy it is to build "
-        + "your own apps!");
-    text.setStyleName("mail-AboutText");
-    outer.add(text);
-
-    // Create the 'OK' button, along with a handler that hides the dialog
-    // when the button is clicked.
-    outer.add(new Button("Close", new ClickHandler() {
-      public void onClick(ClickEvent event) {
-        hide();
-      }
-    }));
-
-    setWidget(outer);
+    setWidget(binder.createAndBindUi(this));
   }
 
   @Override
-  public boolean onKeyDownPreview(char key, int modifiers) {
-    // Use the popup's key preview hooks to close the dialog when either
-    // enter or escape is pressed.
-    switch (key) {
-      case KeyCodes.KEY_ENTER:
-      case KeyCodes.KEY_ESCAPE:
-        hide();
-        break;
-    }
+  protected void onPreviewNativeEvent(NativePreviewEvent preview) {
+    super.onPreviewNativeEvent(preview);
 
-    return true;
+    NativeEvent evt = preview.getNativeEvent();
+    if (evt.getType().equals("keydown")) {
+      // Use the popup's key preview hooks to close the dialog when either
+      // enter or escape is pressed.
+      switch (evt.getKeyCode()) {
+        case KeyCodes.KEY_ENTER:
+        case KeyCodes.KEY_ESCAPE:
+          hide();
+          break;
+      }
+    }
+  }
+
+  @UiHandler("closeButton")
+  void onSignOutClicked(ClickEvent event) {
+    hide();
   }
 }
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/AboutDialog.ui.xml b/samples/mail/src/com/google/gwt/sample/mail/client/AboutDialog.ui.xml
new file mode 100644
index 0000000..3aa9afc
--- /dev/null
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/AboutDialog.ui.xml
@@ -0,0 +1,27 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+  xmlns:g='urn:import:com.google.gwt.user.client.ui'
+  xmlns:mail='urn:import:com.google.gwt.sample.mail.client'>
+
+  <ui:style>
+  .aboutText {
+    width: 24em;
+    padding: 10px;
+    text-align: left;
+  }
+  </ui:style>
+
+  <g:DockPanel>
+    <g:Dock direction='SOUTH'>
+      <g:Button text='Close' ui:field='closeButton' />
+    </g:Dock>
+    <g:Dock direction='CENTER'>
+      <g:HTML styleName='{style.aboutText}'>
+        This sample application demonstrates the
+        construction of a complex user interface using GWT's built-in
+        widgets. Have a look at the code to see how easy it is to build
+        your own apps!
+      </g:HTML>
+    </g:Dock>
+  </g:DockPanel>
+
+</ui:UiBinder>
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/ContactPopup.ui.xml b/samples/mail/src/com/google/gwt/sample/mail/client/ContactPopup.ui.xml
new file mode 100644
index 0000000..fc5b2a0
--- /dev/null
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/ContactPopup.ui.xml
@@ -0,0 +1,41 @@
+<ui:UiBinder
+  xmlns:ui='urn:ui:com.google.gwt.uibinder'
+  xmlns:g='urn:import:com.google.gwt.user.client.ui'
+  xmlns:mail='urn:import:com.google.gwt.sample.mail.client'>
+
+  <ui:style>
+  .popup {
+    background: #fff;
+    border: 1px solid #666;
+    padding: 0.5em;
+    width: 14em;
+    height: 2.5em;
+  }
+
+  .photo {
+    float: left;
+    margin-right: 4px;
+
+    background: url(default_photo.jpg);
+    width: 32px;
+    height: 32px;
+  }
+
+  .right {
+    white-space: nowrap;
+  }
+
+  .email {
+    font-style:italic;
+  }
+  </ui:style>
+
+  <g:HTMLPanel styleName='{style.popup}'>
+    <div class='{style.photo}'/>
+    <div class='{style.right}'>
+      <div ui:field='nameDiv' />
+      <div ui:field='emailDiv' class='{style.email}'/>
+    </div>
+  </g:HTMLPanel>
+
+</ui:UiBinder>
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/Contacts.java b/samples/mail/src/com/google/gwt/sample/mail/client/Contacts.java
index 738b1fb..df69bdf 100644
--- a/samples/mail/src/com/google/gwt/sample/mail/client/Contacts.java
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/Contacts.java
@@ -15,17 +15,18 @@
  */
 package com.google.gwt.sample.mail.client;
 
+import com.google.gwt.core.client.GWT;
+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.ui.AbstractImagePrototype;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiTemplate;
+import com.google.gwt.user.client.ui.Anchor;
 import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.HorizontalPanel;
-import com.google.gwt.user.client.ui.ImageBundle;
-import com.google.gwt.user.client.ui.Label;
 import com.google.gwt.user.client.ui.PopupPanel;
-import com.google.gwt.user.client.ui.SimplePanel;
 import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
 
 /**
  * A component that displays a list of contacts.
@@ -33,14 +34,6 @@
 public class Contacts extends Composite {
 
   /**
-   * An image bundle for this widget and an example of the use of @Resource.
-   */
-  public interface Images extends ImageBundle {
-    @Resource("default_photo.jpg")
-    AbstractImagePrototype defaultPhoto();
-  }
-
-  /**
    * Simple data structure representing a contact.
    */
   private static class Contact {
@@ -56,31 +49,28 @@
   /**
    * A simple popup that displays a contact's information.
    */
-  private class ContactPopup extends PopupPanel {
+  static class ContactPopup extends PopupPanel {
+    @UiTemplate("ContactPopup.ui.xml")
+    interface Binder extends UiBinder<Widget, ContactPopup> { }
+    private static final Binder binder = GWT.create(Binder.class);
+
+    @UiField Element nameDiv;
+    @UiField Element emailDiv;
 
     public ContactPopup(Contact contact) {
       // The popup's constructor's argument is a boolean specifying that it
       // auto-close itself when the user clicks outside of it.
       super(true);
+      add(binder.createAndBindUi(this));
 
-      VerticalPanel inner = new VerticalPanel();
-      Label nameLabel = new Label(contact.name);
-      Label emailLabel = new Label(contact.email);
-      inner.add(nameLabel);
-      inner.add(emailLabel);
-
-      HorizontalPanel hp = new HorizontalPanel();
-      hp.setSpacing(4);
-      hp.add(images.defaultPhoto().createImage());
-      hp.add(inner);
-
-      add(hp);
-      setStyleName("mail-ContactPopup");
-      nameLabel.setStyleName("mail-ContactPopupName");
-      emailLabel.setStyleName("mail-ContactPopupEmail");
+      nameDiv.setInnerText(contact.name);
+      emailDiv.setInnerText(contact.email);
     }
   }
 
+  interface Binder extends UiBinder<VerticalPanel, Contacts> { }
+  private static final Binder binder = GWT.create(Binder.class);
+
   private Contact[] contacts = new Contact[] {
       new Contact("Benoit Mandelbrot", "benoit@example.com"),
       new Contact("Albert Einstein", "albert@example.com"),
@@ -91,26 +81,19 @@
       new Contact("Alan Turing", "alan@example.com"),
       new Contact("John von Neumann", "john@example.com")};
 
-  private VerticalPanel panel = new VerticalPanel();
-  private final Images images;
+  private VerticalPanel panel;
 
-  public Contacts(Images images) {
-    SimplePanel outer = new SimplePanel();
-    outer.setWidget(panel);
+  public Contacts() {
+    initWidget(panel = binder.createAndBindUi(this));
 
-    this.images = images;
     // Add all the contacts to the list.
     for (int i = 0; i < contacts.length; ++i) {
       addContact(contacts[i]);
     }
-
-    initWidget(outer);
-    setStyleName("mail-Contacts");
   }
 
   private void addContact(final Contact contact) {
-    final HTML link = new HTML("<a href='javascript:;'>" + contact.name
-        + "</a>");
+    final Anchor link = new Anchor(contact.name);
     panel.add(link);
 
     // Add a click handler that displays a ContactPopup when it is clicked.
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/Contacts.ui.xml b/samples/mail/src/com/google/gwt/sample/mail/client/Contacts.ui.xml
new file mode 100644
index 0000000..5349613
--- /dev/null
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/Contacts.ui.xml
@@ -0,0 +1,13 @@
+<ui:UiBinder
+  xmlns:ui='urn:ui:com.google.gwt.uibinder'
+  xmlns:g='urn:import:com.google.gwt.user.client.ui'
+  xmlns:mail='urn:import:com.google.gwt.sample.mail.client'>
+
+  <ui:style>
+  .contacts {
+    padding: 0.5em;
+  }
+  </ui:style>
+
+  <g:VerticalPanel styleName='{style.contacts}'/>
+</ui:UiBinder>
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/Mail.java b/samples/mail/src/com/google/gwt/sample/mail/client/Mail.java
index 1b7f652..a639a12 100644
--- a/samples/mail/src/com/google/gwt/sample/mail/client/Mail.java
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/Mail.java
@@ -17,128 +17,58 @@
 
 import com.google.gwt.core.client.EntryPoint;
 import com.google.gwt.core.client.GWT;
-import com.google.gwt.event.logical.shared.ResizeEvent;
-import com.google.gwt.event.logical.shared.ResizeHandler;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.DeferredCommand;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.ui.DockPanel;
-import com.google.gwt.user.client.ui.RootPanel;
-import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.DockLayoutPanel;
+import com.google.gwt.user.client.ui.RootLayoutPanel;
 
 /**
  * This application demonstrates how to construct a relatively complex user
  * interface, similar to many common email readers. It has no back-end,
  * populating its components with hard-coded data.
  */
-public class Mail implements EntryPoint, ResizeHandler {
+public class Mail implements EntryPoint {
 
-  private static Mail singleton;
+  interface Binder extends UiBinder<DockLayoutPanel, Mail> { }
+  private static final Binder binder = GWT.create(Binder.class);
 
-  /**
-   * Instantiate an application-level image bundle. This object will provide
-   * programmatic access to all the images needed by widgets.
-   */
-  private static final Images images = GWT.create(Images.class);
-
-  /**
-   * An aggregate image bundle that pulls together all the images for this
-   * application into a single bundle.
-   */
-  public interface Images extends Shortcuts.Images, TopPanel.Images {
-  }
-
-  /**
-   * Gets the singleton Mail instance.
-   */
-  public static Mail get() {
-    return singleton;
-  }
-
-  private TopPanel topPanel = new TopPanel(images);
-  private VerticalPanel rightPanel = new VerticalPanel();
-  private MailList mailList;
-  private MailDetail mailDetail = new MailDetail();
-  private Shortcuts shortcuts = new Shortcuts(images);
-
-  /**
-   * Displays the specified item.
-   * 
-   * @param item
-   */
-  public void displayItem(MailItem item) {
-    mailDetail.setItem(item);
-  }
+  @UiField TopPanel topPanel;
+  @UiField MailList mailList;
+  @UiField MailDetail mailDetail;
+  @UiField Shortcuts shortcuts;
 
   /**
    * This method constructs the application user interface by instantiating
    * controls and hooking up event handler.
    */
   public void onModuleLoad() {
-    singleton = this;
-
-    topPanel.setWidth("100%");
-
-    // MailList uses Mail.get() in its constructor, so initialize it after
-    // 'singleton'.
-    mailList = new MailList();
-    mailList.setWidth("100%");
-
-    // Create the right panel, containing the email list & details.
-    rightPanel.add(mailList);
-    rightPanel.add(mailDetail);
-    mailList.setWidth("100%");
-    mailDetail.setWidth("100%");
-
-    // Create a dock panel that will contain the menu bar at the top,
-    // the shortcuts to the left, and the mail list & details taking the rest.
-    DockPanel outer = new DockPanel();
-    outer.add(topPanel, DockPanel.NORTH);
-    outer.add(shortcuts, DockPanel.WEST);
-    outer.add(rightPanel, DockPanel.CENTER);
-    outer.setWidth("100%");
-
-    outer.setSpacing(4);
-    outer.setCellWidth(rightPanel, "100%");
-
-    // Hook the window resize event, so that we can adjust the UI.
-    Window.addResizeHandler(this);
+    DockLayoutPanel outer = binder.createAndBindUi(this);
 
     // Get rid of scrollbars, and clear out the window's built-in margin,
     // because we want to take advantage of the entire client area.
     Window.enableScrolling(false);
     Window.setMargin("0px");
 
-    // Finally, add the outer panel to the RootPanel, so that it will be
-    // displayed.
-    RootPanel.get().add(outer);
+    // Special-case stuff to make topPanel overhang a bit.
+    Element topElem = outer.getContainerElementFor(topPanel);
+    topElem.getStyle().setZIndex(2);
+    topElem.getStyle().setOverflow(Overflow.VISIBLE);
 
-    // Call the window resized handler to get the initial sizes setup. Doing
-    // this in a deferred command causes it to occur after all widgets' sizes
-    // have been computed by the browser.
-    DeferredCommand.addCommand(new Command() {
-      public void execute() {
-        onWindowResized(Window.getClientWidth(), Window.getClientHeight());
+    // Listen for item selection, displaying the currently-selected item in
+    // the detail area.
+    mailList.setListener(new MailList.Listener() {
+      public void onItemSelected(MailItem item) {
+        mailDetail.setItem(item);
       }
     });
 
-    onWindowResized(Window.getClientWidth(), Window.getClientHeight());
-  }
-
-  public void onResize(ResizeEvent event) {
-    onWindowResized(event.getWidth(), event.getHeight());
-  }
-
-  public void onWindowResized(int width, int height) {
-    // Adjust the shortcut panel and detail area to take up the available room
-    // in the window.
-    int shortcutHeight = height - shortcuts.getAbsoluteTop() - 8;
-    if (shortcutHeight < 1) {
-      shortcutHeight = 1;
-    }
-    shortcuts.setHeight(shortcutHeight + "px");
-
-    // Give the mail detail widget a chance to resize itself as well.
-    mailDetail.adjustSize(width, height);
+    // Add the outer panel to the RootLayoutPanel, so that it will be
+    // displayed.
+    RootLayoutPanel root = RootLayoutPanel.get();
+    root.add(outer);
+    root.layout();
   }
 }
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/Mail.ui.xml b/samples/mail/src/com/google/gwt/sample/mail/client/Mail.ui.xml
new file mode 100644
index 0000000..08abf6f
--- /dev/null
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/Mail.ui.xml
@@ -0,0 +1,28 @@
+<ui:UiBinder
+  xmlns:ui='urn:ui:com.google.gwt.uibinder'
+  xmlns:g='urn:import:com.google.gwt.user.client.ui'
+  xmlns:mail='urn:import:com.google.gwt.sample.mail.client'>
+
+  <g:DockLayoutPanel unit='EM'>
+    <g:north size='5'>
+      <mail:TopPanel ui:field='topPanel' />
+    </g:north>
+
+    <g:center>
+      <g:SplitLayoutPanel>
+        <g:west size='192'>
+          <mail:Shortcuts ui:field='shortcuts' />
+        </g:west>
+
+        <g:north size='200'>
+          <mail:MailList ui:field='mailList' />
+        </g:north>
+
+        <g:center>
+          <mail:MailDetail ui:field='mailDetail' />
+        </g:center>
+      </g:SplitLayoutPanel>
+    </g:center>
+  </g:DockLayoutPanel>
+
+</ui:UiBinder>
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/MailDetail.java b/samples/mail/src/com/google/gwt/sample/mail/client/MailDetail.java
index 42901ba..b8697ec 100644
--- a/samples/mail/src/com/google/gwt/sample/mail/client/MailDetail.java
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/MailDetail.java
@@ -15,74 +15,39 @@
  */
 package com.google.gwt.sample.mail.client;
 
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.DockPanel;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.ScrollPanel;
-import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.LayoutComposite;
+import com.google.gwt.user.client.ui.Widget;
 
 /**
  * A composite for displaying the details of an email message.
  */
-public class MailDetail extends Composite {
+public class MailDetail extends LayoutComposite {
 
-  private VerticalPanel panel = new VerticalPanel();
-  private VerticalPanel headerPanel = new VerticalPanel();
-  private HTML subject = new HTML();
-  private HTML sender = new HTML();
-  private HTML recipient = new HTML();
-  private HTML body = new HTML();
-  private ScrollPanel scroller = new ScrollPanel(body);
+  interface Binder extends UiBinder<Widget, MailDetail> { }
+  private static final Binder binder = GWT.create(Binder.class);
+
+  @UiField Element subject;
+  @UiField Element sender;
+  @UiField Element recipient;
+  @UiField HTML body;
 
   public MailDetail() {
-    body.setWordWrap(true);
-
-    headerPanel.add(subject);
-    headerPanel.add(sender);
-    headerPanel.add(recipient);
-    headerPanel.setWidth("100%");
-
-    DockPanel innerPanel = new DockPanel();
-    innerPanel.add(headerPanel, DockPanel.NORTH);
-    innerPanel.add(scroller, DockPanel.CENTER);
-
-    innerPanel.setCellHeight(scroller, "100%");
-    panel.add(innerPanel);
-    innerPanel.setSize("100%", "100%");
-    scroller.setSize("100%", "100%");
-    initWidget(panel);
-
-    setStyleName("mail-Detail");
-    headerPanel.setStyleName("mail-DetailHeader");
-    innerPanel.setStyleName("mail-DetailInner");
-    subject.setStyleName("mail-DetailSubject");
-    sender.setStyleName("mail-DetailSender");
-    recipient.setStyleName("mail-DetailRecipient");
-    body.setStyleName("mail-DetailBody");
-  }
-
-  /**
-   * Adjusts the widget's size such that it fits within the window's client
-   * area.
-   */
-  public void adjustSize(int windowWidth, int windowHeight) {
-    int scrollWidth = windowWidth - scroller.getAbsoluteLeft() - 9;
-    if (scrollWidth < 1) {
-      scrollWidth = 1;
-    }
-
-    int scrollHeight = windowHeight - scroller.getAbsoluteTop() - 9;
-    if (scrollHeight < 1) {
-      scrollHeight = 1;
-    }
-
-    scroller.setPixelSize(scrollWidth, scrollHeight);
+    initWidget(binder.createAndBindUi(this));
   }
 
   public void setItem(MailItem item) {
-    subject.setHTML(item.subject);
-    sender.setHTML("<b>From:</b>&nbsp;" + item.sender);
-    recipient.setHTML("<b>To:</b>&nbsp;foo@example.com");
+    subject.setInnerText(item.subject);
+    sender.setInnerText(item.sender);
+    recipient.setInnerHTML("foo@example.com");
+
+    // WARNING: For the purposes of this demo, we're using HTML directly, on
+    // the assumption that the "server" would have appropriately scrubbed the
+    // HTML. Failure to do so would open your application to XSS attacks.
     body.setHTML(item.body);
   }
 }
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/MailDetail.ui.xml b/samples/mail/src/com/google/gwt/sample/mail/client/MailDetail.ui.xml
new file mode 100644
index 0000000..d5872ff
--- /dev/null
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/MailDetail.ui.xml
@@ -0,0 +1,44 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
+  xmlns:g='urn:import:com.google.gwt.user.client.ui'
+  xmlns:mail='urn:import:com.google.gwt.sample.mail.client'>
+
+  <ui:style>
+  .detail {
+    border: 1px solid #666;
+    background-color: white;
+  }
+
+  .header {
+    background: #eee;
+    border-bottom: 1px solid #666;
+    padding: 0.5em;
+  }
+
+  .headerItem {
+    margin-bottom:0.5em;
+  }
+
+  .body {
+    line-height: 150%;
+    padding: 20px 40px 20px 10px;
+    font-family: 'Times New Roman', Times, serif;
+  }
+  </ui:style>
+
+  <g:DockLayoutPanel unit='EM' styleName='{style.detail}'>
+    <g:north size='6'>
+      <g:HTMLPanel styleName='{style.header}'>
+        <div class='{style.headerItem}' ui:field='subject'/>
+        <div class='{style.headerItem}'><b>From:</b> <span ui:field='sender'/></div>
+        <div class='{style.headerItem}'><b>To:</b> <span ui:field='recipient'/></div>
+      </g:HTMLPanel>
+    </g:north>
+
+    <g:center>
+      <g:ScrollPanel>
+        <g:HTML styleName='{style.body}' ui:field='body' wordWrap='true'/>
+      </g:ScrollPanel>
+    </g:center>
+  </g:DockLayoutPanel>
+
+</ui:UiBinder>
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/MailItems.java b/samples/mail/src/com/google/gwt/sample/mail/client/MailItems.java
index d36b789..3b4828c 100644
--- a/samples/mail/src/com/google/gwt/sample/mail/client/MailItems.java
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/MailItems.java
@@ -22,7 +22,7 @@
  */
 public class MailItems {
 
-  private static final int NUM_ITEMS = 37;
+  private static final int NUM_ITEMS = 64;
   private static final int FRAGMENTS_PER_EMAIL = 10;
 
   private static final String[] senders = new String[] {
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/MailList.java b/samples/mail/src/com/google/gwt/sample/mail/client/MailList.java
index 43fa86a..f6345a0 100644
--- a/samples/mail/src/com/google/gwt/sample/mail/client/MailList.java
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/MailList.java
@@ -15,20 +15,29 @@
  */
 package com.google.gwt.sample.mail.client;
 
+import com.google.gwt.dom.client.Style.Unit;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.DockLayoutPanel;
 import com.google.gwt.user.client.ui.FlexTable;
 import com.google.gwt.user.client.ui.HTML;
 import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.LayoutComposite;
+import com.google.gwt.user.client.ui.ScrollPanel;
 import com.google.gwt.user.client.ui.HTMLTable.Cell;
 
 /**
  * A composite that displays a list of emails that can be selected.
  */
-public class MailList extends Composite implements ClickHandler {
+public class MailList extends LayoutComposite implements ClickHandler {
 
-  private static final int VISIBLE_EMAIL_COUNT = 10;
+  private static final int VISIBLE_EMAIL_COUNT = 20;
+
+  public interface Listener {
+    void onItemSelected(MailItem item);
+  }
+
+  private Listener listener;
 
   private HTML countLabel = new HTML();
   private HTML newerButton = new HTML("<a href='javascript:;'>&lt; newer</a>",
@@ -36,6 +45,7 @@
   private HTML olderButton = new HTML("<a href='javascript:;'>older &gt;</a>",
       true);
   private int startIndex, selectedRow = -1;
+  private FlexTable header = new FlexTable();
   private FlexTable table = new FlexTable();
   private HorizontalPanel navBar = new HorizontalPanel();
 
@@ -61,7 +71,13 @@
     navBar.add(innerNavBar);
     navBar.setWidth("100%");
 
-    initWidget(table);
+    DockLayoutPanel dock = new DockLayoutPanel(Unit.EM);
+    dock.addNorth(header, 2);
+    dock.add(new ScrollPanel(table));
+    header.setWidth("100%");
+    table.setWidth("100%");
+    dock.layout();
+    initWidget(dock);
     setStyleName("mail-List");
 
     initTable();
@@ -95,34 +111,60 @@
       Cell cell = table.getCellForEvent(event);
       if (cell != null) {
         int row = cell.getRowIndex();
-        if (row > 0) {
-          selectRow(row - 1);
-        }
+        selectRow(row);
       }
     }
   }
 
   /**
+   * Sets the listener that will be notified when an item is selected.
+   */
+  public void setListener(Listener listener) {
+    this.listener = listener;
+  }
+
+  @Override
+  protected void onLoad() {
+    // Select the first row if none is selected.
+    if (selectedRow == -1) {
+      selectRow(0);
+    }
+  }
+
+  /**
    * Initializes the table so that it contains enough rows for a full page of
    * emails. Also creates the images that will be used as 'read' flags.
    */
   private void initTable() {
-    // Create the header row.
-    table.setText(0, 0, "Sender");
-    table.setText(0, 1, "Email");
-    table.setText(0, 2, "Subject");
-    table.setWidget(0, 3, navBar);
-    table.getRowFormatter().setStyleName(0, "mail-ListHeader");
+    // Create the header.
+    header.getRowFormatter().setStyleName(0, "mail-ListHeader");
+    header.getElement().getStyle().setProperty("tableLayout", "fixed");
+    header.setCellSpacing(0);
 
-    // Initialize the rest of the rows.
+    header.getColumnFormatter().setWidth(0, "96px");
+    header.getColumnFormatter().setWidth(1, "160px");
+    header.getColumnFormatter().setWidth(3, "256px");
+
+    header.setText(0, 0, "Sender");
+    header.setText(0, 1, "Email");
+    header.setText(0, 2, "Subject");
+    header.setWidget(0, 3, navBar);
+
+    // Initialize the table.
+    table.getElement().getStyle().setProperty("tableLayout", "fixed");
+    header.setCellSpacing(0);
+
+    table.getColumnFormatter().setWidth(0, "96px");
+    table.getColumnFormatter().setWidth(1, "160px");
+
     for (int i = 0; i < VISIBLE_EMAIL_COUNT; ++i) {
-      table.setText(i + 1, 0, "");
-      table.setText(i + 1, 1, "");
-      table.setText(i + 1, 2, "");
-      table.getCellFormatter().setWordWrap(i + 1, 0, false);
-      table.getCellFormatter().setWordWrap(i + 1, 1, false);
-      table.getCellFormatter().setWordWrap(i + 1, 2, false);
-      table.getFlexCellFormatter().setColSpan(i + 1, 2, 2);
+      table.setText(i, 0, "");
+      table.setText(i, 1, "");
+      table.setText(i, 2, "");
+      table.getCellFormatter().setWordWrap(i, 0, false);
+      table.getCellFormatter().setWordWrap(i, 1, false);
+      table.getCellFormatter().setWordWrap(i, 2, false);
+      table.getFlexCellFormatter().setColSpan(i, 2, 2);
     }
   }
 
@@ -144,15 +186,18 @@
 
     item.read = true;
     selectedRow = row;
-    Mail.get().displayItem(item);
+
+    if (listener != null) {
+      listener.onItemSelected(item);
+    }
   }
 
   private void styleRow(int row, boolean selected) {
     if (row != -1) {
       if (selected) {
-        table.getRowFormatter().addStyleName(row + 1, "mail-SelectedRow");
+        table.getRowFormatter().addStyleName(row, "mail-SelectedRow");
       } else {
-        table.getRowFormatter().removeStyleName(row + 1, "mail-SelectedRow");
+        table.getRowFormatter().removeStyleName(row, "mail-SelectedRow");
       }
     }
   }
@@ -181,21 +226,16 @@
 
       // Add a new row to the table, then set each of its columns to the
       // email's sender and subject values.
-      table.setText(i + 1, 0, item.sender);
-      table.setText(i + 1, 1, item.email);
-      table.setText(i + 1, 2, item.subject);
+      table.setText(i, 0, item.sender);
+      table.setText(i, 1, item.email);
+      table.setText(i, 2, item.subject);
     }
 
     // Clear any remaining slots.
     for (; i < VISIBLE_EMAIL_COUNT; ++i) {
-      table.setHTML(i + 1, 0, "&nbsp;");
-      table.setHTML(i + 1, 1, "&nbsp;");
-      table.setHTML(i + 1, 2, "&nbsp;");
-    }
-
-    // Select the first row if none is selected.
-    if (selectedRow == -1) {
-      selectRow(0);
+      table.setHTML(i, 0, "&nbsp;");
+      table.setHTML(i, 1, "&nbsp;");
+      table.setHTML(i, 2, "&nbsp;");
     }
   }
 }
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/Mailboxes.java b/samples/mail/src/com/google/gwt/sample/mail/client/Mailboxes.java
index f0e852d..27fb3c5 100644
--- a/samples/mail/src/com/google/gwt/sample/mail/client/Mailboxes.java
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/Mailboxes.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.sample.mail.client;
 
+import com.google.gwt.core.client.GWT;
 import com.google.gwt.user.client.ui.AbstractImagePrototype;
 import com.google.gwt.user.client.ui.Composite;
 import com.google.gwt.user.client.ui.ImageBundle;
@@ -55,7 +56,9 @@
    * 
    * @param images a bundle that provides the images for this widget
    */
-  public Mailboxes(Images images) {
+  public Mailboxes() {
+    Images images = GWT.create(Images.class);
+
     tree = new Tree(images);
     TreeItem root = new TreeItem(
         imageItemHTML(images.home(), "foo@example.com"));
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/Shortcuts.java b/samples/mail/src/com/google/gwt/sample/mail/client/Shortcuts.java
index c58b71a..28687a1 100644
--- a/samples/mail/src/com/google/gwt/sample/mail/client/Shortcuts.java
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/Shortcuts.java
@@ -15,10 +15,11 @@
  */
 package com.google.gwt.sample.mail.client;
 
-import com.google.gwt.user.client.ui.AbstractImagePrototype;
-import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.DecoratedStackPanel;
-import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.ui.LayoutComposite;
+import com.google.gwt.user.client.ui.StackLayoutPanel;
 
 /**
  * A composite that contains the shortcut stack panel on the left side. The
@@ -27,67 +28,29 @@
  * {@link com.google.gwt.user.client.ui.StackPanel},
  * {@link com.google.gwt.user.client.ui.Tree}, and other custom widgets.
  */
-public class Shortcuts extends Composite {
+public class Shortcuts extends LayoutComposite {
 
-  /**
-   * An image bundle specifying the images for this Widget and aggragating
-   * images needed in child widgets.
-   */
-  public interface Images extends Contacts.Images, Mailboxes.Images {
-    AbstractImagePrototype contactsgroup();
+  interface Binder extends UiBinder<StackLayoutPanel, Shortcuts> { }
+  private static final Binder binder = GWT.create(Binder.class);
 
-    AbstractImagePrototype mailgroup();
+  private StackLayoutPanel stackPanel;
 
-    AbstractImagePrototype tasksgroup();
-  }
-
-  private int nextHeaderIndex = 0;
-  private DecoratedStackPanel stackPanel = new DecoratedStackPanel();
+  @UiField Mailboxes mailboxes;
+  @UiField Tasks tasks;
+  @UiField Contacts contacts;
 
   /**
    * Constructs a new shortcuts widget using the specified images.
    * 
    * @param images a bundle that provides the images for this widget
    */
-  public Shortcuts(Images images) {
-    // Create the groups within the stack panel.
-    add(new Mailboxes(images), images.mailgroup(), "Mail");
-    add(new Tasks(), images.tasksgroup(), "Tasks");
-    add(new Contacts(images), images.contactsgroup(), "Contacts");
-
-    initWidget(stackPanel);
+  public Shortcuts() {
+    initWidget(stackPanel = binder.createAndBindUi(this));
   }
 
   @Override
   protected void onLoad() {
     // Show the mailboxes group by default.
-    stackPanel.showStack(0);
-  }
-
-  private void add(Widget widget, AbstractImagePrototype imageProto,
-      String caption) {
-    widget.addStyleName("mail-StackContent");
-    stackPanel.add(widget, createHeaderHTML(imageProto, caption), true);
-  }
-
-  /**
-   * Creates an HTML fragment that places an image & caption together, for use
-   * in a group header.
-   * 
-   * @param imageProto an image prototype for an image
-   * @param caption the group caption
-   * @return the header HTML fragment
-   */
-  private String createHeaderHTML(AbstractImagePrototype imageProto,
-      String caption) {
-    nextHeaderIndex++;
-
-    String captionHTML = "<table class='caption' cellpadding='0' cellspacing='0'>"
-        + "<tr><td class='lcaption'>"
-        + imageProto.getHTML()
-        + "</td><td class='rcaption'><b style='white-space:nowrap'>"
-        + caption
-        + "</b></td></tr></table>";
-    return captionHTML;
+    stackPanel.showWidget(mailboxes);
   }
 }
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/Shortcuts.ui.xml b/samples/mail/src/com/google/gwt/sample/mail/client/Shortcuts.ui.xml
new file mode 100644
index 0000000..2cfa4eb
--- /dev/null
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/Shortcuts.ui.xml
@@ -0,0 +1,45 @@
+<ui:UiBinder
+  xmlns:ui='urn:ui:com.google.gwt.uibinder'
+  xmlns:g='urn:import:com.google.gwt.user.client.ui'
+  xmlns:mail='urn:import:com.google.gwt.sample.mail.client'>
+
+  <ui:style>
+  .shortcuts {
+    border-left: 1px solid #666;
+    border-right: 1px solid #666;
+  }
+
+  .stackHeader {
+    background: #c1eec8 url(gradient.gif) repeat-x 0px -1px;
+    padding-top: 1em;
+    font-weight: bold;
+    text-align: center;
+    border-top: 1px solid #666;
+    border-bottom: 1px solid #666;  
+  }
+  </ui:style>
+
+  <g:StackLayoutPanel styleName='{style.shortcuts}' unit='EM'>
+    <g:stack>
+      <g:header size='4'>
+        <g:Label styleName='{style.stackHeader}'>Mailboxes</g:Label>
+      </g:header>
+      <mail:Mailboxes ui:field='mailboxes'/>
+    </g:stack>
+
+    <g:stack>
+      <g:header size='4'>
+        <g:Label styleName='{style.stackHeader}'>Tasks</g:Label>
+      </g:header>
+      <mail:Tasks ui:field='tasks'/>
+    </g:stack>
+
+    <g:stack>
+      <g:header size='4'>
+        <g:Label styleName='{style.stackHeader}'>Contacts</g:Label>
+      </g:header>
+      <mail:Contacts ui:field='contacts'/>
+    </g:stack>
+  </g:StackLayoutPanel>
+
+</ui:UiBinder>
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/Tasks.java b/samples/mail/src/com/google/gwt/sample/mail/client/Tasks.java
index b8e79d4..73a4fe7 100644
--- a/samples/mail/src/com/google/gwt/sample/mail/client/Tasks.java
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/Tasks.java
@@ -17,7 +17,6 @@
 
 import com.google.gwt.user.client.ui.CheckBox;
 import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.SimplePanel;
 import com.google.gwt.user.client.ui.VerticalPanel;
 
 /**
@@ -26,17 +25,14 @@
 public class Tasks extends Composite {
 
   public Tasks() {
-    SimplePanel panel = new SimplePanel();
     VerticalPanel list = new VerticalPanel();
-    panel.setWidget(list);
     list.add(new CheckBox("Get groceries"));
     list.add(new CheckBox("Walk the dog"));
     list.add(new CheckBox("Start Web 2.0 company"));
     list.add(new CheckBox("Write cool app in GWT"));
     list.add(new CheckBox("Get funding"));
     list.add(new CheckBox("Take a vacation"));
-    initWidget(panel);
+    initWidget(list);
     setStyleName("mail-Tasks");
   }
-
 }
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/TopPanel.java b/samples/mail/src/com/google/gwt/sample/mail/client/TopPanel.java
index 0d6d178..ad5bc84 100644
--- a/samples/mail/src/com/google/gwt/sample/mail/client/TopPanel.java
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/TopPanel.java
@@ -15,71 +15,43 @@
  */
 package com.google.gwt.sample.mail.client;
 
+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.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
 import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.ui.AbstractImagePrototype;
+import com.google.gwt.user.client.ui.Anchor;
 import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.HorizontalPanel;
-import com.google.gwt.user.client.ui.Image;
-import com.google.gwt.user.client.ui.ImageBundle;
-import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
 
 /**
  * The top panel, which contains the 'welcome' message and various links.
  */
-public class TopPanel extends Composite implements ClickHandler {
+public class TopPanel extends Composite {
 
-  /**
-   * An image bundle for this widgets images.
-   */
-  public interface Images extends ImageBundle {
-    AbstractImagePrototype logo();
+  interface Binder extends UiBinder<Widget, TopPanel> { }
+  private static final Binder binder = GWT.create(Binder.class);
+
+  @UiField Anchor signOutLink;
+  @UiField Anchor aboutLink;
+
+  public TopPanel() {
+    initWidget(binder.createAndBindUi(this));
   }
 
-  private HTML signOutLink = new HTML("<a href='javascript:;'>Sign Out</a>");
-  private HTML aboutLink = new HTML("<a href='javascript:;'>About</a>");
-
-  public TopPanel(Images images) {
-    HorizontalPanel outer = new HorizontalPanel();
-    VerticalPanel inner = new VerticalPanel();
-
-    outer.setHorizontalAlignment(HorizontalPanel.ALIGN_RIGHT);
-    inner.setHorizontalAlignment(HorizontalPanel.ALIGN_RIGHT);
-
-    HorizontalPanel links = new HorizontalPanel();
-    links.setSpacing(4);
-    links.add(signOutLink);
-    links.add(aboutLink);
-
-    final Image logo = images.logo().createImage();
-    outer.add(logo);
-    outer.setCellHorizontalAlignment(logo, HorizontalPanel.ALIGN_LEFT);
-
-    outer.add(inner);
-    inner.add(new HTML("<b>Welcome back, foo@example.com</b>"));
-    inner.add(links);
-
-    signOutLink.addClickHandler(this);
-    aboutLink.addClickHandler(this);
-
-    initWidget(outer);
-    setStyleName("mail-TopPanel");
-    links.setStyleName("mail-TopPanelLinks");
+  @UiHandler("aboutLink")
+  void onAboutClicked(ClickEvent event) {
+    // When the 'About' item is selected, show the AboutDialog.
+    // Note that showing a dialog box does not block -- execution continues
+    // normally, and the dialog fires an event when it is closed.
+    AboutDialog dlg = new AboutDialog();
+    dlg.show();
+    dlg.center();
   }
 
-  public void onClick(ClickEvent event) {
-    Object sender = event.getSource();
-    if (sender == signOutLink) {
-      Window.alert("If this were implemented, you would be signed out now.");
-    } else if (sender == aboutLink) {
-      // When the 'About' item is selected, show the AboutDialog.
-      // Note that showing a dialog box does not block -- execution continues
-      // normally, and the dialog fires an event when it is closed.
-      AboutDialog dlg = new AboutDialog();
-      dlg.show();
-      dlg.center();
-    }
+  @UiHandler("signOutLink")
+  void onSignOutClicked(ClickEvent event) {
+    Window.alert("If this were implemented, you would be signed out now.");
   }
 }
diff --git a/samples/mail/src/com/google/gwt/sample/mail/client/TopPanel.ui.xml b/samples/mail/src/com/google/gwt/sample/mail/client/TopPanel.ui.xml
new file mode 100644
index 0000000..1981a59
--- /dev/null
+++ b/samples/mail/src/com/google/gwt/sample/mail/client/TopPanel.ui.xml
@@ -0,0 +1,39 @@
+<ui:UiBinder
+  xmlns:ui='urn:ui:com.google.gwt.uibinder'
+  xmlns:g='urn:import:com.google.gwt.user.client.ui'
+  xmlns:mail='urn:import:com.google.gwt.sample.mail.client'>
+
+  <ui:style>
+  .statusDiv {
+    text-align: right;
+    margin: 1em;
+  }
+
+  .linksDiv {
+    text-align: right;
+  }
+
+  .logo {
+    background: url(logo.png);
+    position: absolute;
+    width: 140px;
+    height: 75px;
+  }
+  </ui:style>
+
+  <g:HTMLPanel>
+    <div class='{style.logo}'/>
+
+    <div class="{style.statusDiv}">
+      <div>
+        <b>Welcome back, foo@example.com</b>
+      </div>
+
+      <div class='{style.linksDiv}'>
+        <g:Anchor href='javascript:;' ui:field='signOutLink'>Sign Out</g:Anchor>
+        <g:Anchor href='javascript:;' ui:field='aboutLink'>About</g:Anchor>
+      </div>
+    </div>
+  </g:HTMLPanel>
+
+</ui:UiBinder>
diff --git a/samples/mail/war/Mail.css b/samples/mail/war/Mail.css
index 2d045a4..023ced9 100644
--- a/samples/mail/war/Mail.css
+++ b/samples/mail/war/Mail.css
@@ -1,11 +1,11 @@
-body, html {
-  height: 100%;
+body, table {
+  font-size: small;
 }
+
 body {
   background: #fff;
   color: black;
   font-family: Helvetica, Arial, sans-serif;
-  font-size: small;
   margin: 8px;
   margin-top: 3px;
 }
@@ -24,7 +24,7 @@
 
 .gwt-DialogBox .Caption {
   background: url(gradient.gif) repeat-x 0px -1px;
-  font-weight: bold;
+  font-weight: normal;
   cursor: default;
   padding: 5px 10px;
   border: 1px solid #666;
@@ -44,26 +44,10 @@
   margin: 10px;
 }
 
-.gwt-MenuBar {
-  background: #c3d9ff;
-  cursor: default;
-}
-
-.gwt-MenuItem {
-  font-size: 80%;
-  margin: 1px;
-  cursor: default;
-}
-
-.gwt-MenuItem-selected {
-  background: #e8eef7;
-}
-
 .gwt-Tree {
 }
 
 .gwt-Tree .gwt-TreeItem {
-  font-size: 80%;
   padding: 1px 3px 0 3px;
   cursor: hand;
   cursor: pointer;
@@ -74,142 +58,10 @@
   background: #ccc;
 }
 
-.gwt-DecoratedStackPanel {
-  width: 15em;
-  border-bottom: 1px solid #666;
-}
-.gwt-DecoratedStackPanel .lcaption {
-  width: 32px;
-  padding: 0 0 4px 5px;
-}
-.gwt-DecoratedStackPanel .rcaption {
-  padding: 0 0 4px 5px;
-}
-.gwt-DecoratedStackPanel .gwt-StackPanelContent {
-  border: 1px solid #666;
-  border-bottom: 0px;
-  background: white;
-  padding: 2px 2px 10px 5px;
-}
-.gwt-DecoratedStackPanel .gwt-StackPanelItem {
-  cursor: pointer;
-  cursor: hand;
-}
-.gwt-DecoratedStackPanel .stackItemTopLeft,
-.gwt-DecoratedStackPanel .stackItemTopRight {
-  width: 4px;
-  height: 4px;
-  zoom: 1;
-}
-html>body .gwt-DecoratedStackPanel .stackItemTopLeft {
-  background: #c1eec8 url(leftCorner.gif) no-repeat;
-  border-left: 1px solid #666;
-}
-html>body .gwt-DecoratedStackPanel .stackItemTopRight {
-  background: #c1eec8 url(rightCorner.gif) no-repeat;
-  border-right: 1px solid #666;
-}
-.gwt-DecoratedStackPanel .stackItemTopLeftInner,
-.gwt-DecoratedStackPanel .stackItemTopRightInner {
-  width: 4px;
-  height: 4px;
-}
-* html .gwt-DecoratedStackPanel .stackItemTopLeftInner {
-  overflow: hidden;
-  border-left: 1px solid #666;
-  background-color: #d3def6;
-  filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='leftCorner.gif',sizingMethod='crop');
-}
-* html .gwt-DecoratedStackPanel .stackItemTopRightInner {
-  overflow: hidden;
-  border-right: 1px solid #666;
-  background-color: #d3def6;
-  filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='rightCorner.gif',sizingMethod='crop');
-}
-.gwt-DecoratedStackPanel .stackItemTopCenter {
-  background: #ddefde url(gradient.gif) repeat-x 0px 0px;
-}
-.gwt-DecoratedStackPanel .stackItemMiddleLeft {
-  background: #d3def6 url(gradient.gif) repeat-x 0px -1px;
-  border-left: 1px solid #666;
-}
-.gwt-DecoratedStackPanel .stackItemMiddleLeftInner,
-.gwt-DecoratedStackPanel .stackItemMiddleRightInner {
-  width: 1px;
-  height: 1px;
-}
-.gwt-DecoratedStackPanel .stackItemMiddleRight {
-  background: #d3def6 url(gradient.gif) repeat-x 0px -1px;
-  border-right: 1px solid #666;
-}
-.gwt-DecoratedStackPanel .stackItemMiddleCenter {
-  font-weight: bold;
-  font-size: 1.3em;
-  background: #d3def6 url(gradient.gif) repeat-x 0px -1px;
-}
-html>body .gwt-DecoratedStackPanel .gwt-StackPanelItem-first .stackItemTopRight,
-html>body .gwt-DecoratedStackPanel .gwt-StackPanelItem-first .stackItemTopLeft {
-  border: 0px;
-  background-color: white;
-}
-html>body .gwt-DecoratedStackPanel .gwt-StackPanelItem-below-selected .stackItemTopLeft,
-html>body .gwt-DecoratedStackPanel .gwt-StackPanelItem-below-selected .stackItemTopRight {
-  background-color: white;
-}
-* html .gwt-DecoratedStackPanel .gwt-StackPanelItem-first .stackItemTopLeftInner,
-* html .gwt-DecoratedStackPanel .gwt-StackPanelItem-first .stackItemTopRightInner {
-  border: 0px;
-  background-color: white;
-}
-* html .gwt-DecoratedStackPanel .gwt-StackPanelItem-first .stackItemTopLeftInner {
-  padding-left: 1px;
-}
-* html .gwt-DecoratedStackPanel .gwt-StackPanelItem-below-selected .stackItemTopLeftInner,
-* html .gwt-DecoratedStackPanel .gwt-StackPanelItem-below-selected .stackItemTopRightInner {
-  background-color: white;
-}
-
-.mail-TopPanel {
-  height: 60px;
-}
-
-.mail-TopPanel table {
-  font-size: 80%;
-}
-
-.mail-TopPanel .gwt-Image {
-  margin-left: 10px;
-  position: absolute;
-}
-
-.mail-TopPanelLinks {
-  font-size: 80%;
-}
-
-.mail-AboutText {
-  width: 24em;
-  font-size: 80%;
-  padding: 10px;
-  text-align: left;
-}
-
-.mail-Contacts td, .mail-Tasks td {
-  padding: 4px 0 0 0;
-}
-
-.mail-Contacts table {
-  font-size: 80%;
-}
-
-.mail-Tasks table {
-  font-size: 80%;
-}
-
 .mail-List {
   border-left: 1px solid #666;
   border-right: 1px solid #666;
   border-bottom: 1px solid #666;
-  font-size: 80%;
   cursor: pointer;
   cursor: hand;
 }
@@ -226,11 +78,11 @@
 
 .mail-ListHeader {
   background: #c1eec8 url(gradient.gif) repeat-x 0px -1px;
-  font-weight: bold;
+  font-weight: normal;
 }
 
 .mail-ListHeader .mail-ListNavBar .gwt-HTML {
-  font-weight: bold;
+  font-weight: normal;
 }
 
 .mail-ListHeader td {
@@ -240,7 +92,6 @@
 }
 
 .mail-ListNavBar table {
-  font-size: 80%;
 }
 
 .mail-ListNavBar td {
@@ -259,76 +110,3 @@
 .mail-SelectedRow {
   background: #eee;
 }
-
-.mail-Toolbar .gwt-Image {
-}
-
-.mail-ToolButton {
-  font-size: 80%;
-  width: 10em;
-}
-
-.mail-Detail {
-  border: 1px solid #666;
-  margin-top: 4px;
-}
-
-.mail-DetailHeader {
-  background: #eee;
-  border-bottom: 1px solid #666;
-  padding: 6px 4px;
-}
-
-.mail-DetailHeader td {
-  padding: 0;
-}
-
-.mail-DetailInner {
-  background-color: white;
-  font-size: 80%;
-}
-
-.mail-DetailSubject {
-  padding: 2px 10px;
-  font-weight: bold;
-}
-
-.mail-DetailSender {
-  font-size: 80%;
-  padding: 2px 10px;
-}
-
-.mail-DetailRecipient {
-  font-size: 80%;
-  padding: 2px 10px;
-}
-
-.mail-DetailBody {
-  line-height: 150%;
-  padding: 20px 40px 20px 10px;
-  font-family: 'Times New Roman', Times, serif;
-}
-
-.mail-ContactPopup {
-  background: #fff;
-  border: 1px solid #666;
-  padding: 4px;
-}
-
-.mail-ContactPopupName {
-  font-size: 80%;
-  font-weight: bold;
-}
-
-.mail-ContactPopupEmail {
-  font-size: 80%;
-  font-style: italic;
-}
-
-.mail-StackContent {
-  height: 100%;
-}
-
-.mail-Contacts {
-  border-bottom: none;
-}
diff --git a/samples/mail/war/default_photo.jpg b/samples/mail/war/default_photo.jpg
new file mode 100644
index 0000000..751970d
--- /dev/null
+++ b/samples/mail/war/default_photo.jpg
Binary files differ
diff --git a/samples/mail/war/logo.png b/samples/mail/war/logo.png
new file mode 100644
index 0000000..a3123b2
--- /dev/null
+++ b/samples/mail/war/logo.png
Binary files differ
diff --git a/user/javadoc/com/google/gwt/examples/DockLayoutPanelExample.java b/user/javadoc/com/google/gwt/examples/DockLayoutPanelExample.java
index 31fa1ad..6474fb1 100644
--- a/user/javadoc/com/google/gwt/examples/DockLayoutPanelExample.java
+++ b/user/javadoc/com/google/gwt/examples/DockLayoutPanelExample.java
@@ -20,7 +20,6 @@
 import com.google.gwt.user.client.ui.DockLayoutPanel;
 import com.google.gwt.user.client.ui.HTML;
 import com.google.gwt.user.client.ui.RootLayoutPanel;
-import com.google.gwt.user.client.ui.DockLayoutPanel.Direction;
 
 public class DockLayoutPanelExample implements EntryPoint {
 
@@ -28,11 +27,11 @@
     // Attach five widgets to a DockLayoutPanel, one in each direction. Lay
     // them out in 'em' units.
     DockLayoutPanel p = new DockLayoutPanel(Unit.EM);
-    p.add(new HTML("north"), Direction.NORTH, 2);
-    p.add(new HTML("south"), Direction.SOUTH, 2);
-    p.add(new HTML("east"), Direction.EAST, 2);
-    p.add(new HTML("west"), Direction.WEST, 2);
-    p.add(new HTML("center"), Direction.CENTER, 2);
+    p.addNorth(new HTML("north"), 2);
+    p.addSouth(new HTML("south"), 2);
+    p.addEast(new HTML("east"), 2);
+    p.addWest(new HTML("west"), 2);
+    p.add(new HTML("center"));
 
     // Note the explicit call to layout(). This is required for the layout to
     // take effect.
diff --git a/user/src/com/google/gwt/uibinder/parsers/DockLayoutPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/DockLayoutPanelParser.java
new file mode 100644
index 0000000..d3c1a66
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/DockLayoutPanelParser.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2009 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.uibinder.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.NotFoundException;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.user.client.ui.DockLayoutPanel;
+import com.google.gwt.user.client.ui.SplitLayoutPanel;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Parses {@link DockLayoutPanel} widgets.
+ * 
+ * TODO(jgw): The code that explicitly excludes SplitLayoutPanel in a fairly
+ * awkward way could be greatly simplified if we hoisted the "dock-ness" into an
+ * interface implemented by both DockLayoutPanel and SplitLayoutPanel, and moved
+ * most of this code into a parser for that specific interface. This parser
+ * would then be reduced to a simple special case for the ctor param.
+ */
+public class DockLayoutPanelParser implements ElementParser {
+
+  private static final Map<String, String> DOCK_NAMES = new HashMap<String, String>();
+
+  static {
+    DOCK_NAMES.put("north", "addNorth");
+    DOCK_NAMES.put("south", "addSouth");
+    DOCK_NAMES.put("east", "addEast");
+    DOCK_NAMES.put("west", "addWest");
+    DOCK_NAMES.put("center", "add");
+  }
+
+  /**
+   * TODO(jgw): This will be moved into EnumAttributeParser as soon as I get
+   * around to building it.
+   */
+  static String getFullyQualifiedEnumName(Enum<?> e) {
+    Class<?> cls = e.getClass();
+    String clsName = cls.getCanonicalName();
+    if (clsName == null) {
+      // A synthesized enum subtype (e.g., Unit$3) will have no canonical name
+      // (yet will not be marked as synthetic). Its superclass will be the
+      // one we want (e.g., Unit).
+      clsName = cls.getSuperclass().getCanonicalName();
+    }
+    return clsName + "." + e.name();
+  }
+
+  public void parse(XMLElement elem, String fieldName, JClassType type,
+      UiBinderWriter writer) throws UnableToCompleteException {
+    // Generate instantiation (requires a 'unit' ctor param).
+    // (Don't generate a ctor for the SplitLayoutPanel; it's implicitly PX).
+    if (type != getSplitLayoutPanelType(writer)) {
+      Unit unit = elem.consumeEnumAttribute("unit", Unit.class);
+      writer.setFieldInitializerAsConstructor(fieldName, writer.getOracle()
+          .findType(DockLayoutPanel.class.getName()),
+          getFullyQualifiedEnumName(unit));
+    }
+
+    // Parse children.
+    for (XMLElement child : elem.consumeChildElements()) {
+      // Make sure the element is one of the fixed set of valid directions.
+      if (!isValidChildElement(elem, child)) {
+        writer.die("In %s, child must be one of {north, south, east, west, center}", elem);
+      }
+
+      // Consume the single widget element.
+      XMLElement widget = child.consumeSingleChildElement();
+      String childFieldName = writer.parseElementToField(widget);
+
+      if (requiresSize(child)) {
+        double size = child.consumeDoubleAttribute("size");
+        writer.addStatement("%s.%s(%s, %f);", fieldName, addMethodName(child),
+            childFieldName, size);
+      } else {
+        writer.addStatement("%s.%s(%s);", fieldName, addMethodName(child),
+            childFieldName);
+      }
+    }
+
+    // Emit the layout() call.
+    writer.addStatement("%s.layout();", fieldName);
+  }
+
+  private String addMethodName(XMLElement elem) {
+    return DOCK_NAMES.get(elem.getLocalName());
+  }
+
+  private JClassType getSplitLayoutPanelType(UiBinderWriter writer)
+      throws UnableToCompleteException {
+    try {
+      return writer.getOracle().getType(SplitLayoutPanel.class.getName());
+    } catch (NotFoundException e) {
+      throw new RuntimeException("Unexpected exception", e);
+    }
+  }
+
+  private boolean isValidChildElement(XMLElement parent, XMLElement child) {
+    return child.getNamespaceUri().equals(parent.getNamespaceUri())
+        && DOCK_NAMES.containsKey(child.getLocalName());
+  }
+
+  private boolean requiresSize(XMLElement elem) {
+    return !elem.getLocalName().equals("center");
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/parsers/StackLayoutPanelParser.java b/user/src/com/google/gwt/uibinder/parsers/StackLayoutPanelParser.java
new file mode 100644
index 0000000..ca773de
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/parsers/StackLayoutPanelParser.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2009 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.uibinder.parsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+import com.google.gwt.user.client.ui.StackLayoutPanel;
+
+/**
+ * Parses {@link StackLayoutPanel} widgets.
+ */
+public class StackLayoutPanelParser implements ElementParser {
+
+  private static final String HEADER_ELEM = "header";
+  private static final String STACK_ELEM = "stack";
+
+  public void parse(XMLElement elem, String fieldName, JClassType type,
+      UiBinderWriter writer) throws UnableToCompleteException {
+    // StackLayoutPanel requires a unit ctor.
+    Unit unit = elem.consumeEnumAttribute("unit", Unit.class);
+    writer.setFieldInitializerAsConstructor(fieldName, writer.getOracle()
+        .findType(StackLayoutPanel.class.getName()), DockLayoutPanelParser
+        .getFullyQualifiedEnumName(unit));
+
+    // Parse children.
+    for (XMLElement child : elem.consumeChildElements()) {
+      // Get the stack element.
+      if (!isElementType(elem, child, STACK_ELEM)) {
+        writer.die("In %s, Only <stack> children are allowed.", elem);
+      }
+
+      XMLElement headerElem = null, widgetElem = null;
+      for (XMLElement stackChild : child.consumeChildElements()) {
+        // Get the header.
+        if (isElementType(elem, stackChild, HEADER_ELEM)) {
+          if (headerElem != null) {
+            writer.die("In %s, Only one <header> allowed per <stack>", elem);
+          }
+          headerElem = stackChild;
+          continue;
+        }
+
+        // Get the widget.
+        if (widgetElem != null) {
+          writer.die("In %s, Only one child widget allowed per <stack>", elem);
+        }
+        widgetElem = stackChild;
+      }
+
+      double size = headerElem.consumeDoubleAttribute("size");
+      XMLElement headerWidgetElem = headerElem.consumeSingleChildElement();
+      String headerFieldName = writer.parseElementToField(headerWidgetElem);
+      String childFieldName = writer.parseElementToField(widgetElem);
+
+      writer.addStatement("%s.add(%s, %s, %f);", fieldName, childFieldName,
+          headerFieldName, size);
+    }
+
+    // Emit the layout() call.
+    writer.addStatement("%s.layout();", fieldName);
+  }
+
+  private boolean isElementType(XMLElement parent, XMLElement child, String type) {
+    return child.getNamespaceUri().equals(parent.getNamespaceUri())
+        && type.equals(child.getLocalName());
+  }
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
index 743ee19..739f365 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
@@ -341,6 +341,7 @@
       // something like that, but in FieldManager.
       fieldName = ("f_" + elem.getLocalName() + (++fieldIndex));
     }
+    fieldName = normalizeFieldName(fieldName);
     fieldManager.registerField(type, fieldName);
     return fieldName;
   }
@@ -874,6 +875,13 @@
     }
   }
 
+  private String normalizeFieldName(String fieldName) {
+    // If a field name has a '.' in it, replace it with '$' to make it a legal
+    // identifier. This can happen with the field names associated with nested
+    // classes.
+    return fieldName.replace('.', '$');
+  }
+
   /**
    * Parse the document element and return the source of the Java class that
    * will implement its UiBinder.
@@ -979,6 +987,9 @@
     addWidgetParser("CellPanel");
     addWidgetParser("CustomButton");
 
+    addWidgetParser("DockLayoutPanel");
+    addWidgetParser("StackLayoutPanel");
+
     addAttributeParser("boolean",
         "com.google.gwt.uibinder.parsers.BooleanAttributeParser");
 
diff --git a/user/src/com/google/gwt/uibinder/rebind/XMLElement.java b/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
index 0ff9000..a1c8055 100644
--- a/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
+++ b/user/src/com/google/gwt/uibinder/rebind/XMLElement.java
@@ -206,6 +206,54 @@
   }
 
   /**
+   * Consumes the given attribute as a double value.
+   * 
+   * @param attr the attribute's full name (including prefix)
+   * @return the attribute's value as a double
+   * @throws UnableToCompleteException
+   */
+  public double consumeDoubleAttribute(String attr)
+      throws UnableToCompleteException {
+    try {
+      return Double.parseDouble(consumeAttribute(attr));
+    } catch (NumberFormatException e) {
+      writer.die(String.format("Error parsing \"%s\" attribute of \"%s\" "
+          + "as a double value", attr, this));
+      return 0; // unreachable line for happy compiler
+    }
+  }
+
+  /**
+   * Consumes the given attribute as an enum value.
+   * 
+   * @param attr the attribute's full name (including prefix)
+   * @param type the enumerated type of which this attribute must be a member
+   * @return the attribute's value
+   * @throws UnableToCompleteException
+   */
+  public <T extends Enum<T>> T consumeEnumAttribute(String attr, Class<T> type)
+      throws UnableToCompleteException {
+    String strValue = consumeAttribute(attr);
+
+    // Get the enum value. Enum.valueOf() throws IAE if the specified string is
+    // not valid.
+    T value = null;
+    try {
+      // Enum.valueOf() doesn't accept null arguments.
+      if (strValue != null) {
+        value = Enum.valueOf(type, strValue);
+      }
+    } catch (IllegalArgumentException e) {
+    }
+
+    if (value == null) {
+      writer.die(String.format("Error parsing \"%s\" attribute of \"%s\" "
+          + "as a %s enum", attr, this, type.getSimpleName()));
+    }
+    return value;
+  }
+
+  /**
    * Consumes all child elements, and returns an HTML interpretation of them.
    * Trailing and leading whitespace is trimmed.
    * <p>
@@ -287,6 +335,23 @@
   }
 
   /**
+   * Consumes the given attribute as an int value.
+   * 
+   * @param attr the attribute's full name (including prefix)
+   * @return the attribute's value as an int
+   * @throws UnableToCompleteException
+   */
+  public int consumeIntAttribute(String attr) throws UnableToCompleteException {
+    try {
+      return Integer.parseInt(consumeAttribute(attr));
+    } catch (NumberFormatException e) {
+      writer.die(String.format("Error parsing \"%s\" attribute of \"%s\" "
+          + "as an int value", attr, this));
+      return 0; // unreachable line for happy compiler
+    }
+  }
+
+  /**
    * Consumes all attributes, and returns a string representing the entire
    * opening tag. E.g., "<div able='baker'>"
    */
@@ -306,7 +371,7 @@
       throws UnableToCompleteException {
     String value = consumeAttribute(name);
     if ("".equals(value)) {
-      writer.die("In %s, missing required attribute name\"%s\"", this, name);
+      writer.die("In %s, missing required attribute name \"%s\"", this, name);
     }
     return value;
   }
diff --git a/user/src/com/google/gwt/user/client/ui/DockLayoutPanel.java b/user/src/com/google/gwt/user/client/ui/DockLayoutPanel.java
index 58e15d3..9c2883f 100644
--- a/user/src/com/google/gwt/user/client/ui/DockLayoutPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/DockLayoutPanel.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style;
 import com.google.gwt.dom.client.Style.Unit;
 import com.google.gwt.layout.client.Layout;
 import com.google.gwt.layout.client.Layout.Layer;
@@ -95,23 +96,69 @@
   }
 
   /**
-   * Adds a widget to the specified edge of the dock. If the widget is already a
-   * child of this panel, this method behaves as though {@link #remove(Widget)}
-   * had already been called.
+   * Adds a widget at the center of the dock. No further widgets may be added
+   * after this one.
    * 
    * @param widget the widget to be added
-   * @param direction the widget's direction in the dock
-   * @param size the child widget's size
-   * 
-   * @throws IllegalArgumentException when adding to the {@link #CENTER} and
-   *           there is already a different widget there
    */
-  public void add(Widget widget, Direction direction, double size) {
-    insert(widget, direction, size, null);
+  @Override
+  public void add(Widget widget) {
+    insert(widget, Direction.CENTER, 0, null);
   }
 
   /**
+   * Adds a widget to the east edge of the dock.
+   * 
+   * @param widget the widget to be added
+   * @param size the child widget's size
+   */
+  public void addEast(Widget widget, double size) {
+    insert(widget, Direction.EAST, size, null);
+  }
+
+  /**
+   * Adds a widget to the north edge of the dock.
+   * 
+   * @param widget the widget to be added
+   * @param size the child widget's size
+   */
+  public void addNorth(Widget widget, double size) {
+    insert(widget, Direction.NORTH, size, null);
+  }
+
+  /**
+   * Adds a widget to the south edge of the dock.
+   * 
+   * @param widget the widget to be added
+   * @param size the child widget's size
+   */
+  public void addSouth(Widget widget, double size) {
+    insert(widget, Direction.SOUTH, size, null);
+  }
+
+  /**
+   * Adds a widget to the west edge of the dock.
+   * 
+   * @param widget the widget to be added
+   * @param size the child widget's size
+   */
+  public void addWest(Widget widget, double size) {
+    insert(widget, Direction.WEST, size, null);
+  }
+
+  /**
+   * Gets the container element associated with the given child widget.
+   * 
+   * <p>
+   * The container element is created by the {@link Layout} class. This should
+   * be used with certain styles, such as {@link Style#setZIndex(int)}, that
+   * must be applied to the container, rather than directly to the child widget.
+   * </p>
+   * 
    * TODO(jgw): Is this really the best way to do this?
+   * 
+   * @param widget the widget whose container element is to be retrieved
+   * @return the widget's container element
    */
   public Element getContainerElementFor(Widget widget) {
     assertIsChild(widget);
@@ -133,46 +180,55 @@
   }
 
   /**
-   * Adds a widget to the specified edge of the dock. If the widget is already a
-   * child of this panel, this method behaves as though {@link #remove(Widget)}
-   * had already been called.
+   * Adds a widget to the east edge of the dock, inserting it before an
+   * existing widget.
    * 
    * @param widget the widget to be added
-   * @param direction the widget's direction in the dock
+   * @param size the child widget's size
    * @param before the widget before which to insert the new child, or
    *          <code>null</code> to append
-   * 
-   * @throws IllegalArgumentException when adding to the {@link #CENTER} and
-   *           there is already a different widget there
    */
-  public void insert(Widget widget, Direction direction, double size,
-      Widget before) {
-    assertIsChild(before);
+  public void insertEast(Widget widget, double size, Widget before) {
+    insert(widget, Direction.EAST, size, before);
+  }
 
-    // Validation.
-    if (before == null) {
-      assert center == null : "No widget may be added after the CENTER widget";
-    } else {
-      assert direction != Direction.CENTER : "A CENTER widget must always be added last";
-    }
+  /**
+   * Adds a widget to the north edge of the dock, inserting it before an
+   * existing widget.
+   * 
+   * @param widget the widget to be added
+   * @param size the child widget's size
+   * @param before the widget before which to insert the new child, or
+   *          <code>null</code> to append
+   */
+  public void insertNorth(Widget widget, double size, Widget before) {
+    insert(widget, Direction.NORTH, size, before);
+  }
 
-    // Detach new child.
-    widget.removeFromParent();
+  /**
+   * Adds a widget to the south edge of the dock, inserting it before an
+   * existing widget.
+   * 
+   * @param widget the widget to be added
+   * @param size the child widget's size
+   * @param before the widget before which to insert the new child, or
+   *          <code>null</code> to append
+   */
+  public void insertSouth(Widget widget, double size, Widget before) {
+    insert(widget, Direction.SOUTH, size, before);
+  }
 
-    // Logical attach.
-    getChildren().add(widget);
-    if (direction == Direction.CENTER) {
-      center = widget;
-    }
-
-    // Physical attach.
-    Layer layer = layout.attachChild(widget.getElement(),
-        (before != null) ? before.getElement() : null, widget);
-    LayoutData data = new LayoutData(direction, size, layer);
-    widget.setLayoutData(data);
-
-    // Adopt.
-    adopt(widget);
+  /**
+   * Adds a widget to the west edge of the dock, inserting it before an
+   * existing widget.
+   * 
+   * @param widget the widget to be added
+   * @param size the child widget's size
+   * @param before the widget before which to insert the new child, or
+   *          <code>null</code> to append
+   */
+  public void insertWest(Widget widget, double size, Widget before) {
+    insert(widget, Direction.WEST, size, before);
   }
 
   public void layout() {
@@ -284,6 +340,46 @@
     return unit;
   }
 
+  /**
+   * Adds a widget to the specified edge of the dock. If the widget is already a
+   * child of this panel, this method behaves as though {@link #remove(Widget)}
+   * had already been called.
+   * 
+   * @param widget the widget to be added
+   * @param direction the widget's direction in the dock
+   * @param before the widget before which to insert the new child, or
+   *          <code>null</code> to append
+   */
+  protected void insert(Widget widget, Direction direction, double size,
+      Widget before) {
+    assertIsChild(before);
+
+    // Validation.
+    if (before == null) {
+      assert center == null : "No widget may be added after the CENTER widget";
+    } else {
+      assert direction != Direction.CENTER : "A CENTER widget must always be added last";
+    }
+
+    // Detach new child.
+    widget.removeFromParent();
+
+    // Logical attach.
+    getChildren().add(widget);
+    if (direction == Direction.CENTER) {
+      center = widget;
+    }
+
+    // Physical attach.
+    Layer layer = layout.attachChild(widget.getElement(),
+        (before != null) ? before.getElement() : null, widget);
+    LayoutData data = new LayoutData(direction, size, layer);
+    widget.setLayoutData(data);
+
+    // Adopt.
+    adopt(widget);
+  }
+
   @Override
   protected void onLoad() {
     layout.onAttach();
@@ -295,6 +391,6 @@
   }
 
   private void assertIsChild(Widget widget) {
-    assert (widget == null) || (widget.getParent() == this) : "TODO";
+    assert (widget == null) || (widget.getParent() == this) : "The specified widget is not a child of this panel";
   }
 }
diff --git a/user/src/com/google/gwt/user/client/ui/SplitLayoutPanel.java b/user/src/com/google/gwt/user/client/ui/SplitLayoutPanel.java
index 46a43e8..db42600 100644
--- a/user/src/com/google/gwt/user/client/ui/SplitLayoutPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/SplitLayoutPanel.java
@@ -212,10 +212,10 @@
   }
 
   @Override
-  public void add(Widget child, Direction direction, double size) {
-    super.add(child, direction, size);
+  public void insert(Widget child, Direction direction, double size, Widget before) {
+    super.insert(child, direction, size, before);
     if (direction != Direction.CENTER) {
-      addSplitter();
+      insertSplitter(before);
     }
   }
 
@@ -251,13 +251,13 @@
     splitter.setMinSize(minSize);
   }
 
-  private void addSplitter() {
+  private void insertSplitter(Widget before) {
     assert getChildren().size() > 0 : "Can't add a splitter before any children";
     assert getCenter() == null : "Can't add a splitter after the CENTER widget";
 
     Widget lastChild = getChildren().get(getChildren().size() - 1);
     LayoutData lastChildLayout = (LayoutData) lastChild.getLayoutData();
-    Splitter splitter;
+    Splitter splitter = null;
     switch (lastChildLayout.direction) {
       case WEST:
         splitter = new HSplitter(lastChild, false);
@@ -273,10 +273,9 @@
         break;
       default:
         assert false : "Unexpected direction";
-        return;
     }
 
-    super.add(splitter, lastChildLayout.direction, SPLITTER_SIZE);
+    super.insert(splitter, lastChildLayout.direction, SPLITTER_SIZE, before);
   }
 
   private Splitter getAssociatedSplitter(Widget child) {