org.csstudio.trends.databrowser2.propsheet.DataBrowserPropertySheetPage.java Source code

Java tutorial

Introduction

Here is the source code for org.csstudio.trends.databrowser2.propsheet.DataBrowserPropertySheetPage.java

Source

/*******************************************************************************
 * Copyright (c) 2010-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
 ******************************************************************************/
package org.csstudio.trends.databrowser2.propsheet;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;

import org.csstudio.apputil.time.RelativeTime;
import org.csstudio.swt.rtplot.SWTMediaPool;
import org.csstudio.swt.rtplot.undo.UndoableActionManager;
import org.csstudio.trends.databrowser2.Messages;
import org.csstudio.trends.databrowser2.model.ArchiveDataSource;
import org.csstudio.trends.databrowser2.model.ArchiveRescale;
import org.csstudio.trends.databrowser2.model.AxisConfig;
import org.csstudio.trends.databrowser2.model.FormulaItem;
import org.csstudio.trends.databrowser2.model.Model;
import org.csstudio.trends.databrowser2.model.ModelItem;
import org.csstudio.trends.databrowser2.model.ModelListener;
import org.csstudio.trends.databrowser2.model.ModelListenerAdapter;
import org.csstudio.trends.databrowser2.model.PVItem;
import org.csstudio.trends.databrowser2.preferences.Preferences;
import org.csstudio.trends.databrowser2.ui.AddPVAction;
import org.csstudio.trends.databrowser2.ui.StartEndTimeAction;
import org.csstudio.ui.util.MinSizeTableColumnLayout;
import org.csstudio.ui.util.dnd.ControlSystemDragSource;
import org.eclipse.jface.action.GroupMarker;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
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.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.ColorDialog;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.FontDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.TabItem;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.part.Page;
import org.eclipse.ui.views.properties.IPropertySheetPage;

/** Sheet for Eclipse Properties View that displays Data Browser Model,
 *  i.e. the Configuration Panel for the Data Browser.
 *  <p>
 *  <b>Note about life cycle, at least under Eclipse 3.5.1:</b>
 *  When a Properties view is open and a DataBrowserEditor is selected,
 *  a corresponding DataBrowserPropertySheetPage is created.
 *
 *  It stays around even after the Properties is closed or changed
 *  to another editor, presumably to allow RCP to quickly re-display
 *  the properties when the editor is again selected.
 *
 *  The DataBrowserPropertySheetPage is only disposed after the associated
 *  DataBrowserEditor closes.
 *
 *  Under some circumstances, the DataBrowserPropertySheetPage is even
 *  created for a closed-then-re-opened DataBrowserEditor while there is
 *  no visible Properties view.
 *
 *  In summary, the 'Model' for this DataBrowserPropertySheetPage never changes.
 *  Each DataBrowserEditor.getAdapter will create a new DataBrowserPropertySheetPage.
 *
 *  @author Kay Kasemir
 */
public class DataBrowserPropertySheetPage extends Page implements IPropertySheetPage {
    /** Model to display/edit in property sheet */
    final private Model model;

    /** Undo/redo operations manager */
    final private UndoableActionManager operations_manager;

    /** Top-level control for the property sheet */
    private Composite control;

    private TableViewer trace_table, archive_table;

    private Composite archive_panel, formula_panel;

    private Text formula_txt, start_time, end_time, title, update_period, scroll_step;

    private ColorBlob background;

    private Button title_font, label_font, scale_font, save_changes, show_grid, rescales[];

