Add a HasKey interface and a maxColumns param for SideBySideTreeView
Note: the location of the getKey() method needs further thought

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

Review by: jgw@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7805 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/Column.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/Column.java
index e2d9d41..730427c 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/Column.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/Column.java
@@ -36,18 +36,33 @@
  */
 // TODO - when can we get rid of a view data object?
 // TODO - should viewData implement some interface? (e.g., with commit/rollback/dispose)
-public abstract class Column<T, C, V> {
+public abstract class Column<T, C, V> implements HasKey<T> {
   protected final Cell<C, V> cell;
-  protected Map<T, V> viewDataMap = new HashMap<T, V>();
   protected FieldUpdater<T, C, V> fieldUpdater;
+  protected Map<Object, V> viewDataMap = new HashMap<Object, V>();
 
   public Column(Cell<C, V> cell) {
     this.cell = cell;
   }
 
+  public boolean consumesEvents() {
+    return cell.consumesEvents();
+  }
+
+  /**
+   * Returns a key to be used to associate view data with the given object.
+   * The default implementation simply returns the object.
+   */
+  public Object getKey(T object) {
+    return object;
+  }
+
+  public abstract C getValue(T object);
+
   public void onBrowserEvent(Element elem, final int index, final T object,
       NativeEvent event) {
-    V viewData = viewDataMap.get(object);
+    Object key = getKey(object);
+    V viewData = viewDataMap.get(key);
     V newViewData = cell.onBrowserEvent(elem,
         getValue(object), viewData, event, fieldUpdater == null ? null
             : new ValueUpdater<C, V>() {
@@ -56,7 +71,7 @@
               }
             });
     if (newViewData != viewData) {
-      viewDataMap.put(object, newViewData);
+      viewDataMap.put(key, newViewData);
     }
   }
 
@@ -76,6 +91,4 @@
   protected FieldUpdater<T, C, V> getFieldUpdater() {
     return fieldUpdater;
   }
-
-  protected abstract C getValue(T object);
 }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/HasKey.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/HasKey.java
new file mode 100644
index 0000000..76eb2cf
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/HasKey.java
@@ -0,0 +1,35 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.bikeshed.list.client;
+
+/**
+ * An interface for extracting a key from a value.  The extracted key
+ * must contain suitable implementations of hashCode() and equals().
+ *
+ * @param <C> the value type for which keys are to be returned
+ */
+public interface HasKey<C> {
+
+  /**
+   * Return a key that may be used to identify values that should
+   * be treated as the same in UI views.
+   *
+   * @param value a value of type C.
+   * @return an Object that implements appropriate hashCode() and equals()
+   * methods.
+   */
+  Object getKey(C value);
+}
\ No newline at end of file
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeNodeView.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeNodeView.java
index 04f0eb0..4a4c0da 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeNodeView.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeNodeView.java
@@ -20,6 +20,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.Display;
 import com.google.gwt.dom.client.Style.Position;
 import com.google.gwt.dom.client.Style.Unit;
 import com.google.gwt.user.client.ui.RequiresResize;
@@ -39,6 +40,8 @@
 
   private int level;
 
+  private int maxColumns;
+
   private String path;
 
   /**
@@ -47,17 +50,19 @@
    * @param tree the parent {@link TreeView}
    * @param parent the parent {@link TreeNodeView}
    * @param parentNodeInfo the {@link NodeInfo} of the parent
-   * @param elem the outer element of this {@link TreeNodeView}.
+   * @param elem the outer element of this {@link TreeNodeView}
    * @param value the value of this node
+   * @param maxColumns the maximum number of columns to display
    */
   SideBySideTreeNodeView(final TreeView tree, final SideBySideTreeNodeView<?> parent,
       NodeInfo<T> parentNodeInfo, Element elem, T value, int level, String path,
-      int columnWidth) {
+      int columnWidth, int maxColumns) {
     super(tree, parent, parentNodeInfo, value);
     this.imageLeft = columnWidth - 16 - tree.getImageWidth();
     this.level = level;
     this.path = path;
     this.columnWidth = columnWidth;
+    this.maxColumns = maxColumns;
 
     setElement(elem);
   }
@@ -66,7 +71,7 @@
   protected <C> TreeNodeView<C> createTreeNodeView(NodeInfo<C> nodeInfo,
       Element childElem, C childValue, Void viewData, int idx) {
     return new SideBySideTreeNodeView<C>(getTree(), this, nodeInfo, childElem,
-        childValue, level + 1, path + "-" + idx, columnWidth);
+        childValue, level + 1, path + "-" + idx, columnWidth, maxColumns);
   }
 
   @Override
