package org.marketcetera.photon.internal.positions.ui; import java.util.Arrays; import net.miginfocom.swt.MigLayout; import org.apache.commons.lang.Validate; import org.eclipse.core.commands.AbstractHandler; import org.eclipse.core.commands.ExecutionEvent; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.commands.IHandler; import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.core.databinding.observable.value.IValueChangeListener; import org.eclipse.core.databinding.observable.value.ValueChangeEvent; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExecutableExtension; import org.eclipse.jface.action.MenuManager; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Menu; import org.eclipse.ui.IMemento; import org.eclipse.ui.IPropertyListener; import org.eclipse.ui.IViewSite; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchPartSite; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.handlers.HandlerUtil; import org.eclipse.ui.menus.WorkbenchWindowControlContribution; import org.eclipse.ui.part.IPage; import org.eclipse.ui.part.PageBook; import org.eclipse.ui.part.PageBookView; import org.marketcetera.core.position.Grouping; import org.marketcetera.photon.commons.ui.workbench.FilterBox; import org.marketcetera.photon.commons.ui.workbench.ChooseColumnsMenu.IColumnProvider; import org.marketcetera.photon.commons.ui.workbench.FilterBox.FilterChangeEvent; import org.marketcetera.util.misc.ClassVersion; /* $License$ */ /** * Real-time Positions and P&L view. The page book view has three pages that can be shown: * <ul> * <li>{@link UnavailablePage} - default page shown when position data is not available.</li> * <li>{@link PositionsViewTablePage} - flat, table based P&L data.</li> * <li>{@link PositionsViewTreePage} - tree based page allowing data to be grouped hierarchically.</li> * </ul> * * This view does not care about the actual active workbench part. So fake parts are used to control * the switching of pages. For example, to switch to the flat page, you call * {@link #partActivated(IWorkbenchPart)}, passing PositionsPart.FLAT as the parameter. * <p> * This view serializes it's state, which includes the last viewed page (flat or hierarchical) and * last selected grouping on the hierarchical page. * * @author <a href="mailto:will@marketcetera.com">Will Horn</a> * @version $Id: PositionsView.java 16154 2012-07-14 16:34:05Z colin $ * @since 1.5.0 */ @ClassVersion("$Id: PositionsView.java 16154 2012-07-14 16:34:05Z colin $") public class PositionsView extends PageBookView implements IColumnProvider { /** * Dummy part that allows us to take advantage of {@link PageBookView} infrastructure to support * switching between table and tree UI. * * This technique was inspired by * org.eclipse.pde.internal.ui.views.dependencies.DependenciesView. */ @ClassVersion("$Id: PositionsView.java 16154 2012-07-14 16:34:05Z colin $") private static enum PositionsPart implements IWorkbenchPart { /** * Flat table UI. */ FLAT, /** * Grouped tree UI. */ HIERARCHICAL; @Override public void addPropertyListener(IPropertyListener listener) { } @Override public void createPartControl(Composite parent) { } @Override public void dispose() { } @Override @SuppressWarnings("unchecked") public Object getAdapter(Class adapter) { return null; } @Override public IWorkbenchPartSite getSite() { return null; } @Override public String getTitle() { return null; } @Override public Image getTitleImage() { return null; } @Override public String getTitleToolTip() { return null; } @Override public void removePropertyListener(IPropertyListener listener) { } @Override public void setFocus() { } } /** * Changes the current page. */ @ClassVersion("$Id: PositionsView.java 16154 2012-07-14 16:34:05Z colin $") public static final class ChangePageHandler extends AbstractHandler implements IHandler, IExecutableExtension { private PositionsPart mPart; @Override public Object execute(ExecutionEvent event) throws ExecutionException { PositionsView view = (PositionsView) HandlerUtil.getActivePart(event); if (view != null && view.mPositionEngine.getValue() != null) { view.showPart(mPart); } return null; } @Override public void setInitializationData(IConfigurationElement config, String propertyName, Object data) throws CoreException { mPart = PositionsPart.valueOf((String) data); } } /** * Toolbar filter box. */ @ClassVersion("$Id: PositionsView.java 16154 2012-07-14 16:34:05Z colin $") public static final class ViewFilter extends WorkbenchWindowControlContribution { @Override protected Control createControl(Composite parent) { Composite composite = new Composite(parent, SWT.NONE); // insets keeps the box outline inside the toolbar area composite.setLayout(new MigLayout("ins 1")); //$NON-NLS-1$ Label filterLabel = new Label(composite, SWT.NONE); filterLabel.setText(Messages.POSITIONS_VIEW_FILTER__LABEL.getText()); FilterBox filter = new FilterBox(composite); filter.addListener(new FilterBox.FilterChangeListener() { @Override public void filterChanged(FilterChangeEvent event) { PositionsView view = getView(); if (view != null) view.setFilterText(event.getFilterText()); } }); return composite; } } /* * memento keys for serialization */ private static final String LAST_PART_KEY = "defaultPart"; //$NON-NLS-1$ private static final String LAST_GROUPING_KEY_1 = "grouping1"; //$NON-NLS-1$ private static final String LAST_GROUPING_KEY_2 = "grouping2"; //$NON-NLS-1$ /* * remembers user choices */ private PositionsPart mLastPart = PositionsPart.FLAT; private Grouping[] mLastGrouping = new Grouping[] { Grouping.Underlying, Grouping.Account }; private final IObservableValue mPositionEngine = Activator.getDefault().getPositionEngine(); private Grouping[] mGrouping = null; private String mFilterText = ""; //$NON-NLS-1$ private IMemento mMemento; private MenuManager mMenuManager; private IValueChangeListener mEngineListener; @Override public void init(IViewSite site, IMemento memento) throws PartInitException { super.init(site, memento); mMemento = memento; // restore saved state, if any if (memento != null) { try { String part = memento.getString(LAST_PART_KEY); if (part != null) { mLastPart = PositionsPart.valueOf(part); } } catch (IllegalArgumentException e) { Messages.POSITIONS_VIEW_STATE_RESTORE_FAILURE.error(this, e); } try { String one = memento.getString(LAST_GROUPING_KEY_1); String two = memento.getString(LAST_GROUPING_KEY_2); if (one != null && two != null) { mLastGrouping = new Grouping[] { Grouping.valueOf(one), Grouping.valueOf(two) }; } } catch (IllegalArgumentException e) { Messages.POSITIONS_VIEW_STATE_RESTORE_FAILURE.error(this, e); } /* * Normally mGrouping gets set in showHierarchicalPage, either when * the view is switched from flat to hierarchical mode, or when the * position engine comes up and mLastPart is hierarchical. However, * if the position engine is already available when the view is * initializing, the last part will be bootstrapped and * showHierarchicalPage isn't called, so mGrouping needs to be set * here. */ if (mLastPart == PositionsPart.HIERARCHICAL && mPositionEngine.getValue() != null) { mGrouping = mLastGrouping; } } // both pages share a context menu mMenuManager = new MenuManager(); site.registerContextMenu(mMenuManager, getSelectionProvider()); // a listener that activates/deactivates the page when the position engine // appears/disappears mEngineListener = new IValueChangeListener() { @Override public void handleValueChange(ValueChangeEvent event) { PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { public void run() { if (mPositionEngine.getValue() == null) { mGrouping = null; partClosed(getCurrentContributingPart()); } else { showPart(mLastPart); } } }); } }; mPositionEngine.addValueChangeListener(mEngineListener); } @Override public void saveState(IMemento memento) { memento.putString(LAST_PART_KEY, mLastPart.name()); memento.putString(LAST_GROUPING_KEY_1, mLastGrouping[0].name()); memento.putString(LAST_GROUPING_KEY_2, mLastGrouping[1].name()); // allow individual pages to save state too PageRec page = getPageRec(PositionsPart.FLAT); if (page != null) ((PositionsViewPage) page.page).saveState(memento); page = getPageRec(PositionsPart.HIERARCHICAL); if (page != null) ((PositionsViewPage) page.page).saveState(memento); } @Override public void dispose() { // cleanup listener mPositionEngine.removeValueChangeListener(mEngineListener); super.dispose(); } @Override protected boolean isImportant(IWorkbenchPart part) { // We don't care about the active workbench part, just dummy parts "activated" by this view return part instanceof PositionsPart; } @Override public Control getColumnWidget() { IPage currentPage = getCurrentPage(); if (currentPage instanceof PositionsViewPage) { return ((PositionsViewPage) currentPage).getColumnWidget(); } else { return null; } } @Override protected IWorkbenchPart getBootstrapPart() { // only can bootstrap if the engine is available return mPositionEngine.getValue() == null ? null : mLastPart; } @Override protected IPage createDefaultPage(PageBook book) { UnavailablePage unavailablePage = new UnavailablePage(); unavailablePage.createControl(book); return unavailablePage; } @Override protected PageRec doCreatePage(IWorkbenchPart part) { final PositionsViewPage page; switch ((PositionsPart) part) { case FLAT: page = new PositionsViewTablePage(this, mMemento); break; case HIERARCHICAL: page = new PositionsViewTreePage(this, mMemento); break; default: throw new AssertionError("Invalid part"); //$NON-NLS-1$ } initPage(page); page.createControl(getPageBook()); page.setFilterText(getFilterText()); return new PageRec(part, page); } @Override protected void doDestroyPage(IWorkbenchPart part, PageRec pageRecord) { IPage page = pageRecord.page; page.dispose(); pageRecord.dispose(); } @Override protected void showPageRec(PageRec pageRec) { super.showPageRec(pageRec); if (!(pageRec.page instanceof UnavailablePage)) { installContextMenu(); ((PositionsViewPage) pageRec.page).setFilterText(getFilterText()); } } /** * Adds the common context menu to the column widget. */ private void installContextMenu() { MenuManager contextMenu = mMenuManager; Control control = getColumnWidget(); Menu menu = contextMenu.createContextMenu(control); control.setMenu(menu); } /** * Sets the filter value. * * NOTE: public access for testing only * * @param filterText the filterText */ public void setFilterText(String filterText) { mFilterText = filterText; if (getCurrentPage() instanceof PositionsViewPage) { ((PositionsViewPage) getCurrentPage()).setFilterText(filterText); } } /** * @return the current filter text */ String getFilterText() { return mFilterText; } /** * @return the current grouping */ Grouping[] getGrouping() { return mGrouping; } /** * Shows the specified part. * * @param part * the part */ void showPart(PositionsPart part) { switch (part) { case HIERARCHICAL: showHierarchicalPage(); break; case FLAT: showFlatPage(); break; default: throw new AssertionError(); } } /** * Shows the flat page. */ void showFlatPage() { mGrouping = null; mLastPart = PositionsPart.FLAT; partClosed(PositionsPart.HIERARCHICAL); partActivated(PositionsPart.FLAT); } /** * Shows the hierarchical page with the the default grouping. */ void showHierarchicalPage() { showHierarchicalPage(mLastGrouping); } /** * Shows the hierarchical page with the provided grouping. * * NOTE: public access for testing * * @param grouping * the grouping */ public void showHierarchicalPage(Grouping[] grouping) { Validate.noNullElements(grouping); Validate.isTrue(grouping.length == 2); if (!Arrays.equals(mGrouping, grouping)) { mLastPart = PositionsPart.HIERARCHICAL; mGrouping = grouping; mLastGrouping = grouping; // dispose the last tree page if it exists to free up resources partClosed(PositionsPart.HIERARCHICAL); partActivated(PositionsPart.HIERARCHICAL); } } /** * Convenience method to get the active positions view instance. * * @return the active PositionsView instance, or null if there is either no active part or the * active part is not a PositionsView */ static PositionsView getView() { IWorkbenchWindow active = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); if (active == null) return null; IWorkbenchPage page = active.getActivePage(); if (page == null) return null; IWorkbenchPart part = page.getActivePart(); if (!(part instanceof PositionsView)) return null; return (PositionsView) part; } }