    final private ModelListener model_listener = new ModelListenerAdapter() {
        /** {@inheritDoc} */
        @Override
        public void changedSaveChangesBehavior(boolean save) {
            save_changes.setSelection(save);
        }

        @Override
        public void changedTitle() {
            title.setText(model.getTitle().orElse("")); //$NON-NLS-1$
        }

        /** {@inheritDoc} */
        @Override
        public void changedTiming() {
            update_period.setText(Double.toString(model.getUpdatePeriod()));
            scroll_step.setText(Double.toString(model.getScrollStep().toMillis() / 1000.0));
        }

        /** {@inheritDoc} */
        @Override
        public void changedArchiveRescale() {
            final int selected = model.getArchiveRescale().ordinal();
            for (int i = 0; i < rescales.length; ++i) {
                boolean desired = i == selected;
                if (rescales[i].getSelection() != desired)
                    rescales[i].setSelection(desired);
            }
            if (model.getArchiveRescale() == ArchiveRescale.STAGGER) {
                for (AxisConfig axis_config : model.getAxes()) {
                    if (axis_config.isVisible())
                        axis_config.setAutoScale(false);
                }
            }
        }

        /** {@inheritDoc} */
        @Override
        public void changedColorsOrFonts() {
            background.setColor(model.getPlotBackground());
            title_font.setText(SWTMediaPool.getFontDescription(model.getTitleFont()));
            label_font.setText(SWTMediaPool.getFontDescription(model.getLabelFont()));
            scale_font.setText(SWTMediaPool.getFontDescription(model.getScaleFont()));
            legend_font.setText(SWTMediaPool.getFontDescription(model.getLegendFont()));
        }

        /** Update the start/end time in the Time axis panel when model changes
         *  {@inheritDoc}
         */
        @Override
        public void changedTimerange() {
            start_time.setText(model.getStartSpecification());
            end_time.setText(model.getEndSpecification());
        }

        /** {@inheritDoc} */
        @Override
        public void changeTimeAxisConfig() {
            show_grid.setSelection(model.isGridVisible());
        }

        /** {@inheritDoc} */
        @Override
        public void changedItemLook(final ModelItem item) {
            // Change of 'units' can arrive on non-UI threads
            control.getDisplay().asyncExec(DataBrowserPropertySheetPage.this::updateTracesTabDetailPanel);
        }

        /** {@inheritDoc} */
        @Override
        public void changedItemDataConfig(final PVItem item) {
            updateTracesTabDetailPanel();
        }

        /** {@inheritDoc} */
        @Override
        public void scrollEnabled(final boolean scroll_enabled) {
            changedTimerange();
        }
    };

    private Button legend_font;

    /** Initialize
     *  @param model Model to display/edit
     */
    public DataBrowserPropertySheetPage(final Model model, final UndoableActionManager operations_manager) {
        this.model = model;
        this.operations_manager = operations_manager;
    }

    /** {@inheritDoc} */
    @Override
    public void selectionChanged(IWorkbenchPart part, ISelection selection) {
        // NOP. Seems not to get called
    }

    /** {@inheritDoc} */
    @Override
    public Control getControl() {
        return control;
    }

    /** {@inheritDoc} */
    @Override
    public void createControl(final Composite parent) {
        control = new Composite(parent, 0);
        control.setLayout(new FillLayout());

        // Tabs: Traces, Time Axis, ...
        final TabFolder tab_folder = new TabFolder(control, SWT.TOP);
        createTracesTab(tab_folder);
        createTimeAxisTab(tab_folder);
        createValueAxesTab(tab_folder);
        createMiscTab(tab_folder);

        model.addListener(model_listener);
    }

    /** {@inheritDoc} */
    @Override
    public void dispose() {
        model.removeListener(model_listener);
        super.dispose();
    }

    /** Create tab for traces (PVs, Formulas)
     *  @param tabs
     */
    private void createTracesTab(final TabFolder tabs) {
        // TabItem with SashForm
        final TabItem tab = new TabItem(tabs, 0);
        tab.setText(Messages.TracesTab);

        final SashForm sashform = new SashForm(tabs, SWT.VERTICAL | SWT.BORDER);
        sashform.setLayout(new FillLayout());
        tab.setControl(sashform);

        createTracesTabItemPanel(sashform);
        createTracesMenuAndToolbarActions();

        createTracesTabDetailPanel(sashform);
        createArchiveMenu();

        trace_table.addSelectionChangedListener(new ISelectionChangedListener() {
            @Override
            public void selectionChanged(SelectionChangedEvent event) {
                updateTracesTabDetailPanel();
            }
        });

        sashform.setWeights(new int[] { 60, 40 });
    }

