Checkpoint work on Stock demo

Review by: jgw@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7691 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/shared/AsyncListModel.java b/bikeshed/src/com/google/gwt/bikeshed/list/shared/AsyncListModel.java
index 903041e..7631110 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/shared/AsyncListModel.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/shared/AsyncListModel.java
@@ -30,6 +30,7 @@
    * @param <T> the data type
    */
   public static interface DataSource<T> {
+
     /**
      * Request that the data source pushes new data to the client. The data
      * source should call {@link #updateViewData} and/or {@link #updateDataSize}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/Columns.java b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/Columns.java
new file mode 100644
index 0000000..8f743ba
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/Columns.java
@@ -0,0 +1,112 @@
+/*
+ * 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.sample.stocks.client;
+
+import com.google.gwt.bikeshed.cells.client.ButtonCell;
+import com.google.gwt.bikeshed.cells.client.CheckboxCell;
+import com.google.gwt.bikeshed.cells.client.CurrencyCell;
+import com.google.gwt.bikeshed.cells.client.TextCell;
+import com.google.gwt.bikeshed.list.client.Column;
+import com.google.gwt.bikeshed.sample.stocks.shared.StockQuote;
+import com.google.gwt.bikeshed.sample.stocks.shared.Transaction;
+
+/**
+ * Column definitions for the stock demo.
+ */
+public class Columns {
+
+  static Column<StockQuote, String> buyColumn = new Column<StockQuote, String>(
+      new ButtonCell()) {
+    @Override
+    protected String getValue(StockQuote object) {
+      return "Buy";
+    }
+  };
+
+  static Column<StockQuote, Integer> dollarsColumn =
+    new Column<StockQuote, Integer>(new CurrencyCell()) {
+    @Override
+    protected Integer getValue(StockQuote object) {
+      return object.getPrice() * object.getSharesOwned();
+    }
+  };
+  
+  static Column<StockQuote, Boolean> favoriteColumn =
+    new Column<StockQuote, Boolean>(new CheckboxCell()) {
+    @Override
+    protected Boolean getValue(StockQuote object) {
+      return object.isFavorite();
+    }
+  };
+
+  static Column<StockQuote, String> nameColumn =
+    new Column<StockQuote, String>(new TextCell()) {
+    @Override
+    protected String getValue(StockQuote object) {
+      return object.getName();
+    }
+  };
+
+  static Column<StockQuote, Integer> priceColumn =
+    new Column<StockQuote, Integer>(new CurrencyCell()) {
+    @Override
+    protected Integer getValue(StockQuote object) {
+      return object.getPrice();
+    }
+  };
+
+  static Column<StockQuote, String> sellColumn =
+    new Column<StockQuote, String>(new ButtonCell()) {
+    @Override
+    protected String getValue(StockQuote object) {
+      return "Sell";
+    }
+  };
+
+  static Column<StockQuote, String> sharesColumn =
+    new Column<StockQuote, String>(new TextCell()) {
+    @Override
+    protected String getValue(StockQuote object) {
+      return "" + object.getSharesOwned();
+    }
+  };
+
+  static Column<Transaction, String> subtotalColumn =
+    new Column<Transaction, String>(new TextCell()) {
+    @Override
+    protected String getValue(Transaction object) {
+      int price = object.getActualPrice() * object.getQuantity();
+      return (object.isBuy() ? " (" : " ") + StockSample.getFormattedPrice(price) + 
+          (object.isBuy() ? ")" : "");
+    }
+  };
+  
+  static Column<StockQuote, String> tickerColumn =
+    new Column<StockQuote, String>(new TextCell()) {
+    @Override
+    protected String getValue(StockQuote object) {
+      return object.getTicker();
+    }
+  };
+  
+  static Column<Transaction, String> transactionColumn =
+    new Column<Transaction, String>(new TextCell()) {
+    @Override
+    protected String getValue(Transaction object) {
+      return object.toString();
+    }
+  };
+}
\ No newline at end of file
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockQueryWidget.java b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockQueryWidget.java
new file mode 100644
index 0000000..13f44e0
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockQueryWidget.java
@@ -0,0 +1,74 @@
+/*
+ * 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.sample.stocks.client;
+
+import com.google.gwt.bikeshed.list.client.PagingTableListView;
+import com.google.gwt.bikeshed.list.shared.ListModel;
+import com.google.gwt.bikeshed.sample.stocks.shared.StockQuote;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.DockLayoutPanel;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.TextBox;
+
+/**
+ * A widget containing a search box and a results table.
+ */
+public class StockQueryWidget extends Composite {
+
+  private final TextBox queryField = new TextBox();
+  private PagingTableListView<StockQuote> resultsTable;
+
+  public StockQueryWidget(ListModel<StockQuote> searchListModel, final Updater updater) { 
+    // Create the results table.
+    resultsTable = new PagingTableListView<StockQuote>(searchListModel, 10);
+    resultsTable.addColumn(Columns.favoriteColumn);
+    resultsTable.addColumn(Columns.tickerColumn);
+    resultsTable.addColumn(Columns.nameColumn);
+    resultsTable.addColumn(Columns.priceColumn);
+    resultsTable.addColumn(Columns.buyColumn);
+   
+    // Focus the cursor on the name field when the app loads
+    queryField.setFocus(true);
+    queryField.selectAll();
+    queryField.setText("G");
+    
+    // Add a handler to send the name to the server
+    queryField.addKeyUpHandler(new KeyUpHandler() {
+      public void onKeyUp(KeyUpEvent event) {
+        updater.update();
+      }
+    });
+    
+    DockLayoutPanel layoutPanel = new DockLayoutPanel(Unit.EM);
+    
+    HorizontalPanel panel = new HorizontalPanel();
+    panel.add(new Label("Enter query: "));
+    panel.add(queryField);
+    layoutPanel.addNorth(panel, 2.0);
+    layoutPanel.add(new ScrollPanel(resultsTable));
+    
+    initWidget(layoutPanel);
+  }
+  
+  public String getSearchQuery() {
+    return queryField.getText();
+  }
+}
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 d74790a..b02bea5 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
@@ -15,40 +15,36 @@
  */
 package com.google.gwt.bikeshed.sample.stocks.client;
 
-import com.google.gwt.bikeshed.cells.client.ButtonCell;
-import com.google.gwt.bikeshed.cells.client.CheckboxCell;
-import com.google.gwt.bikeshed.cells.client.CurrencyCell;
 import com.google.gwt.bikeshed.cells.client.FieldUpdater;
-import com.google.gwt.bikeshed.cells.client.TextCell;
-import com.google.gwt.bikeshed.list.client.Column;
 import com.google.gwt.bikeshed.list.client.PagingTableListView;
 import com.google.gwt.bikeshed.list.shared.AsyncListModel;
 import com.google.gwt.bikeshed.list.shared.ListListModel;
 import com.google.gwt.bikeshed.list.shared.Range;
 import com.google.gwt.bikeshed.list.shared.AsyncListModel.DataSource;
+import com.google.gwt.bikeshed.sample.stocks.client.TransactionTreeViewModel.SectorListModel;
 import com.google.gwt.bikeshed.sample.stocks.shared.StockQuote;
 import com.google.gwt.bikeshed.sample.stocks.shared.StockQuoteList;
 import com.google.gwt.bikeshed.sample.stocks.shared.StockRequest;
 import com.google.gwt.bikeshed.sample.stocks.shared.StockResponse;
 import com.google.gwt.bikeshed.sample.stocks.shared.Transaction;
 import com.google.gwt.bikeshed.tree.client.SideBySideTreeView;
-import com.google.gwt.bikeshed.tree.client.StandardTreeView;
 import com.google.gwt.core.client.EntryPoint;
 import com.google.gwt.core.client.GWT;
-import com.google.gwt.event.dom.client.KeyUpEvent;
-import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.dom.client.Style.Unit;
 import com.google.gwt.event.logical.shared.CloseEvent;
 import com.google.gwt.event.logical.shared.CloseHandler;
 import com.google.gwt.i18n.client.NumberFormat;
 import com.google.gwt.user.client.Timer;
 import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.DockLayoutPanel;
 import com.google.gwt.user.client.ui.HTML;
 import com.google.gwt.user.client.ui.HorizontalPanel;
 import com.google.gwt.user.client.ui.Label;
 import com.google.gwt.user.client.ui.PopupPanel;
-import com.google.gwt.user.client.ui.RootPanel;
-import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.RootLayoutPanel;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.Widget;
 
 import java.util.HashMap;
 import java.util.List;
@@ -57,91 +53,14 @@
 /**
  * Entry point classes define <code>onModuleLoad()</code>.
  */
-public class StockSample implements EntryPoint {
+public class StockSample implements EntryPoint, Updater {
 
-  static class Columns {
-
-    private static Column<StockQuote, String> buyColumn = new Column<StockQuote, String>(
-        new ButtonCell()) {
-      @Override
-      protected String getValue(StockQuote object) {
-        return "Buy";
-      }
-    };
-    
-    private static Column<StockQuote, Boolean> favoriteColumn =
-      new Column<StockQuote, Boolean>(new CheckboxCell()) {
-      @Override
-      protected Boolean getValue(StockQuote object) {
-        return object.isFavorite();
-      }
-    };
-
-    private static Column<StockQuote, String> nameColumn =
-      new Column<StockQuote, String>(new TextCell()) {
-      @Override
-      protected String getValue(StockQuote object) {
-        return object.getName();
-      }
-    };
-
-    private static Column<StockQuote, Integer> priceColumn =
-      new Column<StockQuote, Integer>(new CurrencyCell()) {
-      @Override
-      protected Integer getValue(StockQuote object) {
-        return object.getPrice();
-      }
-    };
-
-    private static Column<StockQuote, String> sellColumn =
-      new Column<StockQuote, String>(new ButtonCell()) {
-      @Override
-      protected String getValue(StockQuote object) {
-        return "Sell";
-      }
-    };
-
-    private static Column<StockQuote, String> sharesColumn =
-      new Column<StockQuote, String>(new TextCell()) {
-      @Override
-      protected String getValue(StockQuote object) {
-        return "" + object.getSharesOwned();
-      }
-    };
-
-    private static Column<Transaction, String> subtotalColumn =
-      new Column<Transaction, String>(new TextCell()) {
-      @Override
-      protected String getValue(Transaction object) {
-        int price = object.getActualPrice() * object.getQuantity();
-        return (object.isBuy() ? " (" : " ") + getFormattedPrice(price) + 
-            (object.isBuy() ? ")" : "");
-      }
-    };
-    
-    private static Column<StockQuote, String> tickerColumn =
-      new Column<StockQuote, String>(new TextCell()) {
-      @Override
-      protected String getValue(StockQuote object) {
-        return object.getTicker();
-      }
-    };
-    
-    private static Column<Transaction, String> transactionColumn =
-      new Column<Transaction, String>(new TextCell()) {
-      @Override
-      protected String getValue(Transaction object) {
-        return object.toString();
-      }
-    };
-  }
-  
   /**
    * The delay between updates in milliseconds.
    */
   private static final int UPDATE_DELAY = 5000;
   
-  private static String getFormattedPrice(int price) {
+  static String getFormattedPrice(int price) {
     return NumberFormat.getCurrencyFormat("USD").format(price / 100.0);
   }
 
@@ -157,30 +76,27 @@
    */
   private final StockServiceAsync dataService = GWT.create(StockService.class);
 
-  private final Label errorLabel = new Label();
-
   private AsyncListModel<StockQuote> favoritesListModel;
   
   private PagingTableListView<StockQuote> favoritesTable;
 
-  private final TextBox queryField = new TextBox();
-
-  private PagingTableListView<StockQuote> resultsTable;
+  private final Label netWorthLabel = new Label();
   
+  private StockQueryWidget queryWidget;
+
   private AsyncListModel<StockQuote> searchListModel;
   
   private Map<String, ListListModel<Transaction>> transactionListListModelsByTicker =
     new HashMap<String, ListListModel<Transaction>>();
-
+  
   private ListListModel<Transaction> transactionListModel;
   
   private List<Transaction> transactions;
-  
-  private PagingTableListView<Transaction> transactionTable;
-  
-  private StandardTreeView transactionTree1;
 
-  private SideBySideTreeView transactionTree2;
+  private PagingTableListView<Transaction> transactionTable;
+
+  private SideBySideTreeView transactionTree;
+
   /**
    * The timer used to update the stock quotes.
    */
@@ -191,20 +107,12 @@
     }
   };
 
+  private TransactionTreeViewModel treeModel;
+
   /**
    * This is the entry point method.
    */
   public void onModuleLoad() {
-    queryField.setText("G");
-
-    // Add the nameField and sendButton to the RootPanel
-    // Use RootPanel.get() to get the entire body element
-    RootPanel.get("queryFieldContainer").add(queryField);
-    RootPanel.get("errorLabelContainer").add(errorLabel);
-
-    // Focus the cursor on the name field when the app loads
-    queryField.setFocus(true);
-    queryField.selectAll();
 
     // Create the list models
     searchListModel = new AsyncListModel<StockQuote>(
@@ -224,19 +132,12 @@
     transactionListModel = new ListListModel<Transaction>();
     transactions = transactionListModel.getList();
 
-    // Create the results table.
-    resultsTable = new PagingTableListView<StockQuote>(searchListModel, 10);
-    resultsTable.addColumn(Columns.favoriteColumn);
-    resultsTable.addColumn(Columns.tickerColumn);
-    resultsTable.addColumn(Columns.nameColumn);
-    resultsTable.addColumn(Columns.priceColumn);
-    resultsTable.addColumn(Columns.buyColumn);
-
     // Create the favorites table.
     favoritesTable = new PagingTableListView<StockQuote>(favoritesListModel, 10);
     favoritesTable.addColumn(Columns.tickerColumn);
     favoritesTable.addColumn(Columns.priceColumn);
     favoritesTable.addColumn(Columns.sharesColumn);
+    favoritesTable.addColumn(Columns.dollarsColumn);
     favoritesTable.addColumn(Columns.buyColumn);
     favoritesTable.addColumn(Columns.sellColumn);
     
@@ -245,14 +146,9 @@
     transactionTable.addColumn(Columns.transactionColumn);
     transactionTable.addColumn(Columns.subtotalColumn);
     
-    // Create the transactions tree.
-    transactionTree1 = new StandardTreeView(new TransactionTreeViewModel(favoritesListModel,
-        transactionListListModelsByTicker), null);
-    transactionTree1.setAnimationEnabled(true);
-    
-    // Create the transactions tree.
-    transactionTree2 = new SideBySideTreeView(new TransactionTreeViewModel(favoritesListModel,
-        transactionListListModelsByTicker), null, 200, 200);
+    treeModel = new TransactionTreeViewModel(this,
+        favoritesListModel, transactionListListModelsByTicker);
+    transactionTree = new SideBySideTreeView(treeModel, null, 200, 200);
 
     Columns.favoriteColumn.setFieldUpdater(new FieldUpdater<StockQuote, Boolean>() {
       public void update(StockQuote object, Boolean value) {
@@ -309,32 +205,42 @@
               t.getList().add(result);
             }
           });
-        }
+       }
       }
     });
 
     // Add components to the page.
