org.csstudio.display.pvtable.ui.PVTable.java Source code

Java tutorial

Introduction

Here is the source code for org.csstudio.display.pvtable.ui.PVTable.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.pvtable.ui;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

import org.csstudio.autocomplete.ui.AutoCompleteTypes;
import org.csstudio.autocomplete.ui.AutoCompleteUIHelper;
import org.csstudio.csdata.ProcessVariable;
import org.csstudio.display.pvtable.Messages;
import org.csstudio.display.pvtable.Plugin;
import org.csstudio.display.pvtable.Preferences;
import org.csstudio.display.pvtable.model.Configuration;
import org.csstudio.display.pvtable.model.Measure;
import org.csstudio.display.pvtable.model.PVTableItem;
import org.csstudio.display.pvtable.model.PVTableModel;
import org.csstudio.display.pvtable.model.PVTableModelListener;
import org.csstudio.display.pvtable.model.SavedValue;
import org.csstudio.display.pvtable.model.TimestampHelper;
import org.csstudio.display.pvtable.model.VTypeHelper;
import org.csstudio.ui.util.MinSizeTableColumnLayout;
import org.csstudio.ui.util.dnd.ControlSystemDragSource;
import org.csstudio.ui.util.dnd.SerializableItemTransfer;
import org.diirt.vtype.AlarmSeverity;
import org.diirt.vtype.VEnum;
import org.diirt.vtype.VType;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.CellLabelProvider;
import org.eclipse.jface.viewers.CheckboxCellEditor;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.ComboBoxCellEditor;
import org.eclipse.jface.viewers.EditingSupport;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
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;

/** PV Table GUI
 *
 *  @author Kay Kasemir
 *  @author Kunal Shroff - Original PVManager version of PVTable that used
 *                         similar editing behavior
 */
public class PVTable implements PVTableModelListener {
    private Image icon_checked, icon_unchecked;
    private PVTableModel model = null;
    private TableViewer viewer;
    private Color changed_background;
    private Map<AlarmSeverity, Color> alarm_colors = new HashMap<AlarmSeverity, Color>();
    private MenuManager manager;
    private Menu menu;

    /**
     * Initialize
     *
     * @param parent
     *            Parent widget
     * @param site
     *            Workbench site or <code>null</code>
     */
    public PVTable(final Composite parent, final IWorkbenchPartSite site) {
        initColors(parent.getDisplay());

        icon_checked = Plugin.getImageDescriptor("icons/checked.gif").createImage();
        icon_unchecked = Plugin.getImageDescriptor("icons/unchecked.gif").createImage();

        createComponents(parent);
        createContextMenu(viewer, site);
        hookDragDrop();
        // Disconnect from model when disposed
        parent.addDisposeListener(e -> {
            if (model != null) {
                model.removeListener(PVTable.this);
                model.dispose();
            }
            icon_unchecked.dispose();
            icon_checked.dispose();
        });
    }

    /**
     * Initialize colors
     *
     * @param display
     *            Display
     */
    private void initColors(final Display display) {
        changed_background = display.getSystemColor(SWT.COLOR_CYAN);
        alarm_colors.put(AlarmSeverity.MINOR, display.getSystemColor(SWT.COLOR_DARK_YELLOW));
        alarm_colors.put(AlarmSeverity.MAJOR, display.getSystemColor(SWT.COLOR_RED));
        alarm_colors.put(AlarmSeverity.INVALID, display.getSystemColor(SWT.COLOR_MAGENTA));
        alarm_colors.put(AlarmSeverity.UNDEFINED, display.getSystemColor(SWT.COLOR_DARK_RED));
    }

