Adding methods to insert columns, get the column count, and get columns by index in a CellTable. Thesemethods are very useful for changing the visible Columns.  Currently, users must remove all columns, then re-add them just to insert a single column.

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

Review by: sbrubaker@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9355 8db76d5a-ed1c-0410-87a9-c151d255dfc7
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 0c5e6a6..1ba3f08 100644
--- a/user/src/com/google/gwt/user/cellview/client/CellTable.java
+++ b/user/src/com/google/gwt/user/cellview/client/CellTable.java
@@ -592,42 +592,198 @@
   }
 
   /**
-   * Adds a column to the table.
+   * Adds a column to the end of the table.
    * 
    * @param col the column to be added
    */
   public void addColumn(Column<T, ?> col) {
-    addColumn(col, (Header<?>) null, (Header<?>) null);
+    insertColumn(getColumnCount(), col);
   }
 
   /**
-   * Adds a column to the table with an associated header.
+   * Adds a column to the end of the table with an associated header.
    * 
    * @param col the column to be added
    * @param header the associated {@link Header}
    */
   public void addColumn(Column<T, ?> col, Header<?> header) {
-    addColumn(col, header, null);
+    insertColumn(getColumnCount(), col, header);
   }
 
   /**
-   * Adds a column to the table with an associated header and footer.
+   * Adds a column to the end of the table with an associated header and footer.
    * 
    * @param col the column to be added
    * @param header the associated {@link Header}
    * @param footer the associated footer (as a {@link Header} object)
    */
   public void addColumn(Column<T, ?> col, Header<?> header, Header<?> footer) {
-    headers.add(header);
-    footers.add(footer);
-    columns.add(col);
+    insertColumn(getColumnCount(), col, header, footer);
+  }
+
+  /**
+   * Adds a column to the end of the table with an associated String header.
+   * 
+   * @param col the column to be added
+   * @param headerString the associated header text, as a String
+   */
+  public void addColumn(Column<T, ?> col, String headerString) {
+    insertColumn(getColumnCount(), col, headerString);
+  }
+
+  /**
+   * Adds a column to the end of the table with an associated {@link SafeHtml}
+   * header.
+   * 
+   * @param col the column to be added
+   * @param headerHtml the associated header text, as safe HTML
+   */
+  public void addColumn(Column<T, ?> col, SafeHtml headerHtml) {
+    insertColumn(getColumnCount(), col, headerHtml);
+  }
+
+  /**
+   * Adds a column to the end of the table with an associated String header and
+   * footer.
+   * 
+   * @param col the column to be added
+   * @param headerString the associated header text, as a String
+   * @param footerString the associated footer text, as a String
+   */
+  public void addColumn(Column<T, ?> col, String headerString,
+      String footerString) {
+    insertColumn(getColumnCount(), col, headerString, footerString);
+  }
+
+  /**
+   * Adds a column to the end of the table with an associated {@link SafeHtml}
+   * header and footer.
+   * 
+   * @param col the column to be added
+   * @param headerHtml the associated header text, as safe HTML
+   * @param footerHtml the associated footer text, as safe HTML
+   */
+  public void addColumn(Column<T, ?> col, SafeHtml headerHtml,
+      SafeHtml footerHtml) {
+    insertColumn(getColumnCount(), col, headerHtml, footerHtml);
+  }
+
+  /**
+   * Add a style name to the {@link TableColElement} at the specified index,
+   * creating it if necessary.
+   * 
+   * @param index the column index
+   * @param styleName the style name to add
+   */
+  public void addColumnStyleName(int index, String styleName) {
+    ensureTableColElement(index).addClassName(styleName);
+  }
+
+  /**
+   * Return the height of the table body.
+   * 
+   * @return an int representing the body height
+   */
+  public int getBodyHeight() {
+    int height = getClientHeight(tbody);
+    return height;
+  }
+
+  /**
+   * Get the column at the specified index.
+   * 
+   * @param col the index of the column to retrieve
+   * @return the {@link Column} at the index
+   */
+  public Column<T, ?> getColumn(int col) {
+    checkColumnBounds(col);
+    return columns.get(col);
+  }
+
+  /**
+   * Get the number of columns in the table.
+   * 
+   * @return the column count
+   */
+  public int getColumnCount() {
+    return columns.size();
+  }
+
+  /**
+   * Return the height of the table header.
+   * 
+   * @return an int representing the header height
+   */
+  public int getHeaderHeight() {
+    int height = getClientHeight(thead);
+    return height;
+  }
+
+  /**
+   * Get the {@link TableRowElement} for the specified row. If the row element
+   * has not been created, null is returned.
+   * 
+   * @param row the row index
+   * @return the row element, or null if it doesn't exists
+   * @throws IndexOutOfBoundsException if the row index is outside of the
+   *           current page
+   */
+  public TableRowElement getRowElement(int row) {
+    getPresenter().flush();
+    checkRowBounds(row);
+    NodeList<TableRowElement> rows = tbody.getRows();
+    return rows.getLength() > row ? rows.getItem(row) : null;
+  }
+
+  /**
+   * Inserts a column into the table at the specified index.
+   * 
+   * @param beforeIndex the index to insert the column
+   * @param col the column to be added
+   */
+  public void insertColumn(int beforeIndex, Column<T, ?> col) {
+    insertColumn(beforeIndex, col, (Header<?>) null, (Header<?>) null);
+  }
+
+  /**
+   * Inserts a column into the table at the specified index with an associated
+   * header.
+   * 
+   * @param beforeIndex the index to insert the column
+   * @param col the column to be added
+   * @param header the associated {@link Header}
+   */
+  public void insertColumn(int beforeIndex, Column<T, ?> col, Header<?> header) {
+    insertColumn(beforeIndex, col, header, null);
+  }
+
+  /**
+   * Inserts a column into the table at the specified index with an associated
+   * header and footer.
+   * 
+   * @param beforeIndex the index to insert the column
+   * @param col the column to be added
+   * @param header the associated {@link Header}
+   * @param footer the associated footer (as a {@link Header} object)
+   * @throws IndexOutOfBoundsException if the index is out of range
+   */
+  public void insertColumn(int beforeIndex, Column<T, ?> col, Header<?> header,
+      Header<?> footer) {
+    // Allow insert at the end.
+    if (beforeIndex != getColumnCount()) {
+      checkColumnBounds(beforeIndex);
+    }
+
+    headers.add(beforeIndex, header);
+    footers.add(beforeIndex, footer);
+    columns.add(beforeIndex, col);
     boolean wasinteractive = isInteractive;
     updateDependsOnSelection();
 
     // Move the keyboard selected column if the current column is not
     // interactive.
     if (!wasinteractive && isInteractive) {
-      keyboardSelectedColumn = columns.size() - 1;
+      keyboardSelectedColumn = beforeIndex;
     }
 
     // Sink events used by the new column.
@@ -656,96 +812,59 @@
   }
 
   /**
-   * Adds a column to the table with an associated String header.
+   * Inserts a column into the table at the specified index with an associated
+   * String header.
    * 
+   * @param beforeIndex the index to insert the column
    * @param col the column to be added
    * @param headerString the associated header text, as a String
    */
