org.csstudio.alarm.beast.ui.alarmtable.AlarmTableView.java Source code

Java tutorial

Introduction

Here is the source code for org.csstudio.alarm.beast.ui.alarmtable.AlarmTableView.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.alarm.beast.ui.alarmtable;

import java.text.SimpleDateFormat;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.csstudio.alarm.beast.client.AlarmTreeItem;
import org.csstudio.alarm.beast.client.AlarmTreePV;
import org.csstudio.alarm.beast.client.AlarmTreeRoot;
import org.csstudio.alarm.beast.ui.actions.AcknowledgeAction;
import org.csstudio.alarm.beast.ui.actions.MaintenanceModeAction;
import org.csstudio.alarm.beast.ui.alarmtable.actions.ColumnConfigureAction;
import org.csstudio.alarm.beast.ui.alarmtable.actions.FilterAction;
import org.csstudio.alarm.beast.ui.alarmtable.actions.NewTableAction;
import org.csstudio.alarm.beast.ui.alarmtable.actions.ResetColumnsAction;
import org.csstudio.alarm.beast.ui.alarmtable.actions.SeparateCombineTablesAction;
import org.csstudio.alarm.beast.ui.alarmtable.actions.ShowFilterAction;
import org.csstudio.alarm.beast.ui.clientmodel.AlarmClientModel;
import org.csstudio.alarm.beast.ui.clientmodel.AlarmClientModelListener;
import org.csstudio.ui.util.dnd.ControlSystemDropTarget;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.part.ViewPart;

/**
 * Eclipse View for the alarm table.
 *
 * @author Kay Kasemir
 * @author Jaka Bobnar - Combined/split alarm tables, configurable columns
 */
public class AlarmTableView extends ViewPart {
    public static final int PROP_FILTER = 555444;
    public static final int PROP_FILTER_ITEM = 555445;

    private static AtomicInteger secondaryId = new AtomicInteger(1);

    /**
     * Return the next secondary id that has not been opened.
     *
     * @return part
     */
    public static String newSecondaryID(IViewPart part) {
        while (part.getSite().getPage().findViewReference(part.getSite().getId(),
                String.valueOf(secondaryId.get())) != null) {
            secondaryId.incrementAndGet();
        }

        return String.valueOf(secondaryId.get());
    }

    private AlarmClientModel model;
    private AlarmClientModel defaultModel;
    private AlarmClientModelListener modelListener = new AlarmClientModelListener() {
        @Override
        public void newAlarmConfiguration(AlarmClientModel model) {
            parent.getDisplay().asyncExec(() -> updateFilterItem());
        }

        @Override
        public void serverTimeout(AlarmClientModel model) {
        }

        @Override
        public void serverModeUpdate(AlarmClientModel model, boolean maintenance_mode) {
        }

        @Override
        public void newAlarmState(AlarmClientModel model, AlarmTreePV pv, boolean parent_changed) {
        }
    };

    private Composite parent;
    private GUI gui;
    private MaintenanceModeAction maintenanceModeAction;
    private IMemento memento;

    private FilterType filterType;
    /** Combined active and acknowledge alarms, group into separate tables? */
    private boolean combinedTables;
    /** Should severity icons blink or not */
    private boolean blinkingIcons;
    /** The time format string used for formatting the alarm time label */
    private String timeFormat;
    /** The name of the filter item */
    private String filterItemPath;
    /** The filter item, which should match the filterItemName and configurationName if the model is available */
    private AlarmTreeItem filterItem;

    private ColumnWrapper[] columns = ColumnWrapper.getNewWrappers();

    @Override
    public void init(IViewSite site, IMemento memento) throws PartInitException {
        this.memento = memento;
        this.blinkingIcons = Preferences.isBlinkUnacknowledged();
        restoreState(memento, site);
        super.init(site);
    }

    @Override
    public void dispose() {
        if (secondaryId.get() > 1) {
            secondaryId.decrementAndGet();
        }
        super.dispose();
    }