    /**
     * Create GUI components
     *
     * @param parent
     */
    private void createComponents(final Composite parent) {
        // TableColumnLayout requires table to be only child of parent.
        // To assert that'll always be the case, create box.
        final Composite table_box = new Composite(parent, 0);
        parent.setLayout(new FillLayout());
        final TableColumnLayout layout = new MinSizeTableColumnLayout(50);
        table_box.setLayout(layout);

        // Tried CheckboxTableViewer, but it lead to inconsistent refreshes:
        // Rows would appear blank. Didn't investigate further, stuck with
        // TableViewer.
        viewer = new TableViewer(table_box,
                SWT.CHECK | SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | SWT.VIRTUAL);

        final Table table = viewer.getTable();
        table.setHeaderVisible(true);
        table.setLinesVisible(true);

        // PV Name column: Has the 'check box' to select, allows editing
        final TableViewerColumn pv_column = createColumn(viewer, layout, Messages.PV, 75, 100,
                new PVTableCellLabelProvider() {
                    @Override
                    public void update(final ViewerCell cell) {
                        final TableItem tab_item = (TableItem) cell.getItem();
                        final PVTableItem item = (PVTableItem) cell.getElement();
                        if (item.isConfHeader()) {
                            cell.setText(item.getConfHeader());
                            cell.setForeground(tab_item.getDisplay().getSystemColor(SWT.COLOR_DARK_BLUE));
                            return;
                        }
                        if (item.isMeasureHeader() && model.getConfig() != null) {
                            cell.setText(item.getMeasureHeader());
                            cell.setForeground(tab_item.getDisplay().getSystemColor(SWT.COLOR_DARK_GREEN));
                            return;
                        }
                        if (item.isComment()) {
                            cell.setText(item.getComment());
                            cell.setForeground(tab_item.getDisplay().getSystemColor(SWT.COLOR_BLUE));
                            return;
                        }
                        tab_item.setChecked(item.isSelected());
                        cell.setText(item.getName());
                        cell.setForeground(null);
                    }
                });
        pv_column.setEditingSupport(new EditingSupport(viewer) {
            @Override
            protected boolean canEdit(final Object element) {
                final PVTableItem item = (PVTableItem) element;
                if (item.isMeasure() && !item.isMeasureHeader())
                    return false;
                return true;
            }

            @Override
            protected CellEditor getCellEditor(final Object element) {
                return AutoCompleteUIHelper.createAutoCompleteTextCellEditor(table, AutoCompleteTypes.PV);
            }

            @Override
            protected void setValue(final Object element, final Object value) {
                final String new_name = value.toString().trim();
                final PVTableItem item = (PVTableItem) element;

                if (item == PVTableModelContentProvider.NEW_ITEM) {
                    // Set name of magic NEW_ITEM: Add new item
                    if (new_name.isEmpty())
                        return;
                    model.addItem(new_name);
                    viewer.setInput(model);
                } else if (item.isMeasureHeader() && !new_name.startsWith("#mesure#"))
                    return;
                else if (new_name.isEmpty()) {
                    if (item.isConfHeader()) {
                        boolean isDelete = MessageDialog.openConfirm(Display.getCurrent().getActiveShell(),
                                Messages.InformationPopup, Messages.InformationPopup_DelConfHeader);

                        if (isDelete == false)
                            return;

                        Configuration conf = model.getConfig();
                        System.out.println("PVTable.createComponents(...).new EditingSupport() {...}.setValue() "
                                + conf + " " + ((conf != null) ? conf.getMeasures() : ""));
                        List<Measure> allMeasures = conf.getMeasures();
                        for (Measure measure : allMeasures) {
                            List<PVTableItem> itemsMeasure = measure.getItems();
                            for (PVTableItem itemMes : itemsMeasure)
                                model.removeItem(itemMes);
                        }
                    }
                    // Setting name to nothing: Remove item
                    model.removeItem(item);
                    viewer.remove(item);

                    viewer.setItemCount(model.getItemCount() + 1);
                    viewer.refresh();
                } else {
                    // Change name of existing item
                    item.updateName(new_name);
                    model.isConfHeaderToAdd(item);
                    model.fireModelChange();
                }
            }

            @Override
            protected Object getValue(final Object element) {
                final PVTableItem item = (PVTableItem) element;
                return item.getName();
            }
        });
        // Allow check/uncheck to select items for restore
        viewer.getTable().addListener(SWT.Selection, new Listener() {
            @Override
            public void handleEvent(final Event event) {
                final TableItem tab_item = (TableItem) event.item;
                final PVTableItem item = (PVTableItem) tab_item.getData();

                if (event.detail != SWT.CHECK) {
                    changeContextMenu(item);
                    return;
                }

                if (item.isComment() || item.isMeasure()) {
                    tab_item.setChecked(false);
                    return;
                }

                /*
                 * Toggle selection of PVTableItem, then update the TableItem to
                 * reflect current state. When instead updating the PVTableItem
                 * from the TableItem's check mark, the result was inconsistent
                 * behavior for selected rows: Could not un-check the checkbox
                 * for a selected row...
                 */
                if (item == PVTableModelContentProvider.NEW_ITEM)
                    item.setSelected(false);
                else
                    item.setSelected(!item.isSelected());
                tab_item.setChecked(item.isSelected());
            }
        });

        // Optionally, add column to display item.getDescription()
        if (Preferences.showDescription()) {
            createColumn(viewer, layout, Messages.Description, 50, 40, new PVTableCellLabelProvider() {
                @Override
                public void update(final ViewerCell cell) {
                    final PVTableItem item = (PVTableItem) cell.getElement();
                    cell.setText(item.getDescription());
                }
            });
        }

        // Read-only time stamp
        createColumn(viewer, layout, Messages.Time, 50, 100, new PVTableCellLabelProvider() {
            @Override
            public void update(final ViewerCell cell) {
                final PVTableItem item = (PVTableItem) cell.getElement();
                final VType value = item.getValue();
                if (!item.isMeasureHeader() && (value == null || item.isComment()))
                    cell.setText(""); //$NON-NLS-1$
                else
                    cell.setText(TimestampHelper.format(VTypeHelper.getTimestamp(value)));
            }
        });
        // Editable value
        final TableViewerColumn value_column = createColumn(viewer, layout, Messages.Value, 100, 50,
                new PVTableCellLabelProviderWithChangeIndicator(changed_background) {
                    @Override
                    public void update(final ViewerCell cell) {
                        final PVTableItem item = (PVTableItem) cell.getElement();
                        final VType value = item.getValue();
                        if (value == null)
                            cell.setText(""); //$NON-NLS-1$
                        else
                            cell.setText(VTypeHelper.toString(value));
                        super.update(cell);
                    }
                });
        value_column.setEditingSupport(new EditingSupport(viewer) {
            /** When a combo box editor is created, its value must be the integer
             *  index. Note that this variable is shared for all rows. When
             *  editing, the UI thread calls getCellEditor() for the row, then
             *  get/setValue().
             */
            private boolean need_index = false;

            @Override
            protected boolean canEdit(final Object element) {
                final PVTableItem item = (PVTableItem) element;
                return item.isWritable();
            }

            @Override
            protected CellEditor getCellEditor(final Object element) {
                final PVTableItem item = (PVTableItem) element;
                final String[] options = item.getValueOptions();
                need_index = options != null;
                if (need_index)
                    return new ComboBoxCellEditor(table, options, SWT.READ_ONLY);
                return new TextCellEditor(table);
            }

            @Override
            protected void setValue(final Object element, final Object value) {
                final PVTableItem item = (PVTableItem) element;
                item.setValue(value.toString());
            }

            @Override
            protected Object getValue(final Object element) {
                final PVTableItem item = (PVTableItem) element;
                final VType value = item.getValue();
                if (need_index) {
                    if (value == null || !(value instanceof VEnum))
                        return 0;
                    return ((VEnum) value).getIndex();
                }
                if (value == null)
                    return ""; //$NON-NLS-1$
                return VTypeHelper.toString(value);
            }
        });
        // Remaining columns are read-only
        createColumn(viewer, layout, Messages.Alarm, 100, 50, new PVTableCellLabelProvider() {
            @Override
            public void update(final ViewerCell cell) {
                final PVTableItem item = (PVTableItem) cell.getElement();
                final VType value = item.getValue();
                if (value == null)
                    cell.setText(""); //$NON-NLS-1$
                else {
                    cell.setText(VTypeHelper.formatAlarm(value));
                    cell.setForeground(alarm_colors.get(VTypeHelper.getSeverity(value)));
                }
            }
        });
        // Column Saved Value
        createColumn(viewer, layout, Messages.Saved, 100, 50,
                new PVTableCellLabelProviderWithChangeIndicator(changed_background) {
                    @Override
                    public void update(final ViewerCell cell) {
                        final PVTableItem item = (PVTableItem) cell.getElement();
                        final SavedValue value = item.getSavedValue().orElse(null);
                        if (value == null)
                            cell.setText(""); //$NON-NLS-1$
                        else
                            cell.setText(value.toString());
                        super.update(cell);
                    }
                });

        // Optionally, add column to display item.getTime_saved()
        if (Preferences.showSaveTimestamp()) {
            createColumn(viewer, layout, Messages.Saved_Value_TimeStamp, 50, 40, new PVTableCellLabelProvider() {
                @Override
                public void update(final ViewerCell cell) {
                    final PVTableItem item = (PVTableItem) cell.getElement();
                    cell.setText(item.getTime_saved());
                }
            });
        }

        final TableViewerColumn completion_column = createColumn(viewer, layout, "Completion", 10, 100,
                new PVTableCellLabelProvider() {
                    @Override
                    public void update(final ViewerCell cell) {
                        final PVTableItem item = (PVTableItem) cell.getElement();
                        if (item.isWritable()) {
                            if (item.isUsingCompletion())
                                cell.setImage(icon_checked);
                            else
                                cell.setImage(icon_unchecked);
                        }
                    }
                });
        completion_column.getColumn().setToolTipText("Await completion (use put-callback) when restoring values?");
        completion_column.setEditingSupport(new EditingSupport(viewer) {
            @Override
            protected boolean canEdit(final Object element) {
                return ((PVTableItem) element).isWritable();
            }

            @Override
            protected CellEditor getCellEditor(final Object element) {
                return new CheckboxCellEditor(table);
            }

            @Override
            protected Object getValue(final Object element) {
                return ((PVTableItem) element).isUsingCompletion();
            }

            @Override
            protected void setValue(final Object element, final Object value) {
                ((PVTableItem) element).setUseCompletion((Boolean) value);
                model.fireModelChange();
            }
        });

        ColumnViewerToolTipSupport.enableFor(viewer);
        viewer.setContentProvider(new PVTableModelContentProvider());
    }