-  public void addColumn(Column<T, ?> col, String headerString) {
-    addColumn(col, new TextHeader(headerString), null);
+  public void insertColumn(int beforeIndex, Column<T, ?> col,
+      String headerString) {
+    insertColumn(beforeIndex, col, new TextHeader(headerString), null);
   }
 
   /**
-   * Adds a column to the table with an associated {@link SafeHtml} header.
+   * Inserts a column into the table at the specified index with an associated
+   * {@link SafeHtml} header.
    * 
+   * @param beforeIndex the index to insert the column
    * @param col the column to be added
    * @param headerHtml the associated header text, as safe HTML
    */
-  public void addColumn(Column<T, ?> col, SafeHtml headerHtml) {
-    addColumn(col, new SafeHtmlHeader(headerHtml), null);
+  public void insertColumn(int beforeIndex, Column<T, ?> col,
+      SafeHtml headerHtml) {
+    insertColumn(beforeIndex, col, new SafeHtmlHeader(headerHtml), null);
   }
 
   /**
-   * Adds a column to the table with an associated String header and footer.
+   * Inserts a column into the table at the specified index with an associated
+   * String header and footer.
    * 
+   * @param beforeIndex the index to insert the column
    * @param col the column to be added
    * @param headerString the associated header text, as a String
    * @param footerString the associated footer text, as a String
    */
-  public void addColumn(Column<T, ?> col, String headerString,
-      String footerString) {
-    addColumn(col, new TextHeader(headerString), new TextHeader(footerString));
+  public void insertColumn(int beforeIndex, Column<T, ?> col,
+      String headerString, String footerString) {
+    insertColumn(beforeIndex, col, new TextHeader(headerString),
+        new TextHeader(footerString));
   }
 
   /**
-   * Adds a column to the table with an associated {@link SafeHtml} header and
-   * footer.
+   * Inserts a column into the table at the specified index with an associated
+   * {@link SafeHtml} header and footer.
    * 
+   * @param beforeIndex the index to insert the column
    * @param col the column to be added
    * @param headerHtml the associated header text, as safe HTML
    * @param footerHtml the associated footer text, as safe HTML
    */
-  public void addColumn(Column<T, ?> col, SafeHtml headerHtml,
-      SafeHtml footerHtml) {
-    addColumn(col, new SafeHtmlHeader(headerHtml), new SafeHtmlHeader(
-        footerHtml));
-  }
-
-  /**
-   * Add a style name to the {@link TableColElement} at the specified index,
-   * creating it if necessary.
-   * 
-   * @param index the column index
-   * @param styleName the style name to add
-   */
-  public void addColumnStyleName(int index, String styleName) {
-    ensureTableColElement(index).addClassName(styleName);
-  }
-
-  /**
-   * Return the height of the table body.
-   * 
-   * @return an int representing the body height
-   */
-  public int getBodyHeight() {
-    int height = getClientHeight(tbody);
-    return height;
-  }
-
-  /**
-   * Return the height of the table header.
-   * 
-   * @return an int representing the header height
-   */
-  public int getHeaderHeight() {
-    int height = getClientHeight(thead);
-    return height;
-  }
-
-  /**
-   * Get the {@link TableRowElement} for the specified row. If the row element
-   * has not been created, null is returned.
-   * 
-   * @param row the row index
-   * @return the row element, or null if it doesn't exists
-   * @throws IndexOutOfBoundsException if the row index is outside of the
-   *           current page
-   */
-  public TableRowElement getRowElement(int row) {
-    getPresenter().flush();
-    checkRowBounds(row);
-    NodeList<TableRowElement> rows = tbody.getRows();
-    return rows.getLength() > row ? rows.getItem(row) : null;
+  public void insertColumn(int beforeIndex, Column<T, ?> col,
+      SafeHtml headerHtml, SafeHtml footerHtml) {
+    insertColumn(beforeIndex, col, new SafeHtmlHeader(headerHtml),
+        new SafeHtmlHeader(footerHtml));
   }
 
   /**
@@ -1198,6 +1317,19 @@
   }
 
   /**
+   * Check that the specified column is within bounds.
+   * 
+   * @param col the column index
+   * @throws IndexOutOfBoundsException if the column is out of bounds
+   */
+  private void checkColumnBounds(int col) {
+    if (col < 0 || col >= getColumnCount()) {
+      throw new IndexOutOfBoundsException("Column index is out of bounds: "
+          + col);
+    }
+  }
+
+  /**
    * Render the header or footer.
    * 
    * @param isFooter true if this is the footer table, false if the header table
@@ -1357,8 +1489,7 @@
    * Fire an event to the Cell within the specified {@link TableCellElement}.
    */
   private <C> void fireEventToCell(Event event, String eventType,
-      TableCellElement tableCell, T value, Context context,
-      Column<T, C> column) {
+      TableCellElement tableCell, T value, Context context, Column<T, C> column) {
     Cell<C> cell = column.getCell();
     if (cellConsumesEventType(cell, eventType)) {
       C cellValue = column.getValue(value);
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 535bcd8..6d3115c 100644
--- a/user/test/com/google/gwt/user/cellview/client/CellTableTest.java
+++ b/user/test/com/google/gwt/user/cellview/client/CellTableTest.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.user.cellview.client;
 
+import com.google.gwt.cell.client.AbstractCell;
 import com.google.gwt.cell.client.Cell;
 import com.google.gwt.cell.client.TextCell;
 import com.google.gwt.core.client.GWT;
@@ -25,6 +26,7 @@
 import com.google.gwt.dom.client.TableRowElement;
 import com.google.gwt.dom.client.TableSectionElement;
 import com.google.gwt.safehtml.shared.SafeHtml;
+import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 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;
@@ -40,6 +42,28 @@
 public class CellTableTest extends AbstractHasDataTestBase {
 
   /**
+   * A concrete column that implements a getter that always returns null.
+   * 
+   * @param <T> the row type
+   * @param <C> the column type
+   */
+  private static class MockColumn<T, C> extends Column<T, C> {
+
+    public MockColumn() {
+      super(new AbstractCell<C>() {
+        @Override
+        public void render(Context context, C value, SafeHtmlBuilder sb) {
+        }
+      });
+    }
+
+    @Override
+    public C getValue(T object) {
+      return null;
+    }
+  }
+
+  /**
    * Test that calls to addColumn results in only one redraw.
    */
   public void testAddColumnSingleRedraw() {
@@ -138,6 +162,38 @@
     RootPanel.get().remove(table);
   }
 
+  public void testGetColumnOutOfBounds() {
+    CellTable<String> table = new CellTable<String>();
+
+    // Get column when there are no columns.
+    try {
+      table.getColumn(0);
+      fail("Expected IndexOutOfBoundsException");
+    } catch (IndexOutOfBoundsException e) {
+      // Expected.
+    }
+
+    // Add some columns.
+    table.addColumn(new MockColumn<String, String>());
+    table.addColumn(new MockColumn<String, String>());
+
+    // Negative index.
+    try {
+      table.getColumn(-1);
+      fail("Expected IndexOutOfBoundsException");
+    } catch (IndexOutOfBoundsException e) {
+      // Expected.
+    }
+
+    // Index too high.
+    try {
+      table.getColumn(2);
+      fail("Expected IndexOutOfBoundsException");
+    } catch (IndexOutOfBoundsException e) {
+      // Expected.
+    }
+  }
+
   public void testGetRowElement() {
     CellTable<String> table = createAbstractHasData(new TextCell());
     table.setRowData(0, createData(0, 10));
@@ -146,6 +202,55 @@
     assertNotNull(table.getRowElement(9));
   }
 
+  public void testInsertColumn() {
+    CellTable<String> table = new CellTable<String>();
+    assertEquals(0, table.getColumnCount());
+
+    // Insert first column.
+    Column<String, ?> a = new MockColumn<String, String>();
+    table.insertColumn(0, a);
+    assertEquals(1, table.getColumnCount());
+    assertEquals(a, table.getColumn(0));
+
+    // Insert column at beginning.
+    Column<String, ?> b = new MockColumn<String, String>();
+    table.insertColumn(0, b);
+    assertEquals(2, table.getColumnCount());
+    assertEquals(b, table.getColumn(0));
+    assertEquals(a, table.getColumn(1));
+
+    // Insert column at end.
+    Column<String, ?> c = new MockColumn<String, String>();
+    table.insertColumn(2, c);
+    assertEquals(3, table.getColumnCount());
+    assertEquals(b, table.getColumn(0));
+    assertEquals(a, table.getColumn(1));
+    assertEquals(c, table.getColumn(2));
+
+    // Insert column in middle.
+    Column<String, ?> d = new MockColumn<String, String>();
+    table.insertColumn(1, d);
+    assertEquals(4, table.getColumnCount());
+    assertEquals(b, table.getColumn(0));
+    assertEquals(d, table.getColumn(1));
+    assertEquals(a, table.getColumn(2));
+    assertEquals(c, table.getColumn(3));
+
+    // Insert column at invalid index.
+    try {
+      table.insertColumn(-1, d);
+      fail("Expected IndexOutOfBoundsExecltion");
+    } catch (IndexOutOfBoundsException e) {
+      // Expected.
+    }
+    try {
+      table.insertColumn(6, d);
+      fail("Expected IndexOutOfBoundsExecltion");
+    } catch (IndexOutOfBoundsException e) {
+      // Expected.
+    }
+  }
+
   /**
    * Test headers that span multiple columns.
    */