    @Override
    public void createPartControl(final Composite parent) {
        this.parent = parent;
        parent.setLayout(new FillLayout());
        try {
            defaultModel = AlarmClientModel.getInstance();
            defaultModel.addListener(modelListener);
            if (filterItemPath != null) {
                model = AlarmClientModel.getInstance(getConfigNameFromPath(filterItemPath));
                model.addListener(modelListener);
            }
        } catch (final Throwable ex) { // Instead of actual GUI, create error message
            final String error = ex.getCause() != null ? ex.getCause().getMessage() : ex.getMessage();
            final String message = NLS.bind(org.csstudio.alarm.beast.ui.Messages.ServerErrorFmt, error);
            // Add to log, also display in text widget
            Logger.getLogger(Activator.ID).log(Level.SEVERE, "Cannot load alarm model", ex); //$NON-NLS-1$
            parent.setLayout(new FillLayout());
            new Text(parent, SWT.READ_ONLY | SWT.BORDER | SWT.MULTI).setText(message);
            return;
        }

        // Arrange for model to be released
        parent.addDisposeListener(new DisposeListener() {
            @Override
            public void widgetDisposed(DisposeEvent e) {
                releaseModel(defaultModel);
                defaultModel = null;
                releaseModel(model);
                model = null;
                ;
            }
        });

        makeGUI();
        createToolbar();
        updateFilterItem();
    }

    private void restoreState(IMemento memento, IViewSite site) {
        if (memento == null) {
            this.combinedTables = Preferences.isCombinedAlarmTable();
            this.filterType = FilterType.TREE;
            this.columns = ColumnWrapper.fromSaveArray(Preferences.getColumns());
            this.timeFormat = Preferences.getTimeFormat();
            //if timeformat is null, the default format is used, which is AlarmTreeLeaf#getTimestampText
        } else {
            Boolean groupSet = memento.getBoolean(Preferences.ALARM_TABLE_COMBINED_TABLES);
            this.combinedTables = groupSet == null ? Preferences.isCombinedAlarmTable() : groupSet;

            String filterTypeSet = memento.getString(Preferences.ALARM_TABLE_FILTER_TYPE);
            this.filterType = filterTypeSet == null || filterTypeSet.isEmpty() ? FilterType.TREE
                    : FilterType.valueOf(filterTypeSet.toUpperCase());

            this.timeFormat = memento.getString(Preferences.ALARM_TABLE_TIME_FORMAT);
            if (this.timeFormat == null || this.timeFormat.isEmpty())
                this.timeFormat = Preferences.getTimeFormat();
            //if timeformat is null, the default format is used, which is AlarmTreeLeaf#getTimestampText

            this.columns = ColumnWrapper.restoreColumns(memento.getChild(Preferences.ALARM_TABLE_COLUMN_SETTING));

            String name = memento.getString(Preferences.ALARM_TABLE_FILTER_ITEM);
            this.filterItemPath = name == null || name.isEmpty() ? null : name;
        }
    }

    @Override
    public void saveState(IMemento memento) {
        super.saveState(memento);
        memento.putBoolean(Preferences.ALARM_TABLE_COMBINED_TABLES, combinedTables);
        memento.putString(Preferences.ALARM_TABLE_FILTER_TYPE, filterType.name());
        if (filterItem != null)
            memento.putString(Preferences.ALARM_TABLE_FILTER_ITEM, filterItem.getPathName());
        else if (filterItemPath != null)
            memento.putString(Preferences.ALARM_TABLE_FILTER_ITEM, filterItemPath);

        if (this.timeFormat != null)
            memento.putString(Preferences.ALARM_TABLE_TIME_FORMAT, timeFormat);

        IMemento columnsMemento = memento.createChild(Preferences.ALARM_TABLE_COLUMN_SETTING);
        ColumnWrapper.saveColumns(columnsMemento, getUpdatedColumns());
        if (gui != null) {
            ColumnInfo info = gui.getSortingColumn();
            if (info != null)
                memento.putString(Preferences.ALARM_TABLE_SORT_COLUMN, info.name());
            memento.putBoolean(Preferences.ALARM_TABLE_SORT_UP, gui.isSortingUp());
        }
    }

    private void createToolbar() {
        final IToolBarManager toolbar = getViewSite().getActionBars().getToolBarManager();
        toolbar.removeAll();
        if (defaultModel.isWriteAllowed()) {
            maintenanceModeAction = new MaintenanceModeAction(defaultModel);
            toolbar.add(maintenanceModeAction);
            toolbar.add(new Separator());
            AcknowledgeAction action = new AcknowledgeAction(true, gui.getActiveAlarmTable());
            action.clearSelectionOnAcknowledgement(gui.getActiveAlarmTable());
            toolbar.add(action);
            action = new AcknowledgeAction(false, gui.getAcknowledgedAlarmTable());
            action.clearSelectionOnAcknowledgement(gui.getAcknowledgedAlarmTable());
            toolbar.add(action);
            toolbar.add(new Separator());
        }

        for (FilterType f : FilterType.values())
            toolbar.add(new FilterAction(this, f, this.filterType == f));
        toolbar.add(new Separator());

        final IMenuManager menu = getViewSite().getActionBars().getMenuManager();
        menu.add(new NewTableAction(this));
        menu.add(new Separator());
        menu.add(new SeparateCombineTablesAction(this, true, combinedTables));
        menu.add(new SeparateCombineTablesAction(this, false, !combinedTables));
        menu.add(new Separator());
        menu.add(new ColumnConfigureAction(this));
        menu.add(new ResetColumnsAction(this));
        menu.add(new Separator());
        menu.add(new ShowFilterAction(this));
    }

