Making Column implement HasAlignment so users can specify the default alignment of cells in a column.  In the future, we may add a way to specify the alignment of specific Cells.
Issue: 5623

Review at http://gwt-code-reviews.appspot.com/1134801

Review by: rchandia@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9269 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellSampler.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellSampler.java
index dbc5850..3e37407 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellSampler.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/CwCellSampler.java
@@ -53,6 +53,7 @@
 import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.rpc.AsyncCallback;
 import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.HasHorizontalAlignment;
 import com.google.gwt.user.client.ui.Widget;
 
 import java.util.ArrayList;
@@ -268,8 +269,8 @@
           pendingChanges.add(new CategoryChange(object, categories[0]));
         } else {
           // If not a relative, use the Contacts Category.
-          pendingChanges.add(
-              new CategoryChange(object, categories[categories.length - 1]));
+          pendingChanges.add(new CategoryChange(object,
+              categories[categories.length - 1]));
         }
       }
     });
@@ -315,8 +316,8 @@
     });
 
     // ActionCell.
-    addColumn(new ActionCell<ContactInfo>(
-        "Click Me", new ActionCell.Delegate<ContactInfo>() {
+    addColumn(new ActionCell<ContactInfo>("Click Me",
+        new ActionCell.Delegate<ContactInfo>() {
           public void execute(ContactInfo contact) {
             Window.alert("You clicked " + contact.getFullName());
           }
@@ -338,8 +339,7 @@
     });
 
     // DateCell.
-    DateTimeFormat dateFormat = DateTimeFormat.getFormat(
-        PredefinedFormat.DATE_MEDIUM);
+    DateTimeFormat dateFormat = DateTimeFormat.getFormat(PredefinedFormat.DATE_MEDIUM);
     addColumn(new DateCell(dateFormat), "Date", new GetValue<Date>() {
       public Date getValue(ContactInfo contact) {
         return contact.getBirthday();
@@ -347,8 +347,8 @@
     }, null);
 
     // DatePickerCell.
-    addColumn(
-        new DatePickerCell(dateFormat), "DatePicker", new GetValue<Date>() {
+    addColumn(new DatePickerCell(dateFormat), "DatePicker",
+        new GetValue<Date>() {
           public Date getValue(ContactInfo contact) {
             return contact.getBirthday();
           }
@@ -359,29 +359,29 @@
         });
 
     // NumberCell.
-    addColumn(new NumberCell(), "Number", new GetValue<Number>() {
-      @SuppressWarnings("deprecation")
-      public Number getValue(ContactInfo contact) {
-        Date today = new Date();
-        Date birthday = contact.getBirthday();
-        int age = today.getYear() - birthday.getYear();
-        if (today.getMonth() > birthday.getMonth()
-            || (today.getMonth() == birthday.getMonth()
-                && today.getDate() > birthday.getDate())) {
-          age--;
-        }
-        return age;
-      }
-    }, null);
-
-    // IconCellDecorator.
-    addColumn(
-        new IconCellDecorator<String>(images.contactsGroup(), new TextCell()),
-        "Icon", new GetValue<String>() {
-          public String getValue(ContactInfo contact) {
-            return contact.getCategory().getDisplayName();
+    Column<ContactInfo, Number> numberColumn = addColumn(new NumberCell(),
+        "Number", new GetValue<Number>() {
+          @SuppressWarnings("deprecation")
+          public Number getValue(ContactInfo contact) {
+            Date today = new Date();
+            Date birthday = contact.getBirthday();
+            int age = today.getYear() - birthday.getYear();
+            if (today.getMonth() > birthday.getMonth()
+                || (today.getMonth() == birthday.getMonth() && today.getDate() > birthday.getDate())) {
+              age--;
+            }
+            return age;
           }
         }, null);
+    numberColumn.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_LOCALE_END);
+
+    // IconCellDecorator.
+    addColumn(new IconCellDecorator<String>(images.contactsGroup(),
+        new TextCell()), "Icon", new GetValue<String>() {
+      public String getValue(ContactInfo contact) {
+        return contact.getCategory().getDisplayName();
+      }
+    }, null);
 
     // ImageCell.
     addColumn(new ImageCell(), "Image", new GetValue<String>() {
@@ -459,7 +459,7 @@
    * @param getter the value getter for the cell
    */
   @ShowcaseSource
-  private <C> void addColumn(Cell<C> cell, String headerText,
+  private <C> Column<ContactInfo, C> addColumn(Cell<C> cell, String headerText,
       final GetValue<C> getter, FieldUpdater<ContactInfo, C> fieldUpdater) {
     Column<ContactInfo, C> column = new Column<ContactInfo, C>(cell) {
       @Override
@@ -472,5 +472,6 @@
       editableCells.add((AbstractEditableCell<?, ?>) cell);
     }
     cellTable.addColumn(column, headerText);
+    return column;
   }
 }
diff --git a/tools/api-checker/config/gwt21_22userApi.conf b/tools/api-checker/config/gwt21_22userApi.conf
index 8735494..959caeb 100644
--- a/tools/api-checker/config/gwt21_22userApi.conf
+++ b/tools/api-checker/config/gwt21_22userApi.conf
@@ -144,3 +144,8 @@
 com.google.gwt.i18n.shared.DirectionEstimator::estimateDirection(Ljava/lang/String;) OVERLOADED_METHOD_CALL
 com.google.gwt.i18n.shared.FirstStrongDirectionEstimator::estimateDirection(Ljava/lang/String;) OVERLOADED_METHOD_CALL
 com.google.gwt.i18n.shared.WordCountDirectionEstimator::estimateDirection(Ljava/lang/String;) OVERLOADED_METHOD_CALL
+
+# Two fields in Column were exposed as protected but should be private.
+com.google.gwt.user.cellview.client.Column::cell MISSING
+com.google.gwt.user.cellview.client.Column::fieldUpdater MISSING
+
diff --git a/user/src/com/google/gwt/user/cellview/client/CellTable.java b/user/src/com/google/gwt/user/cellview/client/CellTable.java
index cbf2365..3c2cf31 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTable.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellTable.java
@@ -39,9 +39,12 @@
 import com.google.gwt.safehtml.client.SafeHtmlTemplates;
 import com.google.gwt.safehtml.shared.SafeHtml;
 import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.safehtml.shared.SafeHtmlUtils;
 import com.google.gwt.user.cellview.client.HasDataPresenter.LoadingState;
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant;
+import com.google.gwt.user.client.ui.HasVerticalAlignment.VerticalAlignmentConstant;
 import com.google.gwt.view.client.ProvidesKey;
 import com.google.gwt.view.client.SelectionModel;
 
@@ -268,22 +271,34 @@
   }
 
   interface Template extends SafeHtmlTemplates {
+    @Template("<div style=\"outline:none;\">{0}</div>")
+    SafeHtml div(SafeHtml contents);
+
+    @Template("<div style=\"outline:none;\" tabindex=\"{0}\">{1}</div>")
+    SafeHtml divFocusable(int tabIndex, SafeHtml contents);
+
+    @Template("<div style=\"outline:none;\" tabindex=\"{0}\" accessKey=\"{1}\">{2}</div>")
+    SafeHtml divFocusableWithKey(int tabIndex, char accessKey, SafeHtml contents);
+
     @Template("<div class=\"{0}\"/>")
     SafeHtml loading(String loading);
 
     @Template("<table><tbody>{0}</tbody></table>")
     SafeHtml tbody(SafeHtml rowHtml);
 
-    @Template("<td class=\"{0}\"><div style=\"outline:none;\">{1}</div></td>")
+    @Template("<td class=\"{0}\">{1}</td>")
     SafeHtml td(String classes, SafeHtml contents);
 
-    @Template("<td class=\"{0}\"><div style=\"outline:none;\" tabindex=\"{1}\">{2}</div></td>")
-    SafeHtml tdFocusable(String classes, int tabIndex, SafeHtml contents);
-
-    @Template("<td class=\"{0}\"><div style=\"outline:none;\" tabindex=\"{1}\" accessKey=\"{2}\">{3}</div></td>")
-    SafeHtml tdFocusableWithKey(String classes, int tabIndex, char accessKey,
+    @Template("<td class=\"{0}\" align=\"{1}\" valign=\"{2}\">{3}</td>")
+    SafeHtml tdBothAlign(String classes, String hAlign, String vAlign,
         SafeHtml contents);
 
+    @Template("<td class=\"{0}\" align=\"{1}\">{2}</td>")
+    SafeHtml tdHorizontalAlign(String classes, String hAlign, SafeHtml contents);
+
+    @Template("<td class=\"{0}\" valign=\"{1}\">{2}</td>")
+    SafeHtml tdVerticalAlign(String classes, String vAlign, SafeHtml contents);
+
     @Template("<table><tfoot>{0}</tfoot></table>")
     SafeHtml tfoot(SafeHtml rowHtml);
 
@@ -522,7 +537,7 @@
   /**
    * Constructs a table with the given page size, the specified
    * {@link Resources}, and the given key provider.
-   *
+   * 
    * @param pageSize the page size
    * @param resources the resources to use for this widget
    * @param keyProvider an instance of ProvidesKey<T>, or null if the record
@@ -545,7 +560,13 @@
     colgroup = Document.get().createColGroupElement();
     table.appendChild(colgroup);
     thead = table.createTHead();
-    table.appendChild(tbody = Document.get().createTBodyElement());
+    // Some browsers create a tbody automatically, others do not.
+    if (table.getTBodies().getLength() > 0) {
+      tbody = table.getTBodies().getItem(0);
+    } else {
+      tbody = Document.get().createTBodyElement();
+      table.appendChild(tbody);
+    }
     table.appendChild(tbodyLoading = Document.get().createTBodyElement());
     tfoot = table.createTFoot();
     setStyleName(this.style.cellTableWidget());
@@ -1048,6 +1069,8 @@
           column.render(value, keyProvider, cellBuilder);
         }
 
+        // Build the contents.
+        SafeHtml contents = SafeHtmlUtils.EMPTY_SAFE_HTML;
         if (i == keyboardSelectedRow && curColumn == keyboardSelectedColumn) {
           // This is the focused cell.
           if (isFocused) {
@@ -1055,14 +1078,31 @@
           }
           char accessKey = getAccessKey();
           if (accessKey != 0) {
-            trBuilder.append(template.tdFocusableWithKey(tdClasses,
-                getTabIndex(), accessKey, cellBuilder.toSafeHtml()));
+            contents = template.divFocusableWithKey(getTabIndex(), accessKey,
+                cellBuilder.toSafeHtml());
           } else {
-            trBuilder.append(template.tdFocusable(tdClasses, getTabIndex(),
-                cellBuilder.toSafeHtml()));
+            contents = template.divFocusable(getTabIndex(),
+                cellBuilder.toSafeHtml());
           }
         } else {
-          trBuilder.append(template.td(tdClasses, cellBuilder.toSafeHtml()));
+          contents = template.div(cellBuilder.toSafeHtml());
+        }
+
+        // Build the cell.
+        HorizontalAlignmentConstant hAlign = column.getHorizontalAlignment();
+        VerticalAlignmentConstant vAlign = column.getVerticalAlignment();
+        if (hAlign != null && vAlign != null) {
+          trBuilder.append(template.tdBothAlign(tdClasses,
+              hAlign.getTextAlignString(), vAlign.getVerticalAlignString(),
+              contents));
+        } else if (hAlign != null) {
+          trBuilder.append(template.tdHorizontalAlign(tdClasses,
+              hAlign.getTextAlignString(), contents));
+        } else if (vAlign != null) {
+          trBuilder.append(template.tdVerticalAlign(tdClasses,
+              vAlign.getVerticalAlignString(), contents));
+        } else {
+          trBuilder.append(template.td(tdClasses, contents));
         }
 
         curColumn++;
diff --git a/user/src/com/google/gwt/user/cellview/client/Column.java b/user/src/com/google/gwt/user/cellview/client/Column.java
index 7802303..0ad4215 100644
--- a/user/src/com/google/gwt/user/cellview/client/Column.java
+++ b/user/src/com/google/gwt/user/cellview/client/Column.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2010 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
@@ -22,6 +22,7 @@
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
 import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
+import com.google.gwt.user.client.ui.HasAlignment;
 import com.google.gwt.view.client.ProvidesKey;
 
 /**
@@ -29,25 +30,28 @@
  * for each cell on demand. New view data, if needed, is created by the cell's
  * onBrowserEvent method, stored in the Column, and passed to future calls to
  * Cell's {@link Cell#onBrowserEvent} and {@link Cell#render} methods.
- *
+ * 
  * @param <T> the row type
  * @param <C> the column type
  */
-public abstract class Column<T, C> implements HasCell<T, C> {
+public abstract class Column<T, C> implements HasCell<T, C>, HasAlignment {
 
   /**
    * The {@link Cell} responsible for rendering items in the column.
    */
-  protected final Cell<C> cell;
+  private final Cell<C> cell;
 
   /**
    * The {@link FieldUpdater} used for updating values in the column.
    */
-  protected FieldUpdater<T, C> fieldUpdater;
+  private FieldUpdater<T, C> fieldUpdater;
+
+  private HorizontalAlignmentConstant hAlign = null;
+  private VerticalAlignmentConstant vAlign = null;
 
   /**
    * Construct a new Column with a given {@link Cell}.
-   *
+   * 
    * @param cell the Cell used by this Column
    */
   public Column(Cell<C> cell) {
@@ -73,14 +77,22 @@
     return fieldUpdater;
   }
 
+  public HorizontalAlignmentConstant getHorizontalAlignment() {
+    return hAlign;
+  }
+
   /**
    * Returns the column value from within the underlying data object.
    */
   public abstract C getValue(T object);
 
+  public VerticalAlignmentConstant getVerticalAlignment() {
+    return vAlign;
+  }
+
   /**
    * Handle a browser event that took place within the column.
-   *
+   * 
    * @param elem the parent Element
    * @param index the current row index of the object
    * @param object the base object to be updated
@@ -91,8 +103,8 @@
   public void onBrowserEvent(Element elem, final int index, final T object,
       NativeEvent event, ProvidesKey<T> keyProvider) {
     Object key = getKey(object, keyProvider);
-    ValueUpdater<C> valueUpdater = (fieldUpdater == null)
-        ? null : new ValueUpdater<C>() {
+    ValueUpdater<C> valueUpdater = (fieldUpdater == null) ? null
+        : new ValueUpdater<C>() {
           public void update(C value) {
             fieldUpdater.update(index, object, value);
           }
@@ -102,7 +114,7 @@
 
   /**
    * Render the object into the cell.
-   *
+   * 
    * @param object the object to render
    * @param keyProvider the {@link ProvidesKey} for the object
    * @param sb the buffer to render into
@@ -114,7 +126,7 @@
 
   /**
    * Set the {@link FieldUpdater} used for updating values in the column.
-   *
+   * 
    * @param fieldUpdater the field updater
    * @see #getFieldUpdater()
    */
@@ -123,9 +135,32 @@
   }
 
   /**
+   * {@inheritDoc}
+   * 
+   * <p>
+   * The new horizontal alignment will apply the next time the table is
+   * rendered.
+   * </p>
+   */
+  public void setHorizontalAlignment(HorizontalAlignmentConstant align) {
+    this.hAlign = align;
+  }
+
+  /**
+   * {@inheritDoc}
+   * 
+   * <p>
+   * The new vertical alignment will apply the next time the table is rendered.
+   * </p>
+   */
+  public void setVerticalAlignment(VerticalAlignmentConstant align) {
+    this.vAlign = align;
+  }
+
+  /**
    * Get the view key for the object given the {@link ProvidesKey}. If the
    * {@link ProvidesKey} is null, the object is used as the key.
-   *
+   * 
    * @param object the row object
    * @param keyProvider the {@link ProvidesKey}
    * @return the key for the object
diff --git a/user/test/com/google/gwt/user/cellview/client/CellTableTest.java b/user/test/com/google/gwt/user/cellview/client/CellTableTest.java
index 99a346d..d47d9c4 100644
--- a/user/test/com/google/gwt/user/cellview/client/CellTableTest.java
+++ b/user/test/com/google/gwt/user/cellview/client/CellTableTest.java
@@ -24,6 +24,8 @@
 import com.google.gwt.safehtml.shared.SafeHtml;
 import com.google.gwt.user.cellview.client.CellTable.Resources;
 import com.google.gwt.user.cellview.client.CellTable.Style;
+import com.google.gwt.user.client.ui.HasHorizontalAlignment;
+import com.google.gwt.user.client.ui.HasVerticalAlignment;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -60,7 +62,61 @@
     table.getPresenter().flush();
     assertEquals(1, replaceValues.size());
   }
-  
+
+  public void testCellAlignment() {
+    CellTable<String> table = createAbstractHasData();
+    Column<String, String> column = new Column<String, String>(new TextCell()) {
+      @Override
+      public String getValue(String object) {
+        return object;
+      }
+    };
+    table.addColumn(column);
+
+    /*
+     * No alignment. Some browsers (FF) return a default value when alignment is
+     * not specified, others (IE/HtmlUnit) return an empty string.
+     */
+    table.setRowData(0, createData(0, 1));
+    table.getPresenter().flush();
+    TableCellElement td = getBodyElement(table, 0, 2);
+    String hAlign = td.getAlign();
+    String vAlign = td.getVAlign();
+    assertTrue("".equals(hAlign) || "left".equals(hAlign));
+    assertTrue("".equals(vAlign) || "middle".equals(vAlign));
+
+    // Horizontal alignment.
+    column.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_RIGHT);
+    table.setRowData(0, createData(0, 1));
+    table.getPresenter().flush();
+    td = getBodyElement(table, 0, 2);
+    hAlign = td.getAlign();
+    vAlign = td.getVAlign();
+    assertTrue("right".equals(hAlign));
+    assertTrue("".equals(vAlign) || "middle".equals(vAlign));
+
+    // Vertical alignment.
+    column.setHorizontalAlignment(null);
+    column.setVerticalAlignment(HasVerticalAlignment.ALIGN_BOTTOM);
+    table.setRowData(0, createData(0, 1));
+    table.getPresenter().flush();
+    td = getBodyElement(table, 0, 2);
+    hAlign = td.getAlign();
+    vAlign = td.getVAlign();
+    assertTrue("".equals(hAlign) || "left".equals(hAlign));
+    assertTrue("bottom".equals(vAlign));
+
+    // Horizontal and vertical alignment.
+    column.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_RIGHT);
+    table.setRowData(0, createData(0, 1));
+    table.getPresenter().flush();
+    td = getBodyElement(table, 0, 2);
+    hAlign = td.getAlign();
+    vAlign = td.getVAlign();
+    assertTrue("right".equals(hAlign));
+    assertTrue("bottom".equals(vAlign));
+  }
+
   public void testGetRowElement() {
     CellTable<String> table = createAbstractHasData();
     table.setRowData(0, createData(0, 10));
@@ -200,6 +256,22 @@
   }
 
   /**
+   * Get a td element from the table body.
+   * 
+   * @param table the {@link CellTable}
+   * @param row the row index
+   * @param column the column index
+   * @return the column header
+   */
+  private TableCellElement getBodyElement(CellTable<?> table, int row,
+      int column) {
+    TableElement tableElem = table.getElement().cast();
+    TableSectionElement tbody = tableElem.getTBodies().getItem(0);
+    TableRowElement tr = tbody.getRows().getItem(row);
+    return tr.getCells().getItem(column);
+  }
+
+  /**
    * Get the number of column headers in the table.
    * 
    * @param table the {@link CellTable}