Adding an option to buy or sell stock the datawidgets sample.


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7563 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/src/com/google/gwt/sample/datawidgets/client/BuySellPopup.java b/bikeshed/src/com/google/gwt/sample/datawidgets/client/BuySellPopup.java
new file mode 100644
index 0000000..98cc9ed
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/datawidgets/client/BuySellPopup.java
@@ -0,0 +1,134 @@
+/*
+ * 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.sample.datawidgets.client;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.sample.datawidgets.shared.StockQuote;
+import com.google.gwt.sample.datawidgets.shared.Transaction;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DeferredCommand;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.DialogBox;
+import com.google.gwt.user.client.ui.FlexTable;
+import com.google.gwt.user.client.ui.TextBox;
+
+/**
+ * A popup used for purchasing stock.
+ */
+public class BuySellPopup extends DialogBox {
+
+  private StockQuote quote;
+
+  /**
+   * The table used for layout.
+   */
+  private FlexTable layout = new FlexTable();
+
+  /**
+   * The box used to change the quantity.
+   */
+  private TextBox quantityBox = new TextBox();
+
+  /**
+   * The button used to buy or sell.
+   */
+  private Button opButton;
+
+  /**
+   * True if we are buying, false if hiding.
+   */
+  private boolean isBuying;
+
+  /**
+   * The last transaction.
+   */
+  private Transaction transaction;
+
+  public BuySellPopup() {
+    super(false, true);
+    setGlassEnabled(true);
+    setWidget(layout);
+
+    layout.setHTML(0, 0, "<b>Ticker:</b>");
+    layout.setHTML(1, 0, "<b>Name:</b>");
+    layout.setHTML(2, 0, "<b>Price:</b>");
+    layout.setHTML(3, 0, "<b>Quantity:</b>");
+    layout.setWidget(3, 1, quantityBox);
+
+    // Buy Button.
+    opButton = new Button("", new ClickHandler() {
+      public void onClick(ClickEvent event) {
+        try {
+          int quantity = Integer.parseInt(quantityBox.getText());
+          transaction = new Transaction(isBuying, quote.getTicker(), quantity);
+          hide();
+        } catch (NumberFormatException e) {
+          Window.alert("You must enter a valid quantity");
+        }
+      }
+    });
+    layout.setWidget(4, 0, opButton);
+
+    // Cancel Button.
+    Button cancelButton = new Button("Cancel", new ClickHandler() {
+      public void onClick(ClickEvent event) {
+        hide();
+      }
+    });
+    layout.setWidget(4, 1, cancelButton);
+  }
+
+  /**
+   * Get the last transaction.
+   * 
+   * @return the last transaction, or null if cancelled
+   */
+  public Transaction getTransaction() {
+    return transaction;
+  }
+
+  /**
+   * Set the current {@link StockQuote}.
+   * 
+   * @param quote the stock quote to buy
+   * @param isBuying true if buying the stock
+   */
+  public void setStockQuote(StockQuote quote, boolean isBuying) {
+    this.quote = quote;
+    String op = isBuying ? "Buy" : "Sell";
+    setText(op + " " + quote.getTicker() + " (" + quote.getName() + ")");
+    layout.setHTML(0, 1, quote.getTicker());
+    layout.setHTML(1, 1, quote.getName());
+    layout.setHTML(2, 1, quote.getDisplayPrice());
+    quantityBox.setText("0");
+    opButton.setText(op);
+    this.isBuying = isBuying;
+    transaction = null;
+  }
+
+  @Override
+  protected void onLoad() {
+    super.onLoad();
+    DeferredCommand.addCommand(new Command() {
+      public void execute() {
+        quantityBox.selectAll();
+        quantityBox.setFocus(true);
+      }
+    });
+  }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/datawidgets/client/DataBackedWidgets.java b/bikeshed/src/com/google/gwt/sample/datawidgets/client/DataBackedWidgets.java
index d98c001..4ceb9dd 100644
--- a/bikeshed/src/com/google/gwt/sample/datawidgets/client/DataBackedWidgets.java
+++ b/bikeshed/src/com/google/gwt/sample/datawidgets/client/DataBackedWidgets.java
@@ -15,15 +15,17 @@
  */
 package com.google.gwt.sample.datawidgets.client;
 
+import com.google.gwt.cells.client.ButtonCell;
 import com.google.gwt.cells.client.CheckboxCell;
 import com.google.gwt.cells.client.CurrencyCell;
 import com.google.gwt.cells.client.Mutator;
 import com.google.gwt.cells.client.TextCell;
-import com.google.gwt.cells.client.TextInputCell;
 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.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
 import com.google.gwt.list.client.Column;
 import com.google.gwt.list.client.PagingTableListView;
 import com.google.gwt.list.shared.AsyncListModel;
@@ -33,33 +35,33 @@
 import com.google.gwt.sample.datawidgets.shared.StockQuoteList;
 import com.google.gwt.sample.datawidgets.shared.StockRequest;
 import com.google.gwt.sample.datawidgets.shared.StockResponse;
+import com.google.gwt.sample.datawidgets.shared.Transaction;
 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.HTML;
 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 java.util.HashMap;
-
 /**
  * Entry point classes define <code>onModuleLoad()</code>.
  */
 public class DataBackedWidgets implements EntryPoint {
-  
+
   /**
    * The delay between updates in milliseconds.
    */
   private static final int UPDATE_DELAY = 5000;
-  
+
   /**
    * The {@link StockService} used to retrieve data.
    */
   private final StockServiceAsync dataService = GWT.create(StockService.class);
 
   private final Label errorLabel = new Label();
-  
+
   private Column<StockQuote, Boolean> favoriteColumn = new Column<StockQuote, Boolean>(
       new CheckboxCell()) {
     @Override
@@ -67,7 +69,7 @@
       return object.isFavorite();
     }
   };
-  
+
   private Column<StockQuote, String> nameColumn = new Column<StockQuote, String>(
       new TextCell()) {
     @Override
@@ -84,6 +86,22 @@
     }
   };
 
