Detect partial changes in the ListModel associated with a TreeNodeView.


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7626 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/shared/ListListModel.java b/bikeshed/src/com/google/gwt/bikeshed/list/shared/ListListModel.java
index 1327de5..5eaf458 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/shared/ListListModel.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/shared/ListListModel.java
@@ -31,8 +31,6 @@
  */
 public class ListListModel<T> extends AbstractListModel<T> {
 
-  public int insertIndex;
-
   /**
    * A wrapper around a list that updates the model on any change.
    */
@@ -77,7 +75,6 @@
 
     public void add(int index, T element) {
       list.add(index, element);
-      // insert(index);
       flush();
     }
 
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/BuySellPopup.java b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/BuySellPopup.java
index c313777..2e7b341 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/BuySellPopup.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/BuySellPopup.java
@@ -112,6 +112,10 @@
     layout.setWidget(6, 1, cancelButton);
   }
 
+  public StockQuote getStockQuote() {
+    return quote;
+  }
+
   /**
    * Get the last transaction.
    * 
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockSample.java b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockSample.java
index c7f2be2..5539171 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockSample.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockSample.java
@@ -168,8 +168,6 @@
   
   private AsyncListModel<StockQuote> searchListModel;
   
-  private ListListModel<String> tickersListModel = new ListListModel<String>();
-  
   private Map<String, ListListModel<Transaction>> transactionListListModelsByTicker =
     new HashMap<String, ListListModel<Transaction>>();
 
@@ -181,8 +179,6 @@
   
   private TreeView transactionTree;
 
-  private List<String> transactionTreeTickers = tickersListModel.getList();
-
   /**
    * The timer used to update the stock quotes.
    */
@@ -248,8 +244,9 @@
     transactionTable.addColumn(Columns.subtotalColumn);
     
     // Create the transactions tree.
