org.csstudio.display.pace.gui.GUI.java Source code

Java tutorial

Introduction

Here is the source code for org.csstudio.display.pace.gui.GUI.java

Source

/*******************************************************************************
 * Copyright (c) 2010 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
 ******************************************************************************/
package org.csstudio.display.pace.gui;

import java.util.ArrayList;

import org.csstudio.display.pace.Messages;
import org.csstudio.display.pace.model.Cell;
import org.csstudio.display.pace.model.Column;
import org.csstudio.display.pace.model.Instance;
import org.csstudio.display.pace.model.Model;
import org.csstudio.display.pace.model.ModelListener;
import org.csstudio.ui.util.MinSizeTableColumnLayout;
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.action.Separator;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.window.ToolTip;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.ui.IWorkbenchPartSite;

/** GUI for the Model
 *  <p>
 *  Creates TableViewer for displaying and editing the Model's Instance rows,
 *  updating the GUI in response to model changes.
 *  Can also act as an ISelectionProvider, handing out the currently
 *  selected Cell (PV).
 *  @author Delphy Nypaver Armstrong
 *  @author Kay Kasemir
 *
 *
 *    reviewed by Delphy 01/28/09
 */
public class GUI implements ModelListener, IMenuListener, ISelectionProvider {
    /** Minimum column width */
    private static final int MIN_SIZE = 80;

    /** Model handed by this GUI */
    final private Model model;

    /** Table Viewer for Model's "Instance" rows */
    private TableViewer table_viewer;

    /** Currently selected Cell in Model or <code>null</code> */
    private Cell selected_cell = null;

    /** Listeners that registered for this ISelectionProvider */
    final private ArrayList<ISelectionChangedListener> listeners = new ArrayList<ISelectionChangedListener>();

    /** Throttle for cell updates.
     *  The model will 'trigger' this throttle in <code>cellUpdate</code>.
     *  For individual cell changes, the throttle causes an <code>update</code>
     *  after a small delay, but bursts of cell changes get combined into a
     *  delayed overall refresh of the table, since that is more efficient
     *  than many individual cell updates.
     *
     *  Statistics are always iffy, but with the "LLRF_HPM_ADC_Limits"
     *  config file which contains about 7 * 97 * 2Hz, i.e. about 1000
     *  updates per second, the CPU load dropped from near 100% to about
     *  15% after replacing direct cell updates with this throttle
     *  on a 2.8GHz Mac.
     */
    final private GUIUpdateThrottle<Cell> gui_update_throttle = new GUIUpdateThrottle<Cell>(300, 2000) {
        @Override
        protected void update(final Cell cell) {
            final Table table = table_viewer.getTable();
            if (table.isDisposed())
                return;
            // Call can originate from non-UI thread in case of PV updates,
            // so transfer to UI thread when accessing the SWT table
            table.getDisplay().asyncExec(new Runnable() {
                @Override
                public void run() {
                    if (table.isDisposed())
                        return;
                    // If currently in a cell editor, do not refresh the table
                    // because that would close the cell editor
                    if (table_viewer.isCellEditorActive()) { // re-schedule
                        GUI.this.gui_update_throttle.trigger(cell);
                        return;
                    }
                    if (cell == null)
                        table_viewer.refresh();
                    else
                        table_viewer.update(cell.getInstance(), null);
                }
            });
        }
    };

    /** Initialize GUI: Create widgets that display the model
     *  @param parent Parent widget
     *  @param model Model to display
     *  @param site Workbench site where GUI can act as ISelectionProvider,
     *              or <code>null</code>.
     */
    public GUI(final Composite parent, final Model model, final IWorkbenchPartSite site) {
        this.model = model;
        createComponents(parent, model);
        addCellTracker();
        model.addListener(this);
        createContextMenu(site);
        if (site != null)
            site.setSelectionProvider(this);
    }

    // @see ISelectionProvider
    @Override
    public void addSelectionChangedListener(final ISelectionChangedListener listener) {
        listeners.add(listener);
    }

