/* * Copyright (c) 2004-2011 Marco Maccaferri and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Marco Maccaferri - initial API and implementation */ package org.eclipsetrader.ui.internal.views; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.text.NumberFormat; import java.util.ArrayList; import java.util.List; import org.eclipse.core.databinding.observable.list.IListChangeListener; import org.eclipse.core.databinding.observable.list.ListChangeEvent; import org.eclipse.core.databinding.observable.list.ListDiffVisitor; import org.eclipse.core.databinding.observable.list.WritableList; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.widgets.Display; import org.eclipsetrader.core.feed.IPricingEnvironment; import org.eclipsetrader.core.feed.IPricingListener; import org.eclipsetrader.core.feed.PricingDelta; import org.eclipsetrader.core.feed.PricingEvent; import org.eclipsetrader.core.instruments.ISecurity; import org.eclipsetrader.core.views.IWatchList; import org.eclipsetrader.core.views.IWatchListColumn; import org.eclipsetrader.core.views.IWatchListElement; import org.eclipsetrader.core.views.WatchList; import org.eclipsetrader.core.views.WatchListColumn; import org.eclipsetrader.core.views.WatchListElement; import org.eclipsetrader.ui.internal.providers.GainValue; public class WatchListViewModel implements IAdaptable { public static final String PROP_NAME = "name"; public static final String PROP_DIRTY = "dirty"; private String name; private final WatchList watchList; private final IPricingEnvironment pricingEnvironment; private final List<WatchListViewColumn> columns = new ArrayList<WatchListViewColumn>(); private final WritableList observableColumns = new WritableList(columns, WatchListViewColumn.class); private final List<WatchListViewItem> items = new ArrayList<WatchListViewItem>(); private final WritableList observableItems = new WritableList(items, WatchListViewItem.class); private WatchListViewModelTotalsItem totalsItem; private NumberFormat formatter = NumberFormat.getInstance(); private NumberFormat percentageFormatter = NumberFormat.getInstance(); private Color positiveColor = Display.getDefault().getSystemColor(SWT.COLOR_GREEN); private Color negativeColor = Display.getDefault().getSystemColor(SWT.COLOR_RED); private boolean dirty; private final PropertyChangeSupport changeSupport = new PropertyChangeSupport(this); private final IPricingListener pricingListener = new IPricingListener() { @Override public void pricingUpdate(PricingEvent event) { doPricingUpdate(event); } }; private final PropertyChangeListener holdingChangeListener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { WatchListViewItem viewItem = (WatchListViewItem) evt.getSource(); updateValues(viewItem); changeSupport.firePropertyChange(PROP_DIRTY, dirty, dirty = true); } }; public WatchListViewModel(WatchList watchList, IPricingEnvironment pricingEnvironment) { this.name = watchList.getName(); this.watchList = watchList; this.pricingEnvironment = pricingEnvironment; formatter.setGroupingUsed(true); formatter.setMinimumIntegerDigits(1); formatter.setMinimumFractionDigits(0); formatter.setMaximumFractionDigits(2); percentageFormatter.setGroupingUsed(true); percentageFormatter.setMinimumIntegerDigits(1); percentageFormatter.setMinimumFractionDigits(2); percentageFormatter.setMaximumFractionDigits(2); IWatchListColumn[] columns = watchList.getColumns(); for (int i = 0; i < columns.length; i++) { observableColumns.add(new WatchListViewColumn(columns[i])); } IWatchListElement[] elements = watchList.getItems(); for (int i = 0; i < elements.length; i++) { WatchListViewItem viewItem = new WatchListViewItem(this, elements[i]); viewItem.setTrade(pricingEnvironment.getTrade(elements[i].getSecurity())); viewItem.setQuote(pricingEnvironment.getQuote(elements[i].getSecurity())); viewItem.setLastClose(pricingEnvironment.getLastClose(elements[i].getSecurity())); viewItem.setTodayOHL(pricingEnvironment.getTodayOHL(elements[i].getSecurity())); viewItem.setBook(pricingEnvironment.getBook(elements[i].getSecurity())); viewItem.addPropertyChangeListener(WatchListViewItem.PROP_QUANTITY, holdingChangeListener); viewItem.addPropertyChangeListener(WatchListViewItem.PROP_PRICE, holdingChangeListener); observableItems.add(viewItem); } totalsItem = new WatchListViewModelTotalsItem(); update(); observableItems.addListChangeListener(new IListChangeListener() { @Override public void handleListChange(ListChangeEvent event) { event.diff.accept(new ListDiffVisitor() { @Override public void handleAdd(int index, Object element) { WatchListViewItem viewItem = (WatchListViewItem) element; init(viewItem); viewItem.addPropertyChangeListener(WatchListViewItem.PROP_QUANTITY, holdingChangeListener); viewItem.addPropertyChangeListener(WatchListViewItem.PROP_PRICE, holdingChangeListener); changeSupport.firePropertyChange(PROP_DIRTY, dirty, dirty = true); } @Override public void handleRemove(int index, Object element) { WatchListViewItem viewItem = (WatchListViewItem) element; viewItem.removePropertyChangeListener(WatchListViewItem.PROP_QUANTITY, holdingChangeListener); viewItem.removePropertyChangeListener(WatchListViewItem.PROP_PRICE, holdingChangeListener); changeSupport.firePropertyChange(PROP_DIRTY, dirty, dirty = true); } }); } }); observableColumns.addListChangeListener(new IListChangeListener() { @Override public void handleListChange(ListChangeEvent event) { event.diff.accept(new ListDiffVisitor() { @Override public void handleAdd(int index, Object element) { WatchListViewColumn column = (WatchListViewColumn) element; for (WatchListViewItem viewItem : items) { column.getDataProvider().init(viewItem); } changeSupport.firePropertyChange(PROP_DIRTY, dirty, dirty = true); } @Override public void handleRemove(int index, Object element) { WatchListViewColumn column = (WatchListViewColumn) element; column.getDataProvider().dispose(); changeSupport.firePropertyChange(PROP_DIRTY, dirty, dirty = true); } }); } }); pricingEnvironment.addPricingListener(pricingListener); } public void init() { WatchListViewColumn[] column = columns.toArray(new WatchListViewColumn[columns.size()]); for (int i = 0; i < column.length; i++) { for (WatchListViewItem viewItem : items) { column[i].getDataProvider().init(viewItem); } } } private void init(WatchListViewItem viewItem) { WatchListViewColumn[] column = columns.toArray(new WatchListViewColumn[columns.size()]); for (int i = 0; i < column.length; i++) { column[i].getDataProvider().init(viewItem); } } public String getName() { return name; } public void setName(String name) { if (this.name != null && name != null && this.name.equals(name)) { return; } changeSupport.firePropertyChange(PROP_NAME, name, this.name = name); changeSupport.firePropertyChange(PROP_DIRTY, this.dirty, this.dirty = true); } public boolean isDirty() { return dirty; } public void setDirty(boolean dirty) { changeSupport.firePropertyChange(PROP_DIRTY, this.dirty, this.dirty = dirty); } public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { changeSupport.addPropertyChangeListener(listener); } public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { changeSupport.removePropertyChangeListener(listener); } public void add(ISecurity security) { WatchListViewItem viewItem = new WatchListViewItem(this, new WatchListElement(security, null, null, null)); viewItem.setLastClose(pricingEnvironment.getLastClose(security)); viewItem.setQuote(pricingEnvironment.getQuote(security)); viewItem.setTrade(pricingEnvironment.getTrade(security)); viewItem.setTodayOHL(pricingEnvironment.getTodayOHL(security)); updateValues(viewItem); observableItems.add(viewItem); } public void commit() { watchList.setName(name); IWatchListColumn[] c = new IWatchListColumn[columns.size()]; for (int i = 0; i < c.length; i++) { WatchListViewColumn viewColumn = columns.get(i); c[i] = viewColumn.getColumn(); if (c[i] != null) { c[i].setName(viewColumn.getName()); } else { c[i] = new WatchListColumn(viewColumn.getName(), viewColumn.getDataProviderFactory()); } } watchList.setColumns(c); List<IWatchListElement> e = new ArrayList<IWatchListElement>(); for (WatchListViewItem viewItem : items) { IWatchListElement element = viewItem.getElement(); element.setPosition(viewItem.getPosition()); element.setPurchasePrice(viewItem.getPurchasePrice()); e.add(element); } watchList.setItems(e.toArray(new IWatchListElement[e.size()])); } public void dispose() { pricingEnvironment.removePricingListener(pricingListener); } public IWatchList getWatchList() { return watchList; } public List<WatchListViewColumn> getColumns() { return columns; } public void setColumns(List<WatchListViewColumn> list) { int i; for (i = observableColumns.size() - 1; i >= 0; i--) { if (!list.contains(observableColumns.get(i))) { observableColumns.remove(i); } } for (i = 0; i < list.size(); i++) { if (!observableColumns.contains(list.get(i))) { observableColumns.add(i, list.get(i)); } } for (i = 0; i < list.size(); i++) { int index = observableColumns.indexOf(list.get(i)); columns.get(index).setName(list.get(i).getName()); if (index != i) { observableColumns.move(index, i); i = -1; } } update(); } public WritableList getObservableColumns() { return observableColumns; } public WritableList getObservableItems() { return observableItems; } protected void doPricingUpdate(PricingEvent event) { PricingDelta[] delta = event.getDelta(); double purchaseValue = 0.0; double marketValue = 0.0; for (WatchListViewItem viewItem : items) { if (viewItem.getSecurity() == event.getSecurity()) { for (int i = 0; i < delta.length; i++) { viewItem.setPriceData(delta[i].getNewValue()); } updateValues(viewItem); } GainValue gainValue = (GainValue) viewItem.getValue("org.eclipsetrader.ui.providers.gain"); if (gainValue != null) { purchaseValue += gainValue.getPurchaseValue(); marketValue += gainValue.getMarketValue(); } } if (purchaseValue != 0.0) { Double value = marketValue - purchaseValue; Double percentage = value / purchaseValue * 100.0; String text = (value > 0 ? "+" : "") + formatter.format(value) + " (" + (value > 0 ? "+" : "") + percentageFormatter.format(percentage) + "%)"; Color color = value != 0 ? value > 0 ? positiveColor : negativeColor : null; totalsItem.putValue("org.eclipsetrader.ui.providers.gain", new GainValue(value, purchaseValue, marketValue, text, color)); } } public void update() { for (WatchListViewItem viewItem : items) { updateValues(viewItem); } } private void updateValues(WatchListViewItem item) { WatchListViewColumn[] column = columns.toArray(new WatchListViewColumn[columns.size()]); for (int i = 0; i < column.length; i++) { IAdaptable value = column[i].getDataProvider().getValue(item); item.putValue(column[i].getId(), value); } } /* (non-Javadoc) * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class) */ @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public Object getAdapter(Class adapter) { if (adapter.isAssignableFrom(pricingEnvironment.getClass())) { return pricingEnvironment; } return null; } }