Fixes Issue #1079.
Re-establishes backward compatibility for set/getStyleName, including the
following changes:
+ Revert setStyleName(...) and getStyleName(...)
+ Add explicit get/setStylePrimaryName(..) methods.
+ Define primary style name to be the first token in the className property.
+ Remove the constraint that there always be an explicit primary style name.
+ Added methods get/setStyleDependentName(...) to promote the form
  primary-depdendent for dependent style names.

Patch by: jgw, knorton
Review by: knorton, bruce, scottb



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1277 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/ui/CustomButton.java b/user/src/com/google/gwt/user/client/ui/CustomButton.java
index 2ef524f..da4b6aa 100644
--- a/user/src/com/google/gwt/user/client/ui/CustomButton.java
+++ b/user/src/com/google/gwt/user/client/ui/CustomButton.java
@@ -753,11 +753,11 @@
      */
     if (curFace != newFace) {
       if (curFace != null) {
-        super.removeStyleName(getCSSStyleName());
+        removeStyleDependentName(curFace.getName());
       }
       curFace = newFace;
       setCurrentFaceElement(newFace.getFace());
-      super.addStyleName(getCSSStyleName());
+      addStyleDependentName(curFace.getName());
     }
   }
 
@@ -806,15 +806,6 @@
     };
   }
 
