org.dawnsci.breadcrumb.navigation.views.AbstractTableDelegate.java Source code

Java tutorial

Introduction

Here is the source code for org.dawnsci.breadcrumb.navigation.views.AbstractTableDelegate.java

Source

/*
 * Copyright (c) 2012 Diamond Light Source Ltd.
 *
 * 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
 */
package org.dawnsci.breadcrumb.navigation.views;

import java.awt.Desktop;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.dawb.common.services.ServiceManager;
import org.dawb.common.services.expressions.IExpressionEngine;
import org.dawb.common.services.expressions.IExpressionService;
import org.dawb.common.ui.actions.ExportTableAction;
import org.dawb.common.ui.menu.CheckableActionGroup;
import org.dawb.common.ui.util.GridUtils;
import org.dawnsci.breadcrumb.navigation.Activator;
import org.dawnsci.breadcrumb.navigation.preference.NavigationConstants;
import org.dawnsci.breadcrumb.navigation.table.AbstractLazyContentProvider;
import org.dawnsci.breadcrumb.navigation.table.AbstractLazyLabelProvider;
import org.dawnsci.breadcrumb.navigation.table.DirectionalIndexedColumnEnum;
import org.dawnsci.breadcrumb.navigation.table.ISortableLazyContentProvider;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IContributionManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.window.ToolTip;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.TableColumn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Class which encapsulates code for generically adding a table to the visit navigation view.
 * 
 * @author Matthew Gerring
 *
 */
public abstract class AbstractTableDelegate implements INavigationDelegate {

    private static final Logger logger = LoggerFactory.getLogger(AbstractTableDelegate.class);

    // The view
    protected AbstractNavigationView view;

    // UI
    protected TableViewer tableViewer;
    protected Map<String, List<Action>> actions;
    protected AbstractLazyLabelProvider labelProvider;

    private Composite contents;
    private Composite searchComposite;
    private CLabel errorLabel;
    private Button executeButton;
    private Combo searchText;

    // Services
    private IExpressionService expressionService;

    // Other
    private DirectionalIndexedColumnEnum firstColumn;
    private String tableId;

    // Jobs
    protected AbstractTableUpdateJob updateJob;

    public AbstractTableDelegate(AbstractNavigationView view, DirectionalIndexedColumnEnum firstColumn,
            String tableId) {

        this.actions = new HashMap<String, List<Action>>();
        this.view = view;
        this.firstColumn = firstColumn;
        this.tableId = tableId;

        this.updateJob = createUpdateJob();
        try {
            this.expressionService = (IExpressionService) ServiceManager.getService(IExpressionService.class);
        } catch (Exception e) {
            logger.error(
                    "Cannot get IExpressionService - ISPyB must have an IExpressionService in order to function!",
                    e);
        }
    }

    protected abstract AbstractTableUpdateJob createUpdateJob();

    @Override
    public void createContent(String groupLabel, String toolTip, Composite card) {

        this.contents = new Composite(card, SWT.NONE);
        contents.setLayout(new GridLayout(1, false));
        GridUtils.removeMargins(contents);

        this.searchComposite = new Composite(contents, SWT.NONE);
        searchComposite.setLayout(new GridLayout(3, false));
        searchComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
        GridUtils.removeMargins(searchComposite);

        searchText = new Combo(searchComposite, SWT.SINGLE | SWT.SEARCH | SWT.ICON_CANCEL);
        searchText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
        searchText.setToolTipText("Search on data collection name.");
        boolean isQuery = NavigationConstants.QUERY
                .equals(Activator.getDefault().getPreferenceStore().getString(NavigationConstants.SEARCH_TYPE));
        if (isQuery) {
            searchText.setToolTipText("Write a query of variables: " + firstColumn.getVariableNames()
                    + " e.g. 'complete>99 and auto eq true'");
        } else {
            searchText.setToolTipText("Wildcard search on name, matches any containing text.");
        }

        // TODO Autocomplete not useful
        //final Collection<String> expressionNames = getVariableNames();
        //new AutoCompleteField(searchText, new ComboContentAdapter(), expressionNames.toArray(new String[expressionNames.size()]));

        final Collection<String> searches = isQuery ? getPreviousSearches() : null;
        if (searches != null && !searches.isEmpty())
            searchText.setItems(searches.toArray(new String[searches.size()]));

        executeButton = new Button(searchComposite, SWT.PUSH);
        executeButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
        executeButton.setText("Execute Query");
        executeButton.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                executeQuery();
            }
        });
        GridUtils.setVisible(executeButton, isQuery);

        final Link link = new Link(searchComposite, SWT.NONE);
        link.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
        link.setText(" ( <a>Syntax</a> ) ");
        link.setToolTipText("When using a query the columns may be searching using inequality expressions.\n"
                + "When using wildcards the name column is searched for containing text.");
        link.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                boolean isJexl = NavigationConstants.QUERY.equals(
                        Activator.getDefault().getPreferenceStore().getString(NavigationConstants.SEARCH_TYPE));
                if (isJexl) {
                    Desktop d = Desktop.getDesktop();
                    try {
                        d.browse(new URI("http://commons.apache.org/proper/commons-jexl/reference/syntax.html"));
                    } catch (Exception e1) {
                        logger.error("Problem opening url!", e1);
                    }
                } else {
                    MessageDialog.openConfirm(Display.getDefault().getActiveShell(), "Help with entering wildcards",
                            "The text you enter is searched in the name column.\nIf that column contains your text, that row is maintained in the list.");
                }
            }
        });

        errorLabel = new CLabel(searchComposite, SWT.NONE);
        errorLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
        errorLabel.setImage(Activator.getDefault().getImageDescriptor("icons/exclamation.png").createImage());
        GridUtils.setVisible(errorLabel, false);

        this.tableViewer = new TableViewer(contents, SWT.FULL_SELECTION | SWT.MULTI | SWT.V_SCROLL | SWT.VIRTUAL);
        tableViewer.setUseHashlookup(true);

        tableViewer.getTable().setLinesVisible(true);
        tableViewer.getTable().setHeaderVisible(true);
        tableViewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        tableViewer.setContentProvider(createContentProvider());
        createColumns(tableViewer);

        tableViewer.getTable().addMouseListener(new MouseAdapter() {
            @Override
            public void mouseDoubleClick(MouseEvent e) {
                final ISelection selection = tableViewer.getSelection();
                if (selection instanceof StructuredSelection) {
                    final Object sel = ((StructuredSelection) selection).getFirstElement();
                    selectionChanged(sel);
                }

            }
        });

        // Because table is lazy, we do search in funny way.
        searchText.addModifyListener(new ModifyListener() {
            @Override
            public void modifyText(ModifyEvent e) {
                if (searchText.getParent().isDisposed())
                    return;

                boolean isJexl = NavigationConstants.QUERY.equals(
                        Activator.getDefault().getPreferenceStore().getString(NavigationConstants.SEARCH_TYPE));
                if (isJexl) {

                    final String expression = searchText.getText();
                    if (expression == null || "".equals(expression)) {
                        executeQuery(null);
                        GridUtils.setVisible(errorLabel, false);
                        errorLabel.getParent().layout();
                        return;
                    }
                    String errMsg = isExpressionValid(expression);
                    if (errMsg == null) {
                        GridUtils.setVisible(errorLabel, false);
                        searchText.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLUE));
                    } else {
                        errorLabel.setText(errMsg);
                        GridUtils.setVisible(errorLabel, true);
                        searchText.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_RED));
                    }
                    errorLabel.getParent().layout();
                    // NOTE They have to press enter in proper search mode.
                } else {
                    searchText.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
                    executeQuery();
                }

            }
        });

        searchText.addKeyListener(new KeyAdapter() {
            public void keyPressed(KeyEvent e) {
                if (e.character == '\n' || e.character == '\r') {
                    executeQuery();
                }
            }
        });

    }

    /**
     * Override to do something when the selection changes
     * @param sel
     */
    protected void selectionChanged(Object sel) {
        // TODO Auto-generated method stub

    }

    protected String isExpressionValid(String expressionString) {
        try {
            final IExpressionEngine engine = expressionService.getExpressionEngine();

            //if (engine==null) engine = JexlUtils.getDawnJexlEngine();
            engine.createExpression(expressionString);
            Collection<String> names = engine.getVariableNamesFromExpression();

            Collection<String> expressionNames = firstColumn.getVariableNames();
            for (final String key : names) {
                if (!expressionNames.contains(key)) {
                    return "The name '" + key + "' is invalid. Legal variables are: " + expressionString;
                }
            }
            return null;
        } catch (Exception ne) {
            return "Cannot parse the search '" + expressionString + "'";
        }
    }

    private Map<DirectionalIndexedColumnEnum, TableViewerColumn> columnCache;

    protected void createColumns(TableViewer viewer) {

        ColumnViewerToolTipSupport.enableFor(viewer, ToolTip.NO_RECREATE);

        this.labelProvider = createLabelProvider();

        // There should be only one of these because it has a worker thread.
        DirectionalIndexedColumnEnum[] columns = firstColumn.allValues();

        this.columnCache = new HashMap<DirectionalIndexedColumnEnum, TableViewerColumn>(
                firstColumn.allValues().length);

        for (int i = 0; i < columns.length; i++) {

            DirectionalIndexedColumnEnum colEnum = columns[i];
            TableViewerColumn column = new TableViewerColumn(viewer, SWT.LEFT, i);
            column.getColumn().setText(colEnum.getName());
            column.getColumn().setData(colEnum);
            column.getColumn().setWidth(colEnum.getWidth());
            column.getColumn().setResizable(colEnum.isExpandable());
            if (labelProvider != null)
                column.setLabelProvider(labelProvider);
            column.getColumn().addSelectionListener(getSelectionAdapter(column.getColumn(), colEnum));
            columnCache.put(colEnum, column);
        }

    }

    /**
     * Thread safe
     * 
     * @param colEnum
     * @param isVis
     */
    public void setColumnsVisible(final boolean isVis, final DirectionalIndexedColumnEnum... enums) {
        for (DirectionalIndexedColumnEnum de : enums) {
            setColumnVisible(de, isVis);
        }
    }

    /**
     * Thread safe
     * 
     * @param colEnum
     * @param isVis
     */
    public void setColumnVisible(final DirectionalIndexedColumnEnum colEnum, final boolean isVis) {

        if (Thread.currentThread() == Display.getDefault().getThread()) {
            setColumnVisibleInternal(colEnum, isVis);
        } else {
            Display.getDefault().asyncExec(new Runnable() {
                @Override
                public void run() {
                    setColumnVisibleInternal(colEnum, isVis);
                }
            });
        }
    }

    private void setColumnVisibleInternal(final DirectionalIndexedColumnEnum colEnum, boolean isVis) {
        if (isVis) {
            columnCache.get(colEnum).getColumn().setWidth(150);
            columnCache.get(colEnum).getColumn().setResizable(true);
        } else {
            columnCache.get(colEnum).getColumn().setWidth(0);
            columnCache.get(colEnum).getColumn().setResizable(false);
        }
    }

    private SelectionAdapter getSelectionAdapter(final TableColumn column,
            final DirectionalIndexedColumnEnum colEnum) {
        SelectionAdapter selectionAdapter = new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                final ISortableLazyContentProvider visitContent = (ISortableLazyContentProvider) tableViewer
                        .getContentProvider();
                final int direction = visitContent.sort(view, colEnum);
                tableViewer.getTable().getSortColumn();
                tableViewer.getTable().setSortDirection(direction);
                tableViewer.getTable().setSortColumn(column);
            }
        };
        return selectionAdapter;
    }

    /**
     * Please override this default method which does not provide content.
     * @return
     */
    protected abstract AbstractLazyContentProvider createContentProvider();

    /**
     * Please override this default method which does not provide good labels.
     * This is the label provider used on each column.
     * @return
     */
    protected abstract AbstractLazyLabelProvider createLabelProvider();

    public boolean isVisible() {
        return tableViewer.getControl().isVisible();
    }

    public void refresh() {
        tableViewer.refresh();
    }

    @Override
    public ISelectionProvider getSelectionProvider() {
        return tableViewer;
    }

    public void setActionsActive(boolean isActive, IContributionManager man) {

        for (String groupId : actions.keySet()) {
            final List<Action> as = actions.get(groupId);
            for (Action action : as) {
                if (isActive) {
                    if (man.find(action.getId()) == null)
                        man.appendToGroup(groupId, action);
                } else {
                    if (man.find(action.getId()) != null)
                        man.remove(action.getId());
                }
            }
        }
    }

    @Override
    public Control getControl() {
        return contents;
    }

    protected void executeQuery() {
        if (!searchText.isEnabled())
            return;
        executeQuery(searchText.getText());
    }

    protected void executeQuery(String query) {
        if (!searchText.isEnabled())
            return;
        AbstractLazyContentProvider lcp = (AbstractLazyContentProvider) tableViewer.getContentProvider();
        lcp.setSearchText(view, query);
    }

    public void saveSearch(final String searchString) {
        final List<String> searches = getPreviousSearches();
        final List<String> save = new ArrayList<String>(searches);
        save.add(0, searchString);
        while (save.size() > 20)
            save.remove(save.size() - 1);
        StringBuilder buf = new StringBuilder();
        for (String search : save) {
            buf.append(search);
            buf.append("#SEP#");
        }
        Activator.getDefault().getPreferenceStore().setValue(NavigationConstants.QUERY_HISTORY, buf.toString());

        Display.getDefault().syncExec(new Runnable() {
            public void run() {
                searchText.setItems(save.toArray(new String[save.size()]));
                searchText.setText(searchString);
                searchText.setFocus();
                int stringLength = searchText.getText().length();
                searchText.setSelection(new Point(stringLength, stringLength));
            }
        });
    }

    private List<String> getPreviousSearches() {
        final String searchP = Activator.getDefault().getPreferenceStore()
                .getString(NavigationConstants.QUERY_HISTORY);
        if (searchP == null)
            return null;
        return Arrays.asList(searchP.split("#SEP#"));
    }

    public void dispose() {
        if (actions != null)
            actions.clear();
        tableViewer.getTable().dispose();
    }

    public void setEnabled(boolean enabled) {
        if (searchText.isDisposed())
            return;
        searchText.setEnabled(enabled);
        tableViewer.getTable().setEnabled(enabled);
        executeButton.setEnabled(enabled);

        if (enabled) {
            searchText.setFocus();
            final int len = searchText.getText().length();
            searchText.setSelection(new Point(len, len));
            tableViewer.getTable().setCursor(null);
        } else {
            tableViewer.getTable().setCursor(Display.getDefault().getSystemCursor(SWT.CURSOR_WAIT));
        }
    }

    protected void clearSearch() {
        searchText.setText("");
    }

    /**
     * clears the content of the viewer.
     */
    public void clear() {
        setContent(Collections.emptyList());
    }

    public void createActions(IContributionManager man, IContributionManager menuMan) {

        man.add(new Separator("search.group." + tableId));
        menuMan.add(new Separator("search.group." + tableId));

        final List<Action> searchActions = new ArrayList<Action>(7);
        actions.put("search.group." + tableId, searchActions);

        final CheckableActionGroup grp = new CheckableActionGroup();

        // Login, log out, sweep.
        Action nonSearch = new Action("No search", IAction.AS_CHECK_BOX) {
            public void run() {
                Activator.getDefault().getPreferenceStore().setValue(NavigationConstants.SEARCH_TYPE,
                        NavigationConstants.NONE);
                clearSearch();
                searchText.setToolTipText("No searching required");
                GridUtils.setVisible(searchComposite, false);
                contents.layout();
            }
        };
        nonSearch.setImageDescriptor(Activator.getImageDescriptor("icons/magnifier--minus.png"));
        grp.add(nonSearch);
        searchActions.add(nonSearch);
        nonSearch.setId("search.group.nonSearch." + tableId);

        Action searchWildcard = new Action("Search using wildcard", IAction.AS_CHECK_BOX) {
            public void run() {
                Activator.getDefault().getPreferenceStore().setValue(NavigationConstants.SEARCH_TYPE,
                        NavigationConstants.WILD);
                clearSearch();
                searchText.setToolTipText("Wildcard search on name, matches any containing text.");
                GridUtils.setVisible(searchComposite, true);
                GridUtils.setVisible(executeButton, false);
                contents.layout();
            }
        };
        searchWildcard.setImageDescriptor(Activator.getImageDescriptor("icons/magnifier--arrow.png"));
        grp.add(searchWildcard);
        searchActions.add(searchWildcard);
        searchWildcard.setId("search.group.wildCard." + tableId);

        Action searchJexl = new Action("Search using query", IAction.AS_CHECK_BOX) {
            public void run() {
                Activator.getDefault().getPreferenceStore().setValue(NavigationConstants.SEARCH_TYPE,
                        NavigationConstants.QUERY);
                clearSearch();
                searchText.setToolTipText("Write a query of variables: " + firstColumn.getVariableNames()
                        + " e.g. 'complete>99 and auto eq true'");
                GridUtils.setVisible(searchComposite, true);
                GridUtils.setVisible(executeButton, true);
                contents.layout();

                List<String> searches = getPreviousSearches();
                searchText.setItems(searches.toArray(new String[searches.size()]));
            }
        };
        searchJexl.setImageDescriptor(Activator.getImageDescriptor("icons/magnifier--pencil.png"));
        grp.add(searchJexl);
        searchActions.add(searchJexl);
        searchJexl.setId("search.group.jexl." + tableId);

        if (NavigationConstants.NONE
                .equals(Activator.getDefault().getPreferenceStore().getString(NavigationConstants.SEARCH_TYPE))) {
            GridUtils.setVisible(searchComposite, false);
            nonSearch.setChecked(true);
        } else {
            GridUtils.setVisible(searchComposite, true);
            searchJexl.setChecked(NavigationConstants.QUERY.equals(
                    Activator.getDefault().getPreferenceStore().getString(NavigationConstants.SEARCH_TYPE)));
            searchWildcard.setChecked(NavigationConstants.WILD.equals(
                    Activator.getDefault().getPreferenceStore().getString(NavigationConstants.SEARCH_TYPE)));
        }

        man.add(new Separator("export.group" + tableId));
        menuMan.add(new Separator("export.group" + tableId));
        Action export = new ExportTableAction(tableViewer, firstColumn.allValues().length);
        export.setId("export.group.action" + tableId);
        actions.put("export.group" + tableId, Arrays.asList(export));

    }

    public void setCursor(Cursor cursor) {
        tableViewer.getTable().setCursor(cursor);
    }

    /**
     * Thread safe
     * @param collections
     */
    public void setContent(final List<Object> collections) {
        if (Thread.currentThread() == Display.getDefault().getThread()) {
            setContentInternal(collections);
        } else {
            Display.getDefault().asyncExec(new Runnable() {
                @Override
                public void run() {
                    setContentInternal(collections);
                }
            });
        }
    }

    private void setContentInternal(List<Object> collections) {
        labelProvider.clear();
        tableViewer.setInput(collections);
        tableViewer.setItemCount(collections.size());
        view.setActiveUI(getControl(), getTableMode());
        tableViewer.refresh();
    }

    protected abstract INavigationDelegateMode getTableMode();

    public void schedule(List<Object> selections, Object bean) {
        if (updateJob != null) {
            updateJob.schedule(selections, bean);
        }
    }

    public String getTableId() {
        return tableId;
    }

    public void setMenu(MenuManager menuMan) {
        tableViewer.getControl().setMenu(menuMan.createContextMenu(tableViewer.getControl()));
    }

    /**
     * Override to give them some content.
     */
    @Override
    public Object getAdapter(Class adapter) {

        if (adapter == Collection.class || adapter == List.class) {
            AbstractLazyContentProvider prov = (AbstractLazyContentProvider) tableViewer.getContentProvider();
            return prov.getFullData();
        }
        return null;
    }

}