-    HorizontalPanel hPanel = new HorizontalPanel();
-    hPanel.add(new HTML("<b>Available cash:</b>"));
-    hPanel.add(cashLabel);
-    RootPanel.get().add(hPanel);
+    
+    Widget headerWidget = new HTML("<b>Stock Game</b>");
+    
+    HorizontalPanel cashPanel = new HorizontalPanel();
+    cashPanel.add(new HTML("<b>Available cash:</b>"));
+    cashPanel.add(cashLabel);
+    
+    HorizontalPanel netWorthPanel = new HorizontalPanel();
+    netWorthPanel.add(new HTML("<b>Net worth:</b>"));
+    netWorthPanel.add(netWorthLabel);
+    
+    DockLayoutPanel footerPanel = new DockLayoutPanel(Unit.PCT);
+    footerPanel.addWest(cashPanel, 50.0);
+    footerPanel.add(netWorthPanel);
+    
+    DockLayoutPanel layoutPanel = new DockLayoutPanel(Unit.EM);
+    layoutPanel.addNorth(headerWidget, 4.0);
+    layoutPanel.addSouth(footerPanel, 2.0);
+    layoutPanel.addNorth(transactionTree, 18.0);
+    
+    DockLayoutPanel innerLayoutPanel = new DockLayoutPanel(Unit.PCT);
+    this.queryWidget = new StockQueryWidget(searchListModel, this);
+    innerLayoutPanel.addWest(queryWidget, 50.0);
+    
+    DockLayoutPanel favoritesLayoutPanel = new DockLayoutPanel(Unit.EM);
+    favoritesLayoutPanel.addNorth(new Label("Portfolio / Favorites"), 2.0);
+    favoritesLayoutPanel.add(new ScrollPanel(favoritesTable));
+    innerLayoutPanel.add(favoritesLayoutPanel);
+    layoutPanel.add(innerLayoutPanel);
 