    /**
     * Helper for creating table column
     *
     * @param viewer
     * @param layout
     * @param header
     * @param weight
     * @param min_width
     * @param label_provider
     * @return Created viewer column
     */
    private TableViewerColumn createColumn(final TableViewer viewer, final TableColumnLayout layout,
            final String header, final int weight, final int min_width, final CellLabelProvider label_provider) {
        final TableViewerColumn view_col = new TableViewerColumn(viewer, 0);
        final TableColumn col = view_col.getColumn();
        col.setText(header);
        col.setResizable(true);
        col.setMoveable(true);
        layout.setColumnData(col, new ColumnWeightData(weight, min_width));
        view_col.setLabelProvider(label_provider);
        return view_col;
    }

    /**
     * Helper for creating context menu. Be carefull if you modify to modify the
     * changeContextMenu method too.
     *
     * @param viewer
     * @param site
     */
    private void createContextMenu(final TableViewer viewer, IWorkbenchPartSite site) {
        manager = new MenuManager();
        manager.add(new SnapshotAction(viewer)); // 0
        manager.add(new RestoreAction(viewer)); // 1
        manager.add(new SelectAllAction(viewer)); // 2
        manager.add(new DeSelectAllAction(viewer)); // 3
        manager.add(new ExportXLSAction(viewer));// 4
        manager.add(new Separator()); // 5
        manager.add(new SnapshotCurrentSelectionAction(viewer)); // 6
        manager.add(new RestoreCurrentSelectionAction(viewer)); // 7
        manager.add(new Separator()); // 8
        manager.add(new TimeoutAction(viewer)); // 9
        manager.add(new ToleranceAction(viewer)); // 10
        manager.add(new Separator()); // 11
        manager.add(new InsertAction(viewer)); // 12
        manager.add(new DeleteAction(viewer)); // 13
        manager.add(new DeleteMeasureAction(viewer)); // 14
        manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS)); // 15
        final Control control = viewer.getControl();
        menu = manager.createContextMenu(control);
        control.setMenu(menu);
        if (site != null) {
            site.registerContextMenu(manager, viewer);
        }
    }

    private void changeContextMenu(PVTableItem item) {

        if (item == null)
            return;
        manager.getItems()[5].setVisible(!(item.isMeasure() || item.isMeasureHeader() || item.isConfHeader()));
        manager.getItems()[6].setVisible(!(item.isMeasure() || item.isMeasureHeader() || item.isConfHeader()));
        manager.getItems()[7].setVisible(!(item.isMeasure() || item.isMeasureHeader() || item.isConfHeader()));
        manager.getItems()[8].setVisible(!item.isMeasure());
        manager.getItems()[10].setVisible(!item.isMeasure());
        manager.getItems()[11].setVisible(!item.isMeasure());

        if (model.getConfig() != null) {
            if (!model.getConfig().getMeasures().isEmpty()) {
                manager.getItems()[12].setVisible(!item.isMeasure()
                        || (model.getConfig().getMeasures().get(0) == item.getMeasure() && item.isMeasureHeader()));
            }
        }
        manager.getItems()[13].setVisible(!item.isMeasure());
        manager.getItems()[14].setVisible(item.isMeasure());
    }

    /**
     * Set to currently dragged items to allow 'drop' to move them instead of
     * adding duplicates.
     */
    private final AtomicReference<List<PVTableItem>> dragged_items = new AtomicReference<>(Collections.emptyList());

    private void hookDragDrop() {
        // Support 'dragging' ProcessVariable[]
        // Tried to drag PVTableItem[] to ease accepting that within this or
        // other table,
        // but combination of ControlSystemDragSource and registered adapter
        // from PVTableItem to ProcessVariable
        // always resulted in receiving ProcessVariable[]
        new ControlSystemDragSource(viewer.getTable()) {
            @Override
            public Object getSelection() {
                final List<PVTableItem> items = new ArrayList<>();
                final IStructuredSelection sel = (IStructuredSelection) viewer.getSelection();
                if (sel != null) {
                    final Iterator<?> iterator = sel.iterator();
                    while (iterator.hasNext()) {

                        PVTableItem itemIt = (PVTableItem) iterator.next();
                        if (itemIt.isMeasure() || itemIt.isConfHeader()) {
                            return null;
                        }
                        items.add(itemIt);
                    }
                }
                dragged_items.set(items);
                final ProcessVariable[] pvs = new ProcessVariable[items.size()];
                for (int i = 0; i < pvs.length; ++i) {
                    pvs[i] = new ProcessVariable(items.get(i).getName());
                }
                return pvs;
            }
        };

        // Allow 'dropping' PV names
        final DropTarget target = new DropTarget(viewer.getTable(), DND.DROP_COPY | DND.DROP_MOVE | DND.DROP_LINK);
        target.setTransfer(new Transfer[] {
                // Tried also receiving PVTableItem[], but always got
                // ProcessVariable[]
                SerializableItemTransfer.getTransfer(ProcessVariable[].class.getName()),
                SerializableItemTransfer.getTransfer(ProcessVariable.class.getName()),
                TextTransfer.getInstance() });
        target.addDropListener(new DropTargetAdapter() {
            @Override
            public void dragEnter(final DropTargetEvent event) {
                if ((event.operations & DND.DROP_COPY) != 0) {
                    event.detail = DND.DROP_COPY;
                } else {
                    event.detail = DND.DROP_NONE;
                }
            }

            @Override
            public void drop(final DropTargetEvent event) {
                // Dropping on a valid existing item?
                PVTableItem existing = null;
                if (event.item instanceof TableItem) {
                    final TableItem tab_item = (TableItem) event.item;
                    if (tab_item.getData() instanceof PVTableItem) {
                        existing = (PVTableItem) tab_item.getData();
                        if (existing.isMeasure() || existing.isMeasureHeader()) {
                            return;
                        }
                        if (existing == PVTableModelContentProvider.NEW_ITEM) {
                            existing = null;
                        }
                    }
                }

                // Was this data dragged from the PV table?
                final List<PVTableItem> moved = dragged_items.getAndSet(Collections.emptyList());
                if (moved.size() > 0) {
                    // Move items within the table
                    for (PVTableItem item : moved) {
                        System.out.println("Moving original " + item);
                        model.removeItem(item);
                        model.addItemAbove(existing, item);
                    }
                } else {
                    // Add items received from outside this table
                    final Object item = event.data;
                    if (item instanceof ProcessVariable) {
                        model.addItemAbove(existing, ((ProcessVariable) item).getName());
                    } else if (item instanceof String) {
                        model.addItemAbove(existing, (String) item);
                    } else if (item instanceof ProcessVariable[]) {
                        for (ProcessVariable pv : (ProcessVariable[]) item) {
                            model.addItemAbove(existing, pv.getName());
                        }
                    } else {
                        return;
                    }
                }
                viewer.setInput(model);
            }
        });
    }

    /** @return Table viewer */
    public TableViewer getTableViewer() {
        return viewer;
    }

    /** @return PV table model */
    public PVTableModel getModel() {
        return model;
    }

    /**
     * @param model
     *            Model to display in table
     */
    public void setModel(final PVTableModel model) {
        // Remove this as listener from previous model
        if (this.model != null) {
            this.model.removeListener(this);
        }
        this.model = model;
        viewer.setInput(model);
        model.addListener(this);
    }

    /** {@inheritDoc} */
    @Override
    public void tableItemSelectionChanged(final PVTableItem item) {
        // System.out.println("PVTable.tableItemSelectionChanged() OK" );
    }

    /** {@inheritDoc} */
    @Override
    public void tableItemChanged(final PVTableItem item) {
        final Table table = viewer.getTable();
        if (table.isDisposed()) {
            return;
        }
        table.getDisplay().asyncExec(() -> {
            if (!table.isDisposed() && !viewer.isCellEditorActive()) {
                viewer.refresh(item);
            }
        });
    }

    /** {@inheritDoc} */
    @Override
    public void tableItemsChanged() {
        final Table table = viewer.getTable();
        if (table.isDisposed()) {
            return;
        }
        table.getDisplay().asyncExec(() -> {
            if (!table.isDisposed() && !viewer.isCellEditorActive()) {
                viewer.refresh();
            }
        });
    }

    /** {@inheritDoc} */
    @Override
    public void modelChanged() {
        // Ignore
    }
}