/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.openjpa.trader.client; import java.util.List; import org.apache.openjpa.trader.client.event.ServiceEvent; import org.apache.openjpa.trader.client.event.ServiceEventHandler.UpdateStockHandler; import org.apache.openjpa.trader.client.ui.HelpLink; import org.apache.openjpa.trader.client.ui.MessageBox; import org.apache.openjpa.trader.domain.Ask; import org.apache.openjpa.trader.domain.Bid; import org.apache.openjpa.trader.domain.Stock; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.KeyUpEvent; import com.google.gwt.event.dom.client.KeyUpHandler; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.FlexTable; import com.google.gwt.user.client.ui.HasHorizontalAlignment; import com.google.gwt.user.client.ui.HasVerticalAlignment; import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.TextBox; /** * This Widget allows the user to enter the details of a trade order (an Ask or * Bid) and call the {@link TradingServiceAdapterAsync Trading Service} via asynchronous RPC * callback to record the order. * <br> * The widget demonstrates the aspect where a displayed element can change either * because other elements within the same widget are changing or because some external * state is changing. For example, the gain/loss of a requested buy/sell offer can * change as the user enters a different price. It can also change if the market * price of the stock has changed externally. The former changes are handled by adding * event handlers to the widget elements (such as onKeyUp in a text box), the later * changes are notified by this widget registering to the {@link OpenTrader main application}. * * * @author Pinaki Poddar * */ class TradeOrderWindow extends FlexTable implements UpdateStockHandler { private final OpenTrader session; final ListBox symbols = new ListBox(false); final Button ask = new Button("Ask"); final Button bid = new Button("Bid"); final TextBox marketPrice = new TextBox(); final TextBox userPrice = new TextBox(); final TextBox userVolume = new TextBox(); final TextBox margin = new TextBox(); final TextBox gain = new TextBox(); public TradeOrderWindow(final OpenTrader session, int w, int h) { super(); this.session = session; setPixelSize(w, h); setStyleName("TradeOrderWindow"); session.registerHandler(ServiceEvent.StockUpdated.TYPE, this); marketPrice.setReadOnly(true); margin.setReadOnly(true); gain.setReadOnly(true); userPrice.setMaxLength(10); userVolume.setMaxLength(10); setCellPadding(-2); setCellSpacing(-1); setHTML(0, 0, "Stock"); setHTML(0, 1, "Market"); setHTML(0, 2, session.getName()); setHTML(0, 3, "Margin"); setHTML(0, 4, "Volume"); setHTML(0, 5, "Gain/Loss"); for (int i = 0; i < 5; i++) { getCellFormatter().addStyleName(0, i, "TradingWindow-Label"); } setWidget(1, 0, symbols); setWidget(1, 1, marketPrice); setWidget(1, 2, userPrice); setWidget(1, 3, margin); setWidget(1, 4, userVolume); setWidget(1, 5, gain); setWidget(2, 2, ask); setWidget(2, 3, bid); DOM.setStyleAttribute(getRowFormatter().getElement(0), "height", "4px"); userPrice.setFocus(true); userPrice.setTabIndex(1); userPrice.setTabIndex(2); ask.setTabIndex(3); bid.setTabIndex(4); userPrice.addKeyUpHandler(new KeyUpHandler() { public void onKeyUp(KeyUpEvent event) { if (userPrice.getText().trim().length() == 0) return; double price = 0.0; try { price = Double.parseDouble(userPrice.getText()); } catch (NumberFormatException e) { MessageBox.alert(userPrice.getText() + " must be a number"); return; } double diff = calculateDiff(price, getSelectedStock().getMarketPrice()); margin.setText(FormatUtil.priceFormat.format(diff)); gain.setText(FormatUtil.changeFormat.format(diff * Integer.parseInt(userVolume.getText()))); } }); userVolume.addKeyUpHandler(new KeyUpHandler() { public void onKeyUp(KeyUpEvent event) { if (userVolume.getText().trim().length() == 0) return; int volume = 0; try { volume = Integer.parseInt(userVolume.getText()); } catch (NumberFormatException e) { MessageBox.alert(userVolume.getText() + " must be a positive integer"); return; } if (volume <= 0) { MessageBox.alert(userVolume.getText() + " must be a positive integer"); return; } double diff = Double.parseDouble(margin.getText()); gain.setText(FormatUtil.changeFormat.format(diff * volume)); } }); List<Stock> stocks = session.getTradedStocks(); int n = stocks.size(); for (int i = 0; i < n; i++) { symbols.addItem(stocks.get(i).getSymbol()); } symbols.setSelectedIndex(0); initialize(stocks.get(0), false); symbols.addChangeHandler(new ChangeHandler() { @Override public void onChange(ChangeEvent event) { Stock stock = getSelectedStock(); initialize(stock, false); } }); ask.addClickHandler(new ClickHandler() { public void onClick(ClickEvent ce) { if (!validateData()) return; session.getService().ask(session.getTrader(), getSelectedStock(), Integer.parseInt(userVolume.getText()), Double.parseDouble(userPrice.getText()), new AskCallback()); } }); bid.addClickHandler(new ClickHandler() { public void onClick(ClickEvent ce) { if (!validateData()) return; session.getService().bid(session.getTrader(), getSelectedStock(), Integer.parseInt(userVolume.getText()), Double.parseDouble(userPrice.getText()), new BidCallback()); } }); } /** * Sets the content of the widgets based on the given stock. The widget * content depends on the current stock price as well as user entered * values. * * @param stock * @param retainUserValue */ void initialize(Stock stock, boolean retainUserValue) { marketPrice.setText(FormatUtil.priceFormat.format(stock.getMarketPrice())); if (retainUserValue && userPrice.getText().length() > 0) { double diff = calculateDiff(Double.parseDouble(userPrice.getText()), stock.getMarketPrice()); margin.setText(FormatUtil.priceFormat.format(diff)); gain.setText(FormatUtil.priceFormat.format(diff * Integer.parseInt(userVolume.getText()))); } else { userPrice.setText(FormatUtil.priceFormat.format(stock.getMarketPrice())); userVolume.setText(FormatUtil.volumeFormat.format(100)); margin.setText(FormatUtil.priceFormat.format(0)); gain.setText(FormatUtil.priceFormat.format(0)); } } public void addHelp(final String url) { HelpLink help = new HelpLink(url); setWidget(0, 6, help); getCellFormatter().setAlignment(0, 6, HasHorizontalAlignment.ALIGN_RIGHT, HasVerticalAlignment.ALIGN_MIDDLE); } Stock getSelectedStock() { return session.getTradedStocks().get(symbols.getSelectedIndex()); } boolean validateData() { try { if (Double.parseDouble(userPrice.getText()) <= 0) { MessageBox.alert("Price [" + userPrice.getText() + "] must be positive number"); return false; } } catch (NumberFormatException e) { MessageBox.alert("Price [" + userPrice.getText() + "] must be a positive number"); return false; } try { if (Integer.parseInt(userVolume.getText()) <= 0) { MessageBox.alert("Volume [" + userVolume.getText() + "] must be a positive integer"); return false; } } catch (NumberFormatException e) { MessageBox.alert("Volume [" + userVolume.getText() + "] must be a positive integer"); return false; } return true; } double calculateDiff(double p1, double p2) { return truncate(Math.abs(p1-p2)); } private static double truncate (double x){ double fract; double whole; if ( x > 0 ){ whole = Math.floor(x); fract = Math.floor( (x - whole) * 100) / 100; } else { whole = Math.ceil(x); fract = Math.ceil( (x - whole) * 100) / 100; } return whole + fract; } /** * --------------------------------------------------------------------------------- * Service Event Response Management * --------------------------------------------------------------------------------- */ public void onStockUpdated(ServiceEvent.StockUpdated event) { Stock updated = event.getPayload(); Stock current = getSelectedStock(); if (updated.equals(current)) { initialize(updated, true); } } /** * --------------------------------------------------------------------------------- * Asynchronous RPC service callbacks * --------------------------------------------------------------------------------- */ /** * Updates display once the offer to sell has been successfully placed. * */ class AskCallback implements AsyncCallback<Ask> { public void onSuccess(Ask result) { session.fireEvent(new ServiceEvent.TradableAdded(result)); } public void onFailure(Throwable caught) { session.handleError(caught); } } /** * Updates display once the offer to buy has been successfully placed. * */ class BidCallback implements AsyncCallback<Bid> { public void onSuccess(Bid result) { session.fireEvent(new ServiceEvent.TradableAdded(result)); } public void onFailure(Throwable caught) { session.handleError(caught); } } }