    /** Within SashForm of the "Traces" tab, create the Model Item table
     *  @param sashform
     */
    private void createTracesTabItemPanel(final SashForm sashform) {
        // TableColumnLayout requires the TableViewer to be in its own Composite!
        final Composite model_item_top = new Composite(sashform, SWT.BORDER);
        final TableColumnLayout table_layout = new MinSizeTableColumnLayout(10);
        model_item_top.setLayout(table_layout);
        // Would like to _not_ use FULL_SELECTION so that only the currently selected
        // cell gets highlighted, allowing the 'color' to still be visible
        // -> On Linux, you only get FULL_SELECTION behavior.
        // -> On Windows, editing is really odd, need to select a column before anything
        //    can be edited, plus color still invisible
        // ---> Using FULL_SELECTION
        trace_table = new TableViewer(model_item_top, SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.FULL_SELECTION);
        final Table table = trace_table.getTable();
        table.setHeaderVisible(true);
        table.setLinesVisible(true);

        final TraceTableHandler tth = new TraceTableHandler();
        tth.createColumns(table_layout, operations_manager, trace_table);

        trace_table.setContentProvider(tth);
        trace_table.setInput(model);

        new ControlSystemDragSource(trace_table.getControl()) {
            @Override
            public Object getSelection() {
                final IStructuredSelection selection = (IStructuredSelection) trace_table.getSelection();
                final Object[] objs = selection.toArray();
                final ModelItem[] items = Arrays.copyOf(objs, objs.length, ModelItem[].class);
                return items;
            }
        };
    }

    /** Within SashForm of the "Traces" tab, create the Item item detail panel:
     *  PV archive data sources, Formula
     *  @param sashform
     *  @param trace_table TableViewer for the trace table
     */
    private void createTracesTabDetailPanel(final SashForm sashform) {
        final Composite item_detail_top = new Composite(sashform, SWT.BORDER);
        item_detail_top.setLayout(new FormLayout());

        archive_panel = new Composite(item_detail_top, 0);
        FormData fd = new FormData();
        fd.left = new FormAttachment(0);
        fd.top = new FormAttachment(0);
        fd.right = new FormAttachment(100);
        fd.bottom = new FormAttachment(100);
        archive_panel.setLayoutData(fd);
        archive_panel.setLayout(new GridLayout());
        Label l = new Label(archive_panel, 0);
        l.setText(Messages.ArchiveDataSources);
        l.setLayoutData(new GridData());

        // TableColumnLayout requires the TableViewer to be in its own Composite!
        final Composite table_parent = new Composite(archive_panel, 0);
        table_parent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        final TableColumnLayout table_layout = new MinSizeTableColumnLayout(10);
        table_parent.setLayout(table_layout);
        archive_table = new TableViewer(table_parent,
                SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.FULL_SELECTION | SWT.VIRTUAL);
        final Table table = archive_table.getTable();
        table.setHeaderVisible(true);
        table.setLinesVisible(true);

        final ArchivesTableHandler ath = new ArchivesTableHandler();
        ath.createColumns(table_layout, operations_manager, archive_table);
        archive_table.setContentProvider(ath);

        formula_panel = new Composite(item_detail_top, 0);
        fd = new FormData();
        fd.left = new FormAttachment(0);
        fd.top = new FormAttachment(0);
        fd.right = new FormAttachment(100);
        fd.bottom = new FormAttachment(100);
        formula_panel.setLayoutData(fd);
        formula_panel.setLayout(new GridLayout(2, false));

        l = new Label(formula_panel, 0);
        l.setText(Messages.FormulaLabel);
        l.setLayoutData(new GridData());

        formula_txt = new Text(formula_panel, SWT.READ_ONLY | SWT.BORDER);
        formula_txt.setLayoutData(new GridData(SWT.FILL, 0, true, false));
        formula_txt.setToolTipText(Messages.FormulaLabelEditTT);
        formula_txt.addMouseListener(new MouseListener() {
            @Override
            public void mouseDown(MouseEvent e) {
                final Object item = ((IStructuredSelection) trace_table.getSelection()).getFirstElement();
                if (!(item instanceof FormulaItem))
                    return;
                final FormulaItem formula = (FormulaItem) item;
                final EditFormulaDialog dlg = new EditFormulaDialog(operations_manager, formula_txt.getShell(),
                        formula);
                dlg.open();
            }

            @Override
            public void mouseUp(MouseEvent e) {
                /* NOP */ }

            @Override
            public void mouseDoubleClick(MouseEvent e) {
                /* NOP */ }
        });

        archive_panel.setVisible(false);
        formula_panel.setVisible(false);
    }