    // @see ISelectionProvider
    @Override
    public void removeSelectionChangedListener(final ISelectionChangedListener listener) {
        listeners.remove(listener);
    }

    // @see ISelectionProvider
    @Override
    public void setSelection(final ISelection selection) {
        // NOP, don't allow outside code to change selection
    }

    /** Provide selected Cells of model or <code>null</code>
     *  @see ISelectionProvider
     */
    @Override
    public ISelection getSelection() {
        if (selected_cell == null)
            return null;
        return new StructuredSelection(selected_cell);
    }

    /** Create GUI elements
     *  @param parent Parent widget
     *  @param model Model to display
     */
    private void createComponents(final Composite parent, final Model model) {
        // Note: TableColumnLayout requires the TableViewer to be in its
        // own Composite!
        final TableColumnLayout table_layout = new MinSizeTableColumnLayout(10);
        parent.setLayout(table_layout);

        // Create TableViewer that displays Model in Table
        table_viewer = new TableViewer(parent,
                SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.VIRTUAL | SWT.FULL_SELECTION);
        // Some tweaks to the underlying table widget
        final Table table = table_viewer.getTable();
        table.setHeaderVisible(true);
        table.setLinesVisible(true);
        createColumns(table_viewer, table_layout);

        ColumnViewerToolTipSupport.enableFor(table_viewer, ToolTip.NO_RECREATE);

        // Connect TableViewer to the Model: Provide content from model...
        table_viewer.setContentProvider(new ModelInstanceProvider());

        // table viewer is set up to handle data of type Model.
        // Connect to specific model
        table_viewer.setInput(model);
    }

