package org.marketcetera.photon.internal.marketdata.ui; import java.util.Comparator; import net.miginfocom.swt.MigLayout; import org.apache.commons.lang.builder.CompareToBuilder; import org.apache.commons.lang.time.DateFormatUtils; import org.eclipse.core.databinding.observable.map.IObservableMap; import org.eclipse.emf.databinding.EMFObservables; import org.eclipse.emf.databinding.EObjectObservableMap; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.databinding.viewers.ObservableListContentProvider; import org.eclipse.jface.databinding.viewers.ObservableMapLabelProvider; import org.eclipse.jface.viewers.ColumnWeightData; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CLabel; import org.eclipse.swt.custom.ScrolledComposite; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Table; import org.eclipse.ui.IViewSite; import org.eclipse.ui.PartInitException; import org.eclipse.ui.part.ViewPart; import org.marketcetera.marketdata.Content; import org.marketcetera.photon.commons.ui.table.ColumnConfiguration; import org.marketcetera.photon.commons.ui.table.TableConfiguration; import org.marketcetera.photon.commons.ui.table.TableSupport; import org.marketcetera.photon.core.ISymbolResolver; import org.marketcetera.photon.marketdata.IMarketData; import org.marketcetera.photon.marketdata.IMarketDataReference; import org.marketcetera.photon.model.marketdata.MDDepthOfBook; import org.marketcetera.photon.model.marketdata.MDPackage; import org.marketcetera.photon.model.marketdata.MDQuote; import org.marketcetera.util.misc.ClassVersion; import com.google.common.collect.ObjectArrays; /* $License$ */ /** * A view that displays market data. It is parameterized by its secondary id in the format: * * <pre> * symbol,product * </pre> * * @author <a href="mailto:will@marketcetera.com">Will Horn</a> * @version $Id: MarketDepthView.java 16859 2014-03-19 00:49:50Z colin $ * @since 1.5.0 */ @ClassVersion("$Id: MarketDepthView.java 16859 2014-03-19 00:49:50Z colin $") public class MarketDepthView extends ViewPart { /** * Create a new MarketDepthView instance. */ public MarketDepthView() { marketDataManager = Activator.getMarketDataManager().getMarketData(); symbolResolver = Activator.getSymbolResolver(); } /* (non-Javadoc) * @see org.eclipse.ui.part.ViewPart#init(org.eclipse.ui.IViewSite) */ @Override public void init(IViewSite site) throws PartInitException { super.init(site); String[] params = site.getSecondaryId().split(PARAMETER_DELIMITER); symbol = params[0]; product = Content.valueOf(params[1]); } @Override public void createPartControl(Composite parent) { final ScrolledComposite sc2 = new ScrolledComposite(parent, SWT.H_SCROLL); sc2.setExpandHorizontal(true); sc2.setExpandVertical(true); Composite composite = new Composite(sc2, SWT.NONE); sc2.setContent(composite); composite.setLayout(new MigLayout("ins 0, gap 0, fill", "[sg][sg]", "[][grow]")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ createHeading(composite).setLayoutData("grow, span, wrap"); //$NON-NLS-1$ mBidsTable = createTableSupport(getBidColumns()); mBidsTable.createTable(composite); mBidsTable.getTableViewer().getTable().getParent().setLayoutData("grow, hmin 0, wmin 0"); //$NON-NLS-1$ MenuManager menuManager = new MenuManager(); getViewSite().registerContextMenu(BIDS_TABLE_POPUP, menuManager, mBidsTable.getTableViewer()); mBidsTable.getTableViewer().getTable().setMenu(menuManager.createContextMenu(mBidsTable.getTableViewer().getTable())); mAsksTable = createTableSupport(getAskColumns()); mAsksTable.createTable(composite); mAsksTable.getTableViewer().getTable().getParent().setLayoutData("grow, hmin 0, wmin 0"); //$NON-NLS-1$ MenuManager menuManager2 = new MenuManager(); getViewSite().registerContextMenu(ASKS_TABLE_POPUP, menuManager2, mAsksTable.getTableViewer()); mAsksTable.getTableViewer().getTable().setMenu(menuManager2.createContextMenu(mAsksTable.getTableViewer().getTable())); sc2.setMinSize(composite.computeSize(SWT.DEFAULT, SWT.DEFAULT).x, 0); hookupData(); } /** * Connects market depth data to the view tables. */ private void hookupData() { mDataItem = marketDataManager.getDepthOfBook(symbolResolver.resolveSymbol(symbol), product); ObservableListContentProvider bids = new ObservableListContentProvider(); mBidsTable.getTableViewer().setContentProvider(bids); mBidsTable.getTableViewer().setLabelProvider(new ObservableMapLabelProvider(ObjectArrays.concat(createTimeMap(bids), EMFObservables.observeMaps(bids.getKnownElements(), new EStructuralFeature[] { MDPackage.Literals.MD_QUOTE__SOURCE, MDPackage.Literals.MD_QUOTE__SIZE, MDPackage.Literals.MD_QUOTE__PRICE })))); setSortColumn(mBidsTable, new BidComparator(), 3, SWT.DOWN); mBidsTable.getTableViewer().setInput(EMFObservables.observeList(mDataItem.get(), MDPackage.Literals.MD_DEPTH_OF_BOOK__BIDS)); ObservableListContentProvider asks = new ObservableListContentProvider(); mAsksTable.getTableViewer().setContentProvider(asks); mAsksTable.getTableViewer().setLabelProvider(new ObservableMapLabelProvider(ObjectArrays.concat(EMFObservables.observeMaps(asks.getKnownElements(), new EStructuralFeature[] { MDPackage.Literals.MD_QUOTE__PRICE, MDPackage.Literals.MD_QUOTE__SIZE, MDPackage.Literals.MD_QUOTE__SOURCE }), createTimeMap(asks)))); setSortColumn(mAsksTable, new AskComparator(), 0, SWT.UP); mAsksTable.getTableViewer().setInput(EMFObservables.observeList(mDataItem.get(), MDPackage.Literals.MD_DEPTH_OF_BOOK__ASKS)); } private void setSortColumn(TableSupport tableSupport, final Comparator<MDQuote> comparator, int column, int dir) { TableViewer viewer = tableSupport.getTableViewer(); Table table = viewer.getTable(); table.setSortColumn(table.getColumn(column)); table.setSortDirection(dir); viewer.setComparator(new ViewerComparator() { @Override public int compare(Viewer viewer, Object e1, Object e2) { MDQuote q1 = (MDQuote) e1; MDQuote q2 = (MDQuote) e2; return comparator.compare(q1, q2); } }); } private IObservableMap createTimeMap(ObservableListContentProvider bids) { // a custom map that formats the time return new EObjectObservableMap(bids.getKnownElements(), MDPackage.Literals.MD_QUOTE__TIME) { @Override protected Object doGet(Object key) { long l = (Long) super.doGet(key); return DateFormatUtils.ISO_TIME_NO_T_FORMAT.format(l); } }; } @Override public void dispose() { super.dispose(); if(mDataItem != null) { mDataItem.dispose(); mDataItem = null; } } private Composite createHeading(Composite parent) { Composite composite = new Composite(parent, SWT.NONE); Label label = new Label(composite, SWT.NONE); label.setText(Messages.MARKET_DEPTH_VIEW_SYMBOL_LABEL.getText()); { CLabel text = new CLabel(composite, SWT.BORDER); text.setText(symbol); text.setLayoutData("growx, w 120"); //$NON-NLS-1$ } label = new Label(composite, SWT.NONE); label.setText(Messages.MARKET_DEPTH_VIEW_SOURCE_LABEL.getText()); label.setLayoutData("gap 10"); //$NON-NLS-1$ { CLabel text = new CLabel(composite, SWT.BORDER); text.setText(getProductLabel(product)); text.setLayoutData("growx, w 120"); //$NON-NLS-1$ } composite.setLayout(new MigLayout()); return composite; } /** * Gets the product label to display for the given content. * * @param inProduct a <code>Content</code> value * @return a <code>String</code> value */ private String getProductLabel(Content inProduct) { switch (inProduct) { case AGGREGATED_DEPTH: return Messages.MARKET_DEPTH_VIEW_AGGREGATED_DEPTH_LABEL.getText(); case BBO10: return Messages.MARKET_DEPTH_VIEW_BBO10_LABEL.getText(); case LEVEL_2: return Messages.MARKET_DEPTH_VIEW_LEVEL_2_LABEL.getText(); case OPEN_BOOK: return Messages.MARKET_DEPTH_VIEW_OPEN_BOOK_LABEL.getText(); case TOTAL_VIEW: return Messages.MARKET_DEPTH_VIEW_TOTAL_VIEW_LABEL.getText(); case UNAGGREGATED_DEPTH: return Messages.MARKET_DEPTH_VIEW_UNAGGREGATED_DEPTH_LABEL.getText(); default: throw new UnsupportedOperationException(); } } private TableSupport createTableSupport(ColumnConfiguration[] bidColumns) { return TableSupport.create(TableConfiguration.defaults().tableStyle( SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | SWT.FULL_SELECTION).columns( bidColumns).headerVisible(true)); } private ColumnConfiguration[] getBidColumns() { return new ColumnConfiguration[] { ColumnConfiguration.defaults().heading( Messages.MARKET_DEPTH_VIEW_TIME_COLUMN_LABEL.getText()).sortable(false), ColumnConfiguration.defaults().heading( Messages.MARKET_DEPTH_VIEW_MPID_COLUMN_LABEL.getText()).sortable(false), ColumnConfiguration.defaults().heading( Messages.MARKET_DEPTH_VIEW_SIZE_COLUMN_LABEL.getText()).style(SWT.RIGHT) .sortable(false), ColumnConfiguration.defaults().heading( Messages.MARKET_DEPTH_VIEW_BID_PRICE_COLUMN_LABEL.getText()).style( SWT.RIGHT).layoutData(new ColumnWeightData(10, 60)).sortable(false) .comparator(new BidComparator()) }; } private ColumnConfiguration[] getAskColumns() { return new ColumnConfiguration[] { ColumnConfiguration.defaults().heading( Messages.MARKET_DEPTH_VIEW_ASK_PRICE_COLUMN_LABEL.getText()).layoutData( new ColumnWeightData(10, 60)).sortable(false).comparator( new AskComparator()), ColumnConfiguration.defaults().heading( Messages.MARKET_DEPTH_VIEW_SIZE_COLUMN_LABEL.getText()).sortable(false), ColumnConfiguration.defaults().heading( Messages.MARKET_DEPTH_VIEW_MPID_COLUMN_LABEL.getText()).sortable(false), ColumnConfiguration.defaults().heading( Messages.MARKET_DEPTH_VIEW_TIME_COLUMN_LABEL.getText()).sortable(false) }; } @Override public void setFocus() { mBidsTable.setFocus(); } /** * Compares {@link MDQuote} based on ascending price, then ascending time. */ @ClassVersion("$Id: MarketDepthView.java 16859 2014-03-19 00:49:50Z colin $") private static class AskComparator implements Comparator<MDQuote> { @Override public int compare(MDQuote o1, MDQuote o2) { // this comparator is only intended to be used on the ask list which does not have null // elements assert o1 != null; assert o2 != null; // lower price, lower time comes first return new CompareToBuilder().append(o1.getPrice(), o2.getPrice()).append(o1.getTime(), o2.getTime()).toComparison(); } } /** * Compares {@link MDQuote} based on descending price, then ascending time. */ @ClassVersion("$Id: MarketDepthView.java 16859 2014-03-19 00:49:50Z colin $") private static class BidComparator implements Comparator<MDQuote> { @Override public int compare(MDQuote o1, MDQuote o2) { // this comparator is only intended to be used on the bid list which does not have null // elements assert o1 != null; assert o2 != null; // higher price, lower time comes first return new CompareToBuilder().append(o2.getPrice(), o1.getPrice()).append(o1.getTime(), o2.getTime()).toComparison(); } } /** * The view id */ public static final String ID = "org.marketcetera.photon.marketdata.MarketDepthView"; //$NON-NLS-1$ /** * The bids table context menu id */ public static final String BIDS_TABLE_POPUP = ID + ".bidsPopup"; //$NON-NLS-1$ /** * The asks table context menu id */ public static final String ASKS_TABLE_POPUP = ID + ".asksPopup"; //$NON-NLS-1$ /** * The secondary id parameter delimiter */ public static final String PARAMETER_DELIMITER = ","; //$NON-NLS-1$ /** * provides access to market data */ private final IMarketData marketDataManager; /** * resolves symbols to instruments */ private final ISymbolResolver symbolResolver; /** * displays bids */ private TableSupport mBidsTable; /** * displays asks */ private TableSupport mAsksTable; /** * entered string */ private String symbol; /** * depth product */ private Content product; /** * data item from which market data flows */ private IMarketDataReference<MDDepthOfBook> mDataItem; }