    private void releaseModel(AlarmClientModel model) {
        if (model != null) {
            model.removeListener(modelListener);
            model.release();
        }
    }

    /**
     * Set the filter item by its path. The path is transformed to an actual item, which is then applied as the filter
     * item. If the item does not exist a null filter is applied.
     *
     * @see AlarmTableView#setFilterItem(AlarmTreeItem)
     *
     * @param path the path to filter on
     * @throws Exception if the model for the given path could not be created
     */
    public void setFilterItemPath(String path) throws Exception {
        this.filterItemPath = path;
        if (filterItemPath == null || filterItemPath.isEmpty()) {
            releaseModel(model);
            model = null;
            setFilterType(FilterType.TREE);
        } else {
            String configName = getConfigNameFromPath(filterItemPath);
            if (model == null || !model.getConfigurationName().equals(configName)) {
                releaseModel(model);
                this.model = AlarmClientModel.getInstance(configName);
                this.model.addListener(modelListener);
            }
            setFilterType(FilterType.ITEM);
        }
        updateFilterItem();
    }

    /**
     * @return the currently applied filter item
     */
    public String getFilterItemPath() {
        return filterItemPath;
    }

    /**
     * Updated the filter item according to the selected filter type and filter item path. The view title
     * is also updated.
     */
    private void updateFilterItem() {
        AlarmClientModel activeModel = null;
        String name;
        if (filterType == FilterType.TREE) {
            if (defaultModel == null)
                return;
            activeModel = defaultModel;
            name = activeModel.getConfigurationName();
            if (gui != null)
                gui.setFilterItem(null, activeModel);
        } else {
            activeModel = model;
            AlarmTreeRoot root = activeModel.getConfigTree().getRoot();
            this.filterItem = root.getItemByPath(filterItemPath);
            if (filterItem != null) {
                if (filterType == FilterType.ITEM)
                    name = filterItem.getName();
                else
                    name = activeModel.getConfigurationName();
            } else {
                //filter path is set, but the item is null, because it
                //either does not exist, or the model is not yet connected
                int idx = filterItemPath.lastIndexOf('/');
                name = idx < 0 ? filterItemPath : filterItemPath.substring(idx + 1);
                name = "\u00BF" + name + "?";
            }
            if (gui != null)
                gui.setFilterItem(filterType == FilterType.ITEM ? filterItem : null, activeModel);
        }
        if (maintenanceModeAction != null)
            maintenanceModeAction.setModel(activeModel);
        setPartName(NLS.bind(Messages.AlarmTablePartName, name));
        setTitleToolTip(NLS.bind(Messages.AlarmTableTitleTT, name));
        firePropertyChange(PROP_FILTER_ITEM);
    }

    /**
     * @return the columns as they are currently visible and ordered in the table
     */
    public ColumnWrapper[] getUpdatedColumns() {
        ColumnWrapper[] columns = ColumnWrapper.getCopy(this.columns);
        if (gui != null)
            gui.updateColumnOrder(columns);
        return columns;
    }

    /**
     * Set the columns for the table. The table will display the columns in the provided order and will show only those
     * columns that have the visible flag set to true
     *
     * @param columns the columns to set on the table
     */
    public void setColumns(ColumnWrapper[] columns) {
        this.columns = columns;
        redoGUI();
    }

    private boolean makeGUI() {
        if (parent.isDisposed())
            return false;
        String s = memento == null ? null : memento.getString(Preferences.ALARM_TABLE_SORT_COLUMN);
        ColumnInfo sorting = s == null ? ColumnInfo.PV : ColumnInfo.valueOf(s);
        boolean sortUp = false;
        if (memento != null) {
            Boolean b = memento.getBoolean(Preferences.ALARM_TABLE_SORT_UP);
            sortUp = b == null ? false : b;
        }
        if (gui != null) {
            sorting = gui.getSortingColumn();
            sortUp = gui.isSortingUp();
            gui.dispose();
        }
        gui = new GUI(parent, getSite(), defaultModel.isWriteAllowed(), !combinedTables, columns, sorting, sortUp,
                true);
        gui.setBlinking(blinkingIcons);
        gui.setTimeFormat(timeFormat);
        gui.setTableColumnsHeadersVisible(true);
        setUpDrop(gui.getActiveAlarmTable().getTable());
        setUpDrop(gui.getAcknowledgedAlarmTable().getTable());
        updateFilterItem();

        return true;
    }

    private void setUpDrop(Control control) {
        if (control == null || control.getData(DND.DROP_TARGET_KEY) != null)
            return;
        new ControlSystemDropTarget(control, AlarmTreeItem.class, AlarmTreeItem[].class, AlarmTreePV.class,
                AlarmTreePV[].class) {
            @Override
            public void handleDrop(Object item) {
                try {
                    if (item instanceof AlarmTreeItem[])
                        setFilterItemPath(((AlarmTreeItem[]) item)[0].getPathName());
                    else if (item instanceof AlarmTreeItem || item instanceof AlarmTreePV)
                        setFilterItemPath(((AlarmTreeItem) item).getPathName());
                    else if (item instanceof AlarmTreePV[])
                        setFilterItemPath(((AlarmTreePV[]) item)[0].getPathName());
                } catch (Exception e) {
                    throw new IllegalArgumentException(e);
                }
            }
        };
    }

    private void redoGUI() {
        if (gui != null) {
            parent.getDisplay().asyncExec(() -> {
                if (makeGUI())
                    parent.layout();
            });
        }
    }

    @Override
    public void setFocus() {
        // NOP
    }

    /**
     * Combine all alarms into a single table or group the alarms into two separate tables (by the acknowledge status).
     *
     * @param combinedTables true if the acknowledged and unacknowledged alarms should be displayed in a single table,
     *            or false if they should be displayed in separate tables
     */
    public void setCombinedTables(boolean separated) {
        this.combinedTables = separated;
        redoGUI();
    }

    /**
     * Set the filter type for the table. The table will display alarms according to this filter. If the filter
     * is {@link FilterType#TREE} all alarms from the root currently selected in the alarm tree; if the filter type
     * is {@link FilterType#ROOT} all alarms from the root that is currently applied to this table (through filter item)
     * will be displayed; if filter type is {@link FilterType#ITEM} only the alarms belonging to the filter item will
     * be displayed.
     *
     * @param filterType the filter type to set
     */
    public void setFilterType(FilterType filterType) {
        if (filterItemPath == null && (filterType == FilterType.ROOT || filterType == FilterType.ITEM)) {
            throw new IllegalStateException("Cannot apply filter type " + filterType //$NON-NLS-1$
                    + " if no filter item is defined."); //$NON-NLS-1$
        }
        this.filterType = filterType;
        updateFilterItem();
        firePropertyChange(PROP_FILTER);
    }

    /**
     * @return the filter type currently selected for this table
     */
    public FilterType getFilterType() {
        return this.filterType;
    }

    /**
     * Enables or disables blinking of icons of the unacknowledged alarms.
     *
     * @param blinking true if the icons should be blinking or false otherwise
     */
    public void setBlinkingIcons(boolean blinking) {
        this.blinkingIcons = blinking;
        if (gui != null)
            gui.setBlinking(blinking);
    }

    /**
     * @return the alarm client model used by this table
     */
    public AlarmClientModel getModel() {
        return filterType == FilterType.TREE ? defaultModel : model;
    }

    /**
     * Sets the time format used for formatting the value in the time column. Format should be in the form acceptable by
     * the {@link SimpleDateFormat}.
     *
     * @param format the format
     */
    public void setTimeFormat(String format) {
        if (format != null && format.isEmpty())
            format = null;
        this.timeFormat = format;
        if (gui != null)
            gui.setTimeFormat(format);
    }

    /**
     * @return the currently used time format or null if default
     */
    public String getTimeFormat() {
        return timeFormat;
    }

    /**
     * Parses the configuration name from the given path.
     *
     * @param path the path to parse
     * @return configuration name
     */
    public static String getConfigNameFromPath(String path) {
        String name = path;
        if (name.charAt(0) == '/')
            name = name.substring(1);
        int idx = name.indexOf('/');
        return idx > 0 ? name.substring(0, idx) : name;
    }
}