    /** Update the lower 'detail' section of the "Traces" tab
     *  based on the currently selected item
     */
    private void updateTracesTabDetailPanel() {
        // Show PV or Formula Panel depending on selection
        final IStructuredSelection sel = (IStructuredSelection) trace_table.getSelection();
        if (sel.size() == 1) {
            final Object item = sel.getFirstElement();
            if (item instanceof PVItem) {
                formula_panel.setVisible(false);
                archive_panel.setVisible(true);
                archive_table.setInput(item);
                return;
            }
            if (item instanceof FormulaItem) {
                final FormulaItem formula = (FormulaItem) item;
                formula_txt.setText(formula.getExpression());
                archive_table.setInput(null);
                archive_panel.setVisible(false);
                formula_panel.setVisible(true);
                return;
            }
        }
        // else: Neither PV nor formula, or multiple items selected
        archive_table.setInput(null);
        archive_panel.setVisible(false);
        formula_panel.setVisible(false);
    }

    /** Create context menu and toolbar actions for the traces table */
    private void createTracesMenuAndToolbarActions() {
        final MenuManager menu = new MenuManager();
        menu.setRemoveAllWhenShown(true);
        final Shell shell = trace_table.getControl().getShell();
        final AddPVAction add_pv = new AddPVAction(operations_manager, shell, model, false);
        final AddPVAction add_formula = new AddPVAction(operations_manager, shell, model, true);
        final EditItemsAction edit_pv = new EditItemsAction(operations_manager, shell, trace_table, model);
        final DeleteItemsAction delete_pv = new DeleteItemsAction(operations_manager, trace_table, model);
        menu.addMenuListener(new IMenuListener() {
            @Override
            public void menuAboutToShow(IMenuManager manager) {
                final PVItem pvs[] = getSelectedPVs();
                menu.add(add_pv);
                menu.add(add_formula);
                menu.add(edit_pv);
                if (pvs.length == 1)
                    menu.add(new MoveItemAction(operations_manager, model, pvs[0], true));
                menu.add(delete_pv);
                if (pvs.length == 1)
                    menu.add(new MoveItemAction(operations_manager, model, pvs[0], false));
                menu.add(new RemoveUnusedAxesAction(operations_manager, model));
                if (pvs.length > 0) {
                    menu.add(new AddArchiveAction(operations_manager, shell, pvs));
                    menu.add(new UseDefaultArchivesAction(operations_manager, pvs));
                }
                menu.add(new Separator());
                menu.add(new GroupMarker(IWorkbenchActionConstants.MB_ADDITIONS));
            }
        });

        final Table table = trace_table.getTable();
        table.setMenu(menu.createContextMenu(table));
        // Allow object contributions based on selected items
        getSite().registerContextMenu(menu.getId(), menu, trace_table);

        // Add to tool bar
        final IToolBarManager toolbar = getSite().getActionBars().getToolBarManager();
        toolbar.add(add_pv);
        toolbar.add(add_formula);
    }

