001/*****************************************************************************
002 * Copyright by The HDF Group.                                               *
003 * All rights reserved.                                                      *
004 *                                                                           *
005 * This file is part of the HDF Java Products distribution.                  *
006 * The full copyright notice, including terms governing use, modification,   *
007 * and redistribution, is contained in the COPYING file, which can be found  *
008 * at the root of the source code distribution tree,                         *
009 * or in https://www.hdfgroup.org/licenses.                                  *
010 * If you do not have access to either file, you may request a copy from     *
011 * help@hdfgroup.org.                                                        *
012 ****************************************************************************/
013
014package hdf.view.TableView;
015
016import java.awt.Toolkit;
017import java.awt.datatransfer.Clipboard;
018import java.awt.datatransfer.DataFlavor;
019import java.awt.datatransfer.StringSelection;
020import java.io.BufferedReader;
021import java.io.BufferedWriter;
022import java.io.DataOutputStream;
023import java.io.File;
024import java.io.FileNotFoundException;
025import java.io.FileOutputStream;
026import java.io.FileReader;
027import java.io.FileWriter;
028import java.io.IOException;
029import java.io.PrintWriter;
030import java.lang.reflect.Array;
031import java.lang.reflect.Constructor;
032import java.nio.ByteOrder;
033import java.text.DecimalFormat;
034import java.text.NumberFormat;
035import java.util.ArrayList;
036import java.util.BitSet;
037import java.util.HashMap;
038import java.util.Iterator;
039import java.util.LinkedHashSet;
040import java.util.List;
041import java.util.Set;
042import java.util.StringTokenizer;
043
044import org.slf4j.Logger;
045import org.slf4j.LoggerFactory;
046
047import org.eclipse.nebula.widgets.nattable.NatTable;
048import org.eclipse.nebula.widgets.nattable.command.StructuralRefreshCommand;
049import org.eclipse.nebula.widgets.nattable.command.VisualRefreshCommand;
050import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration;
051import org.eclipse.nebula.widgets.nattable.config.AbstractUiBindingConfiguration;
052import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;
053import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
054import org.eclipse.nebula.widgets.nattable.config.IEditableRule;
055import org.eclipse.nebula.widgets.nattable.coordinate.Range;
056import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
057import org.eclipse.nebula.widgets.nattable.data.validate.DataValidator;
058import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes;
059import org.eclipse.nebula.widgets.nattable.edit.action.KeyEditAction;
060import org.eclipse.nebula.widgets.nattable.edit.action.MouseEditAction;
061import org.eclipse.nebula.widgets.nattable.edit.config.DefaultEditConfiguration;
062import org.eclipse.nebula.widgets.nattable.edit.config.DialogErrorHandling;
063import org.eclipse.nebula.widgets.nattable.grid.command.ClientAreaResizeCommand;
064import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
065import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
066import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;
067import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;
068import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
069import org.eclipse.nebula.widgets.nattable.layer.ILayer;
070import org.eclipse.nebula.widgets.nattable.layer.IUniqueIndexLayer;
071import org.eclipse.nebula.widgets.nattable.layer.config.DefaultColumnHeaderLayerConfiguration;
072import org.eclipse.nebula.widgets.nattable.layer.config.DefaultColumnHeaderStyleConfiguration;
073import org.eclipse.nebula.widgets.nattable.layer.config.DefaultRowHeaderLayerConfiguration;
074import org.eclipse.nebula.widgets.nattable.layer.config.DefaultRowHeaderStyleConfiguration;
075import org.eclipse.nebula.widgets.nattable.painter.cell.TextPainter;
076import org.eclipse.nebula.widgets.nattable.painter.cell.decorator.BeveledBorderDecorator;
077import org.eclipse.nebula.widgets.nattable.painter.cell.decorator.LineBorderDecorator;
078import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
079import org.eclipse.nebula.widgets.nattable.selection.command.SelectAllCommand;
080import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes;
081import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
082import org.eclipse.nebula.widgets.nattable.style.HorizontalAlignmentEnum;
083import org.eclipse.nebula.widgets.nattable.style.Style;
084import org.eclipse.nebula.widgets.nattable.ui.action.IMouseAction;
085import org.eclipse.nebula.widgets.nattable.ui.binding.UiBindingRegistry;
086import org.eclipse.nebula.widgets.nattable.ui.matcher.CellEditorMouseEventMatcher;
087import org.eclipse.nebula.widgets.nattable.ui.matcher.LetterOrDigitKeyEventMatcher;
088import org.eclipse.nebula.widgets.nattable.ui.matcher.MouseEventMatcher;
089import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuAction;
090import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder;
091import org.eclipse.nebula.widgets.nattable.viewport.command.ShowRowInViewportCommand;
092import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;
093
094import org.eclipse.swt.SWT;
095import org.eclipse.swt.custom.SashForm;
096import org.eclipse.swt.custom.ScrolledComposite;
097import org.eclipse.swt.events.DisposeEvent;
098import org.eclipse.swt.events.DisposeListener;
099import org.eclipse.swt.events.MouseEvent;
100import org.eclipse.swt.events.SelectionAdapter;
101import org.eclipse.swt.events.SelectionEvent;
102import org.eclipse.swt.events.TraverseEvent;
103import org.eclipse.swt.events.TraverseListener;
104import org.eclipse.swt.graphics.Color;
105import org.eclipse.swt.graphics.Font;
106import org.eclipse.swt.graphics.Point;
107import org.eclipse.swt.graphics.Rectangle;
108import org.eclipse.swt.layout.FillLayout;
109import org.eclipse.swt.layout.GridData;
110import org.eclipse.swt.layout.GridLayout;
111import org.eclipse.swt.widgets.Button;
112import org.eclipse.swt.widgets.Combo;
113import org.eclipse.swt.widgets.Composite;
114import org.eclipse.swt.widgets.Dialog;
115import org.eclipse.swt.widgets.Display;
116import org.eclipse.swt.widgets.FileDialog;
117import org.eclipse.swt.widgets.Label;
118import org.eclipse.swt.widgets.Menu;
119import org.eclipse.swt.widgets.MenuItem;
120import org.eclipse.swt.widgets.Shell;
121import org.eclipse.swt.widgets.Text;
122import org.eclipse.swt.widgets.ToolBar;
123import org.eclipse.swt.widgets.ToolItem;
124
125import hdf.hdf5lib.HDF5Constants;
126
127import hdf.object.CompoundDS;
128import hdf.object.DataFormat;
129import hdf.object.Dataset;
130import hdf.object.Datatype;
131import hdf.object.FileFormat;
132import hdf.object.Group;
133import hdf.object.HObject;
134import hdf.object.ScalarDS;
135
136import hdf.object.h5.H5Datatype;
137import hdf.object.h5.H5ReferenceType;
138
139import hdf.view.Chart;
140import hdf.view.DefaultFileFilter;
141import hdf.view.HDFView;
142import hdf.view.Tools;
143import hdf.view.ViewProperties;
144import hdf.view.ViewProperties.BITMASK_OP;
145import hdf.view.DataView.DataViewManager;
146import hdf.view.TableView.DataDisplayConverterFactory.HDFDisplayConverter;
147import hdf.view.TableView.DataProviderFactory.HDFDataProvider;
148import hdf.view.TreeView.TreeView;
149import hdf.view.dialog.InputDialog;
150import hdf.view.dialog.MathConversionDialog;
151import hdf.view.dialog.NewDatasetDialog;
152
153/**
154 * DefaultBaseTableView serves as the base class for a DataView that displays
155 * HDF data in a tabular format. This class is used for internal bookkeeping and
156 * as a place to store higher-level data manipulation functions, whereas its
157 * subclasses are responsible for setting up the actual GUI components.
158 *
159 * @author jhenderson
160 * @version 1.0 4/13/2018
161 */
162public abstract class DefaultBaseTableView implements TableView
163{
164
165    private static final Logger   log = LoggerFactory.getLogger(DefaultBaseTableView.class);
166
167    private final Display                   display = Display.getDefault();
168    /** The reference to the display shell used */
169    protected final Shell                   shell;
170    /** The current font */
171    protected Font                          curFont;
172
173    /** The main HDFView */
174    protected final DataViewManager         viewer;
175
176    /** The reference to the NAT table used */
177    protected NatTable                      dataTable;
178
179    /** The data object to be displayed in the Table */
180    protected final DataFormat              dataObject;
181
182    /** The data value of the data object */
183    protected Object                        dataValue;
184
185    /** The value used for fill */
186    protected Object                        fillValue;
187
188    /** the valid types of tableviews */
189    protected enum ViewType {
190        /** The data view is of type spreadsheet */
191        TABLE,
192        /** The data view is of type image */
193        IMAGE
194    };
195
196    /** The type of view */
197    protected      ViewType                 viewType = ViewType.TABLE;
198
199    /** Changed to use normalized scientific notation (1 is less than coefficient is less than 10). */
200    protected final DecimalFormat           scientificFormat = new DecimalFormat("0.0###E0###");
201    /** custom format pattern */
202    protected DecimalFormat                 customFormat     = new DecimalFormat("###.#####");
203    /** the normal format to be used for numbers */
204    protected final NumberFormat            normalFormat     = null;
205    /** the format to be used for numbers */
206    protected NumberFormat                  numberFormat     = normalFormat;
207
208    /** Used for bitmask operations on data */
209    protected BitSet                        bitmask = null;
210    /** Used for the type of bitmask operation */
211    protected BITMASK_OP                    bitmaskOP = BITMASK_OP.EXTRACT;
212
213    /** Fields to keep track of which 'frame' of 3 dimensional data is being displayed */
214    private Text                            frameField;
215    private long                            curDataFrame = 0;
216    private long                            maxDataFrame = 1;
217
218    /** The index base used for display row and column numbers of data */
219    protected int                           indexBase = 0;
220
221    /** size of default data length */
222    protected int                           fixedDataLength = -1;
223
224    /** default binary order */
225    protected int                           binaryOrder;
226
227    /** status if file is read only */
228    protected boolean                       isReadOnly = false;
229
230    /** status if the enums are to display converted */
231    protected boolean                       isEnumConverted = false;
232
233    /** status if the display type is a char */
234    protected boolean                       isDisplayTypeChar;
235
236    /** status if the data is transposed */
237    protected boolean                       isDataTransposed;
238
239    /** reference status */
240    protected boolean                       isRegRef = false, isObjRef = false, isStdRef = false;
241    /** show data as status */
242    protected boolean                       showAsHex = false, showAsBin = false;
243
244    /** Keep references to the selection layers for ease of access */
245    protected SelectionLayer                selectionLayer;
246    /** Keep references to the data layers for ease of access */
247    protected DataLayer                     dataLayer;
248
249    /** reference to the data provider for the row */
250    protected IDataProvider                 rowHeaderDataProvider;
251    /** reference to the data provider for the column */
252    protected IDataProvider                 columnHeaderDataProvider;
253
254    /** reference to the data provider */
255    protected HDFDataProvider               dataProvider;
256    /** reference to the display converter */
257    protected HDFDisplayConverter           dataDisplayConverter;
258
259    /**
260     * Global variables for GUI components on the default to show data
261     */
262    /** Checkbox menu item for Fixed Data Length default*/
263    protected MenuItem                      checkFixedDataLength = null;
264    /** Checkbox menu item for Custom Notation default*/
265    protected MenuItem                      checkCustomNotation = null;
266    /** Checkbox menu item for Scientific Notation default */
267    protected MenuItem                      checkScientificNotation = null;
268    /** Checkbox menu item for hex default */
269    protected MenuItem                      checkHex = null;
270    /** Checkbox menu item for binary default */
271    protected MenuItem                      checkBin = null;
272    /** Checkbox menu item for enum default*/
273    protected MenuItem                      checkEnum = null;
274
275    /** Labeled Group to display the index base */
276    protected org.eclipse.swt.widgets.Group indexBaseGroup;
277
278    /** Text field to display the value of the currently selected table cell */
279    protected Text                          cellValueField;
280
281    /** Label to indicate the current cell location */
282    protected Label                         cellLabel;
283
284
285    /**
286     * Constructs a base TableView with no additional data properties.
287     *
288     * @param theView
289     *            the main HDFView.
290     */
291    public DefaultBaseTableView(DataViewManager theView) {
292        this(theView, null);
293    }
294
295    /**
296     * Constructs a base TableView with the specified data properties.
297     *
298     * @param theView
299     *            the main HDFView.
300     *
301     * @param dataPropertiesMap
302     *            the properties on how to show the data. The map is used to allow
303     *            applications to pass properties on how to display the data, such
304     *            as: transposing data, showing data as characters, applying a
305     *            bitmask, and etc. Predefined keys are listed at
306     *            ViewProperties.DATA_VIEW_KEY.
307     */
308    @SuppressWarnings("rawtypes")
309    public DefaultBaseTableView(DataViewManager theView, HashMap dataPropertiesMap) {
310        shell = new Shell(display, SWT.SHELL_TRIM);
311
312        shell.setData(this);
313
314        shell.setLayout(new GridLayout(1, true));
315
316        /*
317         * When the table is closed, make sure to prompt the user about saving their
318         * changes, then do any pending cleanup work.
319         */
320        shell.addDisposeListener(new DisposeListener() {
321            @Override
322            public void widgetDisposed(DisposeEvent e) {
323                if (dataProvider != null) {
324                    if (dataProvider.getIsValueChanged() && !isReadOnly) {
325                        if (Tools.showConfirm(shell, "Changes Detected", "\"" + ((HObject) dataObject).getName()
326                                + "\" has changed.\nDo you want to save the changes?"))
327                            updateValueInFile();
328                        else
329                            dataObject.clearData();
330                    }
331                }
332
333                dataValue = null;
334                dataTable = null;
335
336                if (curFont != null)
337                    curFont.dispose();
338
339                viewer.removeDataView(DefaultBaseTableView.this);
340            }
341        });
342
343        /* Grab the current font to be used for all GUI components */
344        try {
345            curFont = new Font(display, ViewProperties.getFontType(), ViewProperties.getFontSize(), SWT.NORMAL);
346        }
347        catch (Exception ex) {
348            curFont = null;
349        }
350
351        viewer = theView;
352
353        /* Retrieve any display properties passed in via the HashMap parameter */
354        HObject hObject = null;
355
356        if (ViewProperties.isIndexBase1())
357            indexBase = 1;
358
359        if (dataPropertiesMap != null) {
360            hObject = (HObject) dataPropertiesMap.get(ViewProperties.DATA_VIEW_KEY.OBJECT);
361
362            bitmask = (BitSet) dataPropertiesMap.get(ViewProperties.DATA_VIEW_KEY.BITMASK);
363            bitmaskOP = (BITMASK_OP) dataPropertiesMap.get(ViewProperties.DATA_VIEW_KEY.BITMASKOP);
364
365            Boolean b = (Boolean) dataPropertiesMap.get(ViewProperties.DATA_VIEW_KEY.CHAR);
366            if (b != null) isDisplayTypeChar = b.booleanValue();
367
368            b = (Boolean) dataPropertiesMap.get(ViewProperties.DATA_VIEW_KEY.TRANSPOSED);
369            if (b != null) isDataTransposed = b.booleanValue();
370
371            b = (Boolean) dataPropertiesMap.get(ViewProperties.DATA_VIEW_KEY.INDEXBASE1);
372            if (b != null) {
373                if (b.booleanValue())
374                    indexBase = 1;
375                else
376                    indexBase = 0;
377            }
378        }
379
380        if (hObject == null)
381            hObject = viewer.getTreeView().getCurrentObject();
382
383        /* Only edit objects which actually contain editable data */
384        if ((hObject == null) || !(hObject instanceof DataFormat)) {
385            log.debug("data object is null or not an instanceof DataFormat");
386            dataObject = null;
387            shell.dispose();
388            return;
389        }
390
391        dataObject = (DataFormat) hObject;
392        if (((HObject) dataObject).getFileFormat() == null) {
393            log.debug("DataFormat object cannot access FileFormat");
394            shell.dispose();
395            return;
396        }
397
398        isReadOnly = ((HObject) dataObject).getFileFormat().isReadOnly();
399
400        if (((HObject) dataObject).getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4))
401                && (dataObject instanceof CompoundDS)) {
402            /* Cannot edit HDF4 VData */
403            isReadOnly = true;
404        }
405
406        /* Disable edit feature for SZIP compression when encode is not enabled */
407        if (!isReadOnly) {
408            String compression = dataObject.getCompression();
409            if ((compression != null) && compression.startsWith("SZIP")) {
410                if (!compression.endsWith("ENCODE_ENABLED"))
411                    isReadOnly = true;
412            }
413        }
414
415        log.trace("dataObject({}) isReadOnly={}", dataObject, isReadOnly);
416
417        long[] dims = dataObject.getDims();
418        long tsize = 1;
419
420        if (dims == null) {
421            log.debug("data object has null dimensions");
422            viewer.showError("Error: Data object '" + ((HObject) dataObject).getName() + "' has null dimensions.");
423            shell.dispose();
424            Tools.showError(display.getActiveShell(), "Error", "Could not open data object '" + ((HObject) dataObject).getName()
425                    + "'. Data object has null dimensions.");
426            return;
427        }
428
429        for (int i = 0; i < dims.length; i++)
430            tsize *= dims[i];
431
432        log.trace("Data object Size={} Height={} Width={}", tsize, dataObject.getHeight(), dataObject.getWidth());
433
434        if (dataObject.getHeight() <= 0 || dataObject.getWidth() <= 0 || tsize <= 0) {
435            log.debug("data object has dimension of size 0");
436            viewer.showError("Error: Data object '" + ((HObject) dataObject).getName() + "' has dimension of size 0.");
437            shell.dispose();
438            Tools.showError(display.getActiveShell(), "Error", "Could not open data object '" + ((HObject) dataObject).getName()
439                    + "'. Data object has dimension of size 0.");
440            return;
441        }
442
443        /*
444         * Determine whether the data is to be displayed as characters and whether or
445         * not enum data is to be converted.
446         */
447        Datatype dtype = dataObject.getDatatype();
448
449        log.trace("Data object getDatatypeClass()={}", dtype.getDatatypeClass());
450        isDisplayTypeChar = (isDisplayTypeChar
451                && (dtype.getDatatypeSize() == 1 || (dtype.isArray() && dtype.getDatatypeBase().isChar())));
452
453        isEnumConverted = ViewProperties.isConvertEnum();
454
455        log.trace("Data object isDisplayTypeChar={} isEnumConverted={}", isDisplayTypeChar, isEnumConverted);
456
457        if (dtype.isRef()) {
458            if (((HObject) dataObject).getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5))) {
459                isStdRef = ((H5Datatype)dtype).isStdRef();
460                isRegRef = ((H5Datatype)dtype).isRegRef();
461                isObjRef = ((H5Datatype)dtype).isRefObj();
462            }
463        }
464
465
466        // Setup subset information
467        int space_type = dataObject.getSpaceType();
468        int rank = dataObject.getRank();
469        int[] selectedIndex = dataObject.getSelectedIndex();
470        long[] count = dataObject.getSelectedDims();
471        long[] stride = dataObject.getStride();
472        long[] start = dataObject.getStartDims();
473        int n = Math.min(3, rank);
474
475        if (rank > 2) {
476            curDataFrame = start[selectedIndex[2]] + indexBase;
477            maxDataFrame = (indexBase == 1) ? dims[selectedIndex[2]] : dims[selectedIndex[2]] - 1;
478        }
479
480        /* Create the toolbar area that contains useful shortcuts */
481        ToolBar toolBar = createToolbar(shell);
482        toolBar.setSize(shell.getSize().x, 30);
483        toolBar.setLocation(0, 0);
484
485        /*
486         * Create the group that contains the text fields for displaying the value and
487         * location of the current cell, as well as the index base.
488         */
489        indexBaseGroup = new org.eclipse.swt.widgets.Group(shell, SWT.SHADOW_ETCHED_OUT);
490        indexBaseGroup.setFont(curFont);
491        indexBaseGroup.setText(indexBase + "-based");
492        indexBaseGroup.setLayout(new GridLayout(1, true));
493        indexBaseGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
494
495        SashForm content = new SashForm(indexBaseGroup, SWT.VERTICAL);
496        content.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
497        content.setSashWidth(10);
498
499        SashForm cellValueComposite = new SashForm(content, SWT.HORIZONTAL);
500        cellValueComposite.setSashWidth(8);
501
502        cellLabel = new Label(cellValueComposite, SWT.RIGHT | SWT.BORDER);
503        cellLabel.setAlignment(SWT.CENTER);
504        cellLabel.setFont(curFont);
505
506        final ScrolledComposite cellValueFieldScroller = new ScrolledComposite(cellValueComposite, SWT.V_SCROLL | SWT.H_SCROLL);
507        cellValueFieldScroller.setLayout(new FillLayout());
508
509        cellValueField = new Text(cellValueFieldScroller, SWT.MULTI | SWT.BORDER | SWT.WRAP);
510        cellValueField.setEditable(false);
511        cellValueField.setBackground(new Color(display, 255, 255, 240));
512        cellValueField.setEnabled(false);
513        cellValueField.setFont(curFont);
514
515        cellValueFieldScroller.setContent(cellValueField);
516        cellValueFieldScroller.setExpandHorizontal(true);
517        cellValueFieldScroller.setExpandVertical(true);
518        cellValueFieldScroller.setMinSize(cellValueField.computeSize(SWT.DEFAULT, SWT.DEFAULT));
519
520        cellValueComposite.setWeights(new int[] { 1, 5 });
521
522        /* Make sure that the Dataset's data value is accessible for conditionally adding GUI components */
523        try {
524            loadData(dataObject);
525            if (isStdRef) {
526                if (dataObject.getRank() > 2)
527                    ((H5ReferenceType)dtype).setRefSize((int)dataObject.getWidth() * (int)dataObject.getWidth());
528                ((H5ReferenceType)dtype).setData(dataValue);
529            }
530        }
531        catch (Exception ex) {
532            log.debug("loadData(): data not loaded: ", ex);
533            viewer.showError("Error: unable to load table data");
534            shell.dispose();
535            Tools.showError(display.getActiveShell(), "Open", "An error occurred while loading data for the table:\n\n" + ex.getMessage());
536            return;
537        }
538
539        /* Create the Shell's MenuBar */
540        shell.setMenuBar(createMenuBar(shell));
541
542        /*
543         * Set the default selection on the "Show Hexadecimal/Show Binary", etc. MenuItems.
544         * This step must be done after the menu bar has actually been created.
545         */
546        if (dataObject.getDatatype().isBitField() || dataObject.getDatatype().isOpaque()) {
547            showAsHex = true;
548            checkHex.setSelection(true);
549            checkScientificNotation.setSelection(false);
550            checkCustomNotation.setSelection(false);
551            checkBin.setSelection(false);
552            showAsBin = false;
553            numberFormat = normalFormat;
554        }
555
556        /*
557         * Set the default selection on the "Show Enum", etc. MenuItems.
558         * This step must be done after the menu bar has actually been created.
559         */
560        if (dataObject.getDatatype().isEnum()) {
561            checkEnum.setSelection(isEnumConverted);
562            checkScientificNotation.setSelection(false);
563            checkCustomNotation.setSelection(false);
564            checkBin.setSelection(false);
565            checkHex.setSelection(false);
566            showAsBin = false;
567            showAsHex = false;
568            numberFormat = normalFormat;
569        }
570
571        /* Create the actual NatTable */
572        log.debug("table creation {}", ((HObject) dataObject).getName());
573        try {
574            dataTable = createTable(content, dataObject);
575            if (dataTable == null) {
576                log.debug("table creation for object {} failed", ((HObject) dataObject).getName());
577                viewer.showError("Creating table for object '" + ((HObject) dataObject).getName() + "' failed.");
578                shell.dispose();
579                Tools.showError(display.getActiveShell(), "Open", "Failed to create Table object");
580                return;
581            }
582        }
583        catch (UnsupportedOperationException ex) {
584            log.debug("Subclass does not implement createTable()");
585            shell.dispose();
586            return;
587        }
588
589        /*
590         * Set the default data display conversion settings.
591         */
592        updateDataConversionSettings();
593
594        dataTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
595
596        /*
597         * Set the Shell's title using the object path and name
598         */
599        StringBuilder sb = new StringBuilder(hObject.getName());
600
601        if (((HObject) dataObject).getFileFormat() != null) {
602            sb.append("  at  ")
603            .append(hObject.getPath())
604            .append("  [")
605            .append(((HObject) dataObject).getFileFormat().getName())
606            .append("  in  ")
607            .append(((HObject) dataObject).getFileFormat().getParent())
608            .append("]");
609        }
610
611        shell.setText(sb.toString());
612
613        /*
614         * Append subsetting information and show this as a status message in the
615         * HDFView main window
616         */
617        sb.append(" [ dims");
618        sb.append(selectedIndex[0]);
619        for (int i = 1; i < n; i++) {
620            sb.append("x");
621            sb.append(selectedIndex[i]);
622        }
623        sb.append(", start");
624        sb.append(start[selectedIndex[0]]);
625        for (int i = 1; i < n; i++) {
626            sb.append("x");
627            sb.append(start[selectedIndex[i]]);
628        }
629        sb.append(", count");
630        sb.append(count[selectedIndex[0]]);
631        for (int i = 1; i < n; i++) {
632            sb.append("x");
633            sb.append(count[selectedIndex[i]]);
634        }
635        sb.append(", stride");
636        sb.append(stride[selectedIndex[0]]);
637        for (int i = 1; i < n; i++) {
638            sb.append("x");
639            sb.append(stride[selectedIndex[i]]);
640        }
641        sb.append(" ] ");
642
643        if (log.isTraceEnabled())
644            log.trace("subset={}", sb);
645
646        viewer.showStatus(sb.toString());
647
648        indexBaseGroup.pack();
649
650        content.setWeights(new int[] { 1, 12 });
651
652        shell.pack();
653
654        int width = 700 + (ViewProperties.getFontSize() - 12) * 15;
655        int height = 500 + (ViewProperties.getFontSize() - 12) * 10;
656        shell.setSize(width, height);
657    }
658
659    /**
660     * Creates the toolbar for the Shell.
661     */
662    private ToolBar createToolbar(final Shell shell) {
663        ToolBar toolbar = new ToolBar(shell, SWT.HORIZONTAL | SWT.RIGHT | SWT.BORDER);
664        toolbar.setFont(curFont);
665        toolbar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
666
667        // Chart button
668        ToolItem item = new ToolItem(toolbar, SWT.PUSH);
669        item.setImage(ViewProperties.getChartIcon());
670        item.setToolTipText("Line Plot");
671        item.addSelectionListener(new SelectionAdapter() {
672            @Override
673            public void widgetSelected(SelectionEvent e) {
674                showLineplot();
675            }
676        });
677
678        if (dataObject.getRank() > 2) {
679            new ToolItem(toolbar, SWT.SEPARATOR).setWidth(20);
680
681            // First frame button
682            item = new ToolItem(toolbar, SWT.PUSH);
683            item.setImage(ViewProperties.getFirstIcon());
684            item.setToolTipText("First Frame");
685            item.addSelectionListener(new SelectionAdapter() {
686                @Override
687                public void widgetSelected(SelectionEvent e) {
688                    firstFrame();
689                }
690            });
691
692            // Previous frame button
693            item = new ToolItem(toolbar, SWT.PUSH);
694            item.setImage(ViewProperties.getPreviousIcon());
695            item.setToolTipText("Previous Frame");
696            item.addSelectionListener(new SelectionAdapter() {
697                @Override
698                public void widgetSelected(SelectionEvent e) {
699                    previousFrame();
700                }
701            });
702
703            ToolItem separator = new ToolItem(toolbar, SWT.SEPARATOR);
704
705            frameField = new Text(toolbar, SWT.SINGLE | SWT.BORDER | SWT.CENTER);
706            frameField.setFont(curFont);
707            frameField.setText(String.valueOf(curDataFrame));
708            frameField.addTraverseListener(new TraverseListener() {
709                @Override
710                public void keyTraversed(TraverseEvent e) {
711                    if (e.detail == SWT.TRAVERSE_RETURN) {
712                        try {
713                            int frame = 0;
714
715                            try {
716                                frame = Integer.parseInt(frameField.getText().trim()) - indexBase;
717                            }
718                            catch (Exception ex) {
719                                frame = -1;
720                            }
721
722                            gotoFrame(frame);
723                        }
724                        catch (Exception ex) {
725                            log.debug("Frame change failure: ", ex);
726                        }
727                    }
728                }
729            });
730
731            frameField.pack();
732
733            separator.setWidth(frameField.getSize().x + 30);
734            separator.setControl(frameField);
735
736            separator = new ToolItem(toolbar, SWT.SEPARATOR);
737
738            Text maxFrameText = new Text(toolbar, SWT.SINGLE | SWT.BORDER | SWT.CENTER);
739            maxFrameText.setFont(curFont);
740            maxFrameText.setText(String.valueOf(maxDataFrame));
741            maxFrameText.setEditable(false);
742            maxFrameText.setEnabled(false);
743
744            maxFrameText.pack();
745
746            separator.setWidth(maxFrameText.getSize().x + 30);
747            separator.setControl(maxFrameText);
748
749            new ToolItem(toolbar, SWT.SEPARATOR).setWidth(10);
750
751            // Next frame button
752            item = new ToolItem(toolbar, SWT.PUSH);
753            item.setImage(ViewProperties.getNextIcon());
754            item.setToolTipText("Next Frame");
755            item.addSelectionListener(new SelectionAdapter() {
756                @Override
757                public void widgetSelected(SelectionEvent e) {
758                    nextFrame();
759                }
760            });
761
762            // Last frame button
763            item = new ToolItem(toolbar, SWT.PUSH);
764            item.setImage(ViewProperties.getLastIcon());
765            item.setToolTipText("Last Frame");
766            item.addSelectionListener(new SelectionAdapter() {
767                @Override
768                public void widgetSelected(SelectionEvent e) {
769                    lastFrame();
770                }
771            });
772        }
773
774        return toolbar;
775    }
776
777    /**
778     * Creates the menubar for the Shell.
779     *
780     * @param theShell
781     *    the reference to the display shell
782     *
783     * @return the newly created menu
784     */
785    protected Menu createMenuBar(final Shell theShell) {
786        Menu menuBar = new Menu(theShell, SWT.BAR);
787        boolean isEditable = !isReadOnly;
788
789        MenuItem tableMenuItem = new MenuItem(menuBar, SWT.CASCADE);
790        tableMenuItem.setText("&Table");
791
792        Menu tableMenu = new Menu(theShell, SWT.DROP_DOWN);
793        tableMenuItem.setMenu(tableMenu);
794
795        MenuItem item = new MenuItem(tableMenu, SWT.PUSH);
796        item.setText("Select All");
797        item.setAccelerator(SWT.CTRL | 'A');
798        item.addSelectionListener(new SelectionAdapter() {
799            @Override
800            public void widgetSelected(SelectionEvent e) {
801                try {
802                    dataTable.doCommand(new SelectAllCommand());
803                }
804                catch (Exception ex) {
805                    theShell.getDisplay().beep();
806                    Tools.showError(theShell, "Select", ex.getMessage());
807                }
808            }
809        });
810
811        item = new MenuItem(tableMenu, SWT.PUSH);
812        item.setText("Copy");
813        item.setAccelerator(SWT.CTRL | 'C');
814        item.addSelectionListener(new SelectionAdapter() {
815            @Override
816            public void widgetSelected(SelectionEvent e) {
817                copyData();
818            }
819        });
820
821        item = new MenuItem(tableMenu, SWT.PUSH);
822        item.setText("Paste");
823        item.setAccelerator(SWT.CTRL | 'V');
824        item.setEnabled(isEditable);
825        item.addSelectionListener(new SelectionAdapter() {
826            @Override
827            public void widgetSelected(SelectionEvent e) {
828                pasteData();
829            }
830        });
831
832        new MenuItem(tableMenu, SWT.SEPARATOR);
833
834        item = new MenuItem(tableMenu, SWT.PUSH);
835        item.setText("Copy to New Dataset");
836        item.setEnabled(isEditable && (dataObject instanceof ScalarDS));
837        item.addSelectionListener(new SelectionAdapter() {
838            @Override
839            public void widgetSelected(SelectionEvent e) {
840                if ((selectionLayer.getSelectedColumnPositions().length <= 0)
841                        || (selectionLayer.getSelectedRowCount() <= 0)) {
842                    Tools.showInformation(theShell, "Copy", "Select table cells to write.");
843                    return;
844                }
845
846                TreeView treeView = viewer.getTreeView();
847                Group pGroup = (Group) (treeView.findTreeItem((HObject) dataObject).getParentItem().getData());
848                HObject root = ((HObject) dataObject).getFileFormat().getRootObject();
849
850                if (root == null) return;
851
852                ArrayList<HObject> list = new ArrayList<>(((HObject) dataObject).getFileFormat().getNumberOfMembers() + 5);
853                Iterator<HObject> it = ((Group) root).depthFirstMemberList().iterator();
854
855                while (it.hasNext())
856                    list.add(it.next());
857                list.add(root);
858
859                NewDatasetDialog dialog = new NewDatasetDialog(theShell, pGroup, list, DefaultBaseTableView.this);
860                dialog.open();
861
862                HObject obj = dialog.getObject();
863                if (obj != null) {
864                    Group pgroup = dialog.getParentGroup();
865                    try {
866                        treeView.addObject(obj, pgroup);
867                    }
868                    catch (Exception ex) {
869                        log.debug("Write selection to dataset:", ex);
870                    }
871                }
872
873                list.clear();
874            }
875        });
876
877        item = new MenuItem(tableMenu, SWT.PUSH);
878        item.setText("Save Changes to File");
879        item.setAccelerator(SWT.CTRL | 'U');
880        item.setEnabled(isEditable);
881        item.addSelectionListener(new SelectionAdapter() {
882            @Override
883            public void widgetSelected(SelectionEvent e) {
884                try {
885                    updateValueInFile();
886                }
887                catch (Exception ex) {
888                    theShell.getDisplay().beep();
889                    Tools.showError(theShell, "Save", ex.getMessage());
890                }
891            }
892        });
893
894        new MenuItem(tableMenu, SWT.SEPARATOR);
895
896        item = new MenuItem(tableMenu, SWT.PUSH);
897        item.setText("Show Lineplot");
898        item.addSelectionListener(new SelectionAdapter() {
899            @Override
900            public void widgetSelected(SelectionEvent e) {
901                showLineplot();
902            }
903        });
904
905        item = new MenuItem(tableMenu, SWT.PUSH);
906        item.setText("Show Statistics");
907        item.addSelectionListener(new SelectionAdapter() {
908            @Override
909            public void widgetSelected(SelectionEvent e) {
910                try {
911                    Object theData = getSelectedData();
912
913                    if (dataObject instanceof CompoundDS) {
914                        int cols = selectionLayer.getFullySelectedColumnPositions().length;
915                        if (cols != 1) {
916                            Tools.showError(theShell, "Statistics", "Please select one column at a time for compound dataset.");
917                            return;
918                        }
919                    }
920                    else if (theData == null) {
921                        theData = dataValue;
922                    }
923
924                    double[] minmax = new double[2];
925                    double[] stat = new double[2];
926
927                    Tools.findMinMax(theData, minmax, fillValue);
928                    if (Tools.computeStatistics(theData, stat, fillValue) > 0) {
929                        String stats = "Min                      = " + minmax[0] + "\nMax                      = "
930                                + minmax[1] + "\nMean                     = " + stat[0] + "\nStandard deviation = "
931                                + stat[1];
932                        Tools.showInformation(theShell, "Statistics", stats);
933                    }
934
935                    System.gc();
936                }
937                catch (Exception ex) {
938                    theShell.getDisplay().beep();
939                    Tools.showError(shell, "Statistics", ex.getMessage());
940                }
941            }
942        });
943
944        new MenuItem(tableMenu, SWT.SEPARATOR);
945
946        item = new MenuItem(tableMenu, SWT.PUSH);
947        item.setText("Math Conversion");
948        item.setEnabled(isEditable);
949        item.addSelectionListener(new SelectionAdapter() {
950            @Override
951            public void widgetSelected(SelectionEvent e) {
952                try {
953                    mathConversion();
954                }
955                catch (Exception ex) {
956                    shell.getDisplay().beep();
957                    Tools.showError(theShell, "Convert", ex.getMessage());
958                }
959            }
960        });
961
962        new MenuItem(tableMenu, SWT.SEPARATOR);
963
964        item = new MenuItem(tableMenu, SWT.PUSH);
965        item.setText("Close");
966        item.addSelectionListener(new SelectionAdapter() {
967            @Override
968            public void widgetSelected(SelectionEvent e) {
969                theShell.dispose();
970            }
971        });
972
973        /********************************************************************
974         *                                                                  *
975         * Set up MenuItems for refreshing the TableView                    *
976         *                                                                  *
977         ********************************************************************/
978        item = new MenuItem(tableMenu, SWT.PUSH);
979        item.setText("Start Timer");
980        item.addSelectionListener(new SelectionAdapter() {
981            @Override
982            public void widgetSelected(SelectionEvent e) {
983                viewer.executeTimer(true);
984            }
985        });
986
987        item = new MenuItem(tableMenu, SWT.PUSH);
988        item.setText("Stop Timer");
989        item.addSelectionListener(new SelectionAdapter() {
990            @Override
991            public void widgetSelected(SelectionEvent e) {
992                viewer.executeTimer(false);
993            }
994        });
995
996
997        /********************************************************************
998         *                                                                  *
999         * Set up MenuItems for Importing/Exporting Data from the TableView *
1000         *                                                                  *
1001         ********************************************************************/
1002        MenuItem importExportMenuItem = new MenuItem(menuBar, SWT.CASCADE);
1003        importExportMenuItem.setText("&Import/Export Data");
1004
1005        Menu importExportMenu = new Menu(theShell, SWT.DROP_DOWN);
1006        importExportMenuItem.setMenu(importExportMenu);
1007
1008        item = new MenuItem(importExportMenu, SWT.CASCADE);
1009        item.setText("Export Data to");
1010
1011        Menu exportMenu = new Menu(item);
1012        item.setMenu(exportMenu);
1013
1014        item = new MenuItem(exportMenu, SWT.PUSH);
1015        item.setText("Text File");
1016        item.addSelectionListener(new SelectionAdapter() {
1017            @Override
1018            public void widgetSelected(SelectionEvent e) {
1019                try {
1020                    saveAsText();
1021                }
1022                catch (Exception ex) {
1023                    theShell.getDisplay().beep();
1024                    Tools.showError(theShell, "Save", ex.getMessage());
1025                }
1026            }
1027        });
1028
1029        item = new MenuItem(importExportMenu, SWT.CASCADE);
1030        item.setText("Import Data from");
1031
1032        Menu importMenu = new Menu(item);
1033        item.setMenu(importMenu);
1034
1035        item = new MenuItem(importMenu, SWT.PUSH);
1036        item.setText("Text File");
1037        item.setEnabled(!isReadOnly);
1038        item.addSelectionListener(new SelectionAdapter() {
1039            @Override
1040            public void widgetSelected(SelectionEvent e) {
1041                String currentDir = ((HObject) dataObject).getFileFormat().getParent();
1042
1043                String filename = null;
1044                if (((HDFView) viewer).getTestState()) {
1045                    filename = currentDir + File.separator + new InputDialog(theShell, "Enter a file name", "").open();
1046                }
1047                else {
1048                    FileDialog fChooser = new FileDialog(theShell, SWT.OPEN);
1049                    fChooser.setFilterPath(currentDir);
1050
1051                    DefaultFileFilter filter = DefaultFileFilter.getFileFilterText();
1052                    fChooser.setFilterExtensions(new String[] { "*", filter.getExtensions() });
1053                    fChooser.setFilterNames(new String[] { "All Files", filter.getDescription() });
1054                    fChooser.setFilterIndex(1);
1055
1056                    filename = fChooser.open();
1057                }
1058
1059                if (filename == null)
1060                    return;
1061
1062                File chosenFile = new File(filename);
1063                if (!chosenFile.exists()) {
1064                    Tools.showError(theShell, "Import Data From Text File", "Data import error: " + filename + " does not exist.");
1065                    return;
1066                }
1067
1068                if (!Tools.showConfirm(theShell, "Import Data From Text File", "Do you want to paste selected data?"))
1069                    return;
1070
1071                importTextData(chosenFile.getAbsolutePath());
1072            }
1073        });
1074
1075        return menuBar;
1076    }
1077
1078    /**
1079     * Loads the data buffer of an object.
1080     *
1081     * @param dataObject
1082     *        the object that has the buffer for the data.
1083     *
1084     * @throws Exception if a failure occurred
1085     */
1086    protected void loadData(DataFormat dataObject) throws Exception {
1087        if (!dataObject.isInited()) {
1088            try {
1089                dataObject.init();
1090            }
1091            catch (Exception ex) {
1092                dataValue = null;
1093                log.debug("loadData(): ", ex);
1094                throw ex;
1095            }
1096        }
1097
1098        // use lazy convert for large number of strings
1099        if (dataObject.getHeight() > 10000 && dataObject instanceof CompoundDS) {
1100            ((CompoundDS) dataObject).setConvertByteToString(false);
1101        }
1102
1103        // Make sure entire dataset is not loaded when looking at 3D
1104        // datasets using the default display mode (double clicking the
1105        // data object)
1106        if (dataObject.getRank() > 2)
1107            dataObject.getSelectedDims()[dataObject.getSelectedIndex()[2]] = 1;
1108
1109        dataValue = null;
1110        try {
1111            log.trace("loadData(): call getData()");
1112            dataValue = dataObject.getData();
1113        }
1114        catch (Exception ex) {
1115            dataValue = null;
1116            log.debug("loadData(): ", ex);
1117            throw ex;
1118        }
1119    }
1120
1121    /**
1122     * Create a data table for a data object.
1123     *
1124     * @param parent
1125     *            the parent object this table will be associated with.
1126     * @param dataObject
1127     *            the data object this table will be associated with.
1128     *
1129     * @return the newly created data table
1130     */
1131    protected abstract NatTable createTable(Composite parent, DataFormat dataObject);
1132
1133    /**
1134     * Show the object reference data.
1135     *
1136     * @param ref
1137     *            the identifer for the object reference.
1138     */
1139    protected abstract void showObjRefData(byte[] ref);
1140
1141    /**
1142     * Show the region reference data.
1143     *
1144     * @param reg
1145     *            the identifier for the region reference.
1146     */
1147    protected abstract void showRegRefData(byte[] reg);
1148
1149    /**
1150     * Show the standard reference data.
1151     *
1152     * @param reg
1153     *            the identifier for the standard reference.
1154     */
1155    protected abstract void showStdRefData(byte[] reg);
1156
1157    /**
1158     * Get the data editing rule for the object.
1159     *
1160     * @param dataObject
1161     *        the data object
1162     *
1163     * @return the rule
1164     */
1165    protected abstract IEditableRule getDataEditingRule(DataFormat dataObject);
1166
1167    /**
1168     * Update the display converters.
1169     */
1170    protected void updateDataConversionSettings() {
1171        if (dataDisplayConverter != null) {
1172            dataDisplayConverter.setShowAsHex(showAsHex);
1173            dataDisplayConverter.setShowAsBin(showAsBin);
1174            dataDisplayConverter.setNumberFormat(numberFormat);
1175            dataDisplayConverter.setConvertEnum(isEnumConverted);
1176        }
1177    }
1178
1179    /**
1180     * Update dataset's value in file. The changes will go to the file.
1181     */
1182    @Override
1183    public void updateValueInFile() {
1184
1185        if (isReadOnly || !dataProvider.getIsValueChanged() || showAsBin || showAsHex) {
1186            log.debug("updateValueInFile(): file not updated; read-only or unchanged data or displayed as hex or binary");
1187            return;
1188        }
1189
1190        try {
1191            dataObject.write();
1192        }
1193        catch (Exception ex) {
1194            shell.getDisplay().beep();
1195            Tools.showError(shell, "Update", ex.getMessage());
1196            log.debug("updateValueInFile(): ", ex);
1197            return;
1198        }
1199
1200        dataProvider.setIsValueChanged(false);
1201    }
1202
1203    @Override
1204    public HObject getDataObject() {
1205        return (HObject) dataObject;
1206    }
1207
1208    @Override
1209    public Object getTable() {
1210        return dataTable;
1211    }
1212
1213    @Override
1214    public int getSelectedRowCount() {
1215        return selectionLayer.getSelectedRowCount();
1216    }
1217
1218    @Override
1219    public int getSelectedColumnCount() {
1220        return selectionLayer.getSelectedColumnPositions().length;
1221    }
1222
1223    /** @return the selection layer */
1224    public SelectionLayer getSelectionLayer() {
1225        return selectionLayer;
1226    }
1227
1228    /** @return the data layer */
1229    public DataLayer getDataLayer() {
1230        return dataLayer;
1231    }
1232
1233    /** refresh the data table */
1234    @Override
1235    public void refreshDataTable() {
1236        log.trace("refreshDataTable()");
1237
1238        shell.setCursor(display.getSystemCursor(SWT.CURSOR_WAIT));
1239        dataValue = dataObject.refreshData();
1240        shell.setCursor(null);
1241
1242        long[] dims = dataObject.getDims();
1243        log.trace("refreshDataTable() dims:{}", dims);
1244        dataProvider.updateDataBuffer(dataValue);
1245        ((RowHeaderDataProvider)rowHeaderDataProvider).updateRows(dataObject);
1246        log.trace("refreshDataTable(): rows={} : cols={}", dataProvider.getRowCount(), dataProvider.getColumnCount());
1247
1248        dataTable.doCommand(new StructuralRefreshCommand());
1249        final ViewportLayer viewportLayer = new ViewportLayer(selectionLayer);
1250        dataTable.doCommand(new ShowRowInViewportCommand(dataProvider.getRowCount()-1));
1251        log.trace("refreshDataTable() finish");
1252    }
1253
1254    // Flip to previous 'frame' of Table data
1255    private void previousFrame() {
1256        // Only valid operation if data object has 3 or more dimensions
1257        if (dataObject.getRank() < 3)
1258            return;
1259
1260        long[] start = dataObject.getStartDims();
1261        int[] selectedIndex = dataObject.getSelectedIndex();
1262        long curFrame = start[selectedIndex[2]];
1263
1264        if (curFrame == 0)
1265            return; // Current frame is the first frame
1266
1267        gotoFrame(curFrame - 1);
1268    }
1269
1270    // Flip to next 'frame' of Table data
1271    private void nextFrame() {
1272        // Only valid operation if data object has 3 or more dimensions
1273        if (dataObject.getRank() < 3)
1274            return;
1275
1276        long[] start = dataObject.getStartDims();
1277        int[] selectedIndex = dataObject.getSelectedIndex();
1278        long[] dims = dataObject.getDims();
1279        long curFrame = start[selectedIndex[2]];
1280
1281        if (curFrame == dims[selectedIndex[2]] - 1)
1282            return; // Current frame is the last frame
1283
1284        gotoFrame(curFrame + 1);
1285    }
1286
1287    // Flip to the first 'frame' of Table data
1288    private void firstFrame() {
1289        // Only valid operation if data object has 3 or more dimensions
1290        if (dataObject.getRank() < 3)
1291            return;
1292
1293        long[] start = dataObject.getStartDims();
1294        int[] selectedIndex = dataObject.getSelectedIndex();
1295        long curFrame = start[selectedIndex[2]];
1296
1297        if (curFrame == 0)
1298            return; // Current frame is the first frame
1299
1300        gotoFrame(0);
1301    }
1302
1303    // Flip to the last 'frame' of Table data
1304    private void lastFrame() {
1305        // Only valid operation if data object has 3 or more dimensions
1306        if (dataObject.getRank() < 3)
1307            return;
1308
1309        long[] start = dataObject.getStartDims();
1310        int[] selectedIndex = dataObject.getSelectedIndex();
1311        long[] dims = dataObject.getDims();
1312        long curFrame = start[selectedIndex[2]];
1313
1314        if (curFrame == dims[selectedIndex[2]] - 1)
1315            return; // Current page is the last page
1316
1317        gotoFrame(dims[selectedIndex[2]] - 1);
1318    }
1319
1320    // Flip to the specified 'frame' of Table data
1321    private void gotoFrame(long idx) {
1322        // Only valid operation if data object has 3 or more dimensions
1323        if (dataObject.getRank() < 3 || idx == (curDataFrame - indexBase))
1324            return;
1325
1326        // Make sure to save any changes to this frame of data before changing frames
1327        if (dataProvider.getIsValueChanged())
1328            updateValueInFile();
1329
1330        long[] start = dataObject.getStartDims();
1331        int[] selectedIndex = dataObject.getSelectedIndex();
1332        long[] dims = dataObject.getDims();
1333
1334        // Do a bit of frame index validation
1335        if ((idx < 0) || (idx >= dims[selectedIndex[2]])) {
1336            shell.getDisplay().beep();
1337            Tools.showError(shell, "Select",
1338                    "Frame number must be between " + indexBase + " and " + (dims[selectedIndex[2]] - 1 + indexBase));
1339            return;
1340        }
1341
1342        start[selectedIndex[2]] = idx;
1343        curDataFrame = idx + indexBase;
1344        frameField.setText(String.valueOf(curDataFrame));
1345
1346        dataObject.clearData();
1347
1348        shell.setCursor(display.getSystemCursor(SWT.CURSOR_WAIT));
1349
1350        try {
1351            dataValue = dataObject.getData();
1352
1353            /*
1354             * TODO: Converting data from unsigned C integers to Java integers
1355             *       is currently unsupported for Compound Datasets.
1356             */
1357            if (!(dataObject instanceof CompoundDS))
1358                dataObject.convertFromUnsignedC();
1359
1360            dataValue = dataObject.getData();
1361        }
1362        catch (Exception ex) {
1363            shell.getDisplay().beep();
1364            Tools.showError(shell, "Error loading data", "Dataset getData: " + ex.getMessage());
1365            log.debug("gotoFrame(): ", ex);
1366            dataValue = null;
1367        }
1368        finally {
1369            shell.setCursor(null);
1370        }
1371
1372        dataProvider.updateDataBuffer(dataValue);
1373
1374        dataTable.doCommand(new VisualRefreshCommand());
1375    }
1376
1377    /**
1378     * Copy data from the spreadsheet to the system clipboard.
1379     */
1380    private void copyData() {
1381        StringBuilder sb = new StringBuilder();
1382
1383        Rectangle selection = selectionLayer.getLastSelectedRegion();
1384        if (selection == null) {
1385            Tools.showError(shell, "Copy", "Select data to copy.");
1386            return;
1387        }
1388
1389        int r0 = selectionLayer.getLastSelectedRegion().y; // starting row
1390        int c0 = selectionLayer.getLastSelectedRegion().x; // starting column
1391
1392        if ((r0 < 0) || (c0 < 0))
1393            return;
1394
1395        int nr = selectionLayer.getSelectedRowCount();
1396        int nc = selectionLayer.getSelectedColumnPositions().length;
1397        int r1 = r0 + nr; // finish row
1398        int c1 = c0 + nc; // finishing column
1399
1400        try {
1401            for (int i = r0; i < r1; i++) {
1402                sb.append(selectionLayer.getDataValueByPosition(c0, i).toString());
1403                for (int j = c0 + 1; j < c1; j++)
1404                    sb.append("\t").append(selectionLayer.getDataValueByPosition(j, i).toString());
1405                sb.append("\n");
1406            }
1407        }
1408        catch (java.lang.OutOfMemoryError err) {
1409            shell.getDisplay().beep();
1410            Tools.showError(shell, "Copy",
1411                    "Copying data to system clipboard failed. \nUse \"export/import data\" for copying/pasting large data.");
1412            return;
1413        }
1414
1415        Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
1416        StringSelection contents = new StringSelection(sb.toString());
1417        cb.setContents(contents, null);
1418    }
1419
1420    /**
1421     * Paste data from the system clipboard to the spreadsheet.
1422     */
1423    private void pasteData() {
1424        if (!Tools.showConfirm(shell, "Clipboard Data", "Do you want to paste selected data?"))
1425            return;
1426
1427        int cols = selectionLayer.getPreferredColumnCount();
1428        int rows = selectionLayer.getPreferredRowCount();
1429        int r0 = 0;
1430        int c0 = 0;
1431
1432        Rectangle selection = selectionLayer.getLastSelectedRegion();
1433        if (selection != null) {
1434            r0 = selection.y;
1435            c0 = selection.x;
1436        }
1437
1438        if (c0 < 0)
1439            c0 = 0;
1440        if (r0 < 0)
1441            r0 = 0;
1442        int r = r0;
1443        int c = c0;
1444
1445        Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
1446        String line = "";
1447        try {
1448            String s = (String) cb.getData(DataFlavor.stringFlavor);
1449
1450            StringTokenizer st = new StringTokenizer(s, "\n");
1451            // read line by line
1452            while (st.hasMoreTokens() && (r < rows)) {
1453                line = st.nextToken();
1454
1455                if (fixedDataLength < 1) {
1456                    // separate by delimiter
1457                    StringTokenizer lt = new StringTokenizer(line, "\t");
1458                    while (lt.hasMoreTokens() && (c < cols)) {
1459                        try {
1460                            dataProvider.setDataValue(c, r, lt.nextToken());
1461                        }
1462                        catch (Exception ex) {
1463                            continue;
1464                        }
1465                        c++;
1466                    }
1467                    r = r + 1;
1468                    c = c0;
1469                }
1470                else {
1471                    // the data has fixed length
1472                    int n = line.length();
1473                    String theVal;
1474                    for (int i = 0; i < n; i = i + fixedDataLength) {
1475                        try {
1476                            theVal = line.substring(i, i + fixedDataLength);
1477                            dataProvider.setDataValue(c, r, theVal);
1478                        }
1479                        catch (Exception ex) {
1480                            continue;
1481                        }
1482                        c++;
1483                    }
1484                }
1485            }
1486        }
1487        catch (Exception ex) {
1488            shell.getDisplay().beep();
1489            Tools.showError(shell, "Paste", ex.getMessage());
1490        }
1491    }
1492
1493    /**
1494     * Save data as text.
1495     *
1496     * @throws Exception
1497     *             if a failure occurred
1498     */
1499    protected void saveAsText() throws Exception {
1500        String currentDir = ((HObject) dataObject).getFileFormat().getParent();
1501
1502        String filename = null;
1503        if (((HDFView) viewer).getTestState()) {
1504            filename = currentDir + File.separator + new InputDialog(shell, "Enter a file name", "").open();
1505        }
1506        else {
1507            FileDialog fChooser = new FileDialog(shell, SWT.SAVE);
1508            fChooser.setFilterPath(currentDir);
1509
1510            DefaultFileFilter filter = DefaultFileFilter.getFileFilterText();
1511            fChooser.setFilterExtensions(new String[] { "*", filter.getExtensions() });
1512            fChooser.setFilterNames(new String[] { "All Files", filter.getDescription() });
1513            fChooser.setFilterIndex(1);
1514            fChooser.setText("Save Current Data To Text File --- " + ((HObject) dataObject).getName());
1515
1516            filename = fChooser.open();
1517        }
1518        if (filename == null)
1519            return;
1520
1521        File chosenFile = new File(filename);
1522        String fname = chosenFile.getAbsolutePath();
1523
1524        log.trace("saveAsText: file={}", fname);
1525
1526        // Check if the file is in use and prompt for overwrite
1527        if (chosenFile.exists()) {
1528            List<?> fileList = viewer.getTreeView().getCurrentFiles();
1529            if (fileList != null) {
1530                FileFormat theFile = null;
1531                Iterator<?> iterator = fileList.iterator();
1532                while (iterator.hasNext()) {
1533                    theFile = (FileFormat) iterator.next();
1534                    if (theFile.getFilePath().equals(fname)) {
1535                        shell.getDisplay().beep();
1536                        Tools.showError(shell, "Save",
1537                                "Unable to save data to file \"" + fname + "\". \nThe file is being used.");
1538                        return;
1539                    }
1540                }
1541            }
1542
1543            if (!Tools.showConfirm(shell, "Save", "File exists. Do you want to replace it?"))
1544                return;
1545        }
1546
1547        PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(chosenFile)));
1548
1549        String delName = ViewProperties.getDataDelimiter();
1550        String delimiter = "";
1551
1552        // delimiter must include a tab to be consistent with copy/paste for
1553        // compound fields
1554        if (dataObject instanceof CompoundDS)
1555            delimiter = "\t";
1556
1557        if (delName.equalsIgnoreCase(ViewProperties.DELIMITER_TAB))
1558            delimiter = "\t";
1559        else if (delName.equalsIgnoreCase(ViewProperties.DELIMITER_SPACE))
1560            delimiter = " " + delimiter;
1561        else if (delName.equalsIgnoreCase(ViewProperties.DELIMITER_COMMA))
1562            delimiter = "," + delimiter;
1563        else if (delName.equalsIgnoreCase(ViewProperties.DELIMITER_COLON))
1564            delimiter = ":" + delimiter;
1565        else if (delName.equalsIgnoreCase(ViewProperties.DELIMITER_SEMI_COLON))
1566            delimiter = ";" + delimiter;
1567
1568        int cols = selectionLayer.getPreferredColumnCount();
1569        int rows = selectionLayer.getPreferredRowCount();
1570
1571        for (int i = 0; i < rows; i++) {
1572            out.print(selectionLayer.getDataValueByPosition(0, i));
1573            for (int j = 1; j < cols; j++) {
1574                out.print(delimiter);
1575                out.print(selectionLayer.getDataValueByPosition(j, i));
1576            }
1577            out.println();
1578        }
1579
1580        out.flush();
1581        out.close();
1582
1583        viewer.showStatus("Data saved to: " + fname);
1584    }
1585
1586    /**
1587     * Save data as binary.
1588     *
1589     * @throws Exception
1590     *             if a failure occurred
1591     */
1592    protected void saveAsBinary() throws Exception {
1593        String currentDir = ((HObject) dataObject).getFileFormat().getParent();
1594
1595        String filename = null;
1596        if (((HDFView) viewer).getTestState()) {
1597            filename = currentDir + File.separator + new InputDialog(shell, "Enter a file name", "").open();
1598        }
1599        else {
1600            FileDialog fChooser = new FileDialog(shell, SWT.SAVE);
1601            fChooser.setFilterPath(currentDir);
1602
1603            DefaultFileFilter filter = DefaultFileFilter.getFileFilterBinary();
1604            fChooser.setFilterExtensions(new String[] { "*", filter.getExtensions() });
1605            fChooser.setFilterNames(new String[] { "All Files", filter.getDescription() });
1606            fChooser.setFilterIndex(1);
1607            fChooser.setText("Save Current Data To Binary File --- " + ((HObject) dataObject).getName());
1608
1609            filename = fChooser.open();
1610        }
1611        if (filename == null)
1612            return;
1613
1614        File chosenFile = new File(filename);
1615        String fname = chosenFile.getAbsolutePath();
1616
1617        log.trace("saveAsBinary: file={}", fname);
1618
1619        // Check if the file is in use and prompt for overwrite
1620        if (chosenFile.exists()) {
1621            List<?> fileList = viewer.getTreeView().getCurrentFiles();
1622            if (fileList != null) {
1623                FileFormat theFile = null;
1624                Iterator<?> iterator = fileList.iterator();
1625                while (iterator.hasNext()) {
1626                    theFile = (FileFormat) iterator.next();
1627                    if (theFile.getFilePath().equals(fname)) {
1628                        shell.getDisplay().beep();
1629                        Tools.showError(shell, "Save",
1630                                "Unable to save data to file \"" + fname + "\". \nThe file is being used.");
1631                        return;
1632                    }
1633                }
1634            }
1635
1636            if (!Tools.showConfirm(shell, "Save", "File exists. Do you want to replace it?"))
1637                return;
1638        }
1639
1640        try (DataOutputStream out = new DataOutputStream(new FileOutputStream(chosenFile))) {
1641            if (dataObject instanceof ScalarDS) {
1642                ((ScalarDS) dataObject).convertToUnsignedC();
1643                Object data = dataObject.getData();
1644                ByteOrder bo = ByteOrder.nativeOrder();
1645
1646                if (binaryOrder == 1)
1647                    bo = ByteOrder.nativeOrder();
1648                else if (binaryOrder == 2)
1649                    bo = ByteOrder.LITTLE_ENDIAN;
1650                else if (binaryOrder == 3)
1651                    bo = ByteOrder.BIG_ENDIAN;
1652
1653                Tools.saveAsBinary(out, data, bo);
1654
1655                viewer.showStatus("Data saved to: " + fname);
1656            }
1657            else
1658                viewer.showError("Data not saved - not a ScalarDS");
1659        }
1660    }
1661
1662    /**
1663     * Import data values from text file.
1664     *
1665     * @param fname
1666     *            the file to import text from
1667     */
1668    protected void importTextData(String fname) {
1669        int cols = selectionLayer.getPreferredColumnCount();
1670        int rows = selectionLayer.getPreferredRowCount();
1671        int r0;
1672        int c0;
1673
1674        Rectangle lastSelection = selectionLayer.getLastSelectedRegion();
1675        if (lastSelection != null) {
1676            r0 = lastSelection.y;
1677            c0 = lastSelection.x;
1678
1679            if (c0 < 0)
1680                c0 = 0;
1681            if (r0 < 0)
1682                r0 = 0;
1683        }
1684        else {
1685            r0 = 0;
1686            c0 = 0;
1687        }
1688
1689        // Start at the first column for compound datasets
1690        if (dataObject instanceof CompoundDS)
1691            c0 = 0;
1692
1693        String importLine = null;
1694        StringTokenizer tokenizer1 = null;
1695        try (BufferedReader in = new BufferedReader(new FileReader(fname))) {
1696            try {
1697                importLine = in.readLine();
1698            }
1699            catch (FileNotFoundException ex) {
1700                log.debug("import data values from text file {}:", fname, ex);
1701                return;
1702            }
1703            catch (IOException ex) {
1704                log.debug("read text file {}:", fname, ex);
1705                return;
1706            }
1707
1708            String delName = ViewProperties.getDataDelimiter();
1709            String delimiter = "";
1710
1711            if (delName.equalsIgnoreCase(ViewProperties.DELIMITER_TAB))
1712                delimiter = "\t";
1713            else if (delName.equalsIgnoreCase(ViewProperties.DELIMITER_SPACE))
1714                delimiter = " " + delimiter;
1715            else if (delName.equalsIgnoreCase(ViewProperties.DELIMITER_COMMA))
1716                delimiter = ",";
1717            else if (delName.equalsIgnoreCase(ViewProperties.DELIMITER_COLON))
1718                delimiter = ":";
1719            else if (delName.equalsIgnoreCase(ViewProperties.DELIMITER_SEMI_COLON))
1720                delimiter = ";";
1721            String token = null;
1722            int r = r0;
1723            int c = c0;
1724            while ((importLine != null) && (r < rows)) {
1725                if (fixedDataLength > 0) {
1726                    // the data has fixed length
1727                    int n = importLine.length();
1728                    String theVal;
1729                    for (int i = 0; i < n; i = i + fixedDataLength) {
1730                        try {
1731                            theVal = importLine.substring(i, i + fixedDataLength);
1732                            dataProvider.setDataValue(c, r, theVal);
1733                        }
1734                        catch (Exception ex) {
1735                            continue;
1736                        }
1737                        c++;
1738                    }
1739                }
1740                else {
1741                    try {
1742                        tokenizer1 = new StringTokenizer(importLine, delimiter);
1743                        while (tokenizer1.hasMoreTokens() && (c < cols)) {
1744                            token = tokenizer1.nextToken();
1745                            StringTokenizer tokenizer2 = new StringTokenizer(token);
1746                            if (tokenizer2.hasMoreTokens()) {
1747                                while (tokenizer2.hasMoreTokens() && (c < cols)) {
1748                                    dataProvider.setDataValue(c, r, tokenizer2.nextToken());
1749                                    c++;
1750                                }
1751                            }
1752                            else
1753                                c++;
1754                        }
1755                    }
1756                    catch (Exception ex) {
1757                        Tools.showError(shell, "Import", ex.getMessage());
1758                        return;
1759                    }
1760                }
1761
1762                try {
1763                    importLine = in.readLine();
1764                }
1765                catch (IOException ex) {
1766                    log.debug("read text file {}:", fname, ex);
1767                    importLine = null;
1768                }
1769
1770                // Start at the first column for compound datasets
1771                if (dataObject instanceof CompoundDS)
1772                    c = 0;
1773                else
1774                    c = c0;
1775
1776                r++;
1777            } // ((line != null) && (r < rows))
1778        }
1779        catch (IOException ex) {
1780            log.debug("import text file {}:", fname, ex);
1781        }
1782    }
1783
1784    /**
1785     * Import data values from binary file.
1786     */
1787    protected void importBinaryData() {
1788        String currentDir = ((HObject) dataObject).getFileFormat().getParent();
1789
1790        String filename = null;
1791        if (((HDFView) viewer).getTestState()) {
1792            filename = currentDir + File.separator + new InputDialog(shell, "Enter a file name", "").open();
1793        }
1794        else {
1795            FileDialog fChooser = new FileDialog(shell, SWT.OPEN);
1796            fChooser.setFilterPath(currentDir);
1797
1798            DefaultFileFilter filter = DefaultFileFilter.getFileFilterBinary();
1799            fChooser.setFilterExtensions(new String[] { "*", filter.getExtensions() });
1800            fChooser.setFilterNames(new String[] { "All Files", filter.getDescription() });
1801            fChooser.setFilterIndex(1);
1802
1803            filename = fChooser.open();
1804        }
1805
1806        if (filename == null)
1807            return;
1808
1809        File chosenFile = new File(filename);
1810        if (!chosenFile.exists()) {
1811            Tools.showError(shell, "Import Data from Binary File", "Data import error: " + chosenFile.getName() + " does not exist.");
1812            return;
1813        }
1814
1815        if (!Tools.showConfirm(shell, "Import Data from Binary File", "Do you want to paste selected data?"))
1816            return;
1817
1818        ByteOrder bo = ByteOrder.nativeOrder();
1819        if (binaryOrder == 1)
1820            bo = ByteOrder.nativeOrder();
1821        else if (binaryOrder == 2)
1822            bo = ByteOrder.LITTLE_ENDIAN;
1823        else if (binaryOrder == 3)
1824            bo = ByteOrder.BIG_ENDIAN;
1825
1826        try {
1827            if (Tools.getBinaryDataFromFile(dataValue, chosenFile.getAbsolutePath(), bo))
1828                dataProvider.setIsValueChanged(true);
1829
1830            dataTable.doCommand(new StructuralRefreshCommand());
1831        }
1832        catch (Exception ex) {
1833            log.debug("importBinaryData():", ex);
1834        }
1835        catch (OutOfMemoryError e) {
1836            log.debug("importBinaryData(): Out of memory");
1837        }
1838    }
1839
1840    /**
1841     * Convert selected data based on predefined math functions.
1842     */
1843    private void mathConversion() throws Exception {
1844        if (isReadOnly) {
1845            log.debug("mathConversion(): can't convert read-only data");
1846            return;
1847        }
1848
1849        int cols = selectionLayer.getSelectedColumnPositions().length;
1850        if ((dataObject instanceof CompoundDS) && (cols > 1)) {
1851            shell.getDisplay().beep();
1852            Tools.showError(shell, "Convert", "Please select one column at a time for math conversion" + "for compound dataset.");
1853            log.debug("mathConversion(): more than one column selected for CompoundDS");
1854            return;
1855        }
1856
1857        Object theData = getSelectedData();
1858        if (theData == null) {
1859            shell.getDisplay().beep();
1860            Tools.showError(shell, "Convert", "No data is selected.");
1861            log.debug("mathConversion(): no data selected");
1862            return;
1863        }
1864
1865        MathConversionDialog dialog = new MathConversionDialog(shell, theData);
1866        dialog.open();
1867
1868        if (dialog.isConverted()) {
1869            if (dataObject instanceof CompoundDS) {
1870                Object colData = null;
1871                try {
1872                    colData = ((List<?>) dataObject.getData()).get(selectionLayer.getSelectedColumnPositions()[0]);
1873                }
1874                catch (Exception ex) {
1875                    log.debug("mathConversion(): ", ex);
1876                }
1877
1878                if (colData != null) {
1879                    int size = Array.getLength(theData);
1880                    System.arraycopy(theData, 0, colData, 0, size);
1881                }
1882            }
1883            else {
1884                int rows = selectionLayer.getSelectedRowCount();
1885
1886                // Since NatTable returns the selected row positions as a Set<Range>, convert
1887                // this to
1888                // an Integer[]
1889                Set<Range> rowPositions = selectionLayer.getSelectedRowPositions();
1890                Set<Integer> selectedRowPos = new LinkedHashSet<>();
1891                Iterator<Range> i1 = rowPositions.iterator();
1892                while (i1.hasNext())
1893                    selectedRowPos.addAll(i1.next().getMembers());
1894
1895                int r0 = selectedRowPos.toArray(new Integer[0])[0];
1896                int c0 = selectionLayer.getSelectedColumnPositions()[0];
1897
1898                int w = dataTable.getPreferredColumnCount() - 1;
1899                int idxSrc = 0;
1900                int idxDst = 0;
1901
1902                for (int i = 0; i < rows; i++) {
1903                    idxDst = (r0 + i) * w + c0;
1904                    System.arraycopy(theData, idxSrc, dataValue, idxDst, cols);
1905                    idxSrc += cols;
1906                }
1907            }
1908
1909            System.gc();
1910
1911            dataProvider.setIsValueChanged(true);
1912        }
1913    }
1914
1915    private void showLineplot() {
1916        // Since NatTable returns the selected row positions as a Set<Range>, convert
1917        // this to
1918        // an Integer[]
1919        Set<Range> rowPositions = selectionLayer.getSelectedRowPositions();
1920        Set<Integer> selectedRowPos = new LinkedHashSet<>();
1921        Iterator<Range> i1 = rowPositions.iterator();
1922        while (i1.hasNext()) {
1923            selectedRowPos.addAll(i1.next().getMembers());
1924        }
1925
1926        Integer[] rows = selectedRowPos.toArray(new Integer[0]);
1927        int[] cols = selectionLayer.getSelectedColumnPositions();
1928
1929        if ((rows == null) || (cols == null) || (rows.length <= 0) || (cols.length <= 0)) {
1930            shell.getDisplay().beep();
1931            Tools.showError(shell, "Select", "Select rows/columns to draw line plot.");
1932            return;
1933        }
1934
1935        int nrow = dataTable.getPreferredRowCount() - 1;
1936        int ncol = dataTable.getPreferredColumnCount() - 1;
1937
1938        log.trace("DefaultTableView showLineplot: {} - {}", nrow, ncol);
1939        LinePlotOption lpo = new LinePlotOption(shell, SWT.NONE, nrow, ncol);
1940        lpo.open();
1941
1942        int plotType = lpo.getPlotBy();
1943        if (plotType == LinePlotOption.NO_PLOT)
1944            return;
1945
1946        boolean isRowPlot = (plotType == LinePlotOption.ROW_PLOT);
1947        int xIndex = lpo.getXindex();
1948
1949        // figure out to plot data by row or by column
1950        // Plot data by rows if all columns are selected and part of
1951        // rows are selected, otherwise plot data by column
1952        double[][] data = null;
1953        int nLines = 0;
1954        String title = "Lineplot - " + ((HObject) dataObject).getPath() + ((HObject) dataObject).getName();
1955        String[] lineLabels = null;
1956        double[] yRange = { Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY };
1957        double[] xData = null;
1958
1959        if (isRowPlot) {
1960            title += " - by row";
1961            nLines = rows.length;
1962            if (nLines > 10) {
1963                shell.getDisplay().beep();
1964                nLines = 10;
1965                Tools.showWarning(shell, "Select",
1966                        "More than 10 rows are selected.\n" + "The first 10 rows will be displayed.");
1967            }
1968            lineLabels = new String[nLines];
1969            data = new double[nLines][cols.length];
1970
1971            double value = 0.0;
1972            for (int i = 0; i < nLines; i++) {
1973                lineLabels[i] = String.valueOf(rows[i] + indexBase);
1974                for (int j = 0; j < cols.length; j++) {
1975                    data[i][j] = 0;
1976                    try {
1977                        value = Double.parseDouble(selectionLayer.getDataValueByPosition(cols[j], rows[i]).toString());
1978                        data[i][j] = value;
1979                        yRange[0] = Math.min(yRange[0], value);
1980                        yRange[1] = Math.max(yRange[1], value);
1981                    }
1982                    catch (NumberFormatException ex) {
1983                        log.debug("rows[{}]:", i, ex);
1984                    }
1985                }
1986            }
1987
1988            if (xIndex >= 0) {
1989                xData = new double[cols.length];
1990                for (int j = 0; j < cols.length; j++) {
1991                    xData[j] = 0;
1992                    try {
1993                        value = Double.parseDouble(selectionLayer.getDataValueByPosition(cols[j], xIndex).toString());
1994                        xData[j] = value;
1995                    }
1996                    catch (NumberFormatException ex) {
1997                        log.debug("xIndex of {}:", xIndex, ex);
1998                    }
1999                }
2000            }
2001        }
2002        else {
2003            title += " - by column";
2004            nLines = cols.length;
2005            if (nLines > 10) {
2006                shell.getDisplay().beep();
2007                nLines = 10;
2008                Tools.showWarning(shell, "Select",
2009                        "More than 10 columns are selected.\n" + "The first 10 columns will be displayed.");
2010            }
2011            lineLabels = new String[nLines];
2012            data = new double[nLines][rows.length];
2013            double value = 0.0;
2014            for (int j = 0; j < nLines; j++) {
2015                lineLabels[j] = columnHeaderDataProvider.getDataValue(cols[j] + indexBase, 0).toString();
2016                for (int i = 0; i < rows.length; i++) {
2017                    data[j][i] = 0;
2018                    try {
2019                        value = Double.parseDouble(selectionLayer.getDataValueByPosition(cols[j], rows[i]).toString());
2020                        data[j][i] = value;
2021                        yRange[0] = Math.min(yRange[0], value);
2022                        yRange[1] = Math.max(yRange[1], value);
2023                    }
2024                    catch (NumberFormatException ex) {
2025                        log.debug("cols[{}]:", j, ex);
2026                    }
2027                }
2028            }
2029
2030            if (xIndex >= 0) {
2031                xData = new double[rows.length];
2032                for (int j = 0; j < rows.length; j++) {
2033                    xData[j] = 0;
2034                    try {
2035                        value = Double.parseDouble(selectionLayer.getDataValueByPosition(xIndex, rows[j]).toString());
2036                        xData[j] = value;
2037                    }
2038                    catch (NumberFormatException ex) {
2039                        log.debug("xIndex of {}:", xIndex, ex);
2040                    }
2041                }
2042            }
2043        }
2044
2045        int n = removeInvalidPlotData(data, xData, yRange);
2046        if (n < data[0].length) {
2047            double[][] dataNew = new double[data.length][n];
2048            for (int i = 0; i < data.length; i++)
2049                System.arraycopy(data[i], 0, dataNew[i], 0, n);
2050
2051            data = dataNew;
2052
2053            if (xData != null) {
2054                double[] xDataNew = new double[n];
2055                System.arraycopy(xData, 0, xDataNew, 0, n);
2056                xData = xDataNew;
2057            }
2058        }
2059
2060        // allow to draw a flat line: all values are the same
2061        if (yRange[0] == yRange[1]) {
2062            yRange[1] += 1;
2063            yRange[0] -= 1;
2064        }
2065        else if (yRange[0] > yRange[1]) {
2066            shell.getDisplay().beep();
2067            Tools.showError(shell, "Select", "Cannot show line plot for the selected data. \n" + "Please check the data range: ("
2068                    + yRange[0] + ", " + yRange[1] + ").");
2069            return;
2070        }
2071        if (xData == null) { // use array index and length for x data range
2072            xData = new double[2];
2073            xData[0] = indexBase; // 1- or zero-based
2074            xData[1] = data[0].length + (double) indexBase - 1; // maximum index
2075        }
2076
2077        Chart cv = new Chart(shell, title, Chart.LINEPLOT, data, xData, yRange);
2078        cv.setLineLabels(lineLabels);
2079
2080        String cname = dataValue.getClass().getName();
2081        char dname = cname.charAt(cname.lastIndexOf('[') + 1);
2082        if ((dname == 'B') || (dname == 'S') || (dname == 'I') || (dname == 'J'))
2083            cv.setTypeToInteger();
2084
2085        cv.open();
2086    }
2087
2088    /**
2089     * Remove values of NaN, INF from the array.
2090     *
2091     * @param data
2092     *            the data array
2093     * @param xData
2094     *            the x-axis data points
2095     * @param yRange
2096     *            the range of data values
2097     *
2098     * @return number of data points in the plot data if successful; otherwise,
2099     *         returns false.
2100     */
2101    private int removeInvalidPlotData(double[][] data, double[] xData, double[] yRange) {
2102        int idx = 0;
2103        boolean hasInvalid = false;
2104
2105        if (data == null || yRange == null)
2106            return -1;
2107
2108        yRange[0] = Double.POSITIVE_INFINITY;
2109        yRange[1] = Double.NEGATIVE_INFINITY;
2110
2111        for (int i = 0; i < data[0].length; i++) {
2112            hasInvalid = false;
2113
2114            for (int j = 0; j < data.length; j++) {
2115                hasInvalid = Tools.isNaNINF(data[j][i]);
2116                if (xData != null)
2117                    hasInvalid = hasInvalid || Tools.isNaNINF(xData[i]);
2118
2119                if (hasInvalid)
2120                    break;
2121                else {
2122                    data[j][idx] = data[j][i];
2123                    if (xData != null)
2124                        xData[idx] = xData[i];
2125                    yRange[0] = Math.min(yRange[0], data[j][idx]);
2126                    yRange[1] = Math.max(yRange[1], data[j][idx]);
2127                }
2128            }
2129
2130            if (!hasInvalid)
2131                idx++;
2132        }
2133
2134        return idx;
2135    }
2136
2137    /**
2138     * An implementation of a GridLayer with support for column grouping and with
2139     * editing triggered by a double click instead of a single click.
2140     */
2141    protected class EditingGridLayer extends GridLayer
2142    {
2143        /** Create the Grid Layer with editing triggered by a
2144         *  double click instead of a single click.
2145         *
2146         * @param bodyLayer
2147         *        the body layer
2148         * @param columnHeaderLayer
2149         *        the Column Header layer
2150         * @param rowHeaderLayer
2151         *        the Row Header layer
2152         * @param cornerLayer
2153         *        the Corner Layer
2154         */
2155        public EditingGridLayer(ILayer bodyLayer, ILayer columnHeaderLayer, ILayer rowHeaderLayer, ILayer cornerLayer) {
2156            super(bodyLayer, columnHeaderLayer, rowHeaderLayer, cornerLayer, false);
2157
2158            // Left-align cells, change font for rendering cell text
2159            // and add cell data display converter for displaying as
2160            // Hexadecimal, Binary, etc.
2161            this.addConfiguration(new AbstractRegistryConfiguration() {
2162                @Override
2163                public void configureRegistry(IConfigRegistry configRegistry) {
2164                    Style cellStyle = new Style();
2165
2166                    cellStyle.setAttributeValue(CellStyleAttributes.HORIZONTAL_ALIGNMENT, HorizontalAlignmentEnum.LEFT);
2167                    cellStyle.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR,
2168                            Display.getCurrent().getSystemColor(SWT.COLOR_WHITE));
2169
2170                    if (curFont != null)
2171                        cellStyle.setAttributeValue(CellStyleAttributes.FONT, curFont);
2172                    else
2173                        cellStyle.setAttributeValue(CellStyleAttributes.FONT, Display.getDefault().getSystemFont());
2174
2175                    configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_STYLE, cellStyle,
2176                            DisplayMode.NORMAL, GridRegion.BODY);
2177
2178                    configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_STYLE, cellStyle,
2179                            DisplayMode.SELECT, GridRegion.BODY);
2180
2181                    // Add data display conversion capability
2182                    try {
2183                        dataDisplayConverter = DataDisplayConverterFactory.getDataDisplayConverter(dataObject);
2184
2185                        configRegistry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER,
2186                                dataDisplayConverter, DisplayMode.NORMAL, GridRegion.BODY);
2187                    }
2188                    catch (Exception ex) {
2189                        log.debug("EditingGridLayer: failed to retrieve a DataDisplayConverter: ", ex);
2190                        dataDisplayConverter = null;
2191                    }
2192                }
2193            });
2194
2195            if (isStdRef || isRegRef || isObjRef) {
2196                // Show data pointed to by reference on double click
2197                this.addConfiguration(new AbstractUiBindingConfiguration() {
2198                    @Override
2199                    public void configureUiBindings(UiBindingRegistry uiBindingRegistry) {
2200                        uiBindingRegistry.registerDoubleClickBinding(new MouseEventMatcher(), new IMouseAction() {
2201                            @Override
2202                            public void run(NatTable table, MouseEvent event) {
2203                                if (!(isStdRef || isRegRef || isObjRef))
2204                                    return;
2205
2206                                viewType = ViewType.TABLE;
2207
2208                                Object theData = null;
2209                                try {
2210                                    theData = ((Dataset) getDataObject()).getData();
2211                                }
2212                                catch (Exception ex) {
2213                                    log.debug("show reference data: ", ex);
2214                                    theData = null;
2215                                    Tools.showError(shell, "Select", ex.getMessage());
2216                                }
2217
2218                                if (theData == null) {
2219                                    shell.getDisplay().beep();
2220                                    Tools.showError(shell, "Select", "No data selected.");
2221                                    return;
2222                                }
2223
2224                                // Since NatTable returns the selected row positions as a Set<Range>, convert
2225                                // this to an Integer[]
2226                                Set<Range> rowPositions = selectionLayer.getSelectedRowPositions();
2227                                Set<Integer> selectedRowPos = new LinkedHashSet<>();
2228                                Iterator<Range> i1 = rowPositions.iterator();
2229                                while (i1.hasNext()) {
2230                                    selectedRowPos.addAll(i1.next().getMembers());
2231                                }
2232
2233                                Integer[] selectedRows = selectedRowPos.toArray(new Integer[0]);
2234                                if (selectedRows == null || selectedRows.length <= 0) {
2235                                    log.debug("show reference data: no data selected");
2236                                    Tools.showError(shell, "Select", "No data selected.");
2237                                    return;
2238                                }
2239                                int len = Array.getLength(selectedRows);
2240                                for (int i = 0; i < len; i++) {
2241                                    byte[] rElements = null;
2242                                    if (theData instanceof ArrayList)
2243                                        rElements = (byte[]) ((ArrayList) theData).get(selectedRows[i]);
2244                                    else
2245                                        rElements = (byte[]) theData;
2246
2247                                    if (isStdRef)
2248                                        showStdRefData(rElements);
2249                                    else if (isRegRef)
2250                                        showRegRefData(rElements);
2251                                    else if (isObjRef)
2252                                        showObjRefData(rElements);
2253                                }
2254                            }
2255                        });
2256                    }
2257                });
2258            }
2259            else {
2260                // Add default bindings for editing
2261                this.addConfiguration(new DefaultEditConfiguration());
2262
2263                // Register cell editing rules with the table and add
2264                // data validation
2265                this.addConfiguration(new AbstractRegistryConfiguration() {
2266                    @Override
2267                    public void configureRegistry(IConfigRegistry configRegistry) {
2268                        IEditableRule editingRule = getDataEditingRule(dataObject);
2269                        if (editingRule != null) {
2270                            // Register cell editing rules with table
2271                            configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITABLE_RULE,
2272                                    editingRule, DisplayMode.EDIT);
2273                        }
2274
2275                        // Add data validator and validation error handler
2276                        DataValidator validator = null;
2277                        try {
2278                            validator = DataValidatorFactory.getDataValidator(dataObject);
2279                        }
2280                        catch (Exception ex) {
2281                            log.debug("EditingGridLayer: no DataValidator retrieved, data editing will be disabled");
2282                        }
2283
2284                        if (validator != null) {
2285                            configRegistry.registerConfigAttribute(EditConfigAttributes.DATA_VALIDATOR, validator,
2286                                    DisplayMode.EDIT, GridRegion.BODY);
2287                        }
2288
2289                        configRegistry.registerConfigAttribute(EditConfigAttributes.VALIDATION_ERROR_HANDLER,
2290                                new DialogErrorHandling(), DisplayMode.EDIT, GridRegion.BODY);
2291                    }
2292                });
2293
2294                // Change cell editing to be on double click rather than single click
2295                // and allow editing of cells by pressing keys as well
2296                this.addConfiguration(new AbstractUiBindingConfiguration() {
2297                    @Override
2298                    public void configureUiBindings(UiBindingRegistry uiBindingRegistry) {
2299                        uiBindingRegistry.registerFirstKeyBinding(new LetterOrDigitKeyEventMatcher(), new KeyEditAction());
2300                        uiBindingRegistry.registerFirstDoubleClickBinding(
2301                                new CellEditorMouseEventMatcher(), new MouseEditAction());
2302                    }
2303                });
2304            }
2305        }
2306    }
2307
2308    /**
2309     * An implementation of the table's Row Header which adapts to the current font.
2310     */
2311    protected class RowHeader extends RowHeaderLayer
2312    {
2313        /** Create the RowHeader which adapts to the current font.
2314         *
2315         * @param baseLayer
2316         *        the base layer
2317         * @param verticalLayerDependency
2318         *        the vertical layer dependency
2319         * @param selectionLayer
2320         *        the selection layer
2321         */
2322        public RowHeader(IUniqueIndexLayer baseLayer, ILayer verticalLayerDependency, SelectionLayer selectionLayer) {
2323            super(baseLayer, verticalLayerDependency, selectionLayer);
2324
2325            this.addConfiguration(new DefaultRowHeaderLayerConfiguration() {
2326                @Override
2327                public void addRowHeaderStyleConfig() {
2328                    this.addConfiguration(new DefaultRowHeaderStyleConfiguration() {
2329                        {
2330                            this.cellPainter = new LineBorderDecorator(new TextPainter(false, true, 2, true));
2331                            this.bgColor = Display.getDefault().getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
2332                            this.font = (curFont == null) ? Display.getDefault().getSystemFont() : curFont;
2333                        }
2334                    });
2335                }
2336            });
2337        }
2338    }
2339
2340    /**
2341     * Custom Row Header data provider to set row indices based on Index Base for
2342     * both Scalar Datasets and Compound Datasets.
2343     */
2344    protected class RowHeaderDataProvider implements IDataProvider
2345    {
2346        private int    rank;
2347        private int    space_type;
2348        private long[] dims;
2349        private long[] startArray;
2350        private long[] strideArray;
2351        private int[]  selectedIndex;
2352
2353        /** the start value. */
2354        protected int  start;
2355        /** the stride value. */
2356        protected int  stride;
2357
2358        private int    nrows;
2359
2360        /** Create the Row Header data provider to set row indices based on Index Base for
2361         *  both Scalar Datasets and Compound Datasets.
2362         *
2363         * @param theDataObject
2364         *        the data object
2365         */
2366        public RowHeaderDataProvider(DataFormat theDataObject) {
2367            this.space_type = theDataObject.getSpaceType();
2368            this.rank = theDataObject.getRank();
2369            this.dims = theDataObject.getSelectedDims();
2370            this.startArray = theDataObject.getStartDims();
2371            this.strideArray = theDataObject.getStride();
2372            this.selectedIndex = theDataObject.getSelectedIndex();
2373
2374            if (rank > 1)
2375                this.nrows = (int) theDataObject.getHeight();
2376            else
2377                this.nrows = (int) dims[0];
2378
2379            start = (int) startArray[selectedIndex[0]];
2380            stride = (int) strideArray[selectedIndex[0]];
2381        }
2382
2383
2384        /** Update the Row Header data provider to set row indices based on Index Base for
2385         *  both Scalar Datasets and Compound Datasets.
2386         *
2387         * @param theDataObject
2388         *        the data object
2389         */
2390        public void updateRows(DataFormat theDataObject) {
2391            this.rank = theDataObject.getRank();
2392            this.dims = theDataObject.getSelectedDims();
2393            this.selectedIndex = theDataObject.getSelectedIndex();
2394
2395            if (rank > 1)
2396                this.nrows = (int) theDataObject.getHeight();
2397            else
2398                this.nrows = (int) dims[0];
2399        }
2400
2401        @Override
2402        public int getColumnCount() {
2403            return 1;
2404        }
2405
2406        @Override
2407        public int getRowCount() {
2408            return nrows;
2409        }
2410
2411        @Override
2412        public Object getDataValue(int columnIndex, int rowIndex) {
2413            return String.valueOf(start + indexBase + (rowIndex * stride));
2414        }
2415
2416        @Override
2417        public void setDataValue(int columnIndex, int rowIndex, Object newValue) {
2418            // Intentional
2419        }
2420    }
2421
2422    /**
2423     * An implementation of the table's Column Header which adapts to the current
2424     * font.
2425     */
2426    protected class ColumnHeader extends ColumnHeaderLayer
2427    {
2428        /** Create the ColumnHeader which adapts to the current font.
2429         *
2430         * @param baseLayer
2431         *        the base layer
2432         * @param horizontalLayerDependency
2433         *        the horizontal layer dependency
2434         * @param selectionLayer
2435         *        the selection layer
2436         */
2437        public ColumnHeader(IUniqueIndexLayer baseLayer, ILayer horizontalLayerDependency,
2438                SelectionLayer selectionLayer) {
2439            super(baseLayer, horizontalLayerDependency, selectionLayer);
2440
2441            this.addConfiguration(new DefaultColumnHeaderLayerConfiguration() {
2442                @Override
2443                public void addColumnHeaderStyleConfig() {
2444                    this.addConfiguration(new DefaultColumnHeaderStyleConfiguration() {
2445                        {
2446                            this.cellPainter = new BeveledBorderDecorator(new TextPainter(false, true, 2, true));
2447                            this.bgColor = Display.getDefault().getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW);
2448                            this.font = (curFont == null) ? Display.getDefault().getSystemFont() : curFont;
2449                        }
2450                    });
2451                }
2452            });
2453        }
2454    }
2455
2456    /** Context-menu for dealing with region and object references */
2457    protected class RefContextMenu extends AbstractUiBindingConfiguration
2458    {
2459        private final Menu contextMenu;
2460
2461        /** Create the Context-menu for dealing with region and object references.
2462         *
2463         * @param table
2464         *        the NatTable object
2465         */
2466        public RefContextMenu(NatTable table) {
2467            this.contextMenu = createMenu(table).build();
2468        }
2469
2470        private void showRefTable() {
2471            log.trace("show reference data: Show data as {}", viewType);
2472
2473            Object theData = getSelectedData();
2474            if (theData == null) {
2475                shell.getDisplay().beep();
2476                Tools.showError(shell, "Select", "No data selected.");
2477                return;
2478            }
2479            if (!(theData instanceof byte[]) && !(theData instanceof ArrayList)) {
2480                shell.getDisplay().beep();
2481                Tools.showError(shell, "Select", "Data selected is not a reference.");
2482                return;
2483            }
2484            log.trace("show reference data: Data is {}", theData);
2485
2486            // Since NatTable returns the selected row positions as a Set<Range>, convert
2487            // this to an Integer[]
2488            Set<Range> rowPositions = selectionLayer.getSelectedRowPositions();
2489            Set<Integer> selectedRowPos = new LinkedHashSet<>();
2490            Iterator<Range> i1 = rowPositions.iterator();
2491            while (i1.hasNext())
2492                selectedRowPos.addAll(i1.next().getMembers());
2493
2494            Integer[] selectedRows = selectedRowPos.toArray(new Integer[0]);
2495            int[] selectedCols = selectionLayer.getSelectedColumnPositions();
2496            if (selectedRows == null || selectedRows.length <= 0) {
2497                shell.getDisplay().beep();
2498                Tools.showError(shell, "Select", "No data selected.");
2499                log.trace("show reference data: Show data as {}: selectedRows is empty", viewType);
2500                return;
2501            }
2502
2503            int len = Array.getLength(selectedRows) * Array.getLength(selectedCols);
2504            log.trace("show reference data: Show data as {}: len={}", viewType, len);
2505            if (len > 1) {
2506                shell.getDisplay().beep();
2507                Tools.showError(shell, "Select", "Reference selection must be one cell.");
2508                log.trace("show reference data: Show data as {}: Too much data", viewType);
2509                return;
2510            }
2511
2512            for (int i = 0; i < len; i++) {
2513                byte[] rElements = null;
2514                if (theData instanceof ArrayList)
2515                    rElements = (byte[]) ((ArrayList) theData).get(i);
2516                else
2517                    rElements = (byte[]) theData;
2518
2519                if (rElements.length == HDF5Constants.H5R_DSET_REG_REF_BUF_SIZE) {
2520                    showRegRefData(rElements);
2521                }
2522                else if (rElements.length == HDF5Constants.H5R_OBJ_REF_BUF_SIZE) {
2523                    showObjRefData(rElements);
2524                }
2525                else {
2526                    showStdRefData(rElements);
2527                }
2528            }
2529        }
2530
2531        private PopupMenuBuilder createMenu(NatTable table) {
2532            Menu menu = new Menu(table);
2533
2534            MenuItem item = new MenuItem(menu, SWT.PUSH);
2535            item.setText("Show As &Table");
2536            item.addSelectionListener(new SelectionAdapter() {
2537                @Override
2538                public void widgetSelected(SelectionEvent e) {
2539                    viewType = ViewType.TABLE;
2540                    showRefTable();
2541                }
2542            });
2543
2544            item = new MenuItem(menu, SWT.PUSH);
2545            item.setText("Show As &Image");
2546            item.addSelectionListener(new SelectionAdapter() {
2547                @Override
2548                public void widgetSelected(SelectionEvent e) {
2549                    viewType = ViewType.IMAGE;
2550                    showRefTable();
2551                }
2552            });
2553
2554            return new PopupMenuBuilder(table, menu);
2555        }
2556
2557        @Override
2558        public void configureUiBindings(UiBindingRegistry uiBindingRegistry) {
2559            uiBindingRegistry.registerMouseDownBinding(
2560                    new MouseEventMatcher(SWT.NONE, GridRegion.BODY, MouseEventMatcher.RIGHT_BUTTON),
2561                    new PopupMenuAction(this.contextMenu));
2562        }
2563    }
2564
2565    private class LinePlotOption extends Dialog
2566    {
2567        private Shell linePlotOptionShell;
2568
2569        private Button rowButton, colButton;
2570
2571        private Combo rowBox, colBox;
2572
2573        public static final int NO_PLOT = -1;
2574        public static final int ROW_PLOT = 0;
2575        public static final int COLUMN_PLOT = 1;
2576
2577        private int nrow, ncol;
2578
2579        private int idx_xaxis = -1;
2580        private int plotType = -1;
2581
2582        public LinePlotOption(Shell parent, int style, int nrow, int ncol) {
2583            super(parent, style);
2584
2585            this.nrow = nrow;
2586            this.ncol = ncol;
2587        }
2588
2589        public void open() {
2590            Shell parent = getParent();
2591            linePlotOptionShell = new Shell(parent, SWT.SHELL_TRIM | SWT.APPLICATION_MODAL);
2592            linePlotOptionShell.setFont(curFont);
2593            linePlotOptionShell.setText("Line Plot Options -- " + ((HObject) dataObject).getName());
2594            linePlotOptionShell.setImages(ViewProperties.getHdfIcons());
2595            linePlotOptionShell.setLayout(new GridLayout(1, true));
2596
2597            Label label = new Label(linePlotOptionShell, SWT.RIGHT);
2598            label.setFont(curFont);
2599            label.setText("Select Line Plot Options:");
2600
2601            Composite content = new Composite(linePlotOptionShell, SWT.BORDER);
2602            content.setLayout(new GridLayout(3, false));
2603            content.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
2604
2605            label = new Label(content, SWT.RIGHT);
2606            label.setFont(curFont);
2607            label.setText(" Series in:");
2608
2609            colButton = new Button(content, SWT.RADIO);
2610            colButton.setFont(curFont);
2611            colButton.setText("Column");
2612            colButton.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, false, false));
2613            colButton.addSelectionListener(new SelectionAdapter() {
2614                @Override
2615                public void widgetSelected(SelectionEvent e) {
2616                    colBox.setEnabled(true);
2617                    rowBox.setEnabled(false);
2618                }
2619            });
2620
2621            rowButton = new Button(content, SWT.RADIO);
2622            rowButton.setFont(curFont);
2623            rowButton.setText("Row");
2624            rowButton.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, false, false));
2625            rowButton.addSelectionListener(new SelectionAdapter() {
2626                @Override
2627                public void widgetSelected(SelectionEvent e) {
2628                    rowBox.setEnabled(true);
2629                    colBox.setEnabled(false);
2630                }
2631            });
2632
2633            label = new Label(content, SWT.RIGHT);
2634            label.setFont(curFont);
2635            label.setText(" For abscissa use:");
2636
2637            long[] startArray = dataObject.getStartDims();
2638            long[] strideArray = dataObject.getStride();
2639            int[] selectedIndex = dataObject.getSelectedIndex();
2640            int start = (int) startArray[selectedIndex[0]];
2641            int stride = (int) strideArray[selectedIndex[0]];
2642
2643            colBox = new Combo(content, SWT.SINGLE | SWT.READ_ONLY);
2644            colBox.setFont(curFont);
2645            GridData colBoxData = new GridData(SWT.FILL, SWT.FILL, true, false);
2646            colBoxData.minimumWidth = 100;
2647            colBox.setLayoutData(colBoxData);
2648
2649            colBox.add("array index");
2650
2651            for (int i = 0; i < ncol; i++)
2652                colBox.add("column " + columnHeaderDataProvider.getDataValue(i, 0));
2653
2654            rowBox = new Combo(content, SWT.SINGLE | SWT.READ_ONLY);
2655            rowBox.setFont(curFont);
2656            GridData rowBoxData = new GridData(SWT.FILL, SWT.FILL, true, false);
2657            rowBoxData.minimumWidth = 100;
2658            rowBox.setLayoutData(rowBoxData);
2659
2660            rowBox.add("array index");
2661
2662            for (int i = 0; i < nrow; i++)
2663                rowBox.add("row " + (start + indexBase + i * stride));
2664
2665            // Create Ok/Cancel button region
2666            Composite buttonComposite = new Composite(linePlotOptionShell, SWT.NONE);
2667            buttonComposite.setLayout(new GridLayout(2, true));
2668            buttonComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));
2669
2670            Button okButton = new Button(buttonComposite, SWT.PUSH);
2671            okButton.setFont(curFont);
2672            okButton.setText("   &OK   ");
2673            okButton.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
2674            okButton.addSelectionListener(new SelectionAdapter() {
2675                @Override
2676                public void widgetSelected(SelectionEvent e) {
2677                    if (colButton.getSelection()) {
2678                        idx_xaxis = colBox.getSelectionIndex() - 1;
2679                        plotType = COLUMN_PLOT;
2680                    }
2681                    else {
2682                        idx_xaxis = rowBox.getSelectionIndex() - 1;
2683                        plotType = ROW_PLOT;
2684                    }
2685
2686                    linePlotOptionShell.dispose();
2687                }
2688            });
2689
2690            Button cancelButton = new Button(buttonComposite, SWT.PUSH);
2691            cancelButton.setFont(curFont);
2692            cancelButton.setText(" &Cancel ");
2693            cancelButton.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, true, false));
2694            cancelButton.addSelectionListener(new SelectionAdapter() {
2695                @Override
2696                public void widgetSelected(SelectionEvent e) {
2697                    plotType = NO_PLOT;
2698                    linePlotOptionShell.dispose();
2699                }
2700            });
2701
2702            colButton.setSelection(true);
2703            rowButton.setSelection(false);
2704
2705            colBox.select(0);
2706            rowBox.select(0);
2707
2708            colBox.setEnabled(colButton.getSelection());
2709            rowBox.setEnabled(rowButton.getSelection());
2710
2711            linePlotOptionShell.pack();
2712
2713            linePlotOptionShell.setMinimumSize(linePlotOptionShell.computeSize(SWT.DEFAULT, SWT.DEFAULT));
2714
2715            Rectangle parentBounds = parent.getBounds();
2716            Point shellSize = linePlotOptionShell.getSize();
2717            linePlotOptionShell.setLocation((parentBounds.x + (parentBounds.width / 2)) - (shellSize.x / 2),
2718                    (parentBounds.y + (parentBounds.height / 2)) - (shellSize.y / 2));
2719
2720            linePlotOptionShell.open();
2721
2722            Display display = parent.getDisplay();
2723            while (!linePlotOptionShell.isDisposed())
2724                if (!display.readAndDispatch()) display.sleep();
2725        }
2726
2727        int getXindex() {
2728            return idx_xaxis;
2729        }
2730
2731        int getPlotBy() {
2732            return plotType;
2733        }
2734    }
2735}