    /** @param table_viewer {@link TableViewer} to which to add columns for GlobalAlarm display
     *  @param table_layout {@link TableColumnLayout} to use for column auto-sizing
     */
    private void createColumns(final TableViewer table_viewer, final TableColumnLayout table_layout) {
        // Fixed 'System' column
        TableViewerColumn view_col = new TableViewerColumn(table_viewer, 0);
        TableColumn col = view_col.getColumn();
        col.setText(Messages.SystemColumn);
        table_layout.setColumnData(col, new ColumnWeightData(100, MIN_SIZE));
        //        col.setMoveable(true);
        view_col.setLabelProvider(new InstanceLabelProvider(-1));

        // Model-driven data columns
        for (int c = 0; c < model.getColumnCount(); ++c) {
            final Column model_col = model.getColumn(c);

            view_col = new TableViewerColumn(table_viewer, 0);
            col = view_col.getColumn();
            col.setText(model_col.getName());
            table_layout.setColumnData(col, new ColumnWeightData(100, MIN_SIZE));
            col.setMoveable(true);
            // Tell column how to display the model elements
            view_col.setLabelProvider(new InstanceLabelProvider(c));
            if (!model_col.isReadonly())
                view_col.setEditingSupport(new ModelCellEditor(table_viewer, c));
            // Clicking on column header allows entry into _all_ cells of model
            col.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    setAllCellsInColumn(model_col);
                }
            });
        }
    }

    /** Set all cells in column to a user-entered value
     *  @param column Column to set
     */
    protected void setAllCellsInColumn(final Column column) {
        // Any rows to set?
        if (model.getInstanceCount() <= 0 || column.isReadonly())
            return;
        // Using value of first selected test as suggestion,
        // prompt for value to be put into all selected cells
        final String message = NLS.bind(Messages.SetColumnValue_Msg, column.getName());
        final InputDialog input = new InputDialog(table_viewer.getTable().getShell(), Messages.SetValue_Title,
                message, model.getInstance(0).getCell(column).getValue(), null);
        if (input.open() != Window.OK)
            return;
        // Update value of selected cells
        final String user_value = input.getValue();
        for (int row = 0; row < model.getInstanceCount(); ++row)
            model.getInstance(row).getCell(column).setUserValue(user_value);
    }

    /** Update <code>selected_cell</code> from mouse position */
    private void addCellTracker() {
        // This part is a bit unfortunate:
        // The TableViewer handles most of the interfacing between the
        // SWT table and the Model, but it only handles selections
        // on the row/Instance level, while we want to perform certain
        // tasks on the Cell level.
        // This code tracks the currently selected Model Cell, the Cell
        // under the last mouse click.
        // Since the Cell has a reference to its Column,
        // and the TableViewer provides all selected rows/Instances,
        // this then leads to all selected Cells.
        table_viewer.getTable().addListener(SWT.MouseDown, new Listener() {
            @Override
            public void handleEvent(final Event event) {
                // Get cell in SWT Table from mouse position
                final Point point = new Point(event.x, event.y);
                final ViewerCell viewer_cell = table_viewer.getCell(point);
                if (viewer_cell == null) { // Didn't hit table??
                    selected_cell = null;
                    return;
                }
                // Get Model's row/Instance, then Model's Cell in there
                final Instance instance = (Instance) viewer_cell.getElement();
                final int col_idx = viewer_cell.getColumnIndex();
                if (col_idx <= 0) { // Special first column with instance name
                    selected_cell = null;
                    return;
                }
                selected_cell = instance.getCell(col_idx - 1);

                // Update selection listeners about newly selected cells
                for (ISelectionChangedListener listener : listeners)
                    listener.selectionChanged(new SelectionChangedEvent(GUI.this, getSelection()));
            }
        });
    }

    /** Create dynamic context menu
     *  @param site Workbench site where menu will get registered or <code>null</code>
     *  @see #menuAboutToShow(IMenuManager)
     */
    private void createContextMenu(final IWorkbenchPartSite site) {
        final MenuManager manager = new MenuManager();
        manager.setRemoveAllWhenShown(true);
        manager.addMenuListener(this);
        final Table table = table_viewer.getTable();
        table.setMenu(manager.createContextMenu(table));
        // Allow CSS extensions to add to the context menu
        if (site != null)
            site.registerContextMenu(manager, this);
    }

    /** Fill context menu with actions for the currently selected cells.
     *  @param manager Menu manager
     *  @see IMenuListener
     */
    @Override
    public void menuAboutToShow(final IMenuManager manager) {
        final Cell cells[] = getSelectedCells();
        manager.add(new RestoreCellAction(cells));
        manager.add(new SetCellValueAction(table_viewer.getTable().getShell(), cells));
        // Placeholder for CSS PV contributions
        manager.add(new GroupMarker("additions")); //$NON-NLS-1$
        manager.add(new Separator());
    }

    /** @return Currently selected editable(!) cells from column that
     *          was latest clicked, or <code>null</code>
     */
    private Cell[] getSelectedCells() {
        // Anything selected at all?
        if (selected_cell == null)
            return null;

        return getSelectedCells(selected_cell.getColumn());
    }

    /** @param column Column from which to get the cells
     *  @return Currently selected editable(!) cells or <code>null</code>
     */
    private Cell[] getSelectedCells(final Column column) {
        // Read-only column has no editable cells
        if (column.isReadonly())
            return null;
        // TableViewer selection has the currently selected _Instance_ entries
        // of the Model.
        // Use the selected Column (see addCellTracker) to
        // get the currently selected _Cells_
        final Object[] sel = ((IStructuredSelection) table_viewer.getSelection()).toArray();
        final Cell cells[] = new Cell[sel.length];
        for (int i = 0; i < sel.length; i++) {
            final Instance instance = (Instance) sel[i];
            cells[i] = instance.getCell(column);
        }
        return cells;
    }

    /** Update table when Cell in Model changed.
     *  <p>
     *  Could be called directly by Cell because PV sent new value.
     *  Could be called by cell after we edited it, or maybe in the
     *  future other forces will change the cell.
     *  In any case: A cell changed, and we have to update the GUI.
     *
     *  @see ModelListener
     */
    @Override
    public void cellUpdate(final Cell cell) {
        // Notify the throttle mechanism of changed cell
        gui_update_throttle.trigger(cell);
    }
}