    /** Create context menu for the archive table,
     *  which depends on the currently selected PVs
     */
    private void createArchiveMenu() {
        // Create dynamic context menu, content changes depending on selections
        final MenuManager menu = new MenuManager();
        menu.setRemoveAllWhenShown(true);
        menu.addMenuListener(new IMenuListener() {
            @Override
            public void menuAboutToShow(IMenuManager manager) {
                // Determine selected PV Items
                final PVItem pvs[] = getSelectedPVs();
                if (pvs.length <= 0)
                    return;

                menu.add(new AddArchiveAction(operations_manager, control.getShell(), pvs));
                menu.add(new UseDefaultArchivesAction(operations_manager, pvs));

                // Only allow removal of archives from single PV
                if (pvs.length == 1) {
                    // Determine selected archives
                    final IStructuredSelection arch_sel = (IStructuredSelection) archive_table.getSelection();
                    if (!arch_sel.isEmpty()) {
                        final Object[] objects = arch_sel.toArray();
                        final ArchiveDataSource archives[] = new ArchiveDataSource[objects.length];
                        for (int i = 0; i < archives.length; i++)
                            archives[i] = (ArchiveDataSource) objects[i];
                        menu.add(new DeleteArchiveAction(operations_manager, pvs[0], archives));
                    }
                }
                menu.add(new Separator());
                menu.add(new GroupMarker(IWorkbenchActionConstants.MB_ADDITIONS));
            }
        });
        final Table table = archive_table.getTable();
        table.setMenu(menu.createContextMenu(table));
    }

    /** @return Currently selected PVs in the table of traces (items). Never <code>null</code> */
    protected PVItem[] getSelectedPVs() {
        final IStructuredSelection selection = (IStructuredSelection) trace_table.getSelection();
        if (selection.isEmpty())
            return new PVItem[0];
        final Object obj[] = selection.toArray();
        final ArrayList<PVItem> pvs = new ArrayList<PVItem>();
        for (int i = 0; i < obj.length; ++i)
            if (obj[i] instanceof PVItem)
                pvs.add((PVItem) obj[i]);
        return pvs.toArray(new PVItem[pvs.size()]);
    }

