Checkpoint


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7702 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/CurrencyCell.java b/bikeshed/src/com/google/gwt/bikeshed/cells/client/CurrencyCell.java
index 02cbbe6..cbbff4e 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/client/CurrencyCell.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/cells/client/CurrencyCell.java
@@ -22,10 +22,17 @@
 
   @Override
   public void render(Integer price, StringBuilder sb) {
+    boolean negative = price < 0;
+    if (negative) {
+      price = -price;
+    }
     int dollars = price / 100;
     int cents = price % 100;
 
-    sb.append("$ ");
+    if (negative) {
+      sb.append("-");
+    }
+    sb.append("$");
     sb.append(dollars);
     sb.append('.');
     if (cents < 10) {
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/ProfitLossCell.java b/bikeshed/src/com/google/gwt/bikeshed/cells/client/ProfitLossCell.java
new file mode 100644
index 0000000..84447d4
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/cells/client/ProfitLossCell.java
@@ -0,0 +1,50 @@
+/*
+ * 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.cells.client;
+
+/**
+ * A {@link Cell} used to render currency.  Positive values are shown in green
+ * with a "+" sign and negative values are shown in red with a "-" sign.
+ */
+public class ProfitLossCell extends Cell<Integer> {
+
+  @Override
+  public void render(Integer priceDelta, StringBuilder sb) {
+    boolean negative = priceDelta < 0;
+    if (negative) {
+      priceDelta = -priceDelta;
+    }
+    int dollars = priceDelta / 100;
+    int cents = priceDelta % 100;
+
+    sb.append("<span style=\"color:");
+    if (priceDelta == 0) {
+      sb.append("green\">  ");
+    } else if (negative) {
+      sb.append("red\">-");
+    } else {
+      sb.append("green\">+");
+    }
+    sb.append("$");
+    sb.append(dollars);
+    sb.append('.');
+    if (cents < 10) {
+      sb.append('0');
+    }
+    sb.append(cents);
+    sb.append("</span>");
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/Header.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/Header.java
index 7a1a923..d2cb09b 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/Header.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/Header.java
@@ -20,25 +20,22 @@
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.NativeEvent;
 
+/**
+ * A table column header.
+ * 
+ * @param <H> the {#link Cell} type
+ */
 public class Header<H> {
   private final Cell<H> cell;
   private ValueUpdater<H> updater;
   private H value;
 
-  public H getValue() {
-    return value;
-  }
-
-  public void setValue(H value) {
-    this.value = value;
-  }
-
   public Header(Cell<H> cell) {
     this.cell = cell;
   }
 
-  public void setUpdater(ValueUpdater<H> updater) {
-    this.updater = updater;
+  public H getValue() {
+    return value;
   }
 
   public void onBrowserEvent(Element elem, NativeEvent event) {
@@ -48,4 +45,12 @@
   public void render(StringBuilder sb) {
     cell.render(value, sb);
   }
+
+  public void setUpdater(ValueUpdater<H> updater) {
+    this.updater = updater;
+  }
+
+  public void setValue(H value) {
+    this.value = value;
+  }
 }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/TextHeader.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/TextHeader.java
index d442c59..05ab116 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/TextHeader.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/TextHeader.java
@@ -17,6 +17,9 @@
 
 import com.google.gwt.bikeshed.cells.client.TextCell;
 
+/**
+ * A Header containing String data rendered by a TextCell.
+ */
 public class TextHeader extends Header<String> {
 
   public TextHeader(String text) {
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/ChangeCell.java b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/ChangeCell.java
new file mode 100644
index 0000000..8aa7f65
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/ChangeCell.java
@@ -0,0 +1,36 @@
+/*
+ * 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.Cell;
+
+/**
+ * A cell that represents a {@link StockQuote}.
+ */
+public class ChangeCell extends Cell<String> {
+
+  @Override
+  public void render(String value, StringBuilder sb) {
+    sb.append("<span style=\"color:");
+    if (value.charAt(0) == '-') {
+      sb.append("red\">");
+    } else {
+      sb.append("green\">");
+    }
+    sb.append(value);
+    sb.append("</span>");
+  }
+}
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
index 8f743ba..889fcfe 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/Columns.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/Columns.java
@@ -18,6 +18,7 @@
 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.ProfitLossCell;
 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;
@@ -36,6 +37,14 @@
     }
   };
 
+  static Column<StockQuote, String> changeColumn =
+    new Column<StockQuote, String>(new ChangeCell()) {
+    @Override
+    protected String getValue(StockQuote object) {
+      return object.getChange();
+    }
+  };
+
   static Column<StockQuote, Integer> dollarsColumn =
     new Column<StockQuote, Integer>(new CurrencyCell()) {
     @Override
@@ -68,6 +77,14 @@
     }
   };
 
+  static Column<StockQuote, Integer> profitLossColumn =
+    new Column<StockQuote, Integer>(new ProfitLossCell()) {
+    @Override
+    protected Integer getValue(StockQuote object) {
+      return object.getValue() - object.getTotalPaid();
+    }
+  };
+
   static Column<StockQuote, String> sellColumn =
     new Column<StockQuote, String>(new ButtonCell()) {
     @Override
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 a16284e..ac2ed0f 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
@@ -29,6 +29,7 @@
 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.TreeNode;
 import com.google.gwt.core.client.EntryPoint;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.dom.client.Style.Unit;
@@ -61,7 +62,7 @@
    * The delay between updates in milliseconds.
    */
   private static final int UPDATE_DELAY = 5000;
-  
+
   static String getFormattedPrice(int price) {
     return NumberFormat.getCurrencyFormat("USD").format(price / 100.0);
   }
@@ -79,26 +80,26 @@
   private final StockServiceAsync dataService = GWT.create(StockService.class);
 
   private AsyncListModel<StockQuote> favoritesListModel;
-  
+
   private PagingTableListView<StockQuote> favoritesTable;
 
   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 SideBySideTreeView transactionTree;
 
+  private TransactionTreeViewModel treeModel;
+
   /**
    * The timer used to update the stock quotes.
    */
@@ -109,8 +110,6 @@
     }
   };
 
-  private TransactionTreeViewModel treeModel;
-
   /**
    * This is the entry point method.
    */
@@ -130,7 +129,7 @@
             update();
           }
         });
-    
+
     transactionListModel = new ListListModel<Transaction>();
     transactions = transactionListModel.getList();
 
@@ -138,16 +137,13 @@
     favoritesTable = new PagingTableListView<StockQuote>(favoritesListModel, 10);
     favoritesTable.addColumn(Columns.tickerColumn, new TextHeader("ticker"));
     favoritesTable.addColumn(Columns.priceColumn, new TextHeader("price"));
+    favoritesTable.addColumn(Columns.changeColumn, new TextHeader("change"));
     favoritesTable.addColumn(Columns.sharesColumn, new TextHeader("shares"));
     favoritesTable.addColumn(Columns.dollarsColumn, new TextHeader("value"));
+    favoritesTable.addColumn(Columns.profitLossColumn, new TextHeader("profit"));
     favoritesTable.addColumn(Columns.buyColumn);
     favoritesTable.addColumn(Columns.sellColumn);
-    
-    // Create the transactions table.
-    transactionTable = new PagingTableListView<Transaction>(transactionListModel, 10);
-    transactionTable.addColumn(Columns.transactionColumn);
-    transactionTable.addColumn(Columns.subtotalColumn);
-    
+
     treeModel = new TransactionTreeViewModel(this,
         favoritesListModel, transactionListListModelsByTicker);
     transactionTree = new SideBySideTreeView(treeModel, null, 200, 200);
@@ -195,7 +191,7 @@
             private void recordTransaction(Transaction result) {
               transactions.add(0, result);
               String ticker = result.getTicker();
-              
+
               // Update the next level of the transaction tree
               // for the given ticker
               ListListModel<Transaction> t =
@@ -212,9 +208,9 @@
     });
 
     // Add components to the page.