+  private Column<StockQuote, String> buyColumn = new Column<StockQuote, String>(
+      new ButtonCell()) {
+    @Override
+    protected String getValue(StockQuote object) {
+      return "Buy";
+    }
+  };
+
+  private Column<StockQuote, String> sellColumn = new Column<StockQuote, String>(
+      new ButtonCell()) {
+    @Override
+    protected String getValue(StockQuote object) {
+      return "Sell";
+    }
+  };
+
   private final TextBox queryField = new TextBox();
 
   private Column<StockQuote, String> tickerColumn = new Column<StockQuote, String>(
@@ -104,11 +122,6 @@
 
   private AsyncListModel<StockQuote> favoritesListModel;
 
-  /**
-   * User supplied notes, indexed by ticker symbol.
-   */
-  private HashMap<String,String> notesByTicker = new HashMap<String,String>();
-
   private PagingTableListView<StockQuote> resultsTable;
 
   private AsyncListModel<StockQuote> searchListModel;
@@ -126,6 +139,11 @@
   private PagingTableListView<StockQuote> favoritesTable;
 
   /**
+   * The popup used to purchase stock.
+   */
+  private BuySellPopup buySellPopup = new BuySellPopup();
+
+  /**
    * This is the entry point method.
    */
   public void onModuleLoad() {
@@ -139,19 +157,21 @@
     // Focus the cursor on the name field when the app loads
     queryField.setFocus(true);
     queryField.selectAll();
-    
+
     // Create the list models
-    searchListModel = new AsyncListModel<StockQuote>(new DataSource<StockQuote>() {
-      public void requestData(AsyncListModel<StockQuote> listModel) {
-        update();
-      }
-    });
-    
-    favoritesListModel = new AsyncListModel<StockQuote>(new DataSource<StockQuote>() {
-      public void requestData(AsyncListModel<StockQuote> listModel) {
-        update();
-      }
-    });
+    searchListModel = new AsyncListModel<StockQuote>(
+        new DataSource<StockQuote>() {
+          public void requestData(AsyncListModel<StockQuote> listModel) {
+            update();
+          }
+        });
+
+    favoritesListModel = new AsyncListModel<StockQuote>(
+        new DataSource<StockQuote>() {
+          public void requestData(AsyncListModel<StockQuote> listModel) {
+            update();
+          }
+        });
 
     // Create the results table.
     resultsTable = new PagingTableListView<StockQuote>(searchListModel, 10);
@@ -159,18 +179,52 @@
     resultsTable.addColumn(tickerColumn);
     resultsTable.addColumn(nameColumn);
     resultsTable.addColumn(priceColumn);
-    
+    resultsTable.addColumn(buyColumn);
+
     favoritesTable = new PagingTableListView<StockQuote>(favoritesListModel, 10);
     favoritesTable.addColumn(tickerColumn);
     favoritesTable.addColumn(priceColumn);
     favoritesTable.addColumn(sharesColumn);
-    
+    favoritesTable.addColumn(buyColumn);
+    favoritesTable.addColumn(sellColumn);
+
     favoriteColumn.setMutator(new Mutator<StockQuote, Boolean>() {
       public void mutate(StockQuote object, Boolean after) {
         setFavorite(object.getTicker(), after);
       }
     });
 
+    buyColumn.setMutator(new Mutator<StockQuote, String>() {
+      public void mutate(StockQuote object, String after) {
+        buySellPopup.setStockQuote(object, true);
+        buySellPopup.center();
+      }
+    });
+
+    sellColumn.setMutator(new Mutator<StockQuote, String>() {
+      public void mutate(StockQuote object, String after) {
+        buySellPopup.setStockQuote(object, false);
+        buySellPopup.center();
+      }
+    });
+
+    buySellPopup.addCloseHandler(new CloseHandler<PopupPanel>() {
+      public void onClose(CloseEvent<PopupPanel> event) {
+        Transaction t = buySellPopup.getTransaction();
+        if (t != null) {
+          dataService.transact(t, new AsyncCallback<Transaction>() {
+            public void onFailure(Throwable caught) {
+              Window.alert("Error: " + caught.getMessage());
+            }
+
+            public void onSuccess(Transaction result) {
+              update();
+            }
+          });
+        }
+      }
+    });
+
     RootPanel.get().add(resultsTable);
     RootPanel.get().add(new HTML("<hr>"));
     RootPanel.get().add(favoritesTable);
@@ -184,10 +238,10 @@
 
     update();
   }
-  
+
   /**
    * Set or unset a ticker symbol as a 'favorite.'
-   *  
+   * 
    * @param ticker the ticker symbol
    * @param favorite if true, make the stock a favorite
    */
@@ -214,22 +268,24 @@
       });
     }
   }