-    RootPanel.get().add(resultsTable);
-    RootPanel.get().add(new HTML("<hr>"));
-    RootPanel.get().add(favoritesTable);
-    RootPanel.get().add(new HTML("<hr>"));
-    RootPanel.get().add(transactionTable);
-    RootPanel.get().add(new HTML("<hr>"));
-    RootPanel.get().add(transactionTree1);
-    RootPanel.get().add(new HTML("<hr>"));
-    RootPanel.get().add(transactionTree2);
-
-    // Add a handler to send the name to the server
-    queryField.addKeyUpHandler(new KeyUpHandler() {
-      public void onKeyUp(KeyUpEvent event) {
-        update();
-      }
-    });
+    RootLayoutPanel.get().add(layoutPanel);
 
     update();
   }
@@ -347,29 +253,75 @@
    */
   public void setFavorite(String ticker, boolean favorite) {
     if (favorite) {
-      dataService.addFavorite(ticker, new AsyncCallback<Void>() {
+      dataService.addFavorite(ticker, favoritesListModel.getRanges()[0],
+          new AsyncCallback<StockResponse>() {
         public void onFailure(Throwable caught) {
           Window.alert("Error adding favorite");
         }
 
-        public void onSuccess(Void result) {
-          // do nothing
+        public void onSuccess(StockResponse response) {
+          updateFavorites(response);
         }
       });
     } else {
-      dataService.removeFavorite(ticker, new AsyncCallback<Void>() {
+      dataService.removeFavorite(ticker, favoritesListModel.getRanges()[0],
+          new AsyncCallback<StockResponse>() {
         public void onFailure(Throwable caught) {
           Window.alert("Error removing favorite");
         }
 
-        public void onSuccess(Void result) {
-          // do nothing
+        public void onSuccess(StockResponse response) {
+          updateFavorites(response);
         }
       });
     }
   }
   
   /**
+   * Request data from the server using the last query string.
+   */
+  public void update() {
+    if (queryWidget == null) {
+      return;
+    }
+    
+    updateTimer.cancel();
+  
+    Range[] searchRanges = searchListModel.getRanges();
+    Range[] favoritesRanges = favoritesListModel.getRanges();
+    SectorListModel sectorListModel = treeModel.getSectorListModel();
+    Range[] sectorRanges = sectorListModel == null ? null : sectorListModel.getRanges();
+  
+    if (searchRanges == null || searchRanges.length == 0
+        || favoritesRanges == null || favoritesRanges.length == 0) {
+      return;
+    }
+  
+    String searchQuery = queryWidget.getSearchQuery();
+    StockRequest request = new StockRequest(searchQuery,
+        sectorListModel != null ? sectorListModel.getSector() : null,
+        searchRanges[0],
+        favoritesRanges[0],
+        sectorRanges != null && sectorRanges.length > 0 ? sectorRanges[0] : null);
+    dataService.getStockQuotes(request, new AsyncCallback<StockResponse>() {
+      public void onFailure(Throwable caught) {
+        String message = caught.getMessage();
+        if (message.contains("Not logged in")) {
+          // Force the user to login.
+          Window.Location.reload();
+        } else {
+          Window.alert("ERROR: " + caught.getMessage());
+          updateTimer.schedule(UPDATE_DELAY);
+        }
+      }
+  
+      public void onSuccess(StockResponse result) {
+        processStockResponse(result);
+      }
+    });
+  }
+
+  /**
    * Process the {@link StockResponse} from the server.
    *
    * @param response the stock response
@@ -382,52 +334,36 @@
         searchResults.size(), searchResults);
 
     // Update the favorites list.
-    StockQuoteList favorites = response.getFavorites();
-    favoritesListModel.updateDataSize(response.getNumFavorites(), true);
-    favoritesListModel.updateViewData(favorites.getStartIndex(),
-        favorites.size(), favorites);
+    updateFavorites(response);
+    updateSector(response);
 
     // Update available cash.
     int cash = response.getCash();
+    int netWorth = response.getNetWorth();
     cashLabel.setText(getFormattedPrice(cash));
+    netWorthLabel.setText(getFormattedPrice(netWorth));
     buySellPopup.setAvailableCash(cash);
 
     // Restart the update timer.
     updateTimer.schedule(UPDATE_DELAY);
   }
 
-  /**
-   * Request data from the server using the last query string.
-   */
-  private void update() {
-    updateTimer.cancel();
+  private void updateFavorites(StockResponse response) {
+    // Update the favorites list.
+    StockQuoteList favorites = response.getFavorites();
+    favoritesListModel.updateDataSize(response.getNumFavorites(), true);
+    favoritesListModel.updateViewData(favorites.getStartIndex(),
+        favorites.size(), favorites);
+  }
 
-    Range[] searchRanges = searchListModel.getRanges();
-    Range[] favoritesRanges = favoritesListModel.getRanges();
-
-    if (searchRanges == null || searchRanges.length == 0
-        || favoritesRanges == null || favoritesRanges.length == 0) {
-      return;
+  private void updateSector(StockResponse response) {
+    // Update the sector list.
+    StockQuoteList sectorList = response.getSector();
+    if (sectorList != null) {
+      SectorListModel sectorListModel = treeModel.getSectorListModel(); 
+      sectorListModel.updateDataSize(response.getNumSector(), true);
+      sectorListModel.updateViewData(sectorList.getStartIndex(),
+          sectorList.size(), sectorList);
     }
-
-    String searchQuery = queryField.getText();
-    StockRequest request = new StockRequest(searchQuery, searchRanges[0],
-        favoritesRanges[0]);
-    dataService.getStockQuotes(request, new AsyncCallback<StockResponse>() {
-      public void onFailure(Throwable caught) {
-        String message = caught.getMessage();
-        if (message.contains("Not logged in")) {
-          // Force the user to login.
-          Window.Location.reload();
-        } else {
-          Window.alert("ERROR: " + caught.getMessage());
-          updateTimer.schedule(UPDATE_DELAY);
-        }
-      }
-
-      public void onSuccess(StockResponse result) {
-        processStockResponse(result);
-      }
-    });
   }
 }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockService.java b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockService.java
index 7589ca88..c9b3578 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockService.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockService.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.bikeshed.sample.stocks.client;
 
+import com.google.gwt.bikeshed.list.shared.Range;
 import com.google.gwt.bikeshed.sample.stocks.shared.StockRequest;
 import com.google.gwt.bikeshed.sample.stocks.shared.StockResponse;
 import com.google.gwt.bikeshed.sample.stocks.shared.Transaction;
@@ -30,9 +31,9 @@
   StockResponse getStockQuotes(StockRequest request)
       throws IllegalArgumentException;
 
-  void addFavorite(String ticker);
+  StockResponse addFavorite(String ticker, Range favoritesRange);
 
-  void removeFavorite(String ticker);
+  StockResponse removeFavorite(String ticker, Range favoritesRange);
 
   Transaction transact(Transaction transaction) throws IllegalArgumentException;
 }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockServiceAsync.java b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockServiceAsync.java
index 88a6b56..f57eeb5 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockServiceAsync.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockServiceAsync.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.bikeshed.sample.stocks.client;
 
+import com.google.gwt.bikeshed.list.shared.Range;
 import com.google.gwt.bikeshed.sample.stocks.shared.StockRequest;
 import com.google.gwt.bikeshed.sample.stocks.shared.StockResponse;
 import com.google.gwt.bikeshed.sample.stocks.shared.Transaction;
@@ -27,9 +28,9 @@
   void getStockQuotes(StockRequest request,
       AsyncCallback<StockResponse> callback);
 
-  void addFavorite(String ticker, AsyncCallback<Void> callback);
+  void addFavorite(String ticker, Range favoritesRange, AsyncCallback<StockResponse> callback);
 
-  void removeFavorite(String ticker, AsyncCallback<Void> callback);
+  void removeFavorite(String ticker, Range favoritesRange, AsyncCallback<StockResponse> callback);
 
   void transact(Transaction transaction, AsyncCallback<Transaction> callback);
 }
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 a44d1d4..7589158 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
@@ -16,13 +16,17 @@
 package com.google.gwt.bikeshed.sample.stocks.client;
 
 import com.google.gwt.bikeshed.cells.client.Cell;
+import com.google.gwt.bikeshed.cells.client.TextCell;
+import com.google.gwt.bikeshed.list.shared.AsyncListModel;
 import com.google.gwt.bikeshed.list.shared.ListListModel;
 import com.google.gwt.bikeshed.list.shared.ListModel;
 import com.google.gwt.bikeshed.sample.stocks.shared.StockQuote;
 import com.google.gwt.bikeshed.sample.stocks.shared.Transaction;
 import com.google.gwt.bikeshed.tree.client.TreeNode;
 import com.google.gwt.bikeshed.tree.client.TreeViewModel;
+import com.google.gwt.bikeshed.tree.client.TreeViewModel.DefaultNodeInfo;
 
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -32,6 +36,24 @@
  */
 class TransactionTreeViewModel implements TreeViewModel {
   
+  class SectorListModel extends AsyncListModel<StockQuote> {
+    
+    String sector;
+    
+    public SectorListModel(final Updater updater, String sector) {
+      super(new DataSource<StockQuote>() {
+        public void requestData(AsyncListModel<StockQuote> listModel) {
+          updater.update();
+        }
+      });
+      this.sector = sector;
+    }
+
+    public String getSector() {
+      return sector;
+    }
+  }
+  
   static class TransactionCell extends Cell<Transaction> {
     @Override
     public void render(Transaction value, StringBuilder sb) {
@@ -49,18 +71,32 @@
   private static final Cell<Transaction> TRANSACTION_CELL =
     new TransactionCell();
   
+  private SectorListModel sectorListModel;
   private ListModel<StockQuote> stockQuoteListModel;
+  private ListListModel<String> topLevelListListModel =
+    new ListListModel<String>();
   private Map<String, ListListModel<Transaction>> transactionListListModelsByTicker;
 
-  public TransactionTreeViewModel(ListModel<StockQuote> stockQuoteListModel,
+  private Updater updater;
+
+  public TransactionTreeViewModel(Updater updater, ListModel<StockQuote> stockQuoteListModel,
       Map<String, ListListModel<Transaction>> transactionListListModelsByTicker) {
+    this.updater = updater;
     this.stockQuoteListModel = stockQuoteListModel;
+    List<String> topLevelList = topLevelListListModel.getList();
+    topLevelList.add("Favorites");
+    topLevelList.add("Internet");
+    topLevelList.add("Energy");
+    topLevelList.add("Networking");
     this.transactionListListModelsByTicker = transactionListListModelsByTicker;
   }
 
   @SuppressWarnings("unused")
   public <T> NodeInfo<?> getNodeInfo(T value, TreeNode<T> treeNode) {
     if (value == null) {
+      return new TreeViewModel.DefaultNodeInfo<String>(topLevelListListModel,
+          new TextCell());
+    } else if ("Favorites".equals(value)) {
       return new TreeViewModel.DefaultNodeInfo<StockQuote>(stockQuoteListModel,
           STOCK_QUOTE_CELL) {
         @Override
@@ -68,6 +104,14 @@
           return value.getTicker();
         }
       };
+    } else if (value instanceof String) {
+      sectorListModel = new SectorListModel(updater, (String) value);
+      return new TreeViewModel.DefaultNodeInfo<StockQuote>(sectorListModel, STOCK_QUOTE_CELL) {
+        @Override
+        public Object getKey(StockQuote value) {
+          return value.getTicker();
+        }
+      };
     } else if (value instanceof StockQuote) {
       String ticker = ((StockQuote) value).getTicker();
       ListListModel<Transaction> listModel = transactionListListModelsByTicker.get(ticker);
@@ -82,6 +126,10 @@
     throw new IllegalArgumentException(value.toString());
   }
 
+  public SectorListModel getSectorListModel() {
+    return sectorListModel;
+  }
+
   public boolean isLeaf(Object value) {
     return value instanceof Transaction;
   }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/Updater.java b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/Updater.java
new file mode 100644
index 0000000..02562bc
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/Updater.java
@@ -0,0 +1,30 @@
+/*
+ * 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.sample.stocks.client;
+
+/**
+ * Bridge between StockSample and StockQueryWidget.
+ */
+public interface Updater {
+  
+  /**
+   * Update the widget.
+   * 
+   * TODO - refactor this
+   */
+  void update();
+
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/server/NasdaqStocks.java b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/server/NasdaqStocks.java
index f1c9022..da176aa 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/server/NasdaqStocks.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/server/NasdaqStocks.java
@@ -182,11 +182,9 @@
       "ANGN", "Angeion Corporation",
       "ANGO", "AngioDynamics, Inc.",
       "ANIK", "Anika Therapeutics Inc.",
-      "ANLY", "Analysts International Corporation",
       "ANNB", "Annapolis Bancorp Inc.",
       "ANPI", "Angiotech Pharmaceuticals, Inc.",
       "ANSS", "ANSYS, Inc.",
-      "ANSV", "Anesiva, Inc.",
       "ANSW", "Answers Corporation",
       "ANTP", "PHAZAR CORP", "AONE", "A123 Systems, Inc.",
       "APAB", "Appalachian Bancshares, Inc. (GA)",
@@ -252,7 +250,6 @@
       "ASTE", "Astec Industries, Inc.",
       "ASTI", "Ascent Solar Technologies, Inc.",
       "ASTIZ", "Ascent Solar Technologies, Inc.",
-      "ASTM", "Aastrom Biosciences, Inc.",
       "ASUR", "Forgent Networks Inc",
       "ASYS", "Amtech Systems, Inc.",
       "ATAC", "ATC Technology Corporation",
@@ -441,7 +438,6 @@
       "CALI", "China Auto Logistics Inc.",
       "CALM", "Cal-Maine Foods, Inc.",
       "CALP", "Caliper Life Sciences Inc",
-      "CAMD", "California Micro Devices Corporation",
       "CAMP", "CalAmp Corp.",
       "CAMT", "Camtek Ltd.",
       "CAPS", "Orthologic Corp.",
@@ -715,7 +711,6 @@
       "CWBS", "Commonwealth Bankshares, Inc.",
       "CWCO", "Consolidated Water Co. Ltd.",
       "CWEI", "Clayton Williams Energy, Inc.",
-      "CWLZ", "Cowlitz Bancorporation",
       "CWST", "Casella Waste Systems, Inc.",
       "CWTR", "Coldwater Creek, Inc.",
       "CYAN", "Cyanotech Corporation",
@@ -1055,7 +1050,6 @@
       "FXCB", "Fox Chase Bancorp, Inc.",
       "FXEN", "FX Energy, Inc.",
       "GABC", "German American Bancorp, Inc.",
-      "GAI", "Global-Tech Advanced Innovations Inc.",
       "GAIA", "Gaiam, Inc.",
       "GAIN", "Gladstone Investment Corporation",
       "GAME", "Shanda Games Limited",
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/server/StockServiceImpl.java b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/server/StockServiceImpl.java
index 8ee31cc..f1f0598 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/server/StockServiceImpl.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/server/StockServiceImpl.java
@@ -67,6 +67,9 @@
 
   private static final int MAX_RESULTS_TO_RETURN = 10000;
 
+  private static final HashMap<String,String> sectorQueries =
+    new HashMap<String,String>();
+
   static {
     int num = NasdaqStocks.SYMBOLS.length;
     for (int i = 0; i < num - 1; i += 2) {
@@ -78,13 +81,31 @@
     }
   }
 
+  static {
+    sectorQueries.put("INTERNET", "GOOG|YHOO|MSFT");
+    sectorQueries.put("ENERGY", "GASS|GLRP");
+    sectorQueries.put("NETWORKING", "GLBC|CSCO");
+  }
+  
   /**
    * A mapping of usernames to {@link PlayerStatus}.
    */
   private Map<String, PlayerStatus> players = new HashMap<String, PlayerStatus>();
-
-  public void addFavorite(String ticker) {
-    ensurePlayer().addFavorite(ticker);
+  
+  public StockResponse addFavorite(String ticker, Range favoritesRange) {
+    PlayerStatus player = ensurePlayer();
+    player.addFavorite(ticker);
+    Result favorites = query(player.getFavoritesQuery(), favoritesRange);
+    return new StockResponse(null, favorites.quotes, null,
+        0, favorites.numRows, 0, player.getCash());
+  }
+  
+  public Result getSectorQuotes(String sector, Range sectorRange) {
+    String sectorQuery = sectorQueries.get(sector.toUpperCase());
+    if (sectorQuery == null) {
+      return null;
+    }
+    return query(sectorQuery, sectorRange);
   }
 
   public StockResponse getStockQuotes(StockRequest request)
@@ -96,17 +117,28 @@
     }
     Range searchRange = request.getSearchRange();
     Range favoritesRange = request.getFavoritesRange();
-
+    Range sectorRange = request.getSectorRange();
+    
     PlayerStatus player = ensurePlayer();
     Result searchResults = query(query, searchRange);
     Result favorites = query(player.getFavoritesQuery(), favoritesRange);
+    Result sector = sectorRange != null ? getSectorQuotes(request.getSector(), sectorRange) : null;
 
-    return new StockResponse(searchResults.quotes, favorites.quotes,
-        searchResults.numRows, favorites.numRows, player.getCash());
+    return new StockResponse(searchResults.quotes,
+        favorites.quotes,
+        sector != null ? sector.quotes : null,
+        searchResults.numRows,
+        favorites.numRows,
+        sector != null ? sector.numRows : 0,
+        player.getCash());
   }
 
-  public void removeFavorite(String ticker) {
-    ensurePlayer().removeFavorite(ticker);
+  public StockResponse removeFavorite(String ticker, Range favoritesRange) {
+    PlayerStatus player = ensurePlayer();
+    player.removeFavorite(ticker);
+    Result favorites = query(player.getFavoritesQuery(), favoritesRange);
+    return new StockResponse(null, favorites.quotes, null,
+        0, favorites.numRows, 0, player.getCash());
   }
 
   public Transaction transact(Transaction transaction)
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/shared/StockQuoteList.java b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/shared/StockQuoteList.java
index 7fa900a..85974ca 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/shared/StockQuoteList.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/shared/StockQuoteList.java
@@ -38,5 +38,16 @@
   public int getStartIndex() {
     return start;
   }
+  
+  /**
+   * Returns the sum of stock prices times shares owned, in pennies.
+   */
+  public int getValue() {
+    int value = 0;
+    for (StockQuote q : this) {
+      value += q.getPrice() * q.getSharesOwned();
+    }
+    return value;
+  }
 }
 
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/shared/StockRequest.java b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/shared/StockRequest.java
index d7724b3..c612728 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/shared/StockRequest.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/shared/StockRequest.java
@@ -24,15 +24,18 @@
  */
 public class StockRequest implements Serializable {
 
+  Range favoritesRange;
   String searchQuery;
   Range searchRange;
-  Range favoritesRange;
-
-  public StockRequest(String searchQuery, Range searchRange,
-      Range favoritesRange) {
+  String sector;
+  Range sectorRange;
+  public StockRequest(String searchQuery, String sector, Range searchRange,
+      Range favoritesRange, Range sectorRange) {
     this.searchQuery = searchQuery;
+    this.sector = sector;
     this.searchRange = searchRange;
     this.favoritesRange = favoritesRange;
+    this.sectorRange = sectorRange;
   }
 
   /**
@@ -52,4 +55,12 @@
   public Range getSearchRange() {
     return searchRange;
   }
+
+  public String getSector() {
+    return sector;
+  }
+
+  public Range getSectorRange() {
+    return sectorRange;
+  }
 }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/shared/StockResponse.java b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/shared/StockResponse.java
index 1df16ab..428a7fc 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/shared/StockResponse.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/shared/StockResponse.java
@@ -22,15 +22,27 @@
  */
 public class StockResponse implements Serializable {
 
-  private StockQuoteList searchResults;
-  private StockQuoteList favorites;
-  private int numSearchResults;
-  private int numFavorites;
-
   /**
    * The amount of available cash in pennies.
    */
   private int cash;
+  private StockQuoteList favorites;
+  private int numFavorites;
+  private int numSearchResults;
+  private int numSector;
+  private StockQuoteList searchResults;
+  private StockQuoteList sector;
+
+  public StockResponse(StockQuoteList searchResults, StockQuoteList favorites,
+      StockQuoteList sector, int numSearchResults, int numFavorites, int numSector, int cash) {
+    this.searchResults = searchResults;
+    this.favorites = favorites;
+    this.sector = sector;
+    this.numSearchResults = numSearchResults;
+    this.numFavorites = numFavorites;
+    this.numSector = numSector;
+    this.cash = cash;
+  }
 
   /**
    * Used for RPC.
@@ -38,15 +50,6 @@
   StockResponse() {
   }
 
-  public StockResponse(StockQuoteList searchResults, StockQuoteList favorites,
-      int numSearchResults, int numFavorites, int cash) {
-    this.searchResults = searchResults;
-    this.favorites = favorites;
-    this.numSearchResults = numSearchResults;
-    this.numFavorites = numFavorites;
-    this.cash = cash;
-  }
-
   public int getCash() {
     return cash;
   }
@@ -54,6 +57,13 @@
   public StockQuoteList getFavorites() {
     return favorites;
   }
+  
+  /**
+   * The sum of cash available and portfolio value. 
+   */
+  public int getNetWorth() {
+    return cash + favorites.getValue();
+  }
 
   public int getNumFavorites() {
     return numFavorites;
@@ -63,7 +73,15 @@
     return numSearchResults;
   }
 
+  public int getNumSector() {
+    return numSector;
+  }
+
   public StockQuoteList getSearchResults() {
     return searchResults;
   }
+
+  public StockQuoteList getSector() {
+    return sector;
+  }
 }
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 87211c6..38c9cbe 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeNodeView.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeNodeView.java
@@ -27,7 +27,7 @@
 import com.google.gwt.dom.client.NativeEvent;
 import com.google.gwt.dom.client.Style.Display;
 import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.UIObject;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -39,7 +39,7 @@
  *
  * @param <T> the type that this {@link TreeNodeView} contains
  */
-public abstract class TreeNodeView<T> extends Composite implements TreeNode<T> {
+public abstract class TreeNodeView<T> extends UIObject implements TreeNode<T> {
 
   /**
    * The element used in place of an image when a node has no children.
diff --git a/bikeshed/war/Stocks.html b/bikeshed/war/Stocks.html
index 5ad342e..0b3e56c 100644
--- a/bikeshed/war/Stocks.html
+++ b/bikeshed/war/Stocks.html
@@ -15,18 +15,5 @@
         in order for this application to display correctly.
       </div>
     </noscript>
-
-    <h1>Data Backed Widgets Sample</h1>
-    <table align="center">
-      <tr>
-        <td colspan="2" style="font-weight:bold;">Enter a search query:</td>        
-      </tr>
-      <tr>
-        <td id="queryFieldContainer"></td>
-      </tr>
-      <tr>
-        <td colspan="2" style="color:red;" id="errorLabelContainer"></td>
-      </tr>
-    </table>
   </body>
 </html>