-    
+
     Widget headerWidget = new HTML("<b>Stock Game</b>");
-    
+
     HorizontalPanel cashPanel = new HorizontalPanel();
     cashPanel.add(new HTML("<b>Available cash:</b>"));
     cashPanel.add(cashLabel);
@@ -224,7 +220,7 @@
     VerticalPanel moneyPanel = new VerticalPanel();
     moneyPanel.add(cashPanel);
     moneyPanel.add(netWorthPanel);
-    
+
     DockLayoutPanel westPanel = new DockLayoutPanel(Unit.PCT);
     westPanel.addNorth(moneyPanel, 25.0);
     westPanel.add(new HTML("<table>" +
@@ -232,16 +228,16 @@
         "<tr><td>Joel Webber</td><td>$10000</td></tr>" +
         "<tr><td>John Labanca</td><td>$10000</td></tr>" +
         "</table>"));
-    
+
     DockLayoutPanel layoutPanel = new DockLayoutPanel(Unit.EM);
     layoutPanel.addNorth(headerWidget, 4.0);
     layoutPanel.addWest(westPanel, 15.0);
     layoutPanel.addNorth(transactionTree, 18.0);
-    
+
     DockLayoutPanel innerLayoutPanel = new DockLayoutPanel(Unit.PCT);
     this.queryWidget = new StockQueryWidget(searchListModel, this);
     innerLayoutPanel.addWest(queryWidget, 60.0);
