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    /** @return the selection layer */
1272    public SelectionLayer getSelectionLayer() { return selectionLayer; }
1273
1274    /** @return the data layer */
1275    public DataLayer getDataLayer() { return dataLayer; }
1276
1277    /** refresh the data table */
1278    @Override
1279    public void refreshDataTable()
1280    {
1281        log.trace("refreshDataTable()");
1282
1283        shell.setCursor(display.getSystemCursor(SWT.CURSOR_WAIT));
1284        dataValue = dataObject.refreshData();
1285        shell.setCursor(null);
1286
1287        long[] dims = dataObject.getDims();
1288        log.trace("refreshDataTable() dims:{}", dims);
1289        dataProvider.updateDataBuffer(dataValue);
1290        ((RowHeaderDataProvider)rowHeaderDataProvider).updateRows(dataObject);
1291        log.trace("refreshDataTable(): rows={} : cols={}", dataProvider.getRowCount(),
1292                  dataProvider.getColumnCount());
1293
1294        dataTable.doCommand(new StructuralRefreshCommand());
1295        final ViewportLayer viewportLayer = new ViewportLayer(selectionLayer);
1296        dataTable.doCommand(new ShowRowInViewportCommand(dataProvider.getRowCount() - 1));
1297        log.trace("refreshDataTable() finish");
1298    }
1299
1300    // Flip to previous 'frame' of Table data
1301    private void previousFrame()
1302    {
1303        // Only valid operation if data object has 3 or more dimensions
1304        if (dataObject.getRank() < 3)
1305            return;
1306
1307        long[] start        = dataObject.getStartDims();
1308        int[] selectedIndex = dataObject.getSelectedIndex();
1309        long curFrame       = start[selectedIndex[2]];
1310
1311        if (curFrame == 0)
1312            return; // Current frame is the first frame
1313
1314        gotoFrame(curFrame - 1);
1315    }
1316
1317    // Flip to next 'frame' of Table data
1318    private void nextFrame()
1319    {
1320        // Only valid operation if data object has 3 or more dimensions
1321        if (dataObject.getRank() < 3)
1322            return;
1323
1324        long[] start        = dataObject.getStartDims();
1325        int[] selectedIndex = dataObject.getSelectedIndex();
1326        long[] dims         = dataObject.getDims();
1327        long curFrame       = start[selectedIndex[2]];
1328
1329        if (curFrame == dims[selectedIndex[2]] - 1)
1330            return; // Current frame is the last frame
1331
1332        gotoFrame(curFrame + 1);
1333    }
1334
1335    // Flip to the first 'frame' of Table data
1336    private void firstFrame()
1337    {
1338        // Only valid operation if data object has 3 or more dimensions
1339        if (dataObject.getRank() < 3)
1340            return;
1341
1342        long[] start        = dataObject.getStartDims();
1343        int[] selectedIndex = dataObject.getSelectedIndex();
1344        long curFrame       = start[selectedIndex[2]];
1345
1346        if (curFrame == 0)
1347            return; // Current frame is the first frame
1348
1349        gotoFrame(0);
1350    }
1351
1352    // Flip to the last 'frame' of Table data
1353    private void lastFrame()
1354    {
1355        // Only valid operation if data object has 3 or more dimensions
1356        if (dataObject.getRank() < 3)
1357            return;
1358
1359        long[] start        = dataObject.getStartDims();
1360        int[] selectedIndex = dataObject.getSelectedIndex();
1361        long[] dims         = dataObject.getDims();
1362        long curFrame       = start[selectedIndex[2]];
1363
1364        if (curFrame == dims[selectedIndex[2]] - 1)
1365            return; // Current page is the last page
1366
1367        gotoFrame(dims[selectedIndex[2]] - 1);
1368    }
1369
1370    // Flip to the specified 'frame' of Table data
1371    private void gotoFrame(long idx)
1372    {
1373        // Only valid operation if data object has 3 or more dimensions
1374        if (dataObject.getRank() < 3 || idx == (curDataFrame - indexBase))
1375            return;
1376
1377        // Make sure to save any changes to this frame of data before changing frames
1378        if (dataProvider.getIsValueChanged())
1379            updateValueInFile();
1380
1381        long[] start        = dataObject.getStartDims();
1382        int[] selectedIndex = dataObject.getSelectedIndex();
1383        long[] dims         = dataObject.getDims();
1384
1385        // Do a bit of frame index validation
1386        if ((idx < 0) || (idx >= dims[selectedIndex[2]])) {
1387            shell.getDisplay().beep();
1388            Tools.showError(shell, "Select",
1389                            "Frame number must be between " + indexBase + " and " +
1390                                (dims[selectedIndex[2]] - 1 + indexBase));
1391            return;
1392        }
1393
1394        start[selectedIndex[2]] = idx;
1395        curDataFrame            = idx + indexBase;
1396        frameField.setText(String.valueOf(curDataFrame));
1397
1398        dataObject.clearData();
1399
1400        shell.setCursor(display.getSystemCursor(SWT.CURSOR_WAIT));
1401
1402        try {
1403            dataValue = dataObject.getData();
1404
1405            /*
1406             * TODO: Converting data from unsigned C integers to Java integers
1407             *       is currently unsupported for Compound Datasets.
1408             */
1409            if (!(dataObject instanceof CompoundDS))
1410                dataObject.convertFromUnsignedC();
1411
1412            dataValue = dataObject.getData();
1413        }
1414        catch (Exception ex) {
1415            shell.getDisplay().beep();
1416            Tools.showError(shell, "Error loading data", "Dataset getData: " + ex.getMessage());
1417            log.debug("gotoFrame(): ", ex);
1418            dataValue = null;
1419        }
1420        finally {
1421            shell.setCursor(null);
1422        }
1423
1424        dataProvider.updateDataBuffer(dataValue);
1425
1426        dataTable.doCommand(new VisualRefreshCommand());
1427    }
1428
1429    /**
1430     * Copy data from the spreadsheet to the system clipboard.
1431     */
1432    private void copyData()
1433    {
1434        StringBuilder sb = new StringBuilder();
1435
1436        Rectangle selection = selectionLayer.getLastSelectedRegion();
1437        if (selection == null) {
1438            Tools.showError(shell, "Copy", "Select data to copy.");
1439            return;
1440        }
1441
1442        int r0 = selectionLayer.getLastSelectedRegion().y; // starting row
1443        int c0 = selectionLayer.getLastSelectedRegion().x; // starting column
1444
1445        if ((r0 < 0) || (c0 < 0))
1446            return;
1447
1448        int nr = selectionLayer.getSelectedRowCount();
1449        int nc = selectionLayer.getSelectedColumnPositions().length;
1450        int r1 = r0 + nr; // finish row
1451        int c1 = c0 + nc; // finishing column
1452
1453        try {
1454            for (int i = r0; i < r1; i++) {
1455                sb.append(selectionLayer.getDataValueByPosition(c0, i).toString());
1456                for (int j = c0 + 1; j < c1; j++)
1457                    sb.append("\t").append(selectionLayer.getDataValueByPosition(j, i).toString());
1458                sb.append("\n");
1459            }
1460        }
1461        catch (java.lang.OutOfMemoryError err) {
1462            shell.getDisplay().beep();
1463            Tools.showError(
1464                shell, "Copy",
1465                "Copying data to system clipboard failed. \nUse \"export/import data\" for copying/pasting large data.");
1466            return;
1467        }
1468
1469        Clipboard cb             = Toolkit.getDefaultToolkit().getSystemClipboard();
1470        StringSelection contents = new StringSelection(sb.toString());
1471        cb.setContents(contents, null);
1472    }
1473
1474    /**
1475     * Paste data from the system clipboard to the spreadsheet.
1476     */
1477    private void pasteData()
1478    {
1479        if (!Tools.showConfirm(shell, "Clipboard Data", "Do you want to paste selected data?"))
1480            return;
1481
1482        int cols = selectionLayer.getPreferredColumnCount();
1483        int rows = selectionLayer.getPreferredRowCount();
1484        int r0   = 0;
1485        int c0   = 0;
1486
1487        Rectangle selection = selectionLayer.getLastSelectedRegion();
1488        if (selection != null) {
1489            r0 = selection.y;
1490            c0 = selection.x;
1491        }
1492
1493        if (c0 < 0)
1494            c0 = 0;
1495        if (r0 < 0)
1496            r0 = 0;
1497        int r = r0;
1498        int c = c0;
1499
1500        Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
1501        String line  = "";
1502        try {
1503            String s = (String)cb.getData(DataFlavor.stringFlavor);
1504
1505            StringTokenizer st = new StringTokenizer(s, "\n");
1506            // read line by line
1507            while (st.hasMoreTokens() && (r < rows)) {
1508                line = st.nextToken();
1509
1510                if (fixedDataLength < 1) {
1511                    // separate by delimiter
1512                    StringTokenizer lt = new StringTokenizer(line, "\t");
1513                    while (lt.hasMoreTokens() && (c < cols)) {
1514                        try {
1515                            dataProvider.setDataValue(c, r, lt.nextToken());
1516                        }
1517                        catch (Exception ex) {
1518                            continue;
1519                        }
1520                        c++;
1521                    }
1522                    r = r + 1;
1523                    c = c0;
1524                }
1525                else {
1526                    // the data has fixed length
1527                    int n = line.length();
1528                    String theVal;
1529                    for (int i = 0; i < n; i = i + fixedDataLength) {
1530                        try {
1531                            theVal = line.substring(i, i + fixedDataLength);
1532                            dataProvider.setDataValue(c, r, theVal);
1533                        }
1534                        catch (Exception ex) {
1535                            continue;
1536                        }
1537                        c++;
1538                    }
1539                }
1540            }
1541        }
1542        catch (Exception ex) {
1543            shell.getDisplay().beep();
1544            Tools.showError(shell, "Paste", ex.getMessage());
1545        }
1546    }
1547
1548    /**
1549     * Save data as text.
1550     *
1551     * @throws Exception
1552     *             if a failure occurred
1553     */
1554    protected void saveAsText() throws Exception
1555    {
1556        String currentDir = ((HObject)dataObject).getFileFormat().getParent();
1557
1558        String filename = null;
1559        if (((HDFView)viewer).getTestState()) {
1560            filename = currentDir + File.separator + new InputDialog(shell, "Enter a file name", "").open();
1561        }
1562        else {
1563            FileDialog fChooser = new FileDialog(shell, SWT.SAVE);
1564            fChooser.setFilterPath(currentDir);
1565
1566            DefaultFileFilter filter = DefaultFileFilter.getFileFilterText();
1567            fChooser.setFilterExtensions(new String[] {"*", filter.getExtensions()});
1568            fChooser.setFilterNames(new String[] {"All Files", filter.getDescription()});
1569            fChooser.setFilterIndex(1);
1570            fChooser.setText("Save Current Data To Text File --- " + ((HObject)dataObject).getName());
1571
1572            filename = fChooser.open();
1573        }
1574        if (filename == null)
1575            return;
1576
1577        File chosenFile = new File(filename);
1578        String fname    = chosenFile.getAbsolutePath();
1579
1580        log.trace("saveAsText: file={}", fname);
1581
1582        // Check if the file is in use and prompt for overwrite
1583        if (chosenFile.exists()) {
1584            List<?> fileList = viewer.getTreeView().getCurrentFiles();
1585            if (fileList != null) {
1586                FileFormat theFile   = null;
1587                Iterator<?> iterator = fileList.iterator();
1588                while (iterator.hasNext()) {
1589                    theFile = (FileFormat)iterator.next();
1590                    if (theFile.getFilePath().equals(fname)) {
1591                        shell.getDisplay().beep();
1592                        Tools.showError(shell, "Save",
1593                                        "Unable to save data to file \"" + fname +
1594                                            "\". \nThe file is being used.");
1595                        return;
1596                    }
1597                }
1598            }
1599
1600            if (!Tools.showConfirm(shell, "Save", "File exists. Do you want to replace it?"))
1601                return;
1602        }
1603
1604        PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(chosenFile)));
1605
1606        String delName   = ViewProperties.getDataDelimiter();
1607        String delimiter = "";
1608
1609        // delimiter must include a tab to be consistent with copy/paste for
1610        // compound fields
1611        if (dataObject instanceof CompoundDS)
1612            delimiter = "\t";
1613
1614        if (delName.equalsIgnoreCase(ViewProperties.DELIMITER_TAB))
1615            delimiter = "\t";
1616        else if (delName.equalsIgnoreCase(ViewProperties.DELIMITER_SPACE))
1617            delimiter = " " + delimiter;
1618        else if (delName.equalsIgnoreCase(ViewProperties.DELIMITER_COMMA))
1619            delimiter = "," + delimiter;
1620        else if (delName.equalsIgnoreCase(ViewProperties.DELIMITER_COLON))
1621            delimiter = ":" + delimiter;
1622        else if (delName.equalsIgnoreCase(ViewProperties.DELIMITER_SEMI_COLON))
1623            delimiter = ";" + delimiter;
1624
1625        int cols = selectionLayer.getPreferredColumnCount();
1626        int rows = selectionLayer.getPreferredRowCount();
1627
1628        for (int i = 0; i < rows; i++) {
1629            out.print(selectionLayer.getDataValueByPosition(0, i));
1630            for (int j = 1; j < cols; j++) {
1631                out.print(delimiter);
1632                out.print(selectionLayer.getDataValueByPosition(j, i));
1633            }
1634            out.println();
1635        }
1636
1637        out.flush();
1638        out.close();
1639
1640        viewer.showStatus("Data saved to: " + fname);
1641    }
1642
1643    /** Save data as text (from TextView). */
1644    // private void saveAsTextTextView() throws Exception {
1645    // FileDialog fChooser = new FileDialog(shell, SWT.SAVE);
1646    // fChooser.setText("Save Current Data To Text File --- " + dataset.getName());
1647    // fChooser.setFilterPath(dataset.getFileFormat().getParent());
1648    //
1649    // DefaultFileFilter filter = DefaultFileFilter.getFileFilterText();
1650    // fChooser.setFilterExtensions(new String[] {"*", filter.getExtensions()});
1651    // fChooser.setFilterNames(new String[] {"All Files", filter.getDescription()});
1652    // fChooser.setFilterIndex(1);
1653    //
1654    // // fchooser.changeToParentDirectory();
1655    // fChooser.setFileName(dataset.getName() + ".txt");
1656    // fChooser.setOverwrite(true);
1657    //
1658    // String filename = fChooser.open();
1659    //
1660    //  (filename == null) return;
1661    //
1662    // File chosenFile = new File(filename);
1663    //
1664    // // check if the file is in use
1665    // String fname = chosenFile.getAbsolutePath();
1666    // List<FileFormat> fileList = viewer.getTreeView().getCurrentFiles();
1667    //  (fileList != null) {
1668    // FileFormat theFile = null;
1669    // Iterator<FileFormat> iterator = fileList.iterator();
1670    // while (iterator.hasNext()) {
1671    // theFile = iterator.next();
1672    //  (theFile.getFilePath().equals(fname)) {
1673    // Tools.showError(shell, "Save", "Unable to save data to file \"" + fname
1674    // + "\". \nThe file is being used.");
1675    // return;
1676    // }
1677    // }
1678    // }
1679    //
1680    // PrintWriter out = new PrintWriter(new BufferedWriter(new
1681    // FileWriter(chosenFile)));
1682    //
1683    // int rows = text.length;
1684    //  (int i = 0; i < rows; i++) {
1685    // out.print(text[i].trim());
1686    // out.println();
1687    // out.println();
1688    // }
1689    //
1690    // out.flush();
1691    // out.close();
1692    //
1693    // viewer.showStatus("Data saved to: " + fname);
1694    //
1695    // try {
1696    // RandomAccessFile rf = new RandomAccessFile(chosenFile, "r");
1697    // long size = rf.length();
1698    // rf.close();
1699    // viewer.showStatus("File size (bytes): " + size);
1700    // }
1701    // catch (Exception ex) {
1702    // log.debug("raf file size:", ex);
1703    // }
1704    // }
1705
1706    // print the table (from TextView)
1707    // private void print() {
1708    // // StreamPrintServiceFactory[] spsf = StreamPrintServiceFactory
1709    // // .lookupStreamPrintServiceFactories(null, null);
1710    // //  (int i = 0; i < spsf.length; i++) {
1711    // // System.out.println(spsf[i]);
1712    // // }
1713    // // DocFlavor[] docFlavors = spsf[0].getSupportedDocFlavors();
1714    // //  (int i = 0; i < docFlavors.length; i++) {
1715    // // System.out.println(docFlavors[i]);
1716    // // }
1717    //
1718    // // TODO: windows url
1719    // // Get a text DocFlavor
1720    // InputStream is = null;
1721    // try {
1722    // is = new BufferedInputStream(new java.io.FileInputStream(
1723    // "e:\\temp\\t.html"));
1724    // }
1725    // catch (Exception ex) {
1726    // log.debug("Get a text DocFlavor:", ex);
1727    // }
1728    // DocFlavor flavor = DocFlavor.STRING.TEXT_HTML;
1729    //
1730    // // Get all available print services
1731    // PrintService[] services = PrintServiceLookup.lookupPrintServices(null,
1732    // null);
1733    //
1734    // // Print it
1735    // try {
1736    // // Print this job on the first print server
1737    // DocPrintJob job = services[0].createPrintJob();
1738    // Doc doc = new SimpleDoc(is, flavor, null);
1739    //
1740    // job.print(doc, null);
1741    // }
1742    // catch (Exception ex) {
1743    // log.debug("print(): failure: ", ex);
1744    // }
1745    // }
1746
1747    /**
1748     * Save data as binary.
1749     *
1750     * @throws Exception
1751     *             if a failure occurred
1752     */
1753    protected void saveAsBinary() throws Exception
1754    {
1755        String currentDir = ((HObject)dataObject).getFileFormat().getParent();
1756
1757        String filename = null;
1758        if (((HDFView)viewer).getTestState()) {
1759            filename = currentDir + File.separator + new InputDialog(shell, "Enter a file name", "").open();
1760        }
1761        else {
1762            FileDialog fChooser = new FileDialog(shell, SWT.SAVE);
1763            fChooser.setFilterPath(currentDir);
1764
1765            DefaultFileFilter filter = DefaultFileFilter.getFileFilterBinary();
1766            fChooser.setFilterExtensions(new String[] {"*", filter.getExtensions()});
1767            fChooser.setFilterNames(new String[] {"All Files", filter.getDescription()});
1768            fChooser.setFilterIndex(1);
1769            fChooser.setText("Save Current Data To Binary File --- " + ((HObject)dataObject).getName());
1770
1771            filename = fChooser.open();
1772        }
1773        if (filename == null)
1774            return;
1775
1776        File chosenFile = new File(filename);
1777        String fname    = chosenFile.getAbsolutePath();
1778
1779        log.trace("saveAsBinary: file={}", fname);
1780
1781        // Check if the file is in use and prompt for overwrite
1782        if (chosenFile.exists()) {
1783            List<?> fileList = viewer.getTreeView().getCurrentFiles();
1784            if (fileList != null) {
1785                FileFormat theFile   = null;
1786                Iterator<?> iterator = fileList.iterator();
1787                while (iterator.hasNext()) {
1788                    theFile = (FileFormat)iterator.next();
1789                    if (theFile.getFilePath().equals(fname)) {
1790                        shell.getDisplay().beep();
1791                        Tools.showError(shell, "Save",
1792                                        "Unable to save data to file \"" + fname +
1793                                            "\". \nThe file is being used.");
1794                        return;
1795                    }
1796                }
1797            }
1798
1799            if (!Tools.showConfirm(shell, "Save", "File exists. Do you want to replace it?"))
1800                return;
1801        }
1802
1803        try (DataOutputStream out = new DataOutputStream(new FileOutputStream(chosenFile))) {
1804            if (dataObject instanceof ScalarDS) {
1805                ((ScalarDS)dataObject).convertToUnsignedC();
1806                Object data  = dataObject.getData();
1807                ByteOrder bo = ByteOrder.nativeOrder();
1808
1809                if (binaryOrder == 1)
1810                    bo = ByteOrder.nativeOrder();
1811                else if (binaryOrder == 2)
1812                    bo = ByteOrder.LITTLE_ENDIAN;
1813                else if (binaryOrder == 3)
1814                    bo = ByteOrder.BIG_ENDIAN;
1815
1816                Tools.saveAsBinary(out, data, bo);
1817
1818                viewer.showStatus("Data saved to: " + fname);
1819            }
1820            else
1821                viewer.showError("Data not saved - not a ScalarDS");
1822        }
1823    }
1824
1825    /**
1826     * Import data values from text file.
1827     *
1828     * @param fname
1829     *            the file to import text from
1830     */
1831    protected void importTextData(String fname)
1832    {
1833        int cols = selectionLayer.getPreferredColumnCount();
1834        int rows = selectionLayer.getPreferredRowCount();
1835        int r0;
1836        int c0;
1837
1838        Rectangle lastSelection = selectionLayer.getLastSelectedRegion();
1839        if (lastSelection != null) {
1840            r0 = lastSelection.y;
1841            c0 = lastSelection.x;
1842
1843            if (c0 < 0)
1844                c0 = 0;
1845            if (r0 < 0)
1846                r0 = 0;
1847        }
1848        else {
1849            r0 = 0;
1850            c0 = 0;
1851        }
1852
1853        // Start at the first column for compound datasets
1854        if (dataObject instanceof CompoundDS)
1855            c0 = 0;
1856
1857        String importLine          = null;
1858        StringTokenizer tokenizer1 = null;
1859        try (BufferedReader in = new BufferedReader(new FileReader(fname))) {
1860            try {
1861                importLine = in.readLine();
1862            }
1863            catch (FileNotFoundException ex) {
1864                log.debug("import data values from text file {}:", fname, ex);
1865                return;
1866            }
1867            catch (IOException ex) {
1868                log.debug("read text file {}:", fname, ex);
1869                return;
1870            }
1871
1872            String delName   = ViewProperties.getDataDelimiter();
1873            String delimiter = "";
1874
1875            if (delName.equalsIgnoreCase(ViewProperties.DELIMITER_TAB))
1876                delimiter = "\t";
1877            else if (delName.equalsIgnoreCase(ViewProperties.DELIMITER_SPACE))
1878                delimiter = " " + delimiter;
1879            else if (delName.equalsIgnoreCase(ViewProperties.DELIMITER_COMMA))
1880                delimiter = ",";
1881            else if (delName.equalsIgnoreCase(ViewProperties.DELIMITER_COLON))
1882                delimiter = ":";
1883            else if (delName.equalsIgnoreCase(ViewProperties.DELIMITER_SEMI_COLON))
1884                delimiter = ";";
1885            String token = null;
1886            int r        = r0;
1887            int c        = c0;
1888            while ((importLine != null) && (r < rows)) {
1889                if (fixedDataLength > 0) {
1890                    // the data has fixed length
1891                    int n = importLine.length();
1892                    String theVal;
1893                    for (int i = 0; i < n; i = i + fixedDataLength) {
1894                        try {
1895                            theVal = importLine.substring(i, i + fixedDataLength);
1896                            dataProvider.setDataValue(c, r, theVal);
1897                        }
1898                        catch (Exception ex) {
1899                            continue;
1900                        }
1901                        c++;
1902                    }
1903                }
1904                else {
1905                    try {
1906                        tokenizer1 = new StringTokenizer(importLine, delimiter);
1907                        while (tokenizer1.hasMoreTokens() && (c < cols)) {
1908                            token                      = tokenizer1.nextToken();
1909                            StringTokenizer tokenizer2 = new StringTokenizer(token);
1910                            if (tokenizer2.hasMoreTokens()) {
1911                                while (tokenizer2.hasMoreTokens() && (c < cols)) {
1912                                    dataProvider.setDataValue(c, r, tokenizer2.nextToken());
1913                                    c++;
1914                                }
1915                            }
1916                            else
1917                                c++;
1918                        }
1919                    }
1920                    catch (Exception ex) {
1921                        Tools.showError(shell, "Import", ex.getMessage());
1922                        return;
1923                    }
1924                }
1925
1926                try {
1927                    importLine = in.readLine();
1928                }
1929                catch (IOException ex) {
1930                    log.debug("read text file {}:", fname, ex);
1931                    importLine = null;
1932                }
1933
1934                // Start at the first column for compound datasets
1935                if (dataObject instanceof CompoundDS)
1936                    c = 0;
1937                else
1938                    c = c0;
1939
1940                r++;
1941            } // ((line != null) && (r < rows))
1942        }
1943        catch (IOException ex) {
1944            log.debug("import text file {}:", fname, ex);
1945        }
1946    }
1947
1948    /**
1949     * Import data values from binary file.
1950     */
1951    protected void importBinaryData()
1952    {
1953        String currentDir = ((HObject)dataObject).getFileFormat().getParent();
1954
1955        String filename = null;
1956        if (((HDFView)viewer).getTestState()) {
1957            filename = currentDir + File.separator + new InputDialog(shell, "Enter a file name", "").open();
1958        }
1959        else {
1960            FileDialog fChooser = new FileDialog(shell, SWT.OPEN);
1961            fChooser.setFilterPath(currentDir);
1962
1963            DefaultFileFilter filter = DefaultFileFilter.getFileFilterBinary();
1964            fChooser.setFilterExtensions(new String[] {"*", filter.getExtensions()});
1965            fChooser.setFilterNames(new String[] {"All Files", filter.getDescription()});
1966            fChooser.setFilterIndex(1);
1967
1968            filename = fChooser.open();
1969        }
1970
1971        if (filename == null)
1972            return;
1973
1974        File chosenFile = new File(filename);
1975        if (!chosenFile.exists()) {
1976            Tools.showError(shell, "Import Data from Binary File",
1977                            "Data import error: " + chosenFile.getName() + " does not exist.");
1978            return;
1979        }
1980
1981        if (!Tools.showConfirm(shell, "Import Data from Binary File", "Do you want to paste selected data?"))
1982            return;
1983
1984        ByteOrder bo = ByteOrder.nativeOrder();
1985        if (binaryOrder == 1)
1986            bo = ByteOrder.nativeOrder();
1987        else if (binaryOrder == 2)
1988            bo = ByteOrder.LITTLE_ENDIAN;
1989        else if (binaryOrder == 3)
1990            bo = ByteOrder.BIG_ENDIAN;
1991
1992        try {
1993            if (Tools.getBinaryDataFromFile(dataValue, chosenFile.getAbsolutePath(), bo))
1994                dataProvider.setIsValueChanged(true);
1995
1996            dataTable.doCommand(new StructuralRefreshCommand());
1997        }
1998        catch (Exception ex) {
1999            log.debug("importBinaryData():", ex);
2000        }
2001        catch (OutOfMemoryError e) {
2002            log.debug("importBinaryData(): Out of memory");
2003        }
2004    }
2005
2006    /**
2007     * Convert selected data based on predefined math functions.
2008     */
2009    private void mathConversion() throws Exception
2010    {
2011        if (isReadOnly) {
2012            log.debug("mathConversion(): can't convert read-only data");
2013            return;
2014        }
2015
2016        int cols = selectionLayer.getSelectedColumnPositions().length;
2017        if ((dataObject instanceof CompoundDS) && (cols > 1)) {
2018            shell.getDisplay().beep();
2019            Tools.showError(shell, "Convert",
2020                            "Please select one column at a time for math conversion"
2021                                + "for compound dataset.");
2022            log.debug("mathConversion(): more than one column selected for CompoundDS");
2023            return;
2024        }
2025
2026        Object theData = getSelectedData();
2027        if (theData == null) {
2028            shell.getDisplay().beep();
2029            Tools.showError(shell, "Convert", "No data is selected.");
2030            log.debug("mathConversion(): no data selected");
2031            return;
2032        }
2033
2034        MathConversionDialog dialog = new MathConversionDialog(shell, theData);
2035        dialog.open();
2036
2037        if (dialog.isConverted()) {
2038            if (dataObject instanceof CompoundDS) {
2039                Object colData = null;
2040                try {
2041                    colData =
2042                        ((List<?>)dataObject.getData()).get(selectionLayer.getSelectedColumnPositions()[0]);
2043                }
2044                catch (Exception ex) {
2045                    log.debug("mathConversion(): ", ex);
2046                }
2047
2048                if (colData != null) {
2049                    int size = Array.getLength(theData);
2050                    System.arraycopy(theData, 0, colData, 0, size);
2051                }
2052            }
2053            else {
2054                int rows = selectionLayer.getSelectedRowCount();
2055
2056                // Since NatTable returns the selected row positions as a Set<Range>, convert
2057                // this to
2058                // an Integer[]
2059                Set<Range> rowPositions     = selectionLayer.getSelectedRowPositions();
2060                Set<Integer> selectedRowPos = new LinkedHashSet<>();
2061                Iterator<Range> i1          = rowPositions.iterator();
2062                while (i1.hasNext())
2063                    selectedRowPos.addAll(i1.next().getMembers());
2064
2065                int r0 = selectedRowPos.toArray(new Integer[0])[0];
2066                int c0 = selectionLayer.getSelectedColumnPositions()[0];
2067
2068                int w      = dataTable.getPreferredColumnCount() - 1;
2069                int idxSrc = 0;
2070                int idxDst = 0;
2071
2072                for (int i = 0; i < rows; i++) {
2073                    idxDst = (r0 + i) * w + c0;
2074                    System.arraycopy(theData, idxSrc, dataValue, idxDst, cols);
2075                    idxSrc += cols;
2076                }
2077            }
2078
2079            System.gc();
2080
2081            dataProvider.setIsValueChanged(true);
2082        }
2083    }
2084
2085    private void showLineplot()
2086    {
2087        // Since NatTable returns the selected row positions as a Set<Range>, convert
2088        // this to
2089        // an Integer[]
2090        Set<Range> rowPositions     = selectionLayer.getSelectedRowPositions();
2091        Set<Integer> selectedRowPos = new LinkedHashSet<>();
2092        Iterator<Range> i1          = rowPositions.iterator();
2093        while (i1.hasNext()) {
2094            selectedRowPos.addAll(i1.next().getMembers());
2095        }
2096
2097        Integer[] rows = selectedRowPos.toArray(new Integer[0]);
2098        int[] cols     = selectionLayer.getSelectedColumnPositions();
2099
2100        if ((rows == null) || (cols == null) || (rows.length <= 0) || (cols.length <= 0)) {
2101            shell.getDisplay().beep();
2102            Tools.showError(shell, "Select", "Select rows/columns to draw line plot.");
2103            return;
2104        }
2105
2106        int nrow = dataTable.getPreferredRowCount() - 1;
2107        int ncol = dataTable.getPreferredColumnCount() - 1;
2108
2109        log.trace("DefaultTableView showLineplot: {} - {}", nrow, ncol);
2110        LinePlotOption lpo = new LinePlotOption(shell, SWT.NONE, nrow, ncol);
2111        lpo.open();
2112
2113        int plotType = lpo.getPlotBy();
2114        if (plotType == LinePlotOption.NO_PLOT)
2115            return;
2116
2117        boolean isRowPlot = (plotType == LinePlotOption.ROW_PLOT);
2118        int xIndex        = lpo.getXindex();
2119
2120        // figure out to plot data by row or by column
2121        // Plot data by rows if all columns are selected and part of
2122        // rows are selected, otherwise plot data by column
2123        double[][] data = null;
2124        int nLines      = 0;
2125        String title    = "Lineplot - " + ((HObject)dataObject).getPath() + ((HObject)dataObject).getName();
2126        String[] lineLabels = null;
2127        double[] yRange     = {Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY};
2128        double[] xData      = null;
2129
2130        if (isRowPlot) {
2131            title += " - by row";
2132            nLines = rows.length;
2133            if (nLines > 10) {
2134                shell.getDisplay().beep();
2135                nLines = 10;
2136                Tools.showWarning(shell, "Select",
2137                                  "More than 10 rows are selected.\n"
2138                                      + "The first 10 rows will be displayed.");
2139            }
2140            lineLabels = new String[nLines];
2141            data       = new double[nLines][cols.length];
2142
2143            double value = 0.0;
2144            for (int i = 0; i < nLines; i++) {
2145                lineLabels[i] = String.valueOf(rows[i] + indexBase);
2146                for (int j = 0; j < cols.length; j++) {
2147                    data[i][j] = 0;
2148                    try {
2149                        value = Double.parseDouble(
2150                            selectionLayer.getDataValueByPosition(cols[j], rows[i]).toString());
2151                        data[i][j] = value;
2152                        yRange[0]  = Math.min(yRange[0], value);
2153                        yRange[1]  = Math.max(yRange[1], value);
2154                    }
2155                    catch (NumberFormatException ex) {
2156                        log.debug("rows[{}]:", i, ex);
2157                    }
2158                }
2159            }
2160
2161            if (xIndex >= 0) {
2162                xData = new double[cols.length];
2163                for (int j = 0; j < cols.length; j++) {
2164                    xData[j] = 0;
2165                    try {
2166                        value = Double.parseDouble(
2167                            selectionLayer.getDataValueByPosition(cols[j], xIndex).toString());
2168                        xData[j] = value;
2169                    }
2170                    catch (NumberFormatException ex) {
2171                        log.debug("xIndex of {}:", xIndex, ex);
2172                    }
2173                }
2174            }
2175        }
2176        else {
2177            title += " - by column";
2178            nLines = cols.length;
2179            if (nLines > 10) {
2180                shell.getDisplay().beep();
2181                nLines = 10;
2182                Tools.showWarning(shell, "Select",
2183                                  "More than 10 columns are selected.\n"
2184                                      + "The first 10 columns will be displayed.");
2185            }
2186            lineLabels   = new String[nLines];
2187            data         = new double[nLines][rows.length];
2188            double value = 0.0;
2189            for (int j = 0; j < nLines; j++) {
2190                lineLabels[j] = columnHeaderDataProvider.getDataValue(cols[j] + indexBase, 0).toString();
2191                for (int i = 0; i < rows.length; i++) {
2192                    data[j][i] = 0;
2193                    try {
2194                        value = Double.parseDouble(
2195                            selectionLayer.getDataValueByPosition(cols[j], rows[i]).toString());
2196                        data[j][i] = value;
2197                        yRange[0]  = Math.min(yRange[0], value);
2198                        yRange[1]  = Math.max(yRange[1], value);
2199                    }
2200                    catch (NumberFormatException ex) {
2201                        log.debug("cols[{}]:", j, ex);
2202                    }
2203                }
2204            }
2205
2206            if (xIndex >= 0) {
2207                xData = new double[rows.length];
2208                for (int j = 0; j < rows.length; j++) {
2209                    xData[j] = 0;
2210                    try {
2211                        value = Double.parseDouble(
2212                            selectionLayer.getDataValueByPosition(xIndex, rows[j]).toString());
2213                        xData[j] = value;
2214                    }
2215                    catch (NumberFormatException ex) {
2216                        log.debug("xIndex of {}:", xIndex, ex);
2217                    }
2218                }
2219            }
2220        }
2221
2222        int n = removeInvalidPlotData(data, xData, yRange);
2223        if (n < data[0].length) {
2224            double[][] dataNew = new double[data.length][n];
2225            for (int i = 0; i < data.length; i++)
2226                System.arraycopy(data[i], 0, dataNew[i], 0, n);
2227
2228            data = dataNew;
2229
2230            if (xData != null) {
2231                double[] xDataNew = new double[n];
2232                System.arraycopy(xData, 0, xDataNew, 0, n);
2233                xData = xDataNew;
2234            }
2235        }
2236
2237        // allow to draw a flat line: all values are the same
2238        if (yRange[0] == yRange[1]) {
2239            yRange[1] += 1;
2240            yRange[0] -= 1;
2241        }
2242        else if (yRange[0] > yRange[1]) {
2243            shell.getDisplay().beep();
2244            Tools.showError(shell, "Select",
2245                            "Cannot show line plot for the selected data. \n"
2246                                + "Please check the data range: (" + yRange[0] + ", " + yRange[1] + ").");
2247            return;
2248        }
2249        if (xData == null) { // use array index and length for x data range
2250            xData    = new double[2];
2251            xData[0] = indexBase;                              // 1- or zero-based
2252            xData[1] = data[0].length + (double)indexBase - 1; // maximum index
2253        }
2254
2255        Chart cv = new Chart(shell, title, Chart.LINEPLOT, data, xData, yRange);
2256        cv.setLineLabels(lineLabels);
2257
2258        String cname = dataValue.getClass().getName();
2259        char dname   = cname.charAt(cname.lastIndexOf('[') + 1);
2260        if ((dname == 'B') || (dname == 'S') || (dname == 'I') || (dname == 'J'))
2261            cv.setTypeToInteger();
2262
2263        cv.open();
2264    }
2265
2266    /**
2267     * Remove values of NaN, INF from the array.
2268     *
2269     * @param data
2270     *            the data array
2271     * @param xData
2272     *            the x-axis data points
2273     * @param yRange
2274     *            the range of data values
2275     *
2276     * @return number of data points in the plot data if successful; otherwise,
2277     *         returns false.
2278     */
2279    private int removeInvalidPlotData(double[][] data, double[] xData, double[] yRange)
2280    {
2281        int idx            = 0;
2282        boolean hasInvalid = false;
2283
2284        if (data == null || yRange == null)
2285            return -1;
2286
2287        yRange[0] = Double.POSITIVE_INFINITY;
2288        yRange[1] = Double.NEGATIVE_INFINITY;
2289
2290        for (int i = 0; i < data[0].length; i++) {
2291            hasInvalid = false;
2292
2293            for (int j = 0; j < data.length; j++) {
2294                hasInvalid = Tools.isNaNINF(data[j][i]);
2295                if (xData != null)
2296                    hasInvalid = hasInvalid || Tools.isNaNINF(xData[i]);
2297
2298                if (hasInvalid)
2299                    break;
2300                else {
2301                    data[j][idx] = data[j][i];
2302                    if (xData != null)
2303                        xData[idx] = xData[i];
2304                    yRange[0] = Math.min(yRange[0], data[j][idx]);
2305                    yRange[1] = Math.max(yRange[1], data[j][idx]);
2306                }
2307            }
2308
2309            if (!hasInvalid)
2310                idx++;
2311        }
2312
2313        return idx;
2314    }
2315
2316    /**
2317     * An implementation of a GridLayer with support for column grouping and with
2318     * editing triggered by a double click instead of a single click.
2319     */
2320    protected class EditingGridLayer extends GridLayer {
2321        /**
2322         * Create the Grid Layer with editing triggered by a
2323         *  double click instead of a single click.
2324         *
2325         * @param bodyLayer
2326         *        the body layer
2327         * @param columnHeaderLayer
2328         *        the Column Header layer
2329         * @param rowHeaderLayer
2330         *        the Row Header layer
2331         * @param cornerLayer
2332         *        the Corner Layer
2333         */
2334        public EditingGridLayer(ILayer bodyLayer, ILayer columnHeaderLayer, ILayer rowHeaderLayer,
2335                                ILayer cornerLayer)
2336        {
2337            super(bodyLayer, columnHeaderLayer, rowHeaderLayer, cornerLayer, false);
2338
2339            // Left-align cells, change font for rendering cell text
2340            // and add cell data display converter for displaying as
2341            // Hexadecimal, Binary, etc.
2342            this.addConfiguration(new AbstractRegistryConfiguration() {
2343                @Override
2344                public void configureRegistry(IConfigRegistry configRegistry)
2345                {
2346                    Style cellStyle = new Style();
2347
2348                    cellStyle.setAttributeValue(CellStyleAttributes.HORIZONTAL_ALIGNMENT,
2349                                                HorizontalAlignmentEnum.LEFT);
2350                    cellStyle.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR,
2351                                                Display.getCurrent().getSystemColor(SWT.COLOR_WHITE));
2352
2353                    if (curFont != null)
2354                        cellStyle.setAttributeValue(CellStyleAttributes.FONT, curFont);
2355                    else
2356                        cellStyle.setAttributeValue(CellStyleAttributes.FONT,
2357                                                    Display.getDefault().getSystemFont());
2358
2359                    configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_STYLE, cellStyle,
2360                                                           DisplayMode.NORMAL, GridRegion.BODY);
2361
2362                    configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_STYLE, cellStyle,
2363                                                           DisplayMode.SELECT, GridRegion.BODY);
2364
2365                    // Add data display conversion capability
2366                    try {
2367                        dataDisplayConverter =
2368                            DataDisplayConverterFactory.getDataDisplayConverter(dataObject);
2369
2370                        configRegistry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER,
2371                                                               dataDisplayConverter, DisplayMode.NORMAL,
2372                                                               GridRegion.BODY);
2373                    }
2374                    catch (Exception ex) {
2375                        log.debug("EditingGridLayer: failed to retrieve a DataDisplayConverter: ", ex);
2376                        dataDisplayConverter = null;
2377                    }
2378                }
2379            });
2380
2381            if (isStdRef || isRegRef || isObjRef) {
2382                // Show data pointed to by reference on double click
2383                this.addConfiguration(new AbstractUiBindingConfiguration() {
2384                    @Override
2385                    public void configureUiBindings(UiBindingRegistry uiBindingRegistry)
2386                    {
2387                        uiBindingRegistry.registerDoubleClickBinding(
2388                            new MouseEventMatcher(), new IMouseAction() {
2389                                @Override
2390                                public void run(NatTable table, MouseEvent event)
2391                                {
2392                                    if (!(isStdRef || isRegRef || isObjRef))
2393                                        return;
2394
2395                                    viewType = ViewType.TABLE;
2396
2397                                    Object theData = null;
2398                                    try {
2399                                        theData = ((Dataset)getDataObject()).getData();
2400                                    }
2401                                    catch (Exception ex) {
2402                                        log.debug("show reference data: ", ex);
2403                                        theData = null;
2404                                        Tools.showError(shell, "Select", ex.getMessage());
2405                                    }
2406
2407                                    if (theData == null) {
2408                                        shell.getDisplay().beep();
2409                                        Tools.showError(shell, "Select", "No data selected.");
2410                                        return;
2411                                    }
2412
2413                                    // Since NatTable returns the selected row positions as a Set<Range>,
2414                                    // convert this to an Integer[]
2415                                    Set<Range> rowPositions     = selectionLayer.getSelectedRowPositions();
2416                                    Set<Integer> selectedRowPos = new LinkedHashSet<>();
2417                                    Iterator<Range> i1          = rowPositions.iterator();
2418                                    while (i1.hasNext()) {
2419                                        selectedRowPos.addAll(i1.next().getMembers());
2420                                    }
2421
2422                                    Integer[] selectedRows = selectedRowPos.toArray(new Integer[0]);
2423                                    if (selectedRows == null || selectedRows.length <= 0) {
2424                                        log.debug("show reference data: no data selected");
2425                                        Tools.showError(shell, "Select", "No data selected.");
2426                                        return;
2427                                    }
2428                                    int len = Array.getLength(selectedRows);
2429                                    for (int i = 0; i < len; i++) {
2430                                        byte[] rElements = null;
2431                                        if (theData instanceof ArrayList)
2432                                            rElements = (byte[])((ArrayList)theData).get(selectedRows[i]);
2433                                        else
2434                                            rElements = (byte[])theData;
2435
2436                                        if (isStdRef)
2437                                            showStdRefData(rElements);
2438                                        else if (isRegRef)
2439                                            showRegRefData(rElements);
2440                                        else if (isObjRef)
2441                                            showObjRefData(rElements);
2442                                    }
2443                                }
2444                            });
2445                    }
2446                });
2447            }
2448            else {
2449                // Add default bindings for editing
2450                this.addConfiguration(new DefaultEditConfiguration());
2451
2452                // Register cell editing rules with the table and add
2453                // data validation
2454                this.addConfiguration(new AbstractRegistryConfiguration() {
2455                    @Override
2456                    public void configureRegistry(IConfigRegistry configRegistry)
2457                    {
2458                        IEditableRule editingRule = getDataEditingRule(dataObject);
2459                        if (editingRule != null) {
2460                            // Register cell editing rules with table
2461                            configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITABLE_RULE,
2462                                                                   editingRule, DisplayMode.EDIT);
2463                        }
2464
2465                        // Add data validator and validation error handler
2466                        DataValidator validator = null;
2467                        try {
2468                            validator = DataValidatorFactory.getDataValidator(dataObject);
2469                        }
2470                        catch (Exception ex) {
2471                            log.debug(
2472                                "EditingGridLayer: no DataValidator retrieved, data editing will be disabled");
2473                        }
2474
2475                        if (validator != null) {
2476                            configRegistry.registerConfigAttribute(EditConfigAttributes.DATA_VALIDATOR,
2477                                                                   validator, DisplayMode.EDIT,
2478                                                                   GridRegion.BODY);
2479                        }
2480
2481                        configRegistry.registerConfigAttribute(EditConfigAttributes.VALIDATION_ERROR_HANDLER,
2482                                                               new DialogErrorHandling(), DisplayMode.EDIT,
2483                                                               GridRegion.BODY);
2484                    }
2485                });
2486
2487                // Change cell editing to be on double click rather than single click
2488                // and allow editing of cells by pressing keys as well
2489                this.addConfiguration(new AbstractUiBindingConfiguration() {
2490                    @Override
2491                    public void configureUiBindings(UiBindingRegistry uiBindingRegistry)
2492                    {
2493                        uiBindingRegistry.registerFirstKeyBinding(new LetterOrDigitKeyEventMatcher(),
2494                                                                  new KeyEditAction());
2495                        uiBindingRegistry.registerFirstDoubleClickBinding(new CellEditorMouseEventMatcher(),
2496                                                                          new MouseEditAction());
2497                    }
2498                });
2499            }
2500        }
2501    }
2502
2503    /**
2504     * An implementation of the table's Row Header which adapts to the current font.
2505     */
2506    protected class RowHeader extends RowHeaderLayer {
2507        /**
2508         * Create the RowHeader which adapts to the current font.
2509         *
2510         * @param baseLayer
2511         *        the base layer
2512         * @param verticalLayerDependency
2513         *        the vertical layer dependency
2514         * @param selectionLayer
2515         *        the selection layer
2516         */
2517        public RowHeader(IUniqueIndexLayer baseLayer, ILayer verticalLayerDependency,
2518                         SelectionLayer selectionLayer)
2519        {
2520            super(baseLayer, verticalLayerDependency, selectionLayer);
2521
2522            this.addConfiguration(new DefaultRowHeaderLayerConfiguration() {
2523                @Override
2524                public void addRowHeaderStyleConfig()
2525                {
2526                    this.addConfiguration(new DefaultRowHeaderStyleConfiguration() {
2527                        {
2528                            this.cellPainter = new LineBorderDecorator(new TextPainter(false, true, 2, true));
2529                            this.bgColor =
2530                                Display.getDefault().getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
2531                            this.font = (curFont == null) ? Display.getDefault().getSystemFont() : curFont;
2532                        }
2533                    });
2534                }
2535            });
2536        }
2537    }
2538
2539    /**
2540     * Custom Row Header data provider to set row indices based on Index Base for
2541     * both Scalar Datasets and Compound Datasets.
2542     */
2543    protected class RowHeaderDataProvider implements IDataProvider {
2544        private int rank;
2545        private int space_type;
2546        private long[] dims;
2547        private long[] startArray;
2548        private long[] strideArray;
2549        private int[] selectedIndex;
2550
2551        /** the start value. */
2552        protected int start;
2553        /** the stride value. */
2554        protected int stride;
2555
2556        private int nrows;
2557
2558        /**
2559         * Create the Row Header data provider to set row indices based on Index Base for
2560         *  both Scalar Datasets and Compound Datasets.
2561         *
2562         * @param theDataObject
2563         *        the data object
2564         */
2565        public RowHeaderDataProvider(DataFormat theDataObject)
2566        {
2567            this.space_type    = theDataObject.getSpaceType();
2568            this.rank          = theDataObject.getRank();
2569            this.dims          = theDataObject.getSelectedDims();
2570            this.startArray    = theDataObject.getStartDims();
2571            this.strideArray   = theDataObject.getStride();
2572            this.selectedIndex = theDataObject.getSelectedIndex();
2573
2574            if (rank > 1)
2575                this.nrows = (int)theDataObject.getHeight();
2576            else
2577                this.nrows = (int)dims[0];
2578
2579            start  = (int)startArray[selectedIndex[0]];
2580            stride = (int)strideArray[selectedIndex[0]];
2581        }
2582
2583        /**
2584         * Update the Row Header data provider to set row indices based on Index Base for
2585         *  both Scalar Datasets and Compound Datasets.
2586         *
2587         * @param theDataObject
2588         *        the data object
2589         */
2590        public void updateRows(DataFormat theDataObject)
2591        {
2592            this.rank          = theDataObject.getRank();
2593            this.dims          = theDataObject.getSelectedDims();
2594            this.selectedIndex = theDataObject.getSelectedIndex();
2595
2596            if (rank > 1)
2597                this.nrows = (int)theDataObject.getHeight();
2598            else
2599                this.nrows = (int)dims[0];
2600        }
2601
2602        @Override
2603        public int getColumnCount()
2604        {
2605            return 1;
2606        }
2607
2608        @Override
2609        public int getRowCount()
2610        {
2611            return nrows;
2612        }
2613
2614        @Override
2615        public Object getDataValue(int columnIndex, int rowIndex)
2616        {
2617            return String.valueOf(start + indexBase + (rowIndex * stride));
2618        }
2619
2620        @Override
2621        public void setDataValue(int columnIndex, int rowIndex, Object newValue)
2622        {
2623            // Intentional
2624        }
2625    }
2626
2627    /**
2628     * An implementation of the table's Column Header which adapts to the current
2629     * font.
2630     */
2631    protected class ColumnHeader extends ColumnHeaderLayer {
2632        /**
2633         * Create the ColumnHeader which adapts to the current font.
2634         *
2635         * @param baseLayer
2636         *        the base layer
2637         * @param horizontalLayerDependency
2638         *        the horizontal layer dependency
2639         * @param selectionLayer
2640         *        the selection layer
2641         */
2642        public ColumnHeader(IUniqueIndexLayer baseLayer, ILayer horizontalLayerDependency,
2643                            SelectionLayer selectionLayer)
2644        {
2645            super(baseLayer, horizontalLayerDependency, selectionLayer);
2646
2647            this.addConfiguration(new DefaultColumnHeaderLayerConfiguration() {
2648                @Override
2649                public void addColumnHeaderStyleConfig()
2650                {
2651                    this.addConfiguration(new DefaultColumnHeaderStyleConfiguration() {
2652                        {
2653                            this.cellPainter =
2654                                new BeveledBorderDecorator(new TextPainter(false, true, 2, true));
2655                            this.bgColor = Display.getDefault().getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW);
2656                            this.font    = (curFont == null) ? Display.getDefault().getSystemFont() : curFont;
2657                        }
2658                    });
2659                }
2660            });
2661        }
2662    }
2663
2664    /** Context-menu for dealing with region and object references */
2665    protected class RefContextMenu extends AbstractUiBindingConfiguration {
2666        private final Menu contextMenu;
2667
2668        /**
2669         * Create the Context-menu for dealing with region and object references.
2670         *
2671         * @param table
2672         *        the NatTable object
2673         */
2674        public RefContextMenu(NatTable table) { this.contextMenu = createMenu(table).build(); }
2675
2676        private void showRefTable()
2677        {
2678            log.trace("show reference data: Show data as {}", viewType);
2679
2680            Object theData = getSelectedData();
2681            if (theData == null) {
2682                shell.getDisplay().beep();
2683                Tools.showError(shell, "Select", "No data selected.");
2684                return;
2685            }
2686            if (!(theData instanceof byte[]) && !(theData instanceof ArrayList)) {
2687                shell.getDisplay().beep();
2688                Tools.showError(shell, "Select", "Data selected is not a reference.");
2689                return;
2690            }
2691            log.trace("show reference data: Data is {}", theData);
2692
2693            // Since NatTable returns the selected row positions as a Set<Range>, convert
2694            // this to an Integer[]
2695            Set<Range> rowPositions     = selectionLayer.getSelectedRowPositions();
2696            Set<Integer> selectedRowPos = new LinkedHashSet<>();
2697            Iterator<Range> i1          = rowPositions.iterator();
2698            while (i1.hasNext())
2699                selectedRowPos.addAll(i1.next().getMembers());
2700
2701            Integer[] selectedRows = selectedRowPos.toArray(new Integer[0]);
2702            int[] selectedCols     = selectionLayer.getSelectedColumnPositions();
2703            if (selectedRows == null || selectedRows.length <= 0) {
2704                shell.getDisplay().beep();
2705                Tools.showError(shell, "Select", "No data selected.");
2706                log.trace("show reference data: Show data as {}: selectedRows is empty", viewType);
2707                return;
2708            }
2709
2710            int len = Array.getLength(selectedRows) * Array.getLength(selectedCols);
2711            log.trace("show reference data: Show data as {}: len={}", viewType, len);
2712            if (len > 1) {
2713                shell.getDisplay().beep();
2714                Tools.showError(shell, "Select", "Reference selection must be one cell.");
2715                log.trace("show reference data: Show data as {}: Too much data", viewType);
2716                return;
2717            }
2718
2719            for (int i = 0; i < len; i++) {
2720                byte[] rElements = null;
2721                if (theData instanceof ArrayList)
2722                    rElements = (byte[])((ArrayList)theData).get(i);
2723                else
2724                    rElements = (byte[])theData;
2725
2726                if (rElements.length == HDF5Constants.H5R_DSET_REG_REF_BUF_SIZE) {
2727                    showRegRefData(rElements);
2728                }
2729                else if (rElements.length == HDF5Constants.H5R_OBJ_REF_BUF_SIZE) {
2730                    showObjRefData(rElements);
2731                }
2732                else {
2733                    showStdRefData(rElements);
2734                }
2735            }
2736        }
2737
2738        private PopupMenuBuilder createMenu(NatTable table)
2739        {
2740            Menu menu = new Menu(table);
2741
2742            MenuItem item = new MenuItem(menu, SWT.PUSH);
2743            item.setText("Show As &Table");
2744            item.addSelectionListener(new SelectionAdapter() {
2745                @Override
2746                public void widgetSelected(SelectionEvent e)
2747                {
2748                    viewType = ViewType.TABLE;
2749                    showRefTable();
2750                }
2751            });
2752
2753            item = new MenuItem(menu, SWT.PUSH);
2754            item.setText("Show As &Image");
2755            item.addSelectionListener(new SelectionAdapter() {
2756                @Override
2757                public void widgetSelected(SelectionEvent e)
2758                {
2759                    viewType = ViewType.IMAGE;
2760                    showRefTable();
2761                }
2762            });
2763
2764            return new PopupMenuBuilder(table, menu);
2765        }
2766
2767        @Override
2768        public void configureUiBindings(UiBindingRegistry uiBindingRegistry)
2769        {
2770            uiBindingRegistry.registerMouseDownBinding(
2771                new MouseEventMatcher(SWT.NONE, GridRegion.BODY, MouseEventMatcher.RIGHT_BUTTON),
2772                new PopupMenuAction(this.contextMenu));
2773        }
2774    }
2775
2776    private class LinePlotOption extends Dialog {
2777        private Shell linePlotOptionShell;
2778
2779        private Button rowButton, colButton;
2780
2781        private Combo rowBox, colBox;
2782
2783        public static final int NO_PLOT     = -1;
2784        public static final int ROW_PLOT    = 0;
2785        public static final int COLUMN_PLOT = 1;
2786
2787        private int nrow, ncol;
2788
2789        private int idx_xaxis = -1;
2790        private int plotType  = -1;
2791
2792        public LinePlotOption(Shell parent, int style, int nrow, int ncol)
2793        {
2794            super(parent, style);
2795
2796            this.nrow = nrow;
2797            this.ncol = ncol;
2798        }
2799
2800        public void open()
2801        {
2802            Shell parent        = getParent();
2803            linePlotOptionShell = new Shell(parent, SWT.SHELL_TRIM | SWT.APPLICATION_MODAL);
2804            linePlotOptionShell.setFont(curFont);
2805            linePlotOptionShell.setText("Line Plot Options -- " + ((HObject)dataObject).getName());
2806            linePlotOptionShell.setImages(ViewProperties.getHdfIcons());
2807            linePlotOptionShell.setLayout(new GridLayout(1, true));
2808
2809            Label label = new Label(linePlotOptionShell, SWT.RIGHT);
2810            label.setFont(curFont);
2811            label.setText("Select Line Plot Options:");
2812
2813            Composite content = new Composite(linePlotOptionShell, SWT.BORDER);
2814            content.setLayout(new GridLayout(3, false));
2815            content.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
2816
2817            label = new Label(content, SWT.RIGHT);
2818            label.setFont(curFont);
2819            label.setText(" Series in:");
2820
2821            colButton = new Button(content, SWT.RADIO);
2822            colButton.setFont(curFont);
2823            colButton.setText("Column");
2824            colButton.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, false, false));
2825            colButton.addSelectionListener(new SelectionAdapter() {
2826                @Override
2827                public void widgetSelected(SelectionEvent e)
2828                {
2829                    colBox.setEnabled(true);
2830                    rowBox.setEnabled(false);
2831                }
2832            });
2833
2834            rowButton = new Button(content, SWT.RADIO);
2835            rowButton.setFont(curFont);
2836            rowButton.setText("Row");
2837            rowButton.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, false, false));
2838            rowButton.addSelectionListener(new SelectionAdapter() {
2839                @Override
2840                public void widgetSelected(SelectionEvent e)
2841                {
2842                    rowBox.setEnabled(true);
2843                    colBox.setEnabled(false);
2844                }
2845            });
2846
2847            label = new Label(content, SWT.RIGHT);
2848            label.setFont(curFont);
2849            label.setText(" For abscissa use:");
2850
2851            long[] startArray   = dataObject.getStartDims();
2852            long[] strideArray  = dataObject.getStride();
2853            int[] selectedIndex = dataObject.getSelectedIndex();
2854            int start           = (int)startArray[selectedIndex[0]];
2855            int stride          = (int)strideArray[selectedIndex[0]];
2856
2857            colBox = new Combo(content, SWT.SINGLE | SWT.READ_ONLY);
2858            colBox.setFont(curFont);
2859            GridData colBoxData     = new GridData(SWT.FILL, SWT.FILL, true, false);
2860            colBoxData.minimumWidth = 100;
2861            colBox.setLayoutData(colBoxData);
2862
2863            colBox.add("array index");
2864
2865            for (int i = 0; i < ncol; i++)
2866                colBox.add("column " + columnHeaderDataProvider.getDataValue(i, 0));
2867
2868            rowBox = new Combo(content, SWT.SINGLE | SWT.READ_ONLY);
2869            rowBox.setFont(curFont);
2870            GridData rowBoxData     = new GridData(SWT.FILL, SWT.FILL, true, false);
2871            rowBoxData.minimumWidth = 100;
2872            rowBox.setLayoutData(rowBoxData);
2873
2874            rowBox.add("array index");
2875
2876            for (int i = 0; i < nrow; i++)
2877                rowBox.add("row " + (start + indexBase + i * stride));
2878
2879            // Create Ok/Cancel button region
2880            Composite buttonComposite = new Composite(linePlotOptionShell, SWT.NONE);
2881            buttonComposite.setLayout(new GridLayout(2, true));
2882            buttonComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));
2883
2884            Button okButton = new Button(buttonComposite, SWT.PUSH);
2885            okButton.setFont(curFont);
2886            okButton.setText("   &OK   ");
2887            okButton.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
2888            okButton.addSelectionListener(new SelectionAdapter() {
2889                @Override
2890                public void widgetSelected(SelectionEvent e)
2891                {
2892                    if (colButton.getSelection()) {
2893                        idx_xaxis = colBox.getSelectionIndex() - 1;
2894                        plotType  = COLUMN_PLOT;
2895                    }
2896                    else {
2897                        idx_xaxis = rowBox.getSelectionIndex() - 1;
2898                        plotType  = ROW_PLOT;
2899                    }
2900
2901                    linePlotOptionShell.dispose();
2902                }
2903            });
2904
2905            Button cancelButton = new Button(buttonComposite, SWT.PUSH);
2906            cancelButton.setFont(curFont);
2907            cancelButton.setText(" &Cancel ");
2908            cancelButton.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, true, false));
2909            cancelButton.addSelectionListener(new SelectionAdapter() {
2910                @Override
2911                public void widgetSelected(SelectionEvent e)
2912                {
2913                    plotType = NO_PLOT;
2914                    linePlotOptionShell.dispose();
2915                }
2916            });
2917
2918            colButton.setSelection(true);
2919            rowButton.setSelection(false);
2920
2921            colBox.select(0);
2922            rowBox.select(0);
2923
2924            colBox.setEnabled(colButton.getSelection());
2925            rowBox.setEnabled(rowButton.getSelection());
2926
2927            linePlotOptionShell.pack();
2928
2929            linePlotOptionShell.setMinimumSize(linePlotOptionShell.computeSize(SWT.DEFAULT, SWT.DEFAULT));
2930
2931            Rectangle parentBounds = parent.getBounds();
2932            Point shellSize        = linePlotOptionShell.getSize();
2933            linePlotOptionShell.setLocation((parentBounds.x + (parentBounds.width / 2)) - (shellSize.x / 2),
2934                                            (parentBounds.y + (parentBounds.height / 2)) - (shellSize.y / 2));
2935
2936            linePlotOptionShell.open();
2937
2938            Display display = parent.getDisplay();
2939            while (!linePlotOptionShell.isDisposed())
2940                if (!display.readAndDispatch())
2941                    display.sleep();
2942        }
2943
2944        int getXindex() { return idx_xaxis; }
2945
2946        int getPlotBy() { return plotType; }
2947    }
2948}