@@ -188,7 +193,7 @@
   private Element createContainer(int level) {
     // Resize the root element
     Element rootElement = getTree().getElement();
-    rootElement.getStyle().setWidth((level + 1) * columnWidth, Unit.PX);
+    rootElement.getStyle().setWidth(Math.min(maxColumns, level + 1) * columnWidth, Unit.PX);
 
     // Create children of the root container as needed.
     int childCount = rootElement.getChildCount();
@@ -198,12 +203,26 @@
       Style style = div.getStyle();
       style.setPosition(Position.ABSOLUTE);
       style.setTop(0, Unit.PX);
-      style.setLeft(level * columnWidth, Unit.PX);
       style.setWidth(columnWidth, Unit.PX);
 
       childCount++;
     }
 
+    Element child = rootElement.getFirstChild().cast();
+
+    int x = Math.min(0, maxColumns - (level + 1));
+    while (child != null) {
+      Style style = child.getStyle();
+      style.setLeft(x * columnWidth, Unit.PX);
+      if (x < 0) {
+        style.setDisplay(Display.NONE);
+      } else {
+        style.clearDisplay();
+      }
+      child = child.getNextSibling().cast();
+      x++;
+    }
+
     return rootElement.getChild(level).cast();
   }
 
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeView.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeView.java
index 0a6616c..f8c3b94 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeView.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeView.java
@@ -31,10 +31,13 @@
  */
 public class SideBySideTreeView extends TreeView {
 
-  protected int columnWidth = 100;
+  protected int columnWidth;
+
+  protected int maxColumns;
 
   /**
-   * Construct a new {@link TreeView}.
+   * Construct a new {@link TreeView} that will display as many columns as
+   * needed.
    *
    * @param <T> the type of data in the root node
    * @param viewModel the {@link TreeViewModel} that backs the tree
@@ -43,9 +46,24 @@
    */
   public <T> SideBySideTreeView(TreeViewModel viewModel, T rootValue,
       int columnWidth) {
+    this(viewModel, rootValue, columnWidth, Integer.MAX_VALUE);
+  }
+
+  /**
+   * Construct a new {@link TreeView}.
+   *
+   * @param <T> the type of data in the root node
+   * @param viewModel the {@link TreeViewModel} that backs the tree
+   * @param rootValue the hidden root value of the tree
+   * @param columnWidth the width of each column
+   * @param maxColumns the maximum number of columns to display horizontally.
+   */
+  public <T> SideBySideTreeView(TreeViewModel viewModel, T rootValue,
+      int columnWidth, int maxColumns) {
     super(viewModel);
 
     this.columnWidth = columnWidth;
+    this.maxColumns = maxColumns;
 
     Element rootElement = Document.get().createDivElement();
     rootElement.setClassName("gwt-sstree");
@@ -59,7 +77,7 @@
 
     // Associate a view with the item.
     TreeNodeView<T> root = new SideBySideTreeNodeView<T>(this, null, null,
-        rootElement, rootValue, 0, "gwt-sstree", columnWidth);
+        rootElement, rootValue, 0, "gwt-sstree", columnWidth, maxColumns);
     setRootNode(root);
     root.setState(true);
   }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeViewModel.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeViewModel.java
index 4b6393e..12cd722 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeViewModel.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeViewModel.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.bikeshed.cells.client.Cell;
 import com.google.gwt.bikeshed.cells.client.ValueUpdater;
+import com.google.gwt.bikeshed.list.client.HasKey;
 import com.google.gwt.bikeshed.list.shared.ListModel;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
@@ -29,7 +30,7 @@
   /**
    * The info needed to create a {@link TreeNodeView}.
    */