-    
+
     DockLayoutPanel favoritesLayoutPanel = new DockLayoutPanel(Unit.EM);
     favoritesLayoutPanel.addNorth(new Label("Portfolio / Favorites"), 2.0);
     favoritesLayoutPanel.add(new ScrollPanel(favoritesTable));
@@ -292,19 +288,21 @@
     if (queryWidget == null) {
       return;
     }
-    
+
     updateTimer.cancel();
-  
+
     Range[] searchRanges = searchListModel.getRanges();
     Range[] favoritesRanges = favoritesListModel.getRanges();
-    SectorListModel sectorListModel = treeModel.getSectorListModel();
+    
+    String sectorName = getSectorName();
+    SectorListModel sectorListModel = sectorName != null ? treeModel.getSectorListModel(sectorName) : null;
     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,
@@ -322,13 +320,26 @@
           updateTimer.schedule(UPDATE_DELAY);
         }
       }
-  
+
       public void onSuccess(StockResponse result) {
         processStockResponse(result);
       }
     });
   }
 
+  // Hack - walk the transaction tree to find the current viewed sector
+  private String getSectorName() {
+    int children = transactionTree.getRootNode().getChildCount();
+    for (int i = 0; i < children; i++) {
+      TreeNode<?> childNode = transactionTree.getRootNode().getChildNode(i);
+      if (childNode.isOpen()) {
+        return (String) childNode.getValue();
+      }
+    }
+    
+    return null;
+  }
+
   /**
    * Process the {@link StockResponse} from the server.
    *
@@ -368,7 +379,7 @@
     // Update the sector list.
     StockQuoteList sectorList = response.getSector();
     if (sectorList != null) {
-      SectorListModel sectorListModel = treeModel.getSectorListModel(); 
+      SectorListModel sectorListModel = treeModel.getSectorListModel(getSectorName());
       sectorListModel.updateDataSize(response.getNumSector(), true);
       sectorListModel.updateViewData(sectorList.getStartIndex(),
           sectorList.size(), sectorList);
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 f15b3d27..2a57bd0 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
@@ -25,6 +25,7 @@
 import com.google.gwt.bikeshed.tree.client.TreeNode;
 import com.google.gwt.bikeshed.tree.client.TreeViewModel;
 
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -34,11 +35,11 @@
  * level containing Transactions.
  */
 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) {
@@ -52,28 +53,29 @@
       return sector;
     }
   }
-  
+
   static class TransactionCell extends Cell<Transaction> {
     @Override
     public void render(Transaction value, StringBuilder sb) {
       sb.append(value.toString());
     }
   }
-  
+
   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 SectorListModel sectorListModel;
+
+  private Map<String, SectorListModel> sectorListModels = new HashMap<String, SectorListModel>();
   private ListModel<StockQuote> stockQuoteListModel;
   private ListListModel<String> topLevelListListModel =
     new ListListModel<String>();
+
   private Map<String, ListListModel<Transaction>> transactionListListModelsByTicker;
 
   private Updater updater;
@@ -88,7 +90,7 @@
     topLevelList.add("S&P 500");
     this.transactionListListModelsByTicker = transactionListListModelsByTicker;
   }