-  /**
-   * Gets the modified style name.
-   * 
-   * @return the modified style name
-   */
-  private String getCSSStyleName() {
-    return getStyleName() + "-" + curFace.getName();
-  }
-
   private Face getFaceFromID(int id) {
     switch (id) {
       case DOWN:
diff --git a/user/src/com/google/gwt/user/client/ui/DisclosurePanel.java b/user/src/com/google/gwt/user/client/ui/DisclosurePanel.java
index adf7868..8b35d4a 100644
--- a/user/src/com/google/gwt/user/client/ui/DisclosurePanel.java
+++ b/user/src/com/google/gwt/user/client/ui/DisclosurePanel.java
@@ -153,9 +153,9 @@
   // Stylename constants.
   private static final String STYLENAME_DEFAULT = "gwt-DisclosurePanel";
 
-  private static final String STYLENAME_SUFFIX_OPEN = "-open";
+  private static final String STYLENAME_SUFFIX_OPEN = "open";
 
-  private static final String STYLENAME_SUFFIX_CLOSED = "-closed";
+  private static final String STYLENAME_SUFFIX_CLOSED = "closed";
 
   private static final String STYLENAME_HEADER = "header";
 
@@ -417,14 +417,12 @@
   }
 
   private void setContentDisplay() {
-    // UIObject#replaceStylename has been suggested and would replace this.
-    String primaryStyleName = getStyleName();
     if (isOpen) {
-      removeStyleName(primaryStyleName + STYLENAME_SUFFIX_CLOSED);
-      addStyleName(primaryStyleName + STYLENAME_SUFFIX_OPEN);
+      removeStyleDependentName(STYLENAME_SUFFIX_CLOSED);
+      addStyleDependentName(STYLENAME_SUFFIX_OPEN);
     } else {
-      removeStyleName(primaryStyleName + STYLENAME_SUFFIX_OPEN);
-      addStyleName(primaryStyleName + STYLENAME_SUFFIX_CLOSED);
+      removeStyleDependentName(STYLENAME_SUFFIX_OPEN);
+      addStyleDependentName(STYLENAME_SUFFIX_CLOSED);
     }
 
     if (content != null) {
diff --git a/user/src/com/google/gwt/user/client/ui/HTMLTable.java b/user/src/com/google/gwt/user/client/ui/HTMLTable.java
index 2662d3b..f35ca86 100644
--- a/user/src/com/google/gwt/user/client/ui/HTMLTable.java
+++ b/user/src/com/google/gwt/user/client/ui/HTMLTable.java
@@ -13,7 +13,6 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-
 package com.google.gwt.user.client.ui;
 
 import com.google.gwt.user.client.DOM;
@@ -67,16 +66,29 @@
     }
 
     /**
-     * Gets a style from a specified row.
+     * Gets the style of a specified cell.
      * 
-     * @param row the row of the cell which the style while be added to
-     * @param column the column of the cell which the style will be added to
+     * @param row the cell's row
+     * @param column the cell's column
      * @see UIObject#getStyleName()
      * @return returns the style name
      * @throws IndexOutOfBoundsException
      */
     public String getStyleName(int row, int column) {
-      return DOM.getElementProperty(getElement(row, column), "className");
+      return UIObject.getStyleName(getElement(row, column));
+    }
+
+    /**
+     * Gets the primary style of a specified cell.
+     * 
+     * @param row the cell's row
+     * @param column the cell's column
+     * @see UIObject#getStylePrimaryName()
+     * @return returns the style name
+     * @throws IndexOutOfBoundsException
+     */
+    public String getStylePrimaryName(int row, int column) {
+      return UIObject.getStylePrimaryName(getElement(row, column));
     }
 
     /**
@@ -165,10 +177,21 @@
      */
     public void setStyleName(int row, int column, String styleName) {
       prepareCell(row, column);
-      Element elem = getCellElement(bodyElem, row, column);
-      // IE uses attribute "className", FireFox uses attribute "class", so
-      // avoiding the problem by using properties instead.
-      DOM.setElementProperty(elem, "className", styleName);
+      UIObject.setStyleName(getCellElement(bodyElem, row, column), styleName);
+    }
+
+    /**
+     * Sets the primary style name associated with the specified cell.
+     * 
+     * @param row the row of the cell whose style name is to be set
+     * @param column the column of the cell whose style name is to be set
+     * @param styleName the new style name
+     * @see UIObject#setStylePrimaryName(String)
+     * @throws IndexOutOfBoundsException
+     */
+    public void setStylePrimaryName(int row, int column, String styleName) {
+      UIObject.setStylePrimaryName(getCellElement(bodyElem, row, column),
+        styleName);
     }
 
     /**
@@ -310,7 +333,7 @@
     /**
      * Adds a style to the specified column.
      * 
-     * @param col the col to which the style while be added
+     * @param col the col to which the style will be added
      * @param styleName the style name to be added
      * @see UIObject#addStyleName(String)
      * @throws IndexOutOfBoundsException
@@ -320,21 +343,33 @@
     }
 
     /**
-     * Gets a style from a specified column.
+     * Gets the style of the specified column.
      * 
-     * @param column the column to which the style while be added
+     * @param column the column to be queried
+     * @return the style name
      * @see UIObject#getStyleName()
      * @throws IndexOutOfBoundsException
-     * @return the style name
      */
     public String getStyleName(int column) {
-      return DOM.getElementProperty(ensureColumn(column), "className");
+      return UIObject.getStyleName(ensureColumn(column));
+    }
+
+    /**
+     * Gets the primary style of the specified column.
+     * 
+     * @param column the column to be queried
+     * @return the style name
+     * @see UIObject#getStylePrimaryName()
+     * @throws IndexOutOfBoundsException
+     */
+    public String getStylePrimaryName(int column) {
+      return UIObject.getStylePrimaryName(ensureColumn(column));
     }
 
     /**
      * Removes a style from the specified column.
      * 
-     * @param column the column to which the style while be removed
+     * @param column the column from which the style will be removed
      * @param styleName the style name to be removed
      * @see UIObject#removeStyleName(String)
      * @throws IndexOutOfBoundsException
@@ -352,7 +387,19 @@
      * @throws IndexOutOfBoundsException
      */
     public void setStyleName(int column, String styleName) {
-      UIObject.resetStyleName(ensureColumn(column), styleName);
+      UIObject.setStyleName(ensureColumn(column), styleName);
+    }
+
+    /**
+     * Sets the primary style name associated with the specified column.
+     * 
+     * @param column the column whose style name is to be set
+     * @param styleName the new style name
+     * @see UIObject#setStylePrimaryName(String)
+     * @throws IndexOutOfBoundsException
+     */
+    public void setStylePrimaryName(int column, String styleName) {
+      UIObject.setStylePrimaryName(ensureColumn(column), styleName);
     }
 
     /**
@@ -395,7 +442,7 @@
     /**
      * Adds a style to the specified row.
      * 
-     * @param row the row to which the style while be added
+     * @param row the row to which the style will be added
      * @param styleName the style name to be added
      * @see UIObject#addStyleName(String)
      * @throws IndexOutOfBoundsException
@@ -417,15 +464,27 @@
     }
 
     /**
-     * Gets a style from a specified row.
+     * Gets the style of the specified row.
      * 
-     * @param row the row to which the style while be added
+     * @param row the row to be queried
+     * @return the style name
      * @see UIObject#getStyleName()
      * @throws IndexOutOfBoundsException
-     * @return the style name
      */
     public String getStyleName(int row) {
-      return DOM.getElementProperty(getElement(row), "className");
+      return UIObject.getStyleName(getElement(row));
+    }
+
+    /**
+     * Gets the primary style of the specified row.
+     * 
+     * @param row the row to be queried
+     * @return the style name
+     * @see UIObject#getStylePrimaryName()
+     * @throws IndexOutOfBoundsException
+     */
+    public String getStylePrimaryName(int row) {
+      return UIObject.getStylePrimaryName(getElement(row));
     }
 
     /**
@@ -443,7 +502,7 @@
     /**
      * Removes a style from the specified row.
      * 
-     * @param row the row to which the style while be removed
+     * @param row the row from which the style will be removed
      * @param styleName the style name to be removed
      * @see UIObject#removeStyleName(String)
      * @throws IndexOutOfBoundsException
@@ -461,7 +520,19 @@
      * @throws IndexOutOfBoundsException
      */
     public void setStyleName(int row, String styleName) {
-      UIObject.resetStyleName(ensureElement(row), styleName);
+      UIObject.setStyleName(ensureElement(row), styleName);
+    }
+
+    /**
+     * Sets the primary style name associated with the specified row.
+     * 
+     * @param row the row whose style name is to be set
+     * @param styleName the new style name
+     * @see UIObject#setStylePrimaryName(String)
+     * @throws IndexOutOfBoundsException
+     */
+    public void setStylePrimaryName(int row, String styleName) {
+      UIObject.setStylePrimaryName(ensureElement(row), styleName);
     }
 
     /**
diff --git a/user/src/com/google/gwt/user/client/ui/TextBoxBase.java b/user/src/com/google/gwt/user/client/ui/TextBoxBase.java
index f701041..241b1cd 100644
--- a/user/src/com/google/gwt/user/client/ui/TextBoxBase.java
+++ b/user/src/com/google/gwt/user/client/ui/TextBoxBase.java
@@ -251,11 +251,11 @@
    */
   public void setReadOnly(boolean readOnly) {
     DOM.setElementPropertyBoolean(getElement(), "readOnly", readOnly);
-    String readOnlyStyle = getStyleName() + "-readonly";
+    String readOnlyStyle = "readonly";
     if (readOnly) {
-      addStyleName(readOnlyStyle);
+      addStyleDependentName(readOnlyStyle);
     } else {
-      removeStyleName(readOnlyStyle);
+      removeStyleDependentName(readOnlyStyle);
     }
   }
 
diff --git a/user/src/com/google/gwt/user/client/ui/UIObject.java b/user/src/com/google/gwt/user/client/ui/UIObject.java
index ef55535..62d8115 100644
--- a/user/src/com/google/gwt/user/client/ui/UIObject.java
+++ b/user/src/com/google/gwt/user/client/ui/UIObject.java
@@ -59,10 +59,10 @@
  * <p>
  * Every <code>UIObject</code> has a <i>primary style name</i> that
  * identifies the key CSS style rule that should always be applied to it. Use
- * {@link #setStyleName(String)} to specify an object's primary style name. In
- * most cases, the primary style name is set in a widget's constructor and never
- * changes again during execution. In the case that no primary style name is
- * specified, it defaults to <code>gwt-nostyle</code>.
+ * {@link #setStylePrimaryName(String)} to specify an object's primary style
+ * name. In most cases, the primary style name is set in a widget's constructor
+ * and never changes again during execution. In the case that no primary style
+ * name is specified, it defaults to the first style name that is added.
  * </p>
  * 
  * <p>
@@ -87,8 +87,6 @@
   private static final String NULL_HANDLE_MSG = "Null widget handle. If you "
       + "are creating a composite, ensure that initWidget() has been called.";
 
-  private static final String STYLE_EMPTY = "gwt-nostyle";
-
   public static native boolean isVisible(Element elem) /*-{
     return (elem.style.display != 'none');
   }-*/;
@@ -98,32 +96,50 @@
   }-*/;
 
   /**
-   * Sets the object's primary style name and updates all dependent style names.
+   * Gets all of the element's style names, as a space-separated list.
    * 
-   * @param elem the element whose style is to be reset
-   * @param style the new primary style name
-   * @see #setStyleName(Element, String, boolean)
+   * @param elem the element whose style is to be retrieved
+   * @return the objects's space-separated style names
    */
-  protected static void resetStyleName(Element elem, String style) {
-    if (elem == null) {
-      throw new RuntimeException(NULL_HANDLE_MSG);
-    }
-
-    // Style names cannot contain leading or trailing whitespace, and cannot
-    // legally be empty.
-    style = style.trim();
-    if (style.length() == 0) {
-      throw new IllegalArgumentException(EMPTY_STYLENAME_MSG);
-    }
-
-    ensurePrimaryStyleName(elem);
-    updatePrimaryAndDependentStyleNames(elem, style);
+  protected static String getStyleName(Element elem) {
+    return DOM.getElementProperty(elem, "className");
   }
 
   /**
-   * This convenience method adds or removes a secondary style name to the
-   * primary style name for a given element. Set {@link #setStyleName(String)}
-   * for a description of how primary and secondary style names are used.
+   * Gets the element's primary style name.
+   * 
+   * @param elem the element whose primary style name is to be retrieved
+   * @return the element's primary style name
+   */
+  protected static String getStylePrimaryName(Element elem) {
+    String fullClassName = getStyleName(elem);
+
+    // The primary style name is always the first token of the full CSS class
+    // name. There can be no leading whitespace in the class name, so it's not
+    // necessary to trim() it.
+    int spaceIdx = fullClassName.indexOf(' ');
+    if (spaceIdx >= 0) {
+      return fullClassName.substring(0, spaceIdx);
+    }
+    return fullClassName;
+  }
+
+  /**
+   * Clears all of the element's style names and sets it to the given style.
+   * 
+   * @param elem the element whose style is to be modified
+   * @param styleName the new style name
+   */
+  protected static void setStyleName(Element elem, String styleName) {
+    DOM.setElementProperty(elem, "className", styleName);
+  }
+
+  /**
+   * This convenience method adds or removes a style name for a given element.
+   * This method is typically used to add and remove secondary style names, but
+   * it can be used to remove primary stylenames as well, but that is not
+   * recommended. See {@link #setStyleName(String)} for a description of how
+   * primary and secondary style names are used.
    * 
    * @param elem the element whose style is to be modified
    * @param style the secondary style name to be added or removed
@@ -141,14 +157,8 @@
     }
 
     // Get the current style string.
-    String oldStyle = ensurePrimaryStyleName(elem);
-    int idx;
-    if (oldStyle == null) {
-      idx = -1;
-      oldStyle = "";
-    } else {
-      idx = oldStyle.indexOf(style);
-    }
+    String oldStyle = getStyleName(elem);
+    int idx = oldStyle.indexOf(style);
 
     // Calculate matching index.
     while (idx != -1) {
@@ -174,72 +184,93 @@
     } else {
       // Don't try to remove the style if it's not there.
       if (idx != -1) {
-        if (idx == 0) {
-          // You can't remove the base (i.e. the first) style name.
-          throw new IllegalArgumentException("Cannot remove base style name");
+        // Get the leading and trailing parts, without the removed name.
+        String begin = oldStyle.substring(0, idx).trim();
+        String end = oldStyle.substring(idx + style.length()).trim();
+
+        // Some contortions to make sure we don't leave extra spaces.
+        String newClassName;
+        if (begin.length() == 0) {
+          newClassName = end;
+        } else if (end.length() == 0) {
+          newClassName = begin;
+        } else {
+          newClassName = begin + " " + end;
         }
-        String begin = oldStyle.substring(0, idx);
-        String end = oldStyle.substring(idx + style.length());
-        DOM.setElementProperty(elem, "className", begin + end);
+
+        DOM.setElementProperty(elem, "className", newClassName);
       }
     }
   }
 
   /**
-   * Ensure that the root element has a primary style name. If one is not
-   * already present, then it is assigned the default style name.
+   * Sets the element's primary style name and updates all dependent style
+   * names.
    * 
-   * @return the primary style name
+   * @param elem the element whose style is to be reset
+   * @param style the new primary style name
+   * @see #setStyleName(Element, String, boolean)
    */
-  private static String ensurePrimaryStyleName(Element elem) {
-    String className = DOM.getElementProperty(elem, "className").trim();
-
-    if ("".equals(className)) {
-      className = STYLE_EMPTY;
-      DOM.setElementProperty(elem, "className", className);
+  protected static void setStylePrimaryName(Element elem, String style) {
+    if (elem == null) {
+      throw new RuntimeException(NULL_HANDLE_MSG);
     }
-    
-    return className;
+
+    // Style names cannot contain leading or trailing whitespace, and cannot
+    // legally be empty.
+    style = style.trim();
+    if (style.length() == 0) {
+      throw new IllegalArgumentException(EMPTY_STYLENAME_MSG);
+    }
+
+    updatePrimaryAndDependentStyleNames(elem, style);
   }
 
   /**
    * Replaces all instances of the primary style name with newPrimaryStyleName.
    */
-  private static native void updatePrimaryAndDependentStyleNames(Element elem, String newStyle) /*-{
-    var className = elem.className;
-
-    var spaceIdx = className.indexOf(' ');
-    if (spaceIdx >= 0) {
-      // Get the old base style name from the beginning of the className.
-      var oldStyle = className.substring(0, spaceIdx);
-
-      // Replace oldStyle with newStyle. We have to do this by hand because
-      // there is no String.replaceAll() and String.replace() takes a regex,
-      // which we can't guarantee is safe on arbitrary class names.
-      var newClassName = '', curIdx = 0;
-      while (true) {
-        var idx = className.indexOf(oldStyle, curIdx);
-        if (idx == -1) {
-          newClassName += className.substring(curIdx);
-          break;
-        }
-
-        newClassName += className.substring(curIdx, idx);
-        newClassName += newStyle;
-        curIdx = idx + oldStyle.length;
-      }
-
-      elem.className = newClassName;
-    } else {
-      // There was no space, and therefore only one class name, which we can
-      // simply clobber.
-      elem.className = newStyle;
+  private static native void updatePrimaryAndDependentStyleNames(Element elem,
+      String newPrimaryStyle) /*-{
+    var classes = elem.className.split(/\s+/);
+    if (!classes) {
+      return;
     }
+    
+    var oldPrimaryStyle = classes[0];
+    var oldPrimaryStyleLen = oldPrimaryStyle.length;
+   
+    classes[0] = newPrimaryStyle;
+    for (var i = 1, n = classes.length; i < n; i++) {
+      var name = classes[i];
+      if (name.length > oldPrimaryStyleLen
+          && name.charAt(oldPrimaryStyleLen) == '-'
+          && name.indexOf(oldPrimaryStyle) == 0) {
+        classes[i] = newPrimaryStyle + name.substring(oldPrimaryStyleLen);
+      }
+    }
+    elem.className = classes.join(" ");
   }-*/;
 
   private Element element;
 
   /**
+   * Adds a dependent style name by specifying the style name's suffix. The
+   * actual form of the style name that is added is:
+   * 
+   * <pre class="code">
+   * getStylePrimaryName() + '-' + styleSuffix
+   * </pre>
+   * 
+   * @param styleSuffix the suffix of the dependent style to be added.
+   * @see #setStylePrimaryName(String)
+   * @see #removeStyleDependentName(String)
+   * @see #addStyleName(String)
+   */
+  public void addStyleDependentName(String styleSuffix) {
+    addStyleName(getStylePrimaryName() + '-' + styleSuffix);
+  }
+
+  /**
    * Adds a secondary or dependent style name to this object. A secondary style
    * name is an additional style name that is, in HTML/CSS terms, included as a
    * space-separated token in the value of the CSS <code>class</code>
@@ -248,10 +279,11 @@
    * <p>
    * The most important use for this method is to add a special kind of
    * secondary style name called a <i>dependent style name</i>. To add a
-   * dependent style name, prefix the 'style' argument with the result of
-   * {@link #getStyleName()}. For example, suppose the primary style name is
-   * <code>gwt-TextBox</code>. If the following method is called as
-   * <code>obj.setReadOnly(true)</code>:
+   * dependent style name, use {@link #setStyleDependentName(String)}, which
+   * will prefix the 'style' argument with the result of
+   * {@link #getStylePrimaryName()} (followed by a '-'). For example, suppose
+   * the primary style name is <code>gwt-TextBox</code>. If the following
+   * method is called as <code>obj.setReadOnly(true)</code>:
    * </p>
    * 
    * <pre class="code">
@@ -259,12 +291,12 @@
    *   isReadOnlyMode = readOnly;
    *   
    *   // Create a dependent style name.
-   *   String readOnlyStyle = getStyleName() + "-readonly";
+   *   String readOnlyStyle = "readonly";
    *    
    *   if (readOnly) {
-   *     addStyleName(readOnlyStyle);
+   *     addStyleDependentName(readOnlyStyle);
    *   } else {
-   *     removeStyleName(readOnlyStyle);
+   *     removeStyleDependentName(readOnlyStyle);
    *   }
    * }</pre>
    * 
@@ -280,7 +312,8 @@
    * }
    * 
    * // This rule is based on a dependent style name that is only active
-   * // when the widget has called addStyleName(getStyleName() + "-readonly"). 
+   * // when the widget has called addStyleName(getStylePrimaryName() +
+   * // "-readonly").
    * .gwt-TextBox-readonly {
    *   background-color: lightgrey;
    *   border: none;
@@ -292,11 +325,11 @@
    * if the primary style name changed due to the following call:
    * </p>
    * 
-   * <pre class="code">setStyleName("my-TextThingy");</pre>
+   * <pre class="code">setStylePrimaryName("my-TextThingy");</pre>
    * 
    * <p>
-   * then the object would be re-associated with style rules below rather than
-   * those above:
+   * then the object would be re-associated with following style rules, removing
+   * those that were shown above.
    * </p>
    * 
    * <pre class="code">
@@ -372,6 +405,18 @@
   }
 
   /**
+   * Gets all of the object's style names, as a space-separated list. If you
+   * wish to retrieve only the primary style name, call
+   * {@link #getStylePrimaryName()}.
+   * 
+   * @return the objects's space-separated style names
+   * @see #getStylePrimaryName()
+   */
+  public String getStyleName() {
+    return getStyleName(getStyleElement());
+  }
+
+  /**
    * Gets the primary style name associated with the object.
    * 
    * @return the object's primary style name
@@ -379,17 +424,8 @@
    * @see #addStyleName(String)
    * @see #removeStyleName(String)
    */
-  public String getStyleName() {
-    String fullClassName = ensurePrimaryStyleName(getStyleElement());
-
-    // The base style name is always the first token of the full CSS class
-    // name. There can be no leading whitespace in the class name, so it's not
-    // necessary to trim() it.
-    int spaceIdx = fullClassName.indexOf(' ');
-    if (spaceIdx >= 0) {
-      return fullClassName.substring(0, spaceIdx);
-    }
-    return fullClassName;
+  public String getStylePrimaryName() {
+    return getStylePrimaryName(getStyleElement());
   }
 
   /**
@@ -412,7 +448,21 @@
   }
 
   /**
-   * Removes a secondary style name.
+   * Removes a dependent style name by specifying the style name's suffix.
+   * 
+   * @param styleSuffix the suffix of the dependent style to be removed
+   * @see #setStylePrimaryName(Element, String)
+   * @see #addStyleDependentName(String)
+   * @see #addStyleName(String)
+   */
+  public void removeStyleDependentName(String styleSuffix) {
+    removeStyleName(getStylePrimaryName() + '-' + styleSuffix);
+  }
+
+  /**
+   * Removes a style name. This method is typically used to remove secondary
+   * style names, but it can be used to remove primary stylenames as well. That
+   * use is not recommended.
    * 
    * @param style the secondary style name to be removed
    * @see #addStyleName(String)
@@ -430,8 +480,7 @@
   public void setHeight(String height) {
     // This exists to deal with an inconsistency in IE's implementation where
     // it won't accept negative numbers in length measurements
-    assert extractLengthValue(height.trim().toLowerCase()) >= 0 :
-      "CSS heights should not be negative";
+    assert extractLengthValue(height.trim().toLowerCase()) >= 0 : "CSS heights should not be negative";
     DOM.setStyleAttribute(element, "height", height);
   }
 
@@ -464,14 +513,26 @@
   }
 
   /**
+   * Clears all of the object's style names and sets it to the given style. You
+   * should normally use {@link #setStylePrimaryName(String)} unless you wish to
+   * explicitly remove all existing styles.
+   * 
+   * @param style the new style name
+   * @see #setStylePrimaryName(String)
+   */
+  public void setStyleName(String style) {
+    setStyleName(getStyleElement(), style);
+  }
+
+  /**
    * Sets the object's primary style name and updates all dependent style names.
    * 
    * @param style the new primary style name
    * @see #addStyleName(String)
    * @see #removeStyleName(String)
    */
-  public void setStyleName(String style) {
-    resetStyleName(getStyleElement(), style);
+  public void setStylePrimaryName(String style) {
+    setStylePrimaryName(getStyleElement(), style);
   }
 
   /**
@@ -507,8 +568,7 @@
   public void setWidth(String width) {
     // This exists to deal with an inconsistency in IE's implementation where
     // it won't accept negative numbers in length measurements
-    assert extractLengthValue(width.trim().toLowerCase()) >= 0 :
-      "CSS widths should not be negative";
+    assert extractLengthValue(width.trim().toLowerCase()) >= 0 : "CSS widths should not be negative";
     DOM.setStyleAttribute(element, "width", width);
   }
 
@@ -580,10 +640,6 @@
     }
 
     this.element = elem;
-
-    // We do not actually force the creation of a primary style name here.
-    // Instead, we do it lazily -- when it is aboslutely required -- 
-    // in getStyleName(), addStyleName(), and removeStyleName().
   }
 
   /**
@@ -602,7 +658,7 @@
       return parseFloat(s);
     }
   }-*/;
-  
+
   private native void replaceNode(Element node, Element newNode) /*-{
     var p = node.parentNode;
     if (!p) {
diff --git a/user/src/com/google/gwt/user/client/ui/impl/AbstractItemPickerImpl.java b/user/src/com/google/gwt/user/client/ui/impl/AbstractItemPickerImpl.java
index 0386dc7..049eae6 100644
--- a/user/src/com/google/gwt/user/client/ui/impl/AbstractItemPickerImpl.java
+++ b/user/src/com/google/gwt/user/client/ui/impl/AbstractItemPickerImpl.java
@@ -76,7 +76,7 @@
     }
   }
 
-  private static final String STYLENAME_DEPENDENT_SELECTED = "-selected";
+  private static final String STYLENAME_DEPENDENT_SELECTED = "selected";
   private static final String STYLENAME_PRIMARY_ITEM = "item";
 
   final Element body;
@@ -210,13 +210,13 @@
 
     // Remove "selected" style from the item.
     if (selectedItem != null) {
-      selectedItem.removeStyleName(selectedItem.getStyleName() + STYLENAME_DEPENDENT_SELECTED);
+      selectedItem.removeStyleDependentName(STYLENAME_DEPENDENT_SELECTED);
     }
 
     // Add the "selected" style to the item.
     selectedItem = item;
     if (selectedItem != null) {
-      selectedItem.addStyleName(selectedItem.getStyleName() + STYLENAME_DEPENDENT_SELECTED);
+      selectedItem.addStyleDependentName(STYLENAME_DEPENDENT_SELECTED);
     }
   }
 
diff --git a/user/test/com/google/gwt/user/client/ui/CustomButtonTest.java b/user/test/com/google/gwt/user/client/ui/CustomButtonTest.java
index c016748..5593cfa 100644
--- a/user/test/com/google/gwt/user/client/ui/CustomButtonTest.java
+++ b/user/test/com/google/gwt/user/client/ui/CustomButtonTest.java
@@ -41,7 +41,7 @@
     ToggleButton b = new ToggleButton("up", "down");
     b.setStyleName("random");
     b.setDown(true);
-    assertEquals(b.getStyleName(), "random");
+    assertEquals(b.getStylePrimaryName(), "random");
 
     Map faces = new HashMap();
     faces.put("downDisabled", b.getDownDisabledFace());
@@ -56,8 +56,8 @@
       Map.Entry entry = (Entry) entries.next();
       Face f = (Face) entry.getValue();
       b.setCurrentFace(f);
-      assertEquals("random random-" + f.getName(), DOM.getElementProperty(
-          b.getElement(), "className").trim());
+      assertEquals("random", b.getStylePrimaryName());
+      assertTrue(b.getStyleName().indexOf("random-" + f.getName()) != -1);
     }
 
     entries = faces.entrySet().iterator();
@@ -154,5 +154,4 @@
     assertFalse(b.isDown());
     assertFalse(b.isEnabled());
   }
-
 }
diff --git a/user/test/com/google/gwt/user/client/ui/GridTest.java b/user/test/com/google/gwt/user/client/ui/GridTest.java
index 41d02ad..44ed1f1 100644
--- a/user/test/com/google/gwt/user/client/ui/GridTest.java
+++ b/user/test/com/google/gwt/user/client/ui/GridTest.java
@@ -65,16 +65,16 @@
     columns.addStyleName(0, "a");
     assertEquals("base a", columns.getStyleName(0));
     columns.addStyleName(0, "b");
-    assertEquals("base a b", getNormalizedStyleName(columns, 0));
+    assertEquals("base a b", columns.getStyleName(0));
     columns.addStyleName(0, "c");
-    assertEquals("base a b c", getNormalizedStyleName(columns, 0));
+    assertEquals("base a b c", columns.getStyleName(0));
     // Remove first.
     columns.removeStyleName(0, "a");
-    assertEquals("base b c", getNormalizedStyleName(columns, 0));
+    assertEquals("base b c", columns.getStyleName(0));
 
     // Remove last.
     columns.removeStyleName(0, "c");
-    assertEquals("base b", getNormalizedStyleName(columns, 0));
+    assertEquals("base b", columns.getStyleName(0));
 
     // Only one column should be created.
     Element e = DOM.getChild(r.getElement(), 0);
@@ -119,9 +119,4 @@
     assertEquals(3, r.getRowCount());
     assertEquals(2, r.getColumnCount());
   }
-
-  private String getNormalizedStyleName(Grid.ColumnFormatter formatter,
-      int index) {
-    return formatter.getStyleName(index).replaceAll("  ", " ").trim();
-  }
 }
diff --git a/user/test/com/google/gwt/user/client/ui/ListBoxTest.java b/user/test/com/google/gwt/user/client/ui/ListBoxTest.java
index ced0ac0..c68c69a 100644
--- a/user/test/com/google/gwt/user/client/ui/ListBoxTest.java
+++ b/user/test/com/google/gwt/user/client/ui/ListBoxTest.java
@@ -16,7 +16,6 @@
 package com.google.gwt.user.client.ui;
 
 import com.google.gwt.junit.client.GWTTestCase;
-import com.google.gwt.user.client.DOM;
 
 /**
  * Tests {@link ListBox}. Needs many, many more tests.
@@ -67,26 +66,21 @@
 
   public void testSetStyleNames() {
     ListBox box = new ListBox();
-    try {
-      box.removeStyleName("gwt-ListBox");
-      fail("Should have thrown illegal argument exception");
-    } catch (IllegalArgumentException e) {
-    }
 
     // Check subset problems.
     box.addStyleName("superset");
     box.addStyleName("super");
-    assertEquals("gwt-ListBox superset super", getNormalizedStyleName(box));
+    assertEquals("gwt-ListBox superset super", box.getStyleName());
 
     // Remove a style that doesn't exist.
     box.removeStyleName("sup");
-    assertEquals("gwt-ListBox superset super", getNormalizedStyleName(box));
+    assertEquals("gwt-ListBox superset super", box.getStyleName());
     box.removeStyleName("super");
-    assertEquals("gwt-ListBox superset", getNormalizedStyleName(box));
+    assertEquals("gwt-ListBox superset", box.getStyleName());
     box.addStyleName("two styles");
-    assertEquals("gwt-ListBox superset two styles", getNormalizedStyleName(box));
+    assertEquals("gwt-ListBox superset two styles", box.getStyleName());
     box.removeStyleName("superset");
-    assertEquals("gwt-ListBox two styles", getNormalizedStyleName(box));
+    assertEquals("gwt-ListBox two styles", box.getStyleName());
     box.removeStyleName("two styles");
     try {
       box.addStyleName("");
@@ -97,7 +91,7 @@
     box.addStyleName("superset");
     box.addStyleName("two");
     box.addStyleName("styles");
-    assertEquals("gwt-ListBox superset two styles", getNormalizedStyleName(box));
+    assertEquals("gwt-ListBox superset two styles", box.getStyleName());
   }
 
   public void testText() {
@@ -164,9 +158,4 @@
       assertEquals("item text", box.getItemText(1));
     }
   }
-
-  private String getNormalizedStyleName(ListBox box) {
-    return DOM.getElementProperty(box.getElement(), "className").replaceAll("  ", " ").trim();
-  }
-
 }
diff --git a/user/test/com/google/gwt/user/client/ui/UIObjectTest.java b/user/test/com/google/gwt/user/client/ui/UIObjectTest.java
index f6f4c7e..86ad0d6 100644
--- a/user/test/com/google/gwt/user/client/ui/UIObjectTest.java
+++ b/user/test/com/google/gwt/user/client/ui/UIObjectTest.java
@@ -23,59 +23,26 @@
  */
 public class UIObjectTest extends GWTTestCase {
 
-  public String getModuleName() {
-    return "com.google.gwt.user.User";
-  }
-
   static class MyObject extends UIObject {
     MyObject() {
       setElement(DOM.createDiv());
     }
   }
 
-  public void testEmpty() {
-    MyObject o = new MyObject();
-
-    assertEquals("gwt-nostyle", o.getStyleName());
-    doStuff(o);
-    assertEquals("gwt-nostyle", o.getStyleName());
+  public String getModuleName() {
+    return "com.google.gwt.user.User";
   }
 
-  public void testNormal() {
-    // Test the basic set/get case.
+  public void testAccidentalPrimary() {
     MyObject o = new MyObject();
-
-    o.setStyleName("baseStyle");
-
-    assertEquals("baseStyle", o.getStyleName());
-    doStuff(o);
-    assertEquals("baseStyle", o.getStyleName());
-  }
-
-  public void testAddStyleBeforeSet() {
-    MyObject o = new MyObject();
-
-    // Test that adding a style name before calling setStyleName() causes the
-    // gwt-nostyle class to get added.
-    o.addStyleName("userStyle");
-    assertStartsWithClass(o, "gwt-nostyle");
-    assertContainsClass(o, "userStyle");
-    o.removeStyleName("userStyle");
-    assertDoesNotContainClass(o, "userStyle");
-
-    // getStyleName() should still be "gwt-nostyle".
-    assertEquals("gwt-nostyle", o.getStyleName());
-
-    doStuff(o);
-
-    assertStartsWithClass(o, "gwt-nostyle");
-    assertEquals("gwt-nostyle", o.getStyleName());
+    o.addStyleName("accidentalPrimary");
+    assertEquals("accidentalPrimary", o.getStylePrimaryName());
   }
 
   public void testAddAndRemoveEmptyStyleName() {
     MyObject o = new MyObject();
 
-    o.setStyleName("base");
+    o.setStylePrimaryName("primary");
     try {
       o.addStyleName("");
       fail();
@@ -104,77 +71,115 @@
       // This *should* throw.
     }
 
-    assertEquals("base", o.getStyleName());
+    assertEquals("primary", o.getStylePrimaryName());
   }
 
-  public void testSetEmptyBaseStyleName() {
+  public void testNormal() {
+    // Test the basic set/get case.
+    MyObject o = new MyObject();
+    o.setStylePrimaryName("primaryStyle");
+
+    // Note: getStyleName() explicitly returns the className attribute, so it
+    // doesn't guarantee that there aren't leading or trailing spaces.
+    assertEquals("primaryStyle", o.getStyleName());
+    doDependentAndSecondaryStyleTest(o);
+    assertEquals("primaryStyle", o.getStyleName());
+  }
+
+  public void testSetEmptyPrimaryStyleName() {
     MyObject o = new MyObject();
     try {
-      o.setStyleName("");
+      o.setStylePrimaryName("");
       fail();
     } catch (IllegalArgumentException e) {
       // This *should* throw.
     }
 
     try {
-      o.setStyleName(" ");
+      o.setStylePrimaryName(" ");
       fail();
     } catch (IllegalArgumentException e) {
       // This *should* throw.
     }
   }
 
-  public void testRemoveBaseStyleName() {
+  public void testSetStyleNameNormalization() {
     MyObject o = new MyObject();
-    o.setStyleName("base");
 
-    try {
-      o.removeStyleName("base");
-      fail();
-    } catch (IllegalArgumentException e) {
-      // This *should* throw.
+    o.setStylePrimaryName(" one ");
+    o.addStyleName("  two  ");
+    o.addStyleName("\tthree\t");
+
+    assertEquals("one two three", o.getStyleName());
+  }
+
+  public void testSetStylePrimaryName() {
+    MyObject o = new MyObject();
+    o.setStylePrimaryName("gwt");
+    o.addStyleDependentName("dependent");
+    o.addStyleName("i-heart-gwt");
+    o.addStyleName("i-gwt-heart");
+
+    assertTrue(containsClass(o, "gwt"));
+    assertTrue(containsClass(o, "gwt-dependent"));
+    assertTrue(containsClass(o, "i-heart-gwt"));
+    assertTrue(containsClass(o, "i-gwt-heart"));
+
+    o.setStylePrimaryName("awt");
+
+    assertPrimaryStyleNameEquals(o, "awt");
+    assertTrue(containsClass(o, "awt-dependent"));
+    assertFalse(containsClass(o, "gwt-dependent"));
+    assertTrue(containsClass(o, "i-heart-gwt"));
+    assertTrue(containsClass(o, "i-gwt-heart"));
+    assertFalse(containsClass(o, "i-heart-awt"));
+    assertFalse(containsClass(o, "i-awt-heart"));
+  }
+
+  private void assertPrimaryStyleNameEquals(UIObject o, String className) {
+    String attr = DOM.getElementProperty(o.getElement(), "className");
+    assertTrue(attr.indexOf(className) == 0);
+    assertTrue(attr.length() == className.length()
+        || attr.charAt(className.length()) == ' ');
+  }
+
+  private boolean containsClass(UIObject o, String className) {
+    String[] classes = DOM.getElementProperty(o.getElement(), "className").split(
+        "\\s+");
+    for (int i = 0; i < classes.length; i++) {
+      if (className.equals(classes[i])) {
+        return true;
+      }
     }
+    return false;
   }
 
   // doStuff() should leave MyObject's style in the same state it started in.
-  private void doStuff(MyObject o) {
-    // Test that the base style remains the first class, and that the dependent
-    // style shows up.
-    o.addStyleName(o.getStyleName() + "-dependent");
-    assertContainsClass(o, o.getStyleName() + "-dependent");
+  private void doDependentAndSecondaryStyleTest(MyObject o) {
+    // Test that the primary style remains the first class, and that the
+    // dependent style shows up.
+    o.addStyleDependentName("dependent");
+    assertTrue(containsClass(o, o.getStylePrimaryName() + "-dependent"));
 
-    String oldBaseStyle = o.getStyleName();
+    String oldPrimaryStyle = o.getStylePrimaryName();
 
-    // Test that replacing the base style name works (and doesn't munge up the
-    // user style).
-    o.addStyleName("userStyle");
-    o.setStyleName("newBaseStyle");
+    // Test that replacing the primary style name works (and doesn't munge up
+    // the secondary style).
+    o.addStyleName("secondaryStyle");
+    o.setStylePrimaryName("newPrimaryStyle");
 
-    assertEquals("newBaseStyle", o.getStyleName());
-    assertStartsWithClass(o, "newBaseStyle");
-    assertContainsClass(o, "newBaseStyle-dependent");
-    assertContainsClass(o, "userStyle");
-    assertDoesNotContainClass(o, oldBaseStyle);
-    assertDoesNotContainClass(o, oldBaseStyle + "-dependent");
+    assertEquals("newPrimaryStyle", o.getStylePrimaryName());
+    assertPrimaryStyleNameEquals(o, "newPrimaryStyle");
+    assertTrue(containsClass(o, "newPrimaryStyle-dependent"));
+    assertTrue(containsClass(o, "secondaryStyle"));
+    assertFalse(containsClass(o, oldPrimaryStyle));
+    assertFalse(containsClass(o, oldPrimaryStyle + "-dependent"));
 
     // Clean up & return.
-    o.setStyleName(oldBaseStyle);
-    o.removeStyleName(oldBaseStyle + "-dependent");
-    o.removeStyleName("userStyle");
-  }
-
-  private void assertContainsClass(UIObject o, String className) {
-    String attr = DOM.getElementProperty(o.getElement(), "className");
-    assertTrue(attr.indexOf(className) != -1);
-  }
-
-  private void assertDoesNotContainClass(UIObject o, String className) {
-    String attr = DOM.getElementProperty(o.getElement(), "className");
-    assertTrue(attr.indexOf(className) == -1);
-  }
-
-  private void assertStartsWithClass(UIObject o, String className) {
-    String attr = DOM.getElementProperty(o.getElement(), "className");
-    assertTrue(attr.indexOf(className) == 0);
+    o.setStylePrimaryName(oldPrimaryStyle);
+    o.removeStyleDependentName("dependent");
+    o.removeStyleName("secondaryStyle");
+    assertFalse(containsClass(o, o.getStylePrimaryName() + "-dependent"));
+    assertFalse(containsClass(o, "secondaryStyle"));
   }
 }