-  interface NodeInfo<C> {
+  interface NodeInfo<C> extends HasKey<C> {
     /**
      * Get the {@link Cell} used to render child nodes.
      * 
@@ -38,16 +39,6 @@
     Cell<C, Void> getCell();
 
     /**
-     * Return a key that may be used to identify values that should
-     * be treated as the same in UI views.
-     *
-     * @param value a value of type C.
-     * @return an Object that implements appropriate hashCode() and equals()
-     * methods.
-     */
-    Object getKey(C value);
-
-    /**
      * Get the {@link ListModel} used to retrieve child node values.
      * 
      * @return the list model
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/Columns.java b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/Columns.java
index 6e9b848..ae4ed2d 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/Columns.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/Columns.java
@@ -32,7 +32,7 @@
   static Column<StockQuote, String, Void> buyColumn = new Column<StockQuote, String, Void>(
       new ButtonCell()) {
     @Override
-    protected String getValue(StockQuote object) {
+    public String getValue(StockQuote object) {
       return "Buy";
     }
   };
@@ -40,7 +40,7 @@
   static Column<StockQuote, String, Void> changeColumn = new Column<StockQuote, String, Void>(
       new ChangeCell()) {
     @Override
-    protected String getValue(StockQuote object) {
+    public String getValue(StockQuote object) {
       return object.getChange();
     }
   };
@@ -48,7 +48,7 @@
   static Column<StockQuote, Integer, Void> dollarsColumn = new Column<StockQuote, Integer, Void>(
       new CurrencyCell()) {
     @Override
-    protected Integer getValue(StockQuote object) {
+    public Integer getValue(StockQuote object) {
       return object.getPrice() * object.getSharesOwned();
     }
   };
@@ -56,7 +56,7 @@
   static Column<StockQuote, Boolean, Void> favoriteColumn = new Column<StockQuote, Boolean, Void>(
       new CheckboxCell()) {
     @Override
-    protected Boolean getValue(StockQuote object) {
+    public Boolean getValue(StockQuote object) {
       return object.isFavorite();
     }
   };
@@ -67,7 +67,7 @@
   static Column<StockQuote, String, Void> nameColumn = new Column<StockQuote, String, Void>(
       nameCell) {
     @Override
-    protected String getValue(StockQuote object) {
+    public String getValue(StockQuote object) {
       return object.getName();
     }
   };
@@ -75,7 +75,7 @@
   static Column<StockQuote, Integer, Void> priceColumn = new Column<StockQuote, Integer, Void>(
       new CurrencyCell()) {
     @Override
-    protected Integer getValue(StockQuote object) {
+    public Integer getValue(StockQuote object) {
       return object.getPrice();
     }
   };
@@ -83,7 +83,7 @@
   static Column<StockQuote, Integer, Void> profitLossColumn = new Column<StockQuote, Integer, Void>(
       new ProfitLossCell()) {
     @Override
-    protected Integer getValue(StockQuote object) {
+    public Integer getValue(StockQuote object) {
       return object.getValue() - object.getTotalPaid();
     }
   };
@@ -91,7 +91,7 @@
   static Column<StockQuote, String, Void> sellColumn = new Column<StockQuote, String, Void>(
       new ButtonCell()) {
     @Override
-    protected String getValue(StockQuote object) {
+    public String getValue(StockQuote object) {
       return "Sell";
     }
   };
@@ -99,7 +99,7 @@
   static Column<StockQuote, String, Void> sharesColumn = new Column<StockQuote, String, Void>(
       new TextCell()) {
     @Override
-    protected String getValue(StockQuote object) {
+    public String getValue(StockQuote object) {
       return "" + object.getSharesOwned();
     }
   };
@@ -107,7 +107,7 @@
   static Column<Transaction, String, Void> subtotalColumn = new Column<Transaction, String, Void>(
       new TextCell()) {
     @Override
-    protected String getValue(Transaction object) {
+    public String getValue(Transaction object) {
       int price = object.getActualPrice() * object.getQuantity();
       return (object.isBuy() ? " (" : " ")
           + StocksDesktop.getFormattedPrice(price) + (object.isBuy() ? ")" : "");
@@ -117,7 +117,7 @@
   static Column<StockQuote, String, Void> tickerColumn = new Column<StockQuote, String, Void>(
       new TextCell()) {
     @Override
-    protected String getValue(StockQuote object) {
+    public String getValue(StockQuote object) {
       return object.getTicker();
     }
   };
@@ -125,7 +125,7 @@
   static Column<Transaction, String, Void> transactionColumn = new Column<Transaction, String, Void>(
       new TextCell()) {
     @Override
-    protected String getValue(Transaction object) {
+    public String getValue(Transaction object) {
       return object.toString();
     }
   };
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/tree/client/TreeSample.java b/bikeshed/src/com/google/gwt/sample/bikeshed/tree/client/TreeSample.java
index 1476fe8..4f91d91 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/tree/client/TreeSample.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/tree/client/TreeSample.java
@@ -33,7 +33,7 @@
 
     RootPanel.get().add(new HTML("<hr>"));
 
-    SideBySideTreeView sstree = new SideBySideTreeView(new MyTreeViewModel(), "...", 100);
+    SideBySideTreeView sstree = new SideBySideTreeView(new MyTreeViewModel(), "...", 100, 4); 
     sstree.setHeight("200px");
     RootPanel.get().add(sstree);
   }
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/validation/client/Validation.java b/bikeshed/src/com/google/gwt/sample/bikeshed/validation/client/Validation.java
index 88addf7..cac83e6 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/validation/client/Validation.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/validation/client/Validation.java
@@ -85,7 +85,7 @@
     Column<Address, String, Void> stateColumn = new Column<Address, String, Void>(
         new TextCell()) {
       @Override
-      protected String getValue(Address object) {
+      public String getValue(Address object) {
         return object.state;
       }
     };
@@ -94,7 +94,12 @@
       new Column<Address, String, ValidatableField<String>>(
         new ValidatableInputCell()) {
       @Override
-      protected String getValue(Address object) {
+      public Object getKey(Address object) {
+        return object.key;
+      }
+
+      @Override
+      public String getValue(Address object) {
         return object.zip;
       }
     };
@@ -128,7 +133,7 @@
     Column<Address, String, Void> messageColumn = new Column<Address, String, Void>(
         new TextCell()) {
       @Override
-      protected String getValue(Address object) {
+      public String getValue(Address object) {
         return object.zipInvalid ? "Please fix the zip code" : "";
       }
     };