-
+  
   @SuppressWarnings("unused")
   public <T> NodeInfo<?> getNodeInfo(T value, TreeNode<T> treeNode) {
     if (value == null) {
@@ -103,8 +105,9 @@
         }
       };
     } else if (value instanceof String) {
-      sectorListModel = new SectorListModel(updater, (String) value);
-      return new TreeViewModel.DefaultNodeInfo<StockQuote>(sectorListModel, STOCK_QUOTE_CELL) {
+      SectorListModel listModel = new SectorListModel(updater, (String) value);
+      sectorListModels.put((String) value, listModel);
+      return new TreeViewModel.DefaultNodeInfo<StockQuote>(listModel, STOCK_QUOTE_CELL) {
         @Override
         public Object getKey(StockQuote value) {
           return value.getTicker();
@@ -120,15 +123,15 @@
       return new TreeViewModel.DefaultNodeInfo<Transaction>(listModel,
           TRANSACTION_CELL);
     }
-    
+
     throw new IllegalArgumentException(value.toString());
   }
 
-  public SectorListModel getSectorListModel() {
-    return sectorListModel;
+  public SectorListModel getSectorListModel(String value) {
+    return sectorListModels.get(value);
   }
 
   public boolean isLeaf(Object value) {
     return value instanceof Transaction;
   }
-}
\ No newline at end of file
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/server/PlayerStatus.java b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/server/PlayerStatus.java
index ea6e072..ea6a4e9 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/server/PlayerStatus.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/server/PlayerStatus.java
@@ -58,6 +58,11 @@
    * The number of shares owned for each symbol.
    */
   private HashMap<String, Integer> sharesOwnedBySymbol = new HashMap<String, Integer>();
+  
+  /**
+   * The total amount paid for each symbol.
+   */
+  private HashMap<String, Double> averagePriceBySymbol = new HashMap<String, Double>();
 
   public PlayerStatus() {
     generateFavoritesQuery();
@@ -91,15 +96,38 @@
 
     // Update the number of shares owned.
     int current = getSharesOwned(ticker);
-    cash -= totalPrice;
+    double averagePrice = getAveragePrice(ticker);
+    double totalPaid = averagePrice * current + totalPrice;
+    
+    // Update case and shares owned
     current += quantity;
+    cash -= totalPrice;
     sharesOwnedBySymbol.put(ticker, current);
-
+    
+    // Update average price
+    averagePrice = totalPaid / current;
+    averagePriceBySymbol.put(ticker, averagePrice);
+    
     // Add this stock to the favorites list.
     addFavorite(ticker);
   }
 
   /**
+   * Returns the total cost of the currently owned shared, using an average cost
+   * basis method.
+   *
+   * @param ticker the stock ticker
+   */
+  public int getAverageCostBasis(String ticker) {
+    return (int) (Math.round(getAveragePrice(ticker) * getSharesOwned(ticker)));
+  }
+
+  public double getAveragePrice(String ticker) {
+    Double current = averagePriceBySymbol.get(ticker);
+    return current == null ? 0.0 : current;
+  }
+
+  /**
    * Get the player's current cash amount.
    * 
    * @return the cash amount
@@ -136,7 +164,7 @@
     Integer current = sharesOwnedBySymbol.get(ticker);
     return current == null ? 0 : current;
   }
-
+  
   /**
    * Check if the stock ticker is in the favorites list.
    * 
@@ -175,9 +203,16 @@
     }
 
     // Perform the transaction.
-    cash += quantity * price;
+    int totalPrice = price * quantity;
+    cash += totalPrice;
     current -= quantity;
     sharesOwnedBySymbol.put(ticker, current);
+    
+    if (current == 0) {
+      sharesOwnedBySymbol.remove(ticker);
+      averagePriceBySymbol.remove(ticker);
+      removeFavorite(ticker);
+    }
   }
 
   /**
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 126e250..2cac311 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
@@ -87,7 +87,7 @@
       companyNamesBySymbol.put(symbol, companyName);
     }
   }
-  
+
   static {
     sectorQueries.put("DOW JONES INDUSTRIALS",
         "AA|AXP|BA|BAC|CAT|CSCO|CVX|DD|DIS|GE|HD|HPQ|IBM|INTC|JNJ|JPM|KFT|KO|" +
@@ -103,7 +103,7 @@
         "CTL|CTSH|CTXS|CVH|CVS|CVX|D|DD|DE|DELL|DF|DFS|DGX|DHI|DHR|DIS|DNB|" +
         "DNR|DO|DOV|DOW|DPS|DRI|DTE|DTV|DUK|DV|DVA|DVN|EBAY|ECL|ED|EFX|EIX|" +
         "EK|EL|EMC|EMN|EMR|EOG|EP|EQR|EQT|ERTS|ESRX|ETFC|ETN|ETR|EXC|EXPD|" +
-        "EXPE|F|FAST|FCX|FDO|FDX|FE|FHN|FII|FIS|FISV|FITB|FLIR|FLR|FLS|FMC|" + 
+        "EXPE|F|FAST|FCX|FDO|FDX|FE|FHN|FII|FIS|FISV|FITB|FLIR|FLR|FLS|FMC|" +
         "FO|FPL|FRX|FSLR|FTI|FTR|GAS|GCI|GD|GE|GENZ|GILD|GIS|GLW|GME|GNW|" +
         "GOOG|GPC|GPS|GR|GS|GT|GWW|HAL|HAR|HAS|HBAN|HCBK|HCN|HCP|HD|HES|HIG|" +
         "HNZ|HOG|HON|HOT|HPQ|HRB|HRL|HRS|HSP|HST|HSY|HUM|IBM|ICE|IFF|IGT|" +
@@ -111,7 +111,7 @@
         "JNS|JPM|JWN|K|KEY|KFT|KG|KIM|KLAC|KMB|KO|KR|KSS|L|LEG|LEN|LH|LIFE|" +
         "LLL|LLTC|LLY|LM|LMT|LNC|LO|LOW|LSI|LTD|LUK|LUV|LXK|M|MA|MAR|MAS|MAT|" +
         "MCD|MCHP|MCK|MCO|MDP|MDT|MEE|MET|MFE|MHP|MHS|MI|MIL|MJN|MKC|MMC|MMM|" +
-        "MO|MOLX|MON|MOT|MRK|MRO|MS|MSFT|MTB|MU|MUR|MWV|MWW|MXB|MYL|NBL|NBR|" + 
+        "MO|MOLX|MON|MOT|MRK|MRO|MS|MSFT|MTB|MU|MUR|MWV|MWW|MXB|MYL|NBL|NBR|" +
         "NDAQ|NEM|NI|NKE|NOC|NOV|NOVL|NSC|NSM|NTAP|NTRS|NU|NUE|NVDA|NVLS|NWL|" +
         "NWSA|NYT|NYX|ODP|OI|OMC|ORCL|ORLY|OXY|PAYX|PBCT|PBG|PBI|PCAR|PCG|" +
         "PCL|PCP|PCS|PDCO|PEG|PEP|PFE|PFG|PG|PGN|PGR|PH|PHM|PKI|PLD|PLL|PM|" +
@@ -124,7 +124,7 @@
         "VNO|VRSN|VTR|VZ|WAG|WAT|WDC|WEC|WFC|WFMI|WFR|WHR|WIN|WLP|WM|WMB|WMT|" +
         "WPI|WPO|WU|WY|WYN|WYNN|X|XEL|XL|XLNX|XOM|XRAY|XRX|XTO|YHOO|YUM|ZION|" +
         "ZMH");
-    
+
     // Precompile each regex
     for (Map.Entry<String,String> entry : sectorQueries.entrySet()) {
       sectorPatterns.put(entry.getKey(), compile(entry.getValue()));
@@ -142,24 +142,26 @@
   /**
    * A mapping of usernames to {@link PlayerStatus}.
    */
-  private Map<String, PlayerStatus> players = new HashMap<String, PlayerStatus>();
-  
+  private Map<String, PlayerStatus> players =
+    new HashMap<String, PlayerStatus>();
+
   public StockResponse addFavorite(String ticker, Range favoritesRange) {
     PlayerStatus player = ensurePlayer();
     player.addFavorite(ticker);
-    Result favorites = query(player.getFavoritesQuery(), player.getFavoritesPattern(), favoritesRange);
-    return new StockResponse(null, favorites.quotes, null,
+    Result favorites = query(player.getFavoritesQuery(),
+        player.getFavoritesPattern(), favoritesRange, false);
+    return new StockResponse(null, favorites.quotes, null, null,
         0, favorites.numRows, 0, player.getCash());
   }
 
   public Result getSectorQuotes(String sector, Range sectorRange) {
     sector = sector.toUpperCase();
     String sectorQuery = sectorQueries.get(sector);
-    Pattern sectorPattern = sectorPatterns.get(sector);
     if (sectorQuery == null) {
       return null;
     }
-    return query(sectorQuery, sectorPattern, sectorRange);
+    Pattern sectorPattern = sectorPatterns.get(sector);
+    return query(sectorQuery, sectorPattern, sectorRange, false);
   }
 
   public StockResponse getStockQuotes(StockRequest request)
@@ -172,26 +174,30 @@
     Range searchRange = request.getSearchRange();
     Range favoritesRange = request.getFavoritesRange();
     Range sectorRange = request.getSectorRange();
-    
+
     PlayerStatus player = ensurePlayer();
-    Result searchResults = query(query, compile(query), searchRange);
-    Result favorites = query(player.getFavoritesQuery(), player.getFavoritesPattern(), favoritesRange);
-    Result sector = sectorRange != null ? getSectorQuotes(request.getSector(), sectorRange) : null;
+    Result searchResults = query(query, compile(query), searchRange, true);
+    Result favorites = query(player.getFavoritesQuery(),
+        player.getFavoritesPattern(), favoritesRange, false);
+    String sectorName = request.getSector();
+    Result sector = sectorRange != null ?
+        getSectorQuotes(sectorName, sectorRange) : null;
 
     return new StockResponse(searchResults.quotes,
         favorites.quotes,
+        sector != null ? sectorName : null,
         sector != null ? sector.quotes : null,
         searchResults.numRows,
         favorites.numRows,
         sector != null ? sector.numRows : 0,
         player.getCash());
   }
-  
+
   public StockResponse removeFavorite(String ticker, Range favoritesRange) {
     PlayerStatus player = ensurePlayer();
     player.removeFavorite(ticker);
-    Result favorites = query(player.getFavoritesQuery(), player.getFavoritesPattern(), favoritesRange);
-    return new StockResponse(null, favorites.quotes, null,
+    Result favorites = query(player.getFavoritesQuery(), player.getFavoritesPattern(), favoritesRange, false);
+    return new StockResponse(null, favorites.quotes, null, null,
         0, favorites.numRows, 0, player.getCash());
   }
 
@@ -199,16 +205,24 @@
       throws IllegalArgumentException {
     // Get the current stock price.
     String ticker = transaction.getTicker();
-    Pattern tickerPattern = compile(ticker);
     if (ticker == null || ticker.length() < 0) {
       throw new IllegalArgumentException("Stock could not be found");
     }
-    Result result = query(ticker, tickerPattern, new DefaultRange(0, 1));
+
+    String tickerRegex = ticker;
+    if (!ticker.startsWith("^")) {
+      tickerRegex = "^" + tickerRegex;
+    }
+    if (!ticker.endsWith("$")) {
+      tickerRegex = tickerRegex + "$";
+    }
+    Pattern tickerPattern = compile(ticker);
+    Result result = query(tickerRegex, tickerPattern, new DefaultRange(0, 1), false);
     if (result.numRows != 1 || result.quotes.size() != 1) {
       throw new IllegalArgumentException("Could not resolve stock ticker");
     }
     StockQuote quote = result.quotes.get(0);
-  
+
     // Perform the transaction with the user.
     int quantity = transaction.getQuantity();
     int price = quote.getPrice();
@@ -217,14 +231,14 @@
     } else {
       ensurePlayer().sell(ticker, quantity, price);
     }
-  
-    return new Transaction(true, ticker, quantity, price);
+
+    return new Transaction(transaction.isBuy(), ticker, quantity, price);
   }
 
   /**
    * Ensure that a {@link PlayerStatus} for the current player exists and return
    * it.
-   * 
+   *
    * @return the {@link PlayerStatus} for the current player
    */
   private PlayerStatus ensurePlayer() {
@@ -237,11 +251,11 @@
     return player;
   }
 
-  private List<String> getTickers(String query, Pattern pattern) {
+  private List<String> getTickers(String query, Pattern pattern, boolean matchNames) {
     Set<String> tickers = new TreeSet<String>();
     if (query.length() > 0) {
       query = query.toUpperCase();
-      
+
       int count = 0;
       for (String ticker : stockTickers) {
         if (ticker.startsWith(query) || (pattern != null && match(ticker, pattern))) {
@@ -252,8 +266,8 @@
           }
         }
       }
-      
-      if (pattern != null) {
+
+      if (matchNames && pattern != null) {
         for (Map.Entry<String,String> entry : companyNamesBySymbol.entrySet()) {
           if (match(entry.getValue(), pattern)) {
             tickers.add(entry.getKey());
@@ -265,7 +279,7 @@
         }
       }
     }
-      
+
     return new ArrayList<String>(tickers);
   }
 
@@ -276,15 +290,16 @@
 
   /**
    * Query the remote service to retrieve current stock prices.
-   * 
+   *
    * @param query the query string
    * @param range the range of results requested
    * @return the stock quotes
    */
-  private Result query(String query, Pattern queryPattern, Range range) {
+  private Result query(String query, Pattern queryPattern, Range range,
+      boolean matchNames) {
     // Get all symbols for the query.
     PlayerStatus player = ensurePlayer();
-    List<String> symbols = getTickers(query, queryPattern);
+    List<String> symbols = getTickers(query, queryPattern, matchNames);
 
     if (symbols.size() == 0) {
       return new Result(new StockQuoteList(0), 0);
@@ -344,6 +359,7 @@
 
       String symbol = null;
       String price = null;
+      String change = null;
 
       Matcher dataMatcher = DATA_PATTERN.matcher(group);
       while (dataMatcher.find()) {
@@ -353,6 +369,8 @@
           symbol = data;
         } else if (tag.equals("l_cur")) {
           price = data;
+        } else if (tag.equals("c")) {
+          change = data;
         }
       }
 
@@ -363,8 +381,10 @@
           String name = companyNamesBySymbol.get(symbol);
           Integer sharesOwned = player.getSharesOwned(symbol);
           boolean favorite = player.isFavorite(symbol);
-          priceMap.put(symbol, new StockQuote(symbol, name, iprice,
-              sharesOwned == null ? 0 : sharesOwned.intValue(), favorite));
+          int totalPaid = player.getAverageCostBasis(symbol);
+          priceMap.put(symbol, new StockQuote(symbol, name, iprice, change,
+              sharesOwned == null ? 0 : sharesOwned.intValue(), favorite,
+                  totalPaid));
         } catch (NumberFormatException e) {
           System.out.println("Bad price " + price + " for symbol " + symbol);
         }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/shared/StockQuote.java b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/shared/StockQuote.java
index 4e3df91..881ba14 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/shared/StockQuote.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/shared/StockQuote.java
@@ -26,8 +26,10 @@
   private String name;
   private transient String notes;
   private int price;
+  private String change;
   private String ticker;
   private int sharesOwned;
+  private int totalPaid; // Total amount paid for all share, int pennies
 
   /**
    * Construct a new {@link StockQuote}.
@@ -38,13 +40,15 @@
    * @param sharesOwned the number of shares owned by the player
    * @param favorite true if the stock is in the player's favorites
    */
-  public StockQuote(String ticker, String name, int price, int sharesOwned,
-      boolean favorite) {
+  public StockQuote(String ticker, String name, int price, String change, int sharesOwned,
+      boolean favorite, int totalPaid) {
     this.ticker = ticker;
     this.name = name;
     this.price = price;
+    this.change = change;
     this.sharesOwned = sharesOwned;
     this.favorite = favorite;
+    this.totalPaid = totalPaid;
   }
 
   /**
@@ -52,6 +56,10 @@
    */
   StockQuote() {
   }
+  
+  public String getChange() {
+    return change;
+  }
 
   public String getDisplayPrice() {
     int dollars = getPrice() / 100;
@@ -87,6 +95,14 @@
   public String getTicker() {
     return ticker;
   }
+  
+  public int getTotalPaid() {
+    return totalPaid;
+  }
+  
+  public int getValue() {
+    return price * sharesOwned;
+  }
 
   public boolean isFavorite() {
     return favorite;
@@ -99,6 +115,7 @@
   @Override
   public String toString() {
     return "StockQuote [ticker=" + ticker + ", name=\"" + name + "\", price="
-        + price + ", notes=\"" + notes + "\", favorite=" + favorite + "]";
+        + price + ", notes=\"" + notes + "\", favorite=" + favorite
+        + ", totalPaid=" + totalPaid + "]";
   }
 }
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 428a7fc..e8844c3 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
@@ -32,11 +32,13 @@
   private int numSector;
   private StockQuoteList searchResults;
   private StockQuoteList sector;
+  private String sectorName;
 
   public StockResponse(StockQuoteList searchResults, StockQuoteList favorites,
-      StockQuoteList sector, int numSearchResults, int numFavorites, int numSector, int cash) {
+      String sectorName, StockQuoteList sector, int numSearchResults, int numFavorites, int numSector, int cash) {
     this.searchResults = searchResults;
     this.favorites = favorites;
+    this.sectorName = sectorName;
     this.sector = sector;
     this.numSearchResults = numSearchResults;
     this.numFavorites = numFavorites;
@@ -84,4 +86,8 @@
   public StockQuoteList getSector() {
     return sector;
   }
+  
+  public String getSectorName() {
+    return sectorName;
+  }
 }
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 f8cd50d..57f42e8 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeView.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeView.java
@@ -92,7 +92,7 @@
             id = id.substring(11);
             String[] path = id.split("-");
 
-            TreeNodeView<?> nodeView = getRootNode();
+            TreeNodeView<?> nodeView = getRootTreeNodeView();
             for (String s : path) {
               nodeView = nodeView.getChildTreeNodeView(Integer.parseInt(s));
             }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/StandardTreeView.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/StandardTreeView.java
index 2a3665b..e1960dc 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/StandardTreeView.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/StandardTreeView.java
@@ -230,7 +230,7 @@
         Element currentTarget = event.getCurrentEventTarget().cast();
         if (currentTarget == getElement()) {
           Element target = event.getEventTarget().cast();
-          elementClicked(target, event, getRootNode());
+          elementClicked(target, event, getRootTreeNodeView());
         }
         break;
     }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeNode.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeNode.java
index e9a0694..88f6492 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeNode.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeNode.java
@@ -29,4 +29,6 @@
   TreeNode<?> getParentNode();
   
   T getValue();
+  
+  boolean isOpen();
 }
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 38c9cbe..98833de 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeNodeView.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeNodeView.java
@@ -147,6 +147,13 @@
   }
 
   /**
+   * Returns true if the node is open.
+   */
+  public boolean isOpen() {
+    return open;
+  }
+  
+  /**
    * Check if this is a root node at the top of the tree.
    *
    * @return true if a root node, false if not
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 c29fa37..f680956 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeView.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeView.java
@@ -133,6 +133,10 @@
   public TreeNodeViewAnimation getAnimation() {
     return animation;
   }
+  
+  public TreeNode<?> getRootNode() {
+    return rootNode;
+  }
 
   public TreeViewModel getTreeViewModel() {
     return model;
@@ -210,7 +214,7 @@
     return openImageHtml;
   }
 
-  protected TreeNodeView<?> getRootNode() {
+  protected TreeNodeView<?> getRootTreeNodeView() {
     return rootNode;
   }
 
diff --git a/bikeshed/src/com/google/gwt/sample/expenses/domain/Storage.java b/bikeshed/src/com/google/gwt/sample/expenses/domain/Storage.java
index 260c2a4..47d3c63 100644
--- a/bikeshed/src/com/google/gwt/sample/expenses/domain/Storage.java
+++ b/bikeshed/src/com/google/gwt/sample/expenses/domain/Storage.java
@@ -253,8 +253,9 @@
       }
 
       public Void visit(Employee employee) {
-        if (null == employee.getUserName())
+        if (null == employee.getUserName()) {
           return null;
+        }
         if (previous != null) {
           Employee prevEmployee = (Employee) previous;
           if (!prevEmployee.getUserName().equals(next)) {
diff --git a/bikeshed/test/com/google/gwt/sample/expenses/domain/EntityTester.java b/bikeshed/test/com/google/gwt/sample/expenses/domain/EntityTester.java
index 706bf2d..2a12c15 100644
--- a/bikeshed/test/com/google/gwt/sample/expenses/domain/EntityTester.java
+++ b/bikeshed/test/com/google/gwt/sample/expenses/domain/EntityTester.java
@@ -15,7 +15,6 @@
  */
 package com.google.gwt.sample.expenses.domain;
 
-
 class EntityTester {
   Currency currency;
   Employee employee;