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