-  
+
   /**
    * Request data from the server using the last query string.
    */
   private void update() {
     updateTimer.cancel();
-    
+
     Range[] searchRanges = searchListModel.getRanges();
     Range[] favoritesRanges = favoritesListModel.getRanges();
-    
-    if (searchRanges == null || searchRanges.length == 0 || favoritesRanges == null || favoritesRanges.length == 0) {
+
+    if (searchRanges == null || searchRanges.length == 0
+        || favoritesRanges == null || favoritesRanges.length == 0) {
       return;
     }
-    
+
     String searchQuery = queryField.getText();
-    StockRequest request = new StockRequest(searchQuery, searchRanges[0], favoritesRanges[0]);
+    StockRequest request = new StockRequest(searchQuery, searchRanges[0],
+        favoritesRanges[0]);
     dataService.getStockQuotes(request, new AsyncCallback<StockResponse>() {
       public void onFailure(Throwable caught) {
         Window.alert("ERROR: " + caught.getMessage());
@@ -238,15 +294,17 @@
 
       public void onSuccess(StockResponse result) {
         StockQuoteList searchResults = result.getSearchResults();
-        
+
         searchListModel.updateDataSize(result.getNumSearchResults(), true);
-        searchListModel.updateViewData(searchResults.getStartIndex(), searchResults.size(), searchResults);        
+        searchListModel.updateViewData(searchResults.getStartIndex(),
+            searchResults.size(), searchResults);
 
         StockQuoteList favorites = result.getFavorites();
-        
+
         favoritesListModel.updateDataSize(result.getNumFavorites(), true);
-        favoritesListModel.updateViewData(favorites.getStartIndex(), favorites.size(), favorites);
-       
+        favoritesListModel.updateViewData(favorites.getStartIndex(),
+            favorites.size(), favorites);
+
         updateTimer.schedule(UPDATE_DELAY);
       }
     });
diff --git a/bikeshed/src/com/google/gwt/sample/datawidgets/client/StockService.java b/bikeshed/src/com/google/gwt/sample/datawidgets/client/StockService.java
index 78d03c8..46d3a3d 100644
--- a/bikeshed/src/com/google/gwt/sample/datawidgets/client/StockService.java
+++ b/bikeshed/src/com/google/gwt/sample/datawidgets/client/StockService.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.sample.datawidgets.shared.StockRequest;
 import com.google.gwt.sample.datawidgets.shared.StockResponse;
+import com.google.gwt.sample.datawidgets.shared.Transaction;
 import com.google.gwt.user.client.rpc.RemoteService;
 import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
 
@@ -25,10 +26,13 @@
  */
 @RemoteServiceRelativePath("stock")
 public interface StockService extends RemoteService {
-  
+
   StockResponse getStockQuotes(StockRequest request)
       throws IllegalArgumentException;
 
   void addFavorite(String ticker);
+
   void removeFavorite(String ticker);
+
+  Transaction transact(Transaction transaction) throws IllegalArgumentException;
 }
diff --git a/bikeshed/src/com/google/gwt/sample/datawidgets/client/StockServiceAsync.java b/bikeshed/src/com/google/gwt/sample/datawidgets/client/StockServiceAsync.java
index bd56425..fca11c4 100644
--- a/bikeshed/src/com/google/gwt/sample/datawidgets/client/StockServiceAsync.java
+++ b/bikeshed/src/com/google/gwt/sample/datawidgets/client/StockServiceAsync.java
@@ -17,15 +17,19 @@
 
 import com.google.gwt.sample.datawidgets.shared.StockRequest;
 import com.google.gwt.sample.datawidgets.shared.StockResponse;
+import com.google.gwt.sample.datawidgets.shared.Transaction;
 import com.google.gwt.user.client.rpc.AsyncCallback;
 
 /**
  * The async counterpart of <code>DataService</code>.
  */
 public interface StockServiceAsync {
-  void getStockQuotes(StockRequest request, AsyncCallback<StockResponse> callback);
+  void getStockQuotes(StockRequest request,
+      AsyncCallback<StockResponse> callback);
 
   void addFavorite(String ticker, AsyncCallback<Void> callback);
 
   void removeFavorite(String ticker, AsyncCallback<Void> callback);
+
+  void transact(Transaction transaction, AsyncCallback<Transaction> callback);
 }
diff --git a/bikeshed/src/com/google/gwt/sample/datawidgets/server/StockServiceImpl.java b/bikeshed/src/com/google/gwt/sample/datawidgets/server/StockServiceImpl.java
index b7225d6..bf64eec 100644
--- a/bikeshed/src/com/google/gwt/sample/datawidgets/server/StockServiceImpl.java
+++ b/bikeshed/src/com/google/gwt/sample/datawidgets/server/StockServiceImpl.java
@@ -21,6 +21,7 @@
 import com.google.gwt.sample.datawidgets.shared.StockQuoteList;
 import com.google.gwt.sample.datawidgets.shared.StockRequest;
 import com.google.gwt.sample.datawidgets.shared.StockResponse;
+import com.google.gwt.sample.datawidgets.shared.Transaction;
 import com.google.gwt.user.server.rpc.RemoteServiceServlet;
 
 import java.io.IOException;
@@ -51,7 +52,7 @@
   private class Result {
     int numRows;
     StockQuoteList quotes;
-    
+
     public Result(StockQuoteList quotes, int numRows) {
       this.quotes = quotes;
       this.numRows = numRows;
@@ -79,34 +80,60 @@
 
   private String favoritesQuery = IMPOSSIBLE_TICKER_SYMBOL;
 
-  private HashMap<String,Integer> sharesOwnedBySymbol = new HashMap<String,Integer>();
+  private HashMap<String, Integer> sharesOwnedBySymbol = new HashMap<String, Integer>();
 
   public void addFavorite(String ticker) {
     favorites.add(ticker);
     generateFavoritesQuery();
   }
-  
+
   public StockResponse getStockQuotes(StockRequest request)
       throws IllegalArgumentException {
-    
+
     String query = request.getSearchQuery();
     if (query == null | query.length() == 0) {
       query = ".*";
     }
     Range searchRange = request.getSearchRange();
     Range favoritesRange = request.getFavoritesRange();
-    
+
     Result searchResults = query(query, searchRange);
     Result favorites = query(favoritesQuery, favoritesRange);
-    
-    return new StockResponse(searchResults.quotes, favorites.quotes, searchResults.numRows, favorites.numRows);
+
+    return new StockResponse(searchResults.quotes, favorites.quotes,
+        searchResults.numRows, favorites.numRows);
   }
-  
+
   public void removeFavorite(String ticker) {
     favorites.remove(ticker);
     generateFavoritesQuery();
   }
 
+  public Transaction transact(Transaction transaction)
+      throws IllegalArgumentException {
+    // TODO: Check that the stock exists.
+    String ticker = transaction.getTicker();
+    Integer current = sharesOwnedBySymbol.get(ticker);
+    if (current == null) {
+      current = 0;
+    }
+
+    int quantity = transaction.getQuantity();
+    if (transaction.isBuy()) {
+      current += quantity;
+      // TODO: Verify player has enough funds.
+      addFavorite(ticker);
+    } else {
+      if (quantity > current) {
+        throw new IllegalArgumentException(
+            "You cannot sell more stock than you own");
+      }
+      current -= quantity;
+    }
+    sharesOwnedBySymbol.put(ticker, current);
+    return new Transaction(true, ticker, quantity);
+  }
+
   private void generateFavoritesQuery() {
     StringBuilder sb = new StringBuilder(IMPOSSIBLE_TICKER_SYMBOL);
     for (String ticker : favorites) {
@@ -153,14 +180,14 @@
   private Result query(String query, Range range) {
     // Get all symbols for the query.
     List<String> symbols = getTickers(query);
-    
+
     if (symbols.size() == 0) {
       return new Result(new StockQuoteList(0), 0);
     }
 
     int start = range.getStart();
     int end = Math.min(start + range.getLength(), symbols.size());
-    
+
     // Get the symbols that are in range.
     Set<String> symbolsInRange = new HashSet<String>();
     if (end > start) {
@@ -256,4 +283,3 @@
     return new Result(toRet, symbols.size());
   }
 }
-
diff --git a/bikeshed/src/com/google/gwt/sample/datawidgets/shared/Transaction.java b/bikeshed/src/com/google/gwt/sample/datawidgets/shared/Transaction.java
new file mode 100644
index 0000000..94b9cd8
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/sample/datawidgets/shared/Transaction.java
@@ -0,0 +1,60 @@
+/*
+ * 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.sample.datawidgets.shared;
+
+import java.io.Serializable;
+
+/**
+ * The buy or sell transaction.
+ */
+public class Transaction implements Serializable {
+
+  /**
+   * True if a buy transaction, false if a sell transaction.
+   */
+  private boolean isBuy;
+
+  private String ticker;
+  private int quantity;
+
+  public Transaction(boolean isBuy, String ticker, int quantity) {
+    super();
+    this.isBuy = isBuy;
+    this.ticker = ticker;
+    this.quantity = quantity;
+  }
+
+  Transaction() {
+  }
+
+  public boolean isBuy() {
+    return isBuy;
+  }
+
+  public String getTicker() {
+    return ticker;
+  }
+
+  public int getQuantity() {
+    return quantity;
+  }
+
+  @Override
+  public String toString() {
+    String op = isBuy ? "Bought" : "Sold";
+    return op + " " + quantity + " shares of " + ticker;
+  }
+}