net.sourceforge.eclipsetrader.trading.views.Level2View.java Source code

Java tutorial

Introduction

Here is the source code for net.sourceforge.eclipsetrader.trading.views.Level2View.java

Source

/*
 * Copyright (c) 2004-2007 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 net.sourceforge.eclipsetrader.trading.views;

import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

import net.sourceforge.eclipsetrader.core.CorePlugin;
import net.sourceforge.eclipsetrader.core.Level2FeedMonitor;
import net.sourceforge.eclipsetrader.core.db.Level2;
import net.sourceforge.eclipsetrader.core.db.Security;
import net.sourceforge.eclipsetrader.core.transfers.SecurityTransfer;
import net.sourceforge.eclipsetrader.core.ui.SecuritySelection;
import net.sourceforge.eclipsetrader.trading.TradingPlugin;
import net.sourceforge.eclipsetrader.trading.actions.ToggleFollowSelectionAction;
import net.sourceforge.eclipsetrader.trading.actions.ToggleLevelColorsAction;
import net.sourceforge.eclipsetrader.trading.actions.TogglePriceGroupingAction;
import net.sourceforge.eclipsetrader.trading.internal.CellTicker;
import net.sourceforge.eclipsetrader.trading.internal.Trendbar;

import org.apache.commons.logging.LogFactory;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.preference.PreferenceStore;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.DropTargetListener;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.dnd.TransferData;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.themes.ITheme;
import org.eclipse.ui.themes.IThemeManager;

public class Level2View extends ViewPart implements Observer, ISelectionListener {
    public static final String VIEW_ID = "net.sourceforge.eclipsetrader.trading.level2";
    /**
     * @deprecated use the instance area preference store instead
     */
    public static final String PREFERENCES_ID = "LEVEL2_PREFS_";
    public static final String PREFS_SECURITY = "security";
    public static final String PREFS_SECONDARY_ID = "secondaryId";
    public static final String PREFS_GROUP_PRICES = "groupPrices";
    public static final String PREFS_COLOR_LEVELS = "colorLevels";
    public static final String PREFS_FOLLOW_SELECTION = "followSelection";
    public static final String TRENDBAR_INDICATOR = "TRENDBAR_INDICATOR";
    public static final String LEVEL_1_BACKGROUND = "LEVEL_1_BACKGROUND";
    public static final String LEVEL_2_BACKGROUND = "LEVEL_2_BACKGROUND";
    public static final String LEVEL_3_BACKGROUND = "LEVEL_3_BACKGROUND";
    public static final String LEVEL_4_BACKGROUND = "LEVEL_4_BACKGROUND";
    public static final String LEVEL_5_BACKGROUND = "LEVEL_5_BACKGROUND";
    public static final String LEVEL_1_FOREGROUND = "LEVEL_1_FOREGROUND";
    public static final String LEVEL_2_FOREGROUND = "LEVEL_2_FOREGROUND";
    public static final String LEVEL_3_FOREGROUND = "LEVEL_3_FOREGROUND";
    public static final String LEVEL_4_FOREGROUND = "LEVEL_4_FOREGROUND";
    public static final String LEVEL_5_FOREGROUND = "LEVEL_5_FOREGROUND";
    static boolean DEFAULT_GROUP_PRICES = false;
    static boolean DEFAULT_COLOR_LEVELS = true;
    static boolean DEFAULT_FOLLOW_SELECTION = false;
    static int UPDATE_DELAY = 200;
    Security security;
    Composite info;
    Label time;
    Label volume;
    Label last;
    Label high;
    Label change;
    Label low;
    Trendbar trendbar;
    Table table;
    boolean groupPrices = DEFAULT_GROUP_PRICES;
    boolean colorLevels = DEFAULT_COLOR_LEVELS;
    boolean followSelection = DEFAULT_FOLLOW_SELECTION;
    private NumberFormat numberFormatter = NumberFormat.getInstance();
    private NumberFormat priceFormatter = NumberFormat.getInstance();
    private NumberFormat priceFormatter2 = NumberFormat.getInstance();
    private NumberFormat percentFormatter = NumberFormat.getInstance();
    private SimpleDateFormat dateFormatter = new SimpleDateFormat("HH:mm:ss"); //$NON-NLS-1$
    private Color background;
    private Color emptyBackground;
    private Color negativeForeground = new Color(null, 224, 0, 0);
    private Color positiveForeground = new Color(null, 0, 224, 0);
    private Color[] bandBackground = new Color[5];
    private Color[] bandForeground = new Color[5];
    private ITheme theme;
    PreferenceStore preferences;
    Action toggleLevelColorsAction;
    Action togglePriceGroupingAction;
    Action toggleFollowSelectionAction;
    private IPropertyChangeListener themeChangeListener = new IPropertyChangeListener() {
        public void propertyChange(PropertyChangeEvent event) {
            if (event.getProperty().equals(LEVEL_1_BACKGROUND)) {
                bandBackground[0] = theme.getColorRegistry().get(LEVEL_1_BACKGROUND);
                trendbar.setBandColor(0, bandBackground[0]);
            } else if (event.getProperty().equals(LEVEL_1_FOREGROUND))
                bandForeground[0] = theme.getColorRegistry().get(LEVEL_1_FOREGROUND);
            else if (event.getProperty().equals(LEVEL_2_BACKGROUND)) {
                bandBackground[1] = theme.getColorRegistry().get(LEVEL_2_BACKGROUND);
                trendbar.setBandColor(1, bandBackground[1]);
            } else if (event.getProperty().equals(LEVEL_2_FOREGROUND))
                bandForeground[1] = theme.getColorRegistry().get(LEVEL_2_FOREGROUND);
            else if (event.getProperty().equals(LEVEL_3_BACKGROUND)) {
                bandBackground[2] = theme.getColorRegistry().get(LEVEL_3_BACKGROUND);
                trendbar.setBandColor(2, bandBackground[2]);
            } else if (event.getProperty().equals(LEVEL_3_FOREGROUND))
                bandForeground[2] = theme.getColorRegistry().get(LEVEL_3_FOREGROUND);
            else if (event.getProperty().equals(LEVEL_4_BACKGROUND)) {
                bandBackground[3] = theme.getColorRegistry().get(LEVEL_4_BACKGROUND);
                trendbar.setBandColor(3, bandBackground[3]);
            } else if (event.getProperty().equals(LEVEL_4_FOREGROUND))
                bandForeground[3] = theme.getColorRegistry().get(LEVEL_4_FOREGROUND);
            else if (event.getProperty().equals(LEVEL_5_BACKGROUND)) {
                bandBackground[4] = theme.getColorRegistry().get(LEVEL_5_BACKGROUND);
                trendbar.setBandColor(4, bandBackground[4]);
            } else if (event.getProperty().equals(LEVEL_5_FOREGROUND))
                bandForeground[4] = theme.getColorRegistry().get(LEVEL_5_FOREGROUND);
            else if (event.getProperty().equals(TRENDBAR_INDICATOR))
                trendbar.setIndicatorColor(theme.getColorRegistry().get(TRENDBAR_INDICATOR));
            updateTable();
        }
    };
    DropTargetListener dropTargetListener = new DropTargetListener() {
        public void dragEnter(DropTargetEvent event) {
            event.detail = DND.DROP_COPY;
            event.currentDataType = null;

            TransferData[] data = event.dataTypes;
            if (event.currentDataType == null) {
                for (int i = 0; i < data.length; i++) {
                    if (SecurityTransfer.getInstance().isSupportedType(data[i])) {
                        event.currentDataType = data[i];
                        break;
                    }
                }
            }
        }

        public void dragOver(DropTargetEvent event) {
            event.feedback = DND.FEEDBACK_SELECT | DND.FEEDBACK_SCROLL;
        }

        public void dragOperationChanged(DropTargetEvent event) {
        }

        public void dragLeave(DropTargetEvent event) {
        }

        public void dropAccept(DropTargetEvent event) {
        }

        public void drop(DropTargetEvent event) {
            if (SecurityTransfer.getInstance().isSupportedType(event.currentDataType)) {
                Security[] securities = (Security[]) event.data;
                setSecurity(securities[0]);
            }
        }
    };
    boolean tableUpdaterScheduled = false;
    Runnable tableUpdater = new Runnable() {
        public void run() {
            tableUpdaterScheduled = false;
            if (!table.isDisposed())
                updateTable();
        }
    };
    boolean infoUpdaterScheduled = false;
    Runnable infoUpdater = new Runnable() {
        public void run() {
            infoUpdaterScheduled = false;
            if (!info.isDisposed())
                updateInfo();
        }
    };

    public Level2View() {
        numberFormatter.setGroupingUsed(true);
        numberFormatter.setMinimumIntegerDigits(1);
        numberFormatter.setMinimumFractionDigits(0);
        numberFormatter.setMaximumFractionDigits(0);

        priceFormatter.setGroupingUsed(true);
        priceFormatter.setMinimumIntegerDigits(1);
        priceFormatter.setMinimumFractionDigits(2);
        priceFormatter.setMaximumFractionDigits(4);

        priceFormatter2.setGroupingUsed(true);
        priceFormatter2.setMinimumIntegerDigits(1);
        priceFormatter2.setMinimumFractionDigits(4);
        priceFormatter2.setMaximumFractionDigits(4);

        percentFormatter.setGroupingUsed(false);
        percentFormatter.setMinimumIntegerDigits(1);
        percentFormatter.setMinimumFractionDigits(2);
        percentFormatter.setMaximumFractionDigits(2);
    }

    public static IPath getPreferenceStoreLocation(Security security) {
        return TradingPlugin.getDefault().getStateLocation()
                .append("level2." + String.valueOf(security.getId()) + ".prefs");
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.part.ViewPart#init(org.eclipse.ui.IViewSite, org.eclipse.ui.IMemento)
     */
    public void init(IViewSite site, IMemento memento) throws PartInitException {
        super.init(site, memento);

        toggleLevelColorsAction = new ToggleLevelColorsAction(this);
        togglePriceGroupingAction = new TogglePriceGroupingAction(this);
        toggleFollowSelectionAction = new ToggleFollowSelectionAction(this);

        Integer id = null;
        if (memento != null)
            id = memento.getInteger(PREFS_SECURITY);

        if (id == null)
            try {
                id = new Integer(getViewSite().getSecondaryId());
            } catch (Exception e) {
            }

        if (id != null)
            security = (Security) CorePlugin.getRepository().load(Security.class, id);
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.part.ViewPart#saveState(org.eclipse.ui.IMemento)
     */
    public void saveState(IMemento memento) {
        if (security != null)
            memento.putInteger(PREFS_SECURITY, security.getId().intValue());
        super.saveState(memento);
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
     */
    public void createPartControl(Composite parent) {
        DropTarget target = new DropTarget(parent, DND.DROP_COPY | DND.DROP_MOVE);
        target.setTransfer(new Transfer[] { SecurityTransfer.getInstance() });
        target.addDropListener(dropTargetListener);

        Composite content = new Composite(parent, SWT.NONE);
        GridLayout gridLayout = new GridLayout();
        gridLayout.marginWidth = gridLayout.marginHeight = 0;
        gridLayout.horizontalSpacing = 0;
        gridLayout.verticalSpacing = 0;
        content.setLayout(gridLayout);

        IThemeManager themeManager = PlatformUI.getWorkbench().getThemeManager();
        theme = themeManager.getCurrentTheme();
        bandBackground[0] = theme.getColorRegistry().get(LEVEL_1_BACKGROUND);
        bandForeground[0] = theme.getColorRegistry().get(LEVEL_1_FOREGROUND);
        bandBackground[1] = theme.getColorRegistry().get(LEVEL_2_BACKGROUND);
        bandForeground[1] = theme.getColorRegistry().get(LEVEL_2_FOREGROUND);
        bandBackground[2] = theme.getColorRegistry().get(LEVEL_3_BACKGROUND);
        bandForeground[2] = theme.getColorRegistry().get(LEVEL_3_FOREGROUND);
        bandBackground[3] = theme.getColorRegistry().get(LEVEL_4_BACKGROUND);
        bandForeground[3] = theme.getColorRegistry().get(LEVEL_4_FOREGROUND);
        bandBackground[4] = theme.getColorRegistry().get(LEVEL_5_BACKGROUND);
        bandForeground[4] = theme.getColorRegistry().get(LEVEL_5_FOREGROUND);
        theme.addPropertyChangeListener(themeChangeListener);

        info = new Composite(content, SWT.NONE);
        gridLayout = new GridLayout(4, false);
        gridLayout.marginWidth = 3;
        gridLayout.marginHeight = 3;
        gridLayout.verticalSpacing = 2;
        gridLayout.horizontalSpacing = 10;
        info.setLayout(gridLayout);
        info.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
        info.addPaintListener(new PaintListener() {
            public void paintControl(PaintEvent e) {
                // Disegna il bordo inferiore
                Rectangle r = info.getClientArea();
                e.gc.setForeground(info.getDisplay().getSystemColor(SWT.COLOR_GRAY));
                e.gc.drawLine(0, r.height - 1, r.width, r.height - 1);
            }
        });
        Label label = new Label(info, SWT.NONE);
        label.setText("Time"); //$NON-NLS-1$
        time = new Label(info, SWT.RIGHT);
        time.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
        label = new Label(info, SWT.NONE);
        label.setText("Volume"); //$NON-NLS-1$
        volume = new Label(info, SWT.RIGHT);
        volume.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
        label = new Label(info, SWT.NONE);
        label.setText("Last Price"); //$NON-NLS-1$
        last = new Label(info, SWT.NONE);
        last.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, false));
        label = new Label(info, SWT.NONE);
        label.setText("High"); //$NON-NLS-1$
        high = new Label(info, SWT.NONE);
        high.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, false));
        label = new Label(info, SWT.NONE);
        label.setText("Change"); //$NON-NLS-1$
        change = new Label(info, SWT.NONE);
        change.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, false));
        label = new Label(info, SWT.NONE);
        label.setText("Low"); //$NON-NLS-1$
        low = new Label(info, SWT.NONE);
        low.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, false));

        trendbar = new Trendbar(content, SWT.NONE);
        GridData gridData = new GridData();
        gridData.grabExcessHorizontalSpace = true;
        gridData.horizontalAlignment = SWT.FILL;
        gridData.heightHint = 16;
        trendbar.setLayoutData(gridData);
        trendbar.setBandColors(bandBackground);
        trendbar.setIndicatorColor(theme.getColorRegistry().get(TRENDBAR_INDICATOR));

        table = new Table(content, SWT.SINGLE | SWT.FULL_SELECTION | SWT.HIDE_SELECTION);
        table.setLayoutData(new GridData(GridData.FILL_BOTH));
        table.setHeaderVisible(true);
        table.setLinesVisible(false);
        background = table.getBackground();
        emptyBackground = parent.getBackground();
        table.setBackground(emptyBackground);
        TableColumn column = new TableColumn(table, SWT.NONE);
        column.setWidth(0);
        column.setResizable(false);
        column = new TableColumn(table, SWT.CENTER);
        column.setText("#");
        column = new TableColumn(table, SWT.CENTER);
        column.setText("Q.ty");
        column = new TableColumn(table, SWT.CENTER);
        column.setText("Bid");
        column = new TableColumn(table, SWT.CENTER);
        column.setText("Ask");
        column = new TableColumn(table, SWT.CENTER);
        column.setText("Q.ty");
        column = new TableColumn(table, SWT.CENTER);
        column.setText("#");
        table.addControlListener(new ControlAdapter() {
            public void controlResized(ControlEvent e) {
                table.setRedraw(false);
                updateColumnWidth();
                table.setRedraw(true);
            }
        });
        table.addFocusListener(new FocusAdapter() {
            public void focusGained(FocusEvent e) {
                ((Table) e.widget).deselectAll();
                info.getParent().setFocus();
            }
        });

        IMenuManager menuManager = getViewSite().getActionBars().getMenuManager();
        menuManager.add(toggleLevelColorsAction);
        menuManager.add(togglePriceGroupingAction);
        menuManager.add(toggleFollowSelectionAction);
        getViewSite().getActionBars().updateActionBars();

        if (security != null) {
            setPartName(security.getDescription());
            loadPreferences();
            content.getDisplay().asyncExec(new Runnable() {
                public void run() {
                    updateInfo();
                    updateTable();

                    security.addObserver(Level2View.this);
                    security.getQuoteMonitor().addObserver(Level2View.this);
                    security.getLevel2Monitor().addObserver(Level2View.this);
                    Level2FeedMonitor.monitor(security);
                }
            });
        }
        getSite().getPage().addSelectionListener(this);
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.part.WorkbenchPart#setFocus()
     */
    public void setFocus() {
        info.getParent().setFocus();
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.part.WorkbenchPart#dispose()
     */
    public void dispose() {
        if (security != null) {
            security.deleteObserver(this);
            security.getQuoteMonitor().deleteObserver(this);
            security.getLevel2Monitor().deleteObserver(this);
            Level2FeedMonitor.cancelMonitor(security);

            savePreferences();
        }

        if (theme != null)
            theme.removePropertyChangeListener(themeChangeListener);

        background.dispose();
        negativeForeground.dispose();
        positiveForeground.dispose();
        emptyBackground.dispose();
        getSite().getPage().removeSelectionListener(this);

        super.dispose();
    }

    protected void loadPreferences() {
        preferences = new PreferenceStore(getPreferenceStoreLocation(security).toOSString());
        preferences.setDefault(PREFS_SECONDARY_ID, "");
        preferences.setDefault(PREFS_GROUP_PRICES, DEFAULT_GROUP_PRICES);
        preferences.setDefault(PREFS_COLOR_LEVELS, DEFAULT_COLOR_LEVELS);
        preferences.setDefault(PREFS_FOLLOW_SELECTION, DEFAULT_FOLLOW_SELECTION);

        try {
            preferences.load();
        } catch (Exception e) {
            // Reads the preferences from the old-style preference store
            String[] prefs = TradingPlugin.getDefault().getPluginPreferences()
                    .getString(PREFERENCES_ID + String.valueOf(security.getId())).split(",");
            if (prefs.length > 0)
                preferences.setValue(PREFS_GROUP_PRICES, prefs[0].equals("true"));
            if (prefs.length > 1)
                preferences.setValue(PREFS_COLOR_LEVELS, prefs[1].equals("true"));
            if (prefs.length > 2)
                preferences.setValue(PREFS_FOLLOW_SELECTION, prefs[2].equals("true"));
            TradingPlugin.getDefault().getPluginPreferences()
                    .setValue(PREFERENCES_ID + String.valueOf(security.getId()), "");
        }

        groupPrices = preferences.getBoolean(PREFS_GROUP_PRICES);
        colorLevels = preferences.getBoolean(PREFS_COLOR_LEVELS);
        followSelection = preferences.getBoolean(PREFS_FOLLOW_SELECTION);

        // Save the secondary id value immediately, so subsequent level2 requests for the same
        // security will open this view
        preferences.setValue(PREFS_SECONDARY_ID, getViewSite().getSecondaryId());
        try {
            preferences.save();
        } catch (Exception e) {
            LogFactory.getLog(getClass()).warn(e);
        }

        toggleLevelColorsAction.setChecked(colorLevels);
        togglePriceGroupingAction.setChecked(groupPrices);
        toggleFollowSelectionAction.setChecked(followSelection);
    }

    protected void savePreferences() {
        preferences.setFilename(getPreferenceStoreLocation(security).toOSString());
        preferences.setValue(PREFS_GROUP_PRICES, groupPrices);
        preferences.setValue(PREFS_COLOR_LEVELS, colorLevels);
        preferences.setValue(PREFS_FOLLOW_SELECTION, followSelection);

        try {
            preferences.save();
        } catch (Exception e) {
            LogFactory.getLog(getClass()).warn(e);
        }
    }

    public void setSecurity(Security newSecurity) {
        if (security == null) {
            security = newSecurity;
            loadPreferences();
        } else {
            security.deleteObserver(this);
            security.getQuoteMonitor().deleteObserver(this);
            security.getLevel2Monitor().deleteObserver(this);
            Level2FeedMonitor.cancelMonitor(security);

            preferences.setValue(PREFS_SECONDARY_ID, "");
            savePreferences();

            security = newSecurity;

            preferences.setValue(PREFS_SECONDARY_ID, getViewSite().getSecondaryId());
            savePreferences();
        }

        updateInfo();
        updateTable();

        security.addObserver(this);
        security.getQuoteMonitor().addObserver(this);
        security.getLevel2Monitor().addObserver(this);
        Level2FeedMonitor.monitor(security);
    }

    void updateInfo() {
        if (!getPartName().equals(security.getDescription()))
            setPartName(security.getDescription());

        if (security.getQuote() != null) {
            if (security.getQuote().getDate() != null)
                time.setText(dateFormatter.format(security.getQuote().getDate()));
            volume.setText(numberFormatter.format(security.getQuote().getVolume()));
            last.setText(priceFormatter2.format(security.getQuote().getLast()));
            if (security.getClose() != null) {
                double chg = (security.getQuote().getLast() - security.getClose().doubleValue())
                        / security.getClose().doubleValue() * 100;
                if (chg > 0) {
                    change.setText("+" + percentFormatter.format(chg) + "%"); //$NON-NLS-1$ //$NON-NLS-2$
                    change.setForeground(positiveForeground);
                } else {
                    change.setText(percentFormatter.format(chg) + "%"); //$NON-NLS-1$ //$NON-NLS-2$
                    if (chg < 0)
                        change.setForeground(negativeForeground);
                    else
                        change.setForeground(null);
                }
            }
        }
        if (security.getHigh() != null)
            high.setText(priceFormatter2.format(security.getHigh()));
        if (security.getLow() != null)
            low.setText(priceFormatter2.format(security.getLow()));
        info.layout(true, true);
    }

    void updateTable() {
        int bidLevel = -1, askLevel = -1;
        String bidValue = "", askValue = "";
        Level2TableItem tableItem = null;

        List bid = new ArrayList();
        if (security.getLevel2Bid() != null) {
            if (groupPrices)
                bid = security.getLevel2Bid().getGrouped();
            else
                bid = security.getLevel2Bid().getList();
        }
        List ask = new ArrayList();
        if (security.getLevel2Ask() != null) {
            if (groupPrices)
                ask = security.getLevel2Ask().getGrouped();
            else
                ask = security.getLevel2Ask().getList();
        }

        int total = Math.max(bid.size(), ask.size());
        for (int index = 0; index < total; index++) {
            if (index < table.getItemCount())
                tableItem = (Level2TableItem) table.getItem(index);
            else
                tableItem = new Level2TableItem(table, SWT.NONE);

            if (index < bid.size()) {
                Level2.Item item = (Level2.Item) bid.get(index);
                String price = priceFormatter.format(item.price);
                String number = numberFormatter.format(item.number);
                String quantity = numberFormatter.format(item.quantity);
                tableItem.setText(1, number);
                tableItem.setText(2, quantity);
                tableItem.setText(3, price);

                if (!tableItem.getText(3).equals(bidValue)) {
                    bidLevel++;
                    bidValue = tableItem.getText(3);
                }
                if (colorLevels && bidLevel < bandBackground.length) {
                    tableItem.setForeground(1, bandForeground[bidLevel]);
                    tableItem.setBackground(1, bandBackground[bidLevel]);
                    tableItem.setForeground(2, bandForeground[bidLevel]);
                    tableItem.setBackground(2, bandBackground[bidLevel]);
                    tableItem.setForeground(3, bandForeground[bidLevel]);
                    tableItem.setBackground(3, bandBackground[bidLevel]);
                } else {
                    tableItem.setForeground(1, null);
                    tableItem.setBackground(1, background);
                    tableItem.setForeground(2, null);
                    tableItem.setBackground(2, background);
                    tableItem.setForeground(3, null);
                    tableItem.setBackground(3, background);
                }
            } else {
                tableItem.setText(1, "");
                tableItem.setText(2, "");
                tableItem.setText(3, "");
                tableItem.setBackground(1, emptyBackground);
                tableItem.setBackground(2, emptyBackground);
                tableItem.setBackground(3, emptyBackground);
            }

            if (index < ask.size()) {
                Level2.Item item = (Level2.Item) ask.get(index);
                tableItem.setText(4, priceFormatter.format(item.price));
                tableItem.setText(5, numberFormatter.format(item.quantity));
                tableItem.setText(6, numberFormatter.format(item.number));
                if (!tableItem.getText(4).equals(askValue)) {
                    askLevel++;
                    askValue = tableItem.getText(4);
                }
                if (colorLevels && askLevel < bandBackground.length) {
                    tableItem.setForeground(4, bandForeground[askLevel]);
                    tableItem.setBackground(4, bandBackground[askLevel]);
                    tableItem.setForeground(5, bandForeground[askLevel]);
                    tableItem.setBackground(5, bandBackground[askLevel]);
                    tableItem.setForeground(6, bandForeground[askLevel]);
                    tableItem.setBackground(6, bandBackground[askLevel]);
                } else {
                    tableItem.setForeground(4, null);
                    tableItem.setBackground(4, background);
                    tableItem.setForeground(5, null);
                    tableItem.setBackground(5, background);
                    tableItem.setForeground(6, null);
                    tableItem.setBackground(6, background);
                }
            } else {
                tableItem.setText(4, "");
                tableItem.setText(5, "");
                tableItem.setText(6, "");
                tableItem.setBackground(4, emptyBackground);
                tableItem.setBackground(5, emptyBackground);
                tableItem.setBackground(6, emptyBackground);
            }
        }

        while (table.getItemCount() > total)
            table.getItem(total).dispose();

        if (security.getLevel2Bid() != null && security.getLevel2Ask() != null)
            trendbar.setData(security.getLevel2Bid(), security.getLevel2Ask());

        updateColumnWidth();
    }

    /**
     * Adapt the columns width to the extents of the contained text.
     */
    void updateColumnWidth() {
        GC gc = new GC(table);

        // Get the maximum extent of the column headers
        int c1 = Math.max(gc.textExtent(table.getColumn(1).getText()).x,
                gc.textExtent(table.getColumn(6).getText()).x);
        int c2 = Math.max(gc.textExtent(table.getColumn(2).getText()).x,
                gc.textExtent(table.getColumn(5).getText()).x);
        int c3 = Math.max(gc.textExtent(table.getColumn(3).getText()).x,
                gc.textExtent(table.getColumn(4).getText()).x);

        // Get the maximum extent of all items
        TableItem[] items = table.getItems();
        for (int i = 0; i < items.length; i++) {
            c1 = Math.max(c1, Math.max(gc.textExtent(items[i].getText(1)).x, gc.textExtent(items[i].getText(6)).x));
            c2 = Math.max(c2, Math.max(gc.textExtent(items[i].getText(2)).x, gc.textExtent(items[i].getText(5)).x));
            c3 = Math.max(c3, Math.max(gc.textExtent(items[i].getText(3)).x, gc.textExtent(items[i].getText(4)).x));
        }

        // Adds a border
        c1 += 12;
        c2 += 12;
        c3 += 12;

        gc.dispose();

        // Calculates the ratio of columns 1 and 2 based on the total width
        double columnsWidth = c1 + c2 + c3;
        double r1 = c1 / columnsWidth;
        double r2 = c2 / columnsWidth;

        // Get the pixel width based on the table size and the columns ratio
        int width = table.getClientArea().width;
        c1 = (int) ((width / 2) * r1);
        c2 = (int) ((width / 2) * r2);
        int c3a = (width / 2) - c1 - c2;
        int c3b = width - (c1 + c2 + c3a + c2 + c1);

        // Set the columns size only if necessary
        if (table.getColumn(1).getWidth() != c1)
            table.getColumn(1).setWidth(c1);
        if (table.getColumn(2).getWidth() != c2)
            table.getColumn(2).setWidth(c2);
        if (table.getColumn(3).getWidth() != c3a)
            table.getColumn(3).setWidth(c3a);
        if (table.getColumn(4).getWidth() != c3b)
            table.getColumn(4).setWidth(c3b);
        if (table.getColumn(5).getWidth() != c2)
            table.getColumn(5).setWidth(c2);
        if (table.getColumn(6).getWidth() != c1)
            table.getColumn(6).setWidth(c1);
    }

    public boolean getGroupPrices() {
        return groupPrices;
    }

    public void setGroupPrices(boolean groupPrices) {
        this.groupPrices = groupPrices;
        updateTable();
    }

    public boolean getColorLevels() {
        return colorLevels;
    }

    public void setColorLevels(boolean colorLevels) {
        this.colorLevels = colorLevels;
        updateTable();
    }

    /* (non-Javadoc)
     * @see java.util.Observer#update(java.util.Observable, java.lang.Object)
     */
    public void update(Observable o, Object arg) {
        if (o == security.getLevel2Monitor()) {
            if (!tableUpdaterScheduled) {
                table.getDisplay().asyncExec(new Runnable() {
                    public void run() {
                        if (!table.isDisposed())
                            table.getDisplay().timerExec(UPDATE_DELAY, tableUpdater);
                    }
                });
                tableUpdaterScheduled = true;
            }
        } else {
            if (!infoUpdaterScheduled) {
                info.getDisplay().asyncExec(infoUpdater);
                infoUpdaterScheduled = true;
            }
        }
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.ISelectionListener#selectionChanged(org.eclipse.ui.IWorkbenchPart, org.eclipse.jface.viewers.ISelection)
     */
    public void selectionChanged(IWorkbenchPart part, ISelection selection) {
        if (followSelection) {
            if (selection instanceof SecuritySelection)
                setSecurity(((SecuritySelection) selection).getSecurity());
        }
    }

    public boolean isFollowSelection() {
        return followSelection;
    }

    public void setFollowSelection(boolean followSelection) {
        this.followSelection = followSelection;
    }

    private class Level2TableItem extends TableItem implements DisposeListener {
        private CellTicker ticker;

        public Level2TableItem(Table parent, int style, int index) {
            super(parent, style, index);
            ticker = new CellTicker(this, CellTicker.BACKGROUND | CellTicker.FOREGROUND);
            addDisposeListener(this);
        }

        public Level2TableItem(Table parent, int style) {
            super(parent, style);
            ticker = new CellTicker(this, CellTicker.BACKGROUND | CellTicker.FOREGROUND);
            addDisposeListener(this);
        }

        /* (non-Javadoc)
         * @see org.eclipse.swt.widgets.TableItem#checkSubclass()
         */
        protected void checkSubclass() {
        }

        public CellTicker getCellTicker() {
            return ticker;
        }

        /* (non-Javadoc)
         * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent)
         */
        public void widgetDisposed(DisposeEvent e) {
            ticker.dispose();
        }
    }
}