-    transactionTree = new TreeView(new TransactionTreeViewModel(tickersListModel,
+    transactionTree = new TreeView(new TransactionTreeViewModel(favoritesListModel,
         transactionListListModelsByTicker), null);
+    transactionTree.setAnimationEnabled(true);
 
     Columns.favoriteColumn.setFieldUpdater(new FieldUpdater<StockQuote, Boolean>() {
       public void update(StockQuote object, Boolean value) {
@@ -293,25 +290,7 @@
              */
             private void recordTransaction(Transaction result) {
               transactions.add(0, result);
-              
-              // Add ticker in alphebetical order
               String ticker = result.getTicker();
-              int index = 0;
-              for (String s : transactionTreeTickers) {
-                int compare = s.compareTo(ticker);
-                if (compare == 0) {
-                  // Already have the ticker
-                  index = -1;
-                  break;
-                } else if (compare > 0) {
-                  break;
-                }
-                
-                index++;
-              }
-              if (index != -1) {
-                transactionTreeTickers.add(index, ticker);
-              }
               
               // Update the next level of the transaction tree
               // for the given ticker
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/TransactionTreeViewModel.java b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/TransactionTreeViewModel.java
index 6f95667..fcaf4b2 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/TransactionTreeViewModel.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/TransactionTreeViewModel.java
@@ -32,36 +32,43 @@
 class TransactionTreeViewModel implements TreeViewModel {
   
   static class TransactionCell extends Cell<Transaction> {
-
     @Override
     public void render(Transaction value, StringBuilder sb) {
       sb.append(value.toString());
     }
   }
   
-  private static final Cell<String> TEXT_CELL = new TextCell();
+  private static final Cell<StockQuote> STOCK_QUOTE_CELL = new Cell<StockQuote>() {
+    @Override
+    public void render(StockQuote value, StringBuilder sb) {
+      sb.append(value.getTicker() + " - " + value.getDisplayPrice());
+    }
+  };
   
   private static final Cell<Transaction> TRANSACTION_CELL =
     new TransactionCell();
   
-  private ListListModel<String> tickersListModel;
+  private ListModel<StockQuote> stockQuoteListModel;
   private Map<String, ListListModel<Transaction>> transactionListListModelsByTicker;
 
-  public TransactionTreeViewModel(ListListModel<String> tickersListModel,
+  public TransactionTreeViewModel(ListModel<StockQuote> stockQuoteListModel,
       Map<String, ListListModel<Transaction>> transactionListListModelsByTicker) {
-    this.tickersListModel = tickersListModel;
+    this.stockQuoteListModel = stockQuoteListModel;
     this.transactionListListModelsByTicker = transactionListListModelsByTicker;
   }
 
   public <T> NodeInfo<?> getNodeInfo(T value, TreeNodeView<T> treeNodeView) {
     if (value == null) {
-      return new TreeViewModel.DefaultNodeInfo<String>(tickersListModel,
-          TEXT_CELL);
-    } else if (value instanceof String) {
+      return new TreeViewModel.DefaultNodeInfo<StockQuote>(stockQuoteListModel,
+          STOCK_QUOTE_CELL) {
+        @Override
+        public Object getKey(StockQuote value) {
+          return value.getTicker();
+        }
+      };
+    } else if (value instanceof StockQuote) {
       return new TreeViewModel.DefaultNodeInfo<Transaction>(
-          transactionListListModelsByTicker.get(value), TRANSACTION_CELL);
-    } else if (value instanceof Transaction) {
-      return null;
+          transactionListListModelsByTicker.get(((StockQuote) value).getTicker()), TRANSACTION_CELL);
     }
     
     throw new IllegalArgumentException(value.toString());
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeNodeView.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeNodeView.java
index c82a8f3..ab35216 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeNodeView.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeNodeView.java
@@ -31,7 +31,9 @@
 import com.google.gwt.user.client.ui.Composite;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * A view of a tree node.
@@ -45,6 +47,8 @@
    */
   private static final String LEAF_IMAGE = "<div style='position:absolute;display:none;'></div>";
 
+  private boolean animate;
+
   /**
    * The children of this {@link TreeNodeView}.
    */
@@ -183,6 +187,7 @@
       return;
     }
 
+    this.animate = true;
     this.open = open;
     if (open) {
       if (!nodeInfoLoaded) {
@@ -203,6 +208,12 @@
     updateImage();
   }
 
+  boolean consumeAnimate() {
+    boolean hasAnimate = animate;
+    animate = false;
+    return hasAnimate;
+  }
+
   /**
    * Fire an event to the {@link Cell}.
    * 
@@ -282,6 +293,10 @@
     return childContainer;
   }
 
+  private Object getValueKey() {
+    return parentNodeInfo.getKey(getValue());
+  }
+
   /**
    * Check if this is a root node at the top of the tree.
    * 
@@ -292,7 +307,7 @@
   }
 
   /**
-   * Setup the node when it is opened.
+   * Set up the node when it is opened.
    * 
    * @param nodeInfo the {@link NodeInfo} that provides information about the
    *          child values
@@ -309,6 +324,18 @@
       public void onDataChanged(ListEvent<C> event) {
         // TODO - handle event start and length
 
+        // Construct a map of former child views based on their value keys.
+        Map<Object, TreeNodeView<?>> map = new HashMap<Object, TreeNodeView<?>>();
+        if (children != null) {
+          for (TreeNodeView<?> child : children) {
+            // Ignore child nodes that are closed
+            if (child.getState()) {
+              Object key = child.getValueKey();
+              map.put(key, child);
+            }
+          }
+        }
+
         // Hide the child container so we can animate it.
         ensureAnimationFrame().getStyle().setDisplay(Display.NONE);
 
@@ -317,12 +344,21 @@
         int imageWidth = tree.getImageWidth();
         Cell<C> theCell = nodeInfo.getCell();
         StringBuilder sb = new StringBuilder();
-        children = new ArrayList<TreeNodeView<?>>();
+
         for (C childValue : event.getValues()) {
+          // Remove any child elements that correspond to prior children
+          // so the call to setInnerHtml will not destroy them
+          TreeNodeView<?> savedView = map.get(nodeInfo.getKey(childValue));
+          if (savedView != null) {
+            savedView.getElement().removeFromParent();
+          }
+
           sb.append("<div style=\"position:relative;padding-left:");
           sb.append(imageWidth);
           sb.append("px;\">");
-          if (model.isLeaf(childValue)) {
+          if (savedView != null) {
+            sb.append(tree.getOpenImageHtml());
+          } else if (model.isLeaf(childValue)) {
             sb.append(LEAF_IMAGE);
           } else {
             sb.append(tree.getClosedImageHtml());
@@ -338,8 +374,21 @@
         children = new ArrayList<TreeNodeView<?>>();
         Element childElem = childContainer.getFirstChildElement();
         for (C childValue : event.getValues()) {
-          TreeNodeView<C> child = new TreeNodeView<C>(tree, TreeNodeView.this,
-              nodeInfo, childElem, childValue);
+          TreeNodeView<C> child = new TreeNodeView<C>(tree, TreeNodeView.this, nodeInfo, childElem, childValue);
+          TreeNodeView<?> savedChild = map.get(nodeInfo.getKey(childValue));
+          // Copy the saved child's state into the new child
+          if (savedChild != null) {
+            child.children = savedChild.children;
+            child.childContainer = savedChild.childContainer;
+            child.listReg = savedChild.listReg;
+            child.nodeInfo = savedChild.nodeInfo;
+            child.nodeInfoLoaded = savedChild.nodeInfoLoaded;
+            child.open = savedChild.open;
+
+            // Copy the animation frame element to the new child
+            child.getElement().appendChild(savedChild.childContainer.getParentElement());
+          }
+
           children.add(child);
           childElem = childElem.getNextSiblingElement();
         }
@@ -349,17 +398,6 @@
       }
 
       public void onSizeChanged(SizeChangeEvent event) {
-        if (children == null) {
-          return;
-        }
-
-        // Shrink the list based on the new size.
-        int size = event.getSize();
-        int currentSize = children.size();
-        for (int i = currentSize - 1; i >= size; i--) {
-          childContainer.getLastChild().removeFromParent();
-          children.remove(i);
-        }
       }
     });
     listReg.setRangeOfInterest(0, 100);
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeView.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeView.java
index 62b128b..529ccba 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeView.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeView.java
@@ -444,7 +444,7 @@
    * @param node the node to animate
    */
   void maybeAnimateTreeNode(TreeNodeView<?> node) {
-    animation.animate(node, isAnimationEnabled);
+    animation.animate(node, node.consumeAnimate() && isAnimationEnabled);
   }
 
   /**
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 78dc7e1..b3ae963 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeViewModel.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeViewModel.java
@@ -38,6 +38,16 @@
     Cell<C> 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
@@ -91,6 +101,10 @@
       return cell;
     }
 
+    public Object getKey(C value) {
+      return value;
+    }
+
     public ListModel<C> getListModel() {
       return listModel;
     }