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