    /** Create tab for traces (PVs, Formulas)
     *  @param tabs
     */
    private void createTimeAxisTab(final TabFolder tab_folder) {
        final TabItem time_tab = new TabItem(tab_folder, 0);
        time_tab.setText(Messages.TimeAxis);

        final Composite parent = new Composite(tab_folder, 0);
        parent.setLayout(new GridLayout(3, false));

        // Start Time: ______ [...]
        Label label = new Label(parent, 0);
        label.setText(Messages.StartTimeLbl);
        label.setLayoutData(new GridData());

        start_time = new Text(parent, SWT.BORDER);
        start_time.setToolTipText(Messages.StartTimeTT);
        start_time.setLayoutData(new GridData(SWT.FILL, 0, true, false));

        final Button start_end = new Button(parent, SWT.PUSH);
        start_end.setText(Messages.StartEndDialogBtn);
        start_end.setToolTipText(Messages.StartEndDialogTT);
        start_end.setLayoutData(new GridData());

        // End Time:   ______ [...]
        label = new Label(parent, 0);
        label.setText(Messages.EndTimeLbl);
        label.setLayoutData(new GridData());

        end_time = new Text(parent, SWT.BORDER);
        end_time.setToolTipText(Messages.EndTimeTT);
        end_time.setLayoutData(new GridData(SWT.FILL, 0, true, false));

        final Button start_end2 = new Button(parent, SWT.PUSH);
        start_end2.setText(Messages.StartEndDialogBtn);
        start_end2.setToolTipText(Messages.StartEndDialogTT);
        start_end2.setLayoutData(new GridData());

        time_tab.setControl(parent);

        // Initialize with model's current start/end time
        model_listener.changedTimerange();

        // Allow entry of start/end times in text boxes
        final SelectionAdapter times_entered = new SelectionAdapter() {
            @Override
            public void widgetDefaultSelected(final SelectionEvent e) {
                try {
                    StartEndTimeAction.run(model, operations_manager, start_time.getText(), end_time.getText());
                } catch (Exception ex) {
                    MessageDialog.openError(parent.getShell(), Messages.Error, Messages.InvalidStartEndTimeError);
                    // Restore unchanged model time range
                    model_listener.changedTimerange();
                }
            }
        };
        start_time.addSelectionListener(times_entered);
        end_time.addSelectionListener(times_entered);

        // Buttons start start/end dialog
        final SelectionListener start_end_action = new SelectionAdapter() {
            @Override
            public void widgetSelected(final SelectionEvent e) {
                StartEndTimeAction.run(parent.getShell(), model, operations_manager);
            }
        };
        start_end.addSelectionListener(start_end_action);
        start_end2.addSelectionListener(start_end_action);

        // Time span shortcut buttons, with filler to align under start/end time text fields
        label = new Label(parent, 0);
        label.setText(""); //$NON-NLS-1$
        label.setLayoutData(new GridData());

        final Composite shortcut_bar = new Composite(parent, 0);
        shortcut_bar.setLayoutData(new GridData(SWT.FILL, 0, true, false, 2, 1));
        final RowLayout row_layout = new RowLayout();
        row_layout.marginLeft = 0;
        row_layout.marginTop = 0;
        shortcut_bar.setLayout(row_layout);
        final String[][] shortcuts = Preferences.getTimespanShortcuts();
        for (String[] title_start : shortcuts) {
            final String start_spec = title_start[1];
            Button shortcut = new Button(shortcut_bar, SWT.PUSH);
            shortcut.setText(title_start[0]);
            shortcut.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(final SelectionEvent e) {
                    new ChangeTimerangeCommand(model, operations_manager, true, start_spec, RelativeTime.NOW);
                }
            });
        }

        label = new Label(parent, 0);
        label.setText(Messages.GridLbl);
        label.setLayoutData(new GridData());

        show_grid = new Button(parent, SWT.CHECK);
        show_grid.setToolTipText(Messages.GridTT);
        show_grid.setLayoutData(new GridData(0, 0, false, false, 2, 1));
        show_grid.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(final SelectionEvent e) {
                new ChangeTimeAxisConfigCommand(model, operations_manager, show_grid.getSelection());
            }
        });
        model_listener.changeTimeAxisConfig();
    }

    /** Create tab for traces (PVs, Formulas)
     *  @param tabs
     */
    private void createValueAxesTab(final TabFolder tab_folder) {
        final TabItem axes_tab = new TabItem(tab_folder, 0);
        axes_tab.setText(Messages.ValueAxes);

        final Composite parent = new Composite(tab_folder, 0);
        parent.setLayout(new GridLayout());

        // Rescale options
        final Composite rescale = new Composite(parent, 0);
        rescale.setLayout(new RowLayout());
        rescale.setLayoutData(new GridData(SWT.FILL, 0, true, false));
        final Label l = new Label(rescale, 0);
        l.setText(Messages.ArchiveRescale_Label);
        final ArchiveRescale rescale_items[] = ArchiveRescale.values();
        rescales = new Button[rescale_items.length];
        for (int i = 0; i < rescales.length; ++i) {
            final ArchiveRescale item = rescale_items[i];
            if (item.ordinal() != i)
                throw new Error("ArchiveRescale items out of order"); //$NON-NLS-1$

            rescales[i] = new Button(rescale, SWT.RADIO);
            rescales[i].setText(item.toString());
            if (model.getArchiveRescale() == item)
                rescales[i].setSelection(true);
            rescales[i].addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(final SelectionEvent e) {
                    // Called for both the newly selected radio button
                    // and the one that's not de-selected.
                    // Only react to the selected button
                    if (model.getArchiveRescale() == item)
                        return;
                    new ChangeArchiveRescaleCommand(model, operations_manager, item);
                }
            });
        }

        // TableColumnLayout requires the TableViewer to be in its own Composite!
        final Composite table_parent = new Composite(parent, 0);
        table_parent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        final TableColumnLayout table_layout = new MinSizeTableColumnLayout(10);
        table_parent.setLayout(table_layout);
        final AxesTableHandler ath = new AxesTableHandler(table_parent, table_layout, operations_manager);
        ath.getAxesTable().setInput(model);

        axes_tab.setControl(parent);
    }

    /** Create tab for misc. config items (update period, colors)
     *  @param tabs
     */
    private void createMiscTab(final TabFolder tab_folder) {
        final TabItem misc_tab = new TabItem(tab_folder, 0);
        misc_tab.setText(Messages.Miscellaneous);

        final Composite parent = new Composite(tab_folder, 0);
        parent.setLayout(new GridLayout(4, false));

        // Title:         ______   Title Font: [Sans|14|1]
        Label label = new Label(parent, 0);
        label.setText(Messages.TitleLbl);
        label.setLayoutData(new GridData());

        title = new Text(parent, SWT.BORDER);
        title.setToolTipText(Messages.TitleTT);
        title.setLayoutData(new GridData(SWT.FILL, 0, true, false));
        title.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetDefaultSelected(final SelectionEvent e) {
                new ChangeTitleCommand(model, operations_manager, title.getText());
            }
        });

        label = new Label(parent, 0);
        label.setText(Messages.TitleFontLbl);
        label.setLayoutData(new GridData());

        title_font = new Button(parent, SWT.PUSH);
        title_font.setToolTipText(Messages.TitleFontTT);
        title_font.setLayoutData(new GridData(SWT.FILL, 0, true, false));
        title_font.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(final SelectionEvent e) {
                final FontDialog dialog = new FontDialog(parent.getShell());
                dialog.setFontList(new FontData[] { model.getTitleFont() });
                final FontData selected = dialog.open();
                if (selected != null)
                    new ChangeTitleFontCommand(model, operations_manager, selected);
            }
        });

        // Redraw period: ______   Label Font: [Sans|10|2]
        label = new Label(parent, 0);
        label.setText(Messages.UpdatePeriodLbl);
        label.setLayoutData(new GridData());

        update_period = new Text(parent, SWT.BORDER);
        update_period.setToolTipText(Messages.UpdatePeriodTT);
        update_period.setLayoutData(new GridData(SWT.FILL, 0, true, false));
        update_period.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetDefaultSelected(final SelectionEvent e) {
                try {
                    final double period = Double.parseDouble(update_period.getText().trim());
                    new ChangeUpdatePeriodCommand(model, operations_manager, period);
                } catch (Exception ex) {
                    update_period.setText(Double.toString(model.getUpdatePeriod()));
                }
            }
        });

        label = new Label(parent, 0);
        label.setText(Messages.LabelFontLbl);
        label.setLayoutData(new GridData());

        label_font = new Button(parent, SWT.PUSH);
        label_font.setToolTipText(Messages.LabelFontTT);
        label_font.setLayoutData(new GridData(SWT.FILL, 0, true, false));
        label_font.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(final SelectionEvent e) {
                final FontDialog dialog = new FontDialog(parent.getShell());
                dialog.setFontList(new FontData[] { model.getLabelFont() });
                final FontData selected = dialog.open();
                if (selected != null)
                    new ChangeLabelFontCommand(model, operations_manager, selected);
            }
        });

        // Scroll Step [secs]: ______   Scale Font: [Sans|9|0]
        label = new Label(parent, 0);
        label.setText(Messages.ScrollStepLbl);
        label.setLayoutData(new GridData());

        scroll_step = new Text(parent, SWT.BORDER);
        scroll_step.setToolTipText(Messages.ScrollStepTT);
        scroll_step.setLayoutData(new GridData(SWT.FILL, 0, true, false));
        scroll_step.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetDefaultSelected(final SelectionEvent e) {
                try {
                    final Duration step = Duration
                            .ofMillis(Math.round(Double.parseDouble(scroll_step.getText().trim()) * 1000.0));
                    new ChangeScrollStepCommand(model, operations_manager, step);
                } catch (Exception ex) {
                    scroll_step.setText(Double.toString(model.getScrollStep().toMillis() / 1000.0));
                }
            }
        });

        label = new Label(parent, 0);
        label.setText(Messages.ScaleFontLbl);
        label.setLayoutData(new GridData());

        scale_font = new Button(parent, SWT.PUSH);
        scale_font.setToolTipText(Messages.AxesFontTT);
        scale_font.setLayoutData(new GridData(SWT.FILL, 0, true, false));
        scale_font.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(final SelectionEvent e) {
                final FontDialog dialog = new FontDialog(parent.getShell());
                dialog.setFontList(new FontData[] { model.getScaleFont() });
                final FontData selected = dialog.open();
                if (selected != null)
                    new ChangeScaleFontCommand(model, operations_manager, selected);
            }
        });

        // Empty label to fill 2 grib boxes to alight the legend font below the rest of the fonts
        label = new Label(parent, 0);
        label = new Label(parent, 0);

        label = new Label(parent, 0);
        label.setText(Messages.LegendFontLbl);
        label.setLayoutData(new GridData());

        legend_font = new Button(parent, SWT.PUSH);
        legend_font.setToolTipText(Messages.LegendFontTT);
        legend_font.setLayoutData(new GridData(SWT.FILL, 0, true, false));
        legend_font.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(final SelectionEvent e) {
                final FontDialog dialog = new FontDialog(parent.getShell());
                dialog.setFontList(new FontData[] { model.getLegendFont() });
                final FontData selected = dialog.open();
                if (selected != null)
                    new ChangeLegendFontCommand(model, operations_manager, selected);
            }
        });

        // Background Color: ______
        label = new Label(parent, 0);
        label.setText(Messages.BackgroundColorLbl);
        label.setLayoutData(new GridData());

        background = new ColorBlob(parent, model.getPlotBackground());
        background.setToolTipText(Messages.BackgroundColorTT);
        final GridData gd = new GridData();
        gd.minimumWidth = 80;
        gd.widthHint = 80;
        gd.heightHint = 15;
        gd.horizontalSpan = 3;
        background.setLayoutData(gd);
        background.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(final SelectionEvent e) {
                final ColorDialog dialog = new ColorDialog(parent.getShell());
                dialog.setRGB(model.getPlotBackground());
                final RGB value = dialog.open();
                if (value != null)
                    new ChangePlotBackgroundCommand(model, operations_manager, value);
            }
        });

        // Save Changes: [x]
        label = new Label(parent, 0);
        label.setText(Messages.SaveChangesLbl);
        label.setLayoutData(new GridData());

        save_changes = new Button(parent, SWT.CHECK);
        save_changes.setToolTipText(Messages.SaveChangesTT);
        save_changes.setLayoutData(new GridData(SWT.LEFT, 0, true, false, 3, 1));
        save_changes.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(final SelectionEvent e) {
                new ChangeSaveChangesCommand(model, operations_manager, save_changes.getSelection());
            }
        });

        misc_tab.setControl(parent);

        model_listener.changedTitle();
        model_listener.changedColorsOrFonts();
        model_listener.changedSaveChangesBehavior(model.shouldSaveChanges());
        model_listener.changedTiming();
    }

    /** {@inheritDoc} */
    @Override
    public void setFocus() {
        trace_table.getTable().setFocus();
    }
}