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