org.csstudio.scan.ui.scanmonitor.GUI.java Source code

Java tutorial

Introduction

Here is the source code for org.csstudio.scan.ui.scanmonitor.GUI.java

Source

/*******************************************************************************
 * Copyright (c) 2011-2015 Oak Ridge National Laboratory.
 * 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
 *
 * The scan engine idea is based on the "ScanEngine" developed
 * by the Software Services Group (SSG),  Advanced Photon Source,
 * Argonne National Laboratory,
 * Copyright (c) 2011 , UChicago Argonne, LLC.
 *
 * This implementation, however, contains no SSG "ScanEngine" source code
 * and is not endorsed by the SSG authors.
 ******************************************************************************/
package org.csstudio.scan.ui.scanmonitor;

import java.util.Iterator;
import java.util.List;

import org.csstudio.java.time.TimestampFormats;
import org.csstudio.scan.ScanSystemPreferences;
import org.csstudio.scan.client.ScanInfoModel;
import org.csstudio.scan.client.ScanInfoModelListener;
import org.csstudio.scan.data.ScanSampleFormatter;
import org.csstudio.scan.server.ScanInfo;
import org.csstudio.scan.server.ScanServerInfo;
import org.csstudio.scan.server.ScanState;
import org.csstudio.scan.ui.scanmonitor.actions.AbortAction;
import org.csstudio.scan.ui.scanmonitor.actions.NextAction;
import org.csstudio.scan.ui.scanmonitor.actions.PauseAction;
import org.csstudio.scan.ui.scanmonitor.actions.RemoveAction;
import org.csstudio.scan.ui.scanmonitor.actions.RemoveCompletedAction;
import org.csstudio.scan.ui.scanmonitor.actions.ResumeAction;
import org.csstudio.scan.ui.scanmonitor.actions.ShowDevicesAction;
import org.csstudio.ui.util.MinSizeTableColumnLayout;
import org.csstudio.ui.util.dialogs.ExceptionDetailsErrorDialog;
import org.eclipse.jface.action.GroupMarker;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.viewers.CellLabelProvider;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MenuEvent;
import org.eclipse.swt.events.MenuListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.handlers.IHandlerService;

/** GUI for the {@link ScanInfoModel}
 *  @author Kay Kasemir
 */
public class GUI implements ScanInfoModelListener {
    final private ScanInfoModel model;

    final private ScanInfoComparator comparator = new ScanInfoComparator();

    /** {@link TableViewer} for {@link ScanInfoModelContentProvider} */
    private TableViewer table_viewer;

    /** Set when context menu is open, to suppress table refreshes */
    private boolean context_menu_is_active = false;

    /** If table updates arrive while context menu is active,
     *  this flag is set instead of refreshing the table
     */
    private boolean suppressed_table_updates = false;

    private Bar mem_info;

    /** Initialize
     *  @param parent Parent component
     *  @param model Model to display
     *  @param site Site or <code>null</code>
     */
    public GUI(final Composite parent, final ScanInfoModel model, IWorkbenchPartSite site) {
        this.model = model;

        createComponents(parent, site);
        hookActions(site);
        createContextMenu(site);
        table_viewer.setInput(model);
        model.addListener(this);
    }

    /** Create GUI elements
     *  @param parent Parent component
     *  @param site Site or <code>null</code>
     */
    private void createComponents(final Composite parent, final IWorkbenchPartSite site) {
        final Display display = parent.getDisplay();

        parent.setLayout(new GridLayout(2, false));

        // Note: TableColumnLayout requires that table is only child element!
        final Composite table_box = new Composite(parent, 0);
        table_box.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));

        // If more components are added, the table will need to be wrapped
        // into its own Composite.
        final TableColumnLayout table_layout = new MinSizeTableColumnLayout(10);
        table_box.setLayout(table_layout);

        table_viewer = new TableViewer(table_box,
                SWT.MULTI | SWT.FULL_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
        final Table table = table_viewer.getTable();
        table.setHeaderVisible(true);
        table.setLinesVisible(true);
        createColumn(0, table_viewer, table_layout, Messages.ID, 30, 25, new CellLabelProvider() {
            @Override
            public String getToolTipText(final Object element) {
                final ScanInfo info = (ScanInfo) element;
                return NLS.bind(Messages.ID_Fmt, info.getId());
            }

            @Override
            public void update(final ViewerCell cell) {
                final ScanInfo info = (ScanInfo) cell.getElement();
                cell.setText(Long.toString(info.getId()));
            }
        });
        createColumn(1, table_viewer, table_layout, Messages.CreateTime, 150, 25, new CellLabelProvider() {
            @Override
            public String getToolTipText(final Object element) {
                final ScanInfo info = (ScanInfo) element;
                return NLS.bind(Messages.CreateTimeFmt, TimestampFormats.formatCompactDateTime(info.getCreated()));
            }

            @Override
            public void update(final ViewerCell cell) {
                final ScanInfo info = (ScanInfo) cell.getElement();
                cell.setText(ScanSampleFormatter.format(info.getCreated()));
            }
        });
        createColumn(2, table_viewer, table_layout, Messages.Name, 120, 100, new CellLabelProvider() {
            @Override
            public String getToolTipText(final Object element) {
                final ScanInfo info = (ScanInfo) element;
                return NLS.bind(Messages.NameFmt, info.getName());
            }

            @Override
            public void update(final ViewerCell cell) {
                final ScanInfo info = (ScanInfo) cell.getElement();
                cell.setText(info.getName());
            }
        });
        createColumn(3, table_viewer, table_layout, Messages.State, 90, 50, new CellLabelProvider() {
            @Override
            public String getToolTipText(final Object element) {
                final ScanInfo info = (ScanInfo) element;
                return NLS.bind(Messages.StateFmt, info.getState());
            }

            @Override
            public void update(final ViewerCell cell) {
                final ScanInfo info = (ScanInfo) cell.getElement();
                cell.setText(info.getState().toString());
                cell.setForeground(getStateColor(display, info));
            }
        });
        final TableViewerColumn perc_col = createColumn(4, table_viewer, table_layout, Messages.Percent, 25, 25,
                new CellLabelProvider() {
                    @Override
                    public String getToolTipText(final Object element) {
                        final ScanInfo info = (ScanInfo) element;
                        return NLS.bind(Messages.PercentFmt, new Object[] { info.getPerformedWorkUnits(),
                                info.getTotalWorkUnits(), info.getPercentage() });
                    }

                    @Override
                    public void update(final ViewerCell cell) {
                        /* Custom painted...
                        final ScanInfo info = (ScanInfo) cell.getElement();
                        cell.setText(Integer.toString(info.getPercentage()));
                        */
                    }
                });
        createColumn(5, table_viewer, table_layout, Messages.Runtime, 65, 5, new CellLabelProvider() {
            @Override
            public String getToolTipText(final Object element) {
                return Messages.Runtime_TT;
            }

            @Override
            public void update(final ViewerCell cell) {
                final ScanInfo info = (ScanInfo) cell.getElement();
                if (info.getState() != ScanState.Logged)
                    cell.setText(info.getRuntimeText());
            }
        });
        createColumn(6, table_viewer, table_layout, Messages.FinishTime, 65, 5, new CellLabelProvider() {
            @Override
            public String getToolTipText(final Object element) {
                final ScanInfo info = (ScanInfo) element;
                return NLS.bind(Messages.FinishTimeFmt,
                        TimestampFormats.formatCompactDateTime(info.getFinishTime()));
            }

            @Override
            public void update(final ViewerCell cell) {
                final ScanInfo info = (ScanInfo) cell.getElement();
                cell.setText(TimestampFormats.formatCompactDateTime(info.getFinishTime()));
            }
        });
        createColumn(7, table_viewer, table_layout, Messages.CurrentCommand, 80, 100, new CellLabelProvider() {
            @Override
            public String getToolTipText(final Object element) {
                final ScanInfo info = (ScanInfo) element;
                final String command = info.getCurrentCommand();
                if (command.length() > 0)
                    return NLS.bind(Messages.CurrentCommandFmt, command);
                else
                    return Messages.CurrentCommandEmpty;
            }

            @Override
            public void update(final ViewerCell cell) {
                final ScanInfo info = (ScanInfo) cell.getElement();
                cell.setText(info.getCurrentCommand());
            }
        });
        createColumn(8, table_viewer, table_layout, Messages.Error, 80, 150, new CellLabelProvider() {
            @Override
            public String getToolTipText(final Object element) {
                final ScanInfo info = (ScanInfo) element;
                if (info.getError().isPresent())
                    return NLS.bind(Messages.ErrorMsgFmt, info.getError().get());
                else
                    return Messages.NoError;
            }

            @Override
            public void update(final ViewerCell cell) {
                final ScanInfo info = (ScanInfo) cell.getElement();
                cell.setText(info.getError().orElse("")); //$NON-NLS-1$
            }
        });

        // Custom-paint the perc_col cells to get progress bar
        table.addListener(SWT.PaintItem, new Listener() {
            @Override
            public void handleEvent(final Event event) {
                if (event.index != 4) // index of the percent column
                    return;
                final GC gc = event.gc;
                final TableItem item = (TableItem) event.item;
                final ScanInfo info = (ScanInfo) item.getData();
                if (info.getState() == ScanState.Logged)
                    return;

                final Color foreground = gc.getForeground();
                final Color background = gc.getBackground();
                gc.setForeground(getStateColor(display, info));
                gc.setBackground(display.getSystemColor(SWT.COLOR_GRAY));
                final int width = (perc_col.getColumn().getWidth() - 1) * info.getPercentage() / 100;
                gc.fillGradientRectangle(event.x, event.y, width, event.height, false);
                gc.drawRectangle(event.x, event.y, width - 1, event.height - 1);
                gc.setForeground(foreground);
                gc.setBackground(background);
            }
        });

        table_viewer.setComparator(comparator);

        ColumnViewerToolTipSupport.enableFor(table_viewer);
        table_viewer.setContentProvider(new ScanInfoModelContentProvider());

        if (ScanSystemPreferences.getShowMemoryUsage()) {
            final Label l = new Label(parent, 0);
            l.setText(Messages.MemInfo);
            l.setLayoutData(new GridData());
            mem_info = new Bar(parent, 0);
            mem_info.setLayoutData(new GridData(SWT.FILL, 0, true, false));
            mem_info.setToolTipText(Messages.MemInfoTT);
        }

        // Publish current selection
        if (site != null)
            site.setSelectionProvider(table_viewer);
    }

    /** @param display Display
     *  @param info ScanInfo
     *  @return Color associated with the state of the scan
     */
    protected Color getStateColor(final Display display, final ScanInfo info) {
        switch (info.getState()) {
        case Idle:
            return display.getSystemColor(SWT.COLOR_DARK_BLUE);
        case Aborted:
            return display.getSystemColor(SWT.COLOR_DARK_YELLOW);
        case Failed:
            return display.getSystemColor(SWT.COLOR_RED);
        case Finished:
            return display.getSystemColor(SWT.COLOR_DARK_GREEN);
        default:
            return display.getSystemColor(SWT.COLOR_BLACK);
        }
    }

    /** Helper for creating a resizable column
     *  @return {@link TableViewerColumn}
     */
    private TableViewerColumn createColumn(final int column_index, final TableViewer table_viewer,
            final TableColumnLayout table_layout, final String header, final int width, final int weight,
            final CellLabelProvider label) {
        final TableViewerColumn view_col = new TableViewerColumn(table_viewer, 0);
        final TableColumn col = view_col.getColumn();
        col.setText(header);
        col.setMoveable(true);
        col.setResizable(true);
        table_layout.setColumnData(col, new ColumnWeightData(weight, width));
        view_col.setLabelProvider(label);
        // Sort when column header is clicked
        col.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(final SelectionEvent e) {
                // Update sorting
                comparator.setColumn(column_index);
                // Show sort indicator
                table_viewer.getTable().setSortColumn(col);
                table_viewer.getTable().setSortDirection(comparator.getDirection());
                table_viewer.refresh();
            }
        });
        return view_col;
    }

    /** Connect actions to GUI
     *  @param site
     */
    @SuppressWarnings("nls")
    private void hookActions(final IWorkbenchPartSite site) {
        // Double-click on scan opens editor
        table_viewer.addDoubleClickListener(new IDoubleClickListener() {
            @Override
            public void doubleClick(final DoubleClickEvent event) {
                final IHandlerService handler = site.getService(IHandlerService.class);
                try {
                    handler.executeCommand("org.csstudio.scan.ui.scantree.open", null);
                } catch (Exception ex) {
                    ExceptionDetailsErrorDialog.openError(site.getShell(), "Cannot open scan editor", ex);
                }
            }
        });
    }

    /** @return Currently selected {@link ScanInfo} or <code>null</code> */
    @SuppressWarnings("unchecked")
    private ScanInfo[] getSelectedScans() {
        final IStructuredSelection selection = (IStructuredSelection) table_viewer.getSelection();
        if (selection.isEmpty())
            return null;
        final ScanInfo[] infos = new ScanInfo[selection.size()];
        final Iterator<ScanInfo> iter = selection.iterator();
        for (int i = 0; i < infos.length; ++i)
            infos[i] = iter.next();
        return infos;
    }

    /** Add context menu to table
     *  @param site
     */
    private void createContextMenu(final IWorkbenchPartSite site) {
        final Shell shell = table_viewer.getControl().getShell();
        final MenuManager manager = new MenuManager();
        manager.setRemoveAllWhenShown(true);
        manager.addMenuListener(new IMenuListener() {
            @Override
            public void menuAboutToShow(final IMenuManager manager) {
                final ScanInfo[] infos = getSelectedScans();
                manager.add(new GroupMarker("scan")); //$NON-NLS-1$
                if (infos == null)
                    return;
                // Allow resume if anything's paused
                for (ScanInfo info : infos)
                    if (info.getState() == ScanState.Paused) {
                        manager.add(new ResumeAction(shell, model, infos));
                        break;
                    }
                // Allow pause when anything's running
                for (ScanInfo info : infos)
                    if (info.getState() == ScanState.Running) {
                        manager.add(new PauseAction(shell, model, infos));
                        manager.add(new NextAction(shell, model, infos));
                        break;
                    }
                // Abort if anything is not done
                for (ScanInfo info : infos)
                    if (!info.getState().isDone()) {
                        manager.add(new AbortAction(shell, model, infos));
                        break;
                    }
                // Remove if anything is done
                for (ScanInfo info : infos)
                    if (info.getState().isDone()) {
                        manager.add(new RemoveAction(shell, model, infos));
                        break;
                    }
                manager.add(new RemoveCompletedAction(shell, model));
                if (infos.length >= 1)
                    manager.add(new ShowDevicesAction(shell, model, infos));
                manager.add(new GroupMarker(IWorkbenchActionConstants.MB_ADDITIONS));
            }
        });

        final Table table = table_viewer.getTable();
        final Menu menu = manager.createContextMenu(table);
        menu.addMenuListener(new MenuListener() {
            @Override
            public void menuShown(final MenuEvent e) {
                context_menu_is_active = true;
            }

            @Override
            public void menuHidden(final MenuEvent e) {
                context_menu_is_active = false;
                if (suppressed_table_updates)
                    // We _are_ on the UI thread, refreshing the table now
                    // will change the selection, and then selection-based
                    // context menu entries won't work even though they were
                    // shown in the menu OK.
                    // -> Schedule this later
                    table_viewer.getTable().getDisplay().asyncExec(new Runnable() {
                        @Override
                        public void run() {
                            suppressed_table_updates = false;
                            table_viewer.refresh(false);
                        }
                    });
            }
        });
        table.setMenu(menu);

        // Allow contributions to the menu
        if (site != null)
            site.registerContextMenu(manager, table_viewer);
    }

    /** @see ScanInfoModelListener */
    @Override
    public void scanServerUpdate(final ScanServerInfo server_info) {
        if (mem_info == null || mem_info.isDisposed())
            return;
        mem_info.getDisplay().asyncExec(new Runnable() {
            @Override
            public void run() {
                if (mem_info.isDisposed())
                    return;
                mem_info.update(server_info.getMemoryInfo(), server_info.getMemoryPercentage());
            }
        });
    }

    /** @see ScanInfoModelListener */
    @Override
    public void scanUpdate(final List<ScanInfo> infos) {
        final Table table = table_viewer.getTable();
        if (table.isDisposed())
            return;
        table.getDisplay().asyncExec(new Runnable() {
            @Override
            public void run() {
                if (table.isDisposed())
                    return;
                // Received update -> enable table and display info
                if (!table.getEnabled())
                    table.setEnabled(true);
                if (context_menu_is_active)
                    suppressed_table_updates = true;
                else
                    table_viewer.refresh(false);
            }
        });
    }

    /** @see ScanInfoModelListener */
    @Override
    public void connectionError() {
        final Table table = table_viewer.getTable();
        if (table.isDisposed())
            return;
        table.getDisplay().asyncExec(new Runnable() {
            @Override
            public void run() {
                if (table.isDisposed())
                    return;
                // Disable table to indicate communication problem
                table.setEnabled(false);
            }
        });
    }
}