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.TreeView;
016
017import java.io.BufferedInputStream;
018import java.io.BufferedOutputStream;
019import java.io.File;
020import java.io.FileInputStream;
021import java.io.FileNotFoundException;
022import java.io.FileOutputStream;
023import java.io.Serializable;
024import java.lang.reflect.Method;
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.BitSet;
028import java.util.Enumeration;
029import java.util.HashMap;
030import java.util.Iterator;
031import java.util.LinkedList;
032import java.util.List;
033import java.util.Queue;
034
035import org.slf4j.Logger;
036import org.slf4j.LoggerFactory;
037
038import org.eclipse.swt.SWT;
039import org.eclipse.swt.events.DisposeEvent;
040import org.eclipse.swt.events.DisposeListener;
041import org.eclipse.swt.events.KeyAdapter;
042import org.eclipse.swt.events.KeyEvent;
043import org.eclipse.swt.events.MenuAdapter;
044import org.eclipse.swt.events.MenuDetectEvent;
045import org.eclipse.swt.events.MenuDetectListener;
046import org.eclipse.swt.events.MenuEvent;
047import org.eclipse.swt.events.MouseAdapter;
048import org.eclipse.swt.events.MouseEvent;
049import org.eclipse.swt.events.SelectionAdapter;
050import org.eclipse.swt.events.SelectionEvent;
051import org.eclipse.swt.graphics.Font;
052import org.eclipse.swt.graphics.Image;
053import org.eclipse.swt.graphics.Point;
054import org.eclipse.swt.graphics.Rectangle;
055import org.eclipse.swt.layout.GridData;
056import org.eclipse.swt.layout.GridLayout;
057import org.eclipse.swt.widgets.Button;
058import org.eclipse.swt.widgets.Combo;
059import org.eclipse.swt.widgets.Composite;
060import org.eclipse.swt.widgets.Dialog;
061import org.eclipse.swt.widgets.Display;
062import org.eclipse.swt.widgets.Event;
063import org.eclipse.swt.widgets.FileDialog;
064import org.eclipse.swt.widgets.Label;
065import org.eclipse.swt.widgets.Listener;
066import org.eclipse.swt.widgets.Menu;
067import org.eclipse.swt.widgets.MenuItem;
068import org.eclipse.swt.widgets.Shell;
069import org.eclipse.swt.widgets.Tree;
070import org.eclipse.swt.widgets.TreeItem;
071
072import hdf.hdf5lib.HDF5Constants;
073
074import hdf.object.CompoundDS;
075import hdf.object.DataFormat;
076import hdf.object.Dataset;
077import hdf.object.Datatype;
078import hdf.object.FileFormat;
079import hdf.object.Group;
080import hdf.object.HObject;
081import hdf.object.MetaDataContainer;
082import hdf.object.ScalarDS;
083
084import hdf.view.DefaultFileFilter;
085import hdf.view.HDFView;
086import hdf.view.Tools;
087import hdf.view.ViewProperties;
088import hdf.view.ViewProperties.DATA_VIEW_KEY;
089import hdf.view.ViewProperties.DataViewType;
090import hdf.view.DataView.DataView;
091import hdf.view.DataView.DataViewFactory;
092import hdf.view.DataView.DataViewFactoryProducer;
093import hdf.view.DataView.DataViewManager;
094import hdf.view.MetaDataView.MetaDataView;
095import hdf.view.dialog.DataOptionDialog;
096import hdf.view.dialog.InputDialog;
097import hdf.view.dialog.NewCompoundDatasetDialog;
098import hdf.view.dialog.NewDatasetDialog;
099import hdf.view.dialog.NewDatatypeDialog;
100import hdf.view.dialog.NewGroupDialog;
101import hdf.view.dialog.NewImageDialog;
102import hdf.view.dialog.NewLinkDialog;
103
104/**
105 * TreeView defines APIs for opening files and displaying the file structure in
106 * a tree structure.
107 *
108 * TreeView uses folders and leaf items to represent groups and data objects in
109 * the file. You can expand or collapse folders to navigate data objects in the
110 * file.
111 *
112 * From the TreeView, you can open data content or metadata of the selected object.
113 * You can select object(s) to delete or add new objects to the file.
114 *
115 * @author Jordan T. Henderson
116 * @version 2.4 12//2015
117 */
118public class DefaultTreeView implements TreeView {
119
120    private static final Logger log = LoggerFactory.getLogger(DefaultTreeView.class);
121
122    private Shell                         shell;
123
124    private Font                          curFont;
125
126    /** The owner of this TreeView */
127    private DataViewManager               viewer;
128
129    /** Thread to load TableView Data in the background */
130    private LoadDataThread                loadDataThread;
131
132    /**
133     * The tree which holds file structures.
134     */
135    private final Tree                    tree;
136
137    /** The currently selected tree item */
138    private TreeItem                      selectedItem = null;
139
140    /** The list of current selected objects for copying */
141    private TreeItem[]                    objectsToCopy = null;
142
143    private TreeItem[]                    currentSelectionsForMove = null;
144
145    /** The currently selected object */
146    private HObject                       selectedObject;
147
148    /** The currently selected file */
149    private FileFormat                    selectedFile;
150
151    /** Maintains a list of TreeItems in the tree in breadth-first order
152     * to prevent many calls of getAllItemsBreadthFirst.
153     */
154    //private ArrayList<TreeItem>         breadthFirstItems = null;
155
156    /** A list of currently open files */
157    private final List<FileFormat>        fileList = new ArrayList<>();
158
159    /** A list of editing GUI components */
160    private List<MenuItem>                editGUIs = new ArrayList<>();
161
162    /**
163     * The popup menu used to display user choice of actions on data object.
164     */
165    private final Menu                    popupMenu;
166
167    private Menu                          newObjectMenu;
168    private Menu                          exportDatasetMenu;
169
170    private MenuItem                      openVirtualFilesMenuItem;
171    private MenuItem                      addDatasetMenuItem;
172    private MenuItem                      exportDatasetMenuItem;
173    private MenuItem                      addTableMenuItem;
174    private MenuItem                      addDatatypeMenuItem;
175    private MenuItem                      addLinkMenuItem;
176    private MenuItem                      setLibVerBoundsItem;
177    private MenuItem                      changeIndexItem;
178
179    /** Keep Image instances to prevent many calls to ViewProperties.getTypeIcon() */
180    private Image h4Icon = ViewProperties.getH4Icon();
181    private Image h4IconR = ViewProperties.getH4IconR();
182    private Image h5Icon = ViewProperties.getH5Icon();
183    private Image h5IconR = ViewProperties.getH5IconR();
184    private Image nc3Icon = ViewProperties.getNC3Icon();
185    private Image nc3IconR = ViewProperties.getNC3IconR();
186    private Image imageIcon = ViewProperties.getImageIcon();
187    private Image imageIconA = ViewProperties.getImageIconA();
188    private Image textIcon = ViewProperties.getTextIcon();
189    private Image textIconA = ViewProperties.getTextIconA();
190    private Image datasetIcon = ViewProperties.getDatasetIcon();
191    private Image datasetIconA = ViewProperties.getDatasetIconA();
192    private Image tableIcon = ViewProperties.getTableIcon();
193    private Image tableIconA = ViewProperties.getTableIconA();
194    private Image datatypeIcon = ViewProperties.getDatatypeIcon();
195    private Image datatypeIconA = ViewProperties.getDatatypeIconA();
196    private Image folderCloseIcon = ViewProperties.getFoldercloseIcon();
197    private Image folderCloseIconA = ViewProperties.getFoldercloseIconA();
198    private Image folderOpenIcon = ViewProperties.getFolderopenIcon();
199    private Image folderOpenIconA = ViewProperties.getFolderopenIconA();
200    private Image questionIcon = ViewProperties.getQuestionIcon();
201
202    /** Flag to indicate if the dataset is displayed as default */
203    private boolean                       isDefaultDisplay = true;
204
205    /** Flag to indicate if TreeItems are being moved */
206    private boolean                       moveFlag = false;
207
208    private boolean                       isApplyBitmaskOnly  = false;
209
210    private int                           binaryOrder;
211
212    private String                        currentSearchPhrase = null;
213
214    /** Used to open a File using a temporary indexing type and order */
215    private int                           tempIdxType = -1;
216    private int                           tempIdxOrder = -1;
217
218    private enum OBJECT_TYPE {GROUP, DATASET, IMAGE, TABLE, DATATYPE, LINK};
219
220    /**
221     * Create a visual component for opening files and displaying the file
222     * structure in a tree structure.
223     *
224     * @param parent
225     *        the parent component
226     * @param theView
227     *        the associated data view manager
228     */
229    public DefaultTreeView(Composite parent, DataViewManager theView) {
230        viewer = theView;
231        shell = parent.getShell();
232
233        try {
234            curFont = new Font(
235                    Display.getCurrent(),
236                    ViewProperties.getFontType(),
237                    ViewProperties.getFontSize(),
238                    SWT.NORMAL);
239        }
240        catch (Exception ex) {
241            curFont = null;
242        }
243
244        // Initialize the Tree
245        tree = new Tree(parent, SWT.MULTI | SWT.VIRTUAL);
246        tree.setSize(tree.computeSize(SWT.DEFAULT, SWT.DEFAULT));
247        tree.setFont(curFont);
248
249        // Create the context menu for the Tree
250        popupMenu = createPopupMenu();
251        tree.setMenu(popupMenu);
252
253        // Handle tree key events
254        tree.addKeyListener(new KeyAdapter() {
255            @Override
256            public void keyReleased(KeyEvent e) {
257                int key = e.keyCode;
258
259                if (key == SWT.ARROW_DOWN || key == SWT.ARROW_UP || key == SWT.KEYPAD_2 || key == SWT.KEYPAD_8) {
260                    TreeItem[] selectedItems = tree.getSelection();
261                    TreeItem theItem = selectedItems[0];
262
263                    if(theItem.equals(selectedItem)) return;
264
265                    selectedItem = theItem;
266                    selectedObject = ((HObject) (selectedItem.getData()));
267                    FileFormat theFile = selectedObject.getFileFormat();
268                    if ((theFile != null) && !theFile.equals(selectedFile)) {
269                        // A different file is selected, handle only one file at a time
270                        selectedFile = theFile;
271                        tree.deselectAll();
272                    }
273
274                    ((HDFView) viewer).showMetaData(selectedObject);
275                }
276                else if (key == SWT.ARROW_LEFT || key == SWT.KEYPAD_4) {
277                    if(selectedObject instanceof Group) {
278                        selectedItem.setExpanded(false);
279
280                        Event collapse = new Event();
281                        collapse.item = selectedItem;
282
283                        tree.notifyListeners(SWT.Collapse, collapse);
284                    }
285                }
286                else if (key == SWT.ARROW_RIGHT || key == SWT.KEYPAD_6) {
287                    if(selectedObject instanceof Group) {
288                        selectedItem.setExpanded(true);
289
290                        Event expand = new Event();
291                        expand.item = selectedItem;
292
293                        tree.notifyListeners(SWT.Expand, expand);
294                    }
295                }
296            }
297        });
298
299        /**
300         * If user presses Enter on a TreeItem, expand/collapse the item if it
301         * is a group, or try to show the data content if it is a data object.
302         */
303        tree.addListener(SWT.Traverse, new Listener() {
304            @Override
305            public void handleEvent(Event event) {
306                if (event.detail != SWT.TRAVERSE_RETURN)
307                    return;
308
309                TreeItem item = selectedItem;
310                if(item == null)
311                    return;
312
313                final HObject obj = (HObject) item.getData();
314                if(obj == null)
315                    return;
316
317                if(obj instanceof Group) {
318                    boolean isExpanded = item.getExpanded();
319
320                    item.setExpanded(!isExpanded);
321
322                    Event expand = new Event();
323                    expand.item = item;
324
325                    if(isExpanded)
326                        tree.notifyListeners(SWT.Collapse, expand);
327                    else
328                        tree.notifyListeners(SWT.Expand, expand);
329                }
330                else {
331                    try {
332                        loadDataThread = new LoadDataThread();
333                        loadDataThread.start();
334                    }
335                    catch (Exception err) {
336                        shell.getDisplay().beep();
337                        Tools.showError(shell, "Select", err.getMessage());
338                    }
339                }
340            }
341        });
342
343        /**
344         * Handle mouse clicks on data objects in the tree view. A right mouse-click
345         * to show the popup menu for user choice. A double left-mouse-click to
346         * display the data content. A single left-mouse-click to select the current
347         * data object.
348         */
349        tree.addMouseListener(new MouseAdapter() {
350            // Double click opens data content of selected data object
351            @Override
352            public void mouseDoubleClick(MouseEvent e) {
353                isDefaultDisplay = true;
354
355                try {
356                    if(!(selectedObject instanceof Group)) {
357                        loadDataThread = new LoadDataThread();
358                        loadDataThread.start();
359                    }
360                    else {
361                        boolean isExpanded = selectedItem.getExpanded();
362
363                        selectedItem.setExpanded(!isExpanded);
364
365                        Event expand = new Event();
366                        expand.item = selectedItem;
367
368                        if(isExpanded)
369                            tree.notifyListeners(SWT.Collapse, expand);
370                        else
371                            tree.notifyListeners(SWT.Expand, expand);
372                    }
373                }
374                catch (Exception ex) {
375                    log.trace("defaultDisplay showDataContent failed: {}", ex.getMessage());
376                    ex.printStackTrace();
377                }
378            }
379
380            // When a mouse release is detected, attempt to set the selected item
381            // and object to the TreeItem under the pointer
382            @Override
383            public void mouseUp(MouseEvent e) {
384                // Make sure user clicked on a TreeItem
385                TreeItem theItem = tree.getItem(new Point(e.x, e.y));
386
387                if (theItem == null) {
388                    tree.deselectAll();
389                    selectedItem = null;
390                    selectedObject = null;
391                    selectedFile = null;
392
393                    // Clear any information shown in the object info panel
394                    ((HDFView) viewer).showMetaData(null);
395
396                    return;
397                }
398
399                if (theItem.equals(selectedItem))
400                    return;
401
402                FileFormat theFile = null;
403
404                selectedItem = theItem;
405
406                try {
407                    selectedObject = (HObject) selectedItem.getData();
408                }
409                catch(NullPointerException ex) {
410                    viewer.showError("Object " + selectedItem.getText() + " had no associated data.");
411                    return;
412                }
413
414                try {
415                    theFile = selectedObject.getFileFormat();
416                }
417                catch(NullPointerException ex) {
418                    viewer.showError("Error retrieving FileFormat of HObject " + selectedObject.getName() + ".");
419                    return;
420                }
421
422                if ((theFile != null) && !theFile.equals(selectedFile)) {
423                    // A different file is selected, handle only one file at a time
424                    selectedFile = theFile;
425                }
426
427                // Set this file to the most recently selected file in the recent files bar
428                Combo recentFilesCombo = ((HDFView) viewer).getUrlBar();
429                String filename = selectedFile.getAbsolutePath();
430
431                try {
432                    recentFilesCombo.remove(filename);
433                }
434                catch (Exception ex) {}
435
436                // first entry is always the workdir
437                recentFilesCombo.add(filename, 1);
438                recentFilesCombo.select(1);
439
440                ((HDFView) viewer).showMetaData(selectedObject);
441            }
442        });
443
444        // Show context menu only if user has selected a data object
445        tree.addMenuDetectListener(new MenuDetectListener() {
446            @Override
447            public void menuDetected(MenuDetectEvent e) {
448                Display display = Display.getDefault();
449
450                Point pt = display.map(null, tree, new Point(e.x, e.y));
451                TreeItem item = tree.getItem(pt);
452                if(item == null) { e.doit = false; return; }
453
454                FileFormat theFile = null;
455
456                selectedItem = item;
457
458                log.trace("tree.addMenuDetectListener(): selectedItem={}", selectedItem.getText());
459                try {
460                    selectedObject = (HObject) selectedItem.getData();
461                }
462                catch(NullPointerException ex) {
463                    viewer.showError("Object " + selectedItem.getText() + " had no associated data.");
464                    return;
465                }
466
467                try {
468                    theFile = selectedObject.getFileFormat();
469                }
470                catch(NullPointerException ex) {
471                    viewer.showError("Error retrieving FileFormat of HObject " + selectedObject.getName() + ".");
472                    return;
473                }
474
475                if ((theFile != null) && !theFile.equals(selectedFile)) {
476                    // A different file is selected, handle only one file at a time
477                    selectedFile = theFile;
478                    //tree.deselectAll();
479                    //tree.setSelection(selPath);
480                    log.trace("tree.addMenuDetectListener(): selectedFile={}", selectedFile.getAbsolutePath());
481                }
482
483                ((HDFView) viewer).showMetaData(selectedObject);
484
485                popupMenu.setLocation(display.map(tree, null, pt));
486                popupMenu.setVisible(true);
487            }
488        });
489
490        tree.addListener(SWT.Expand, new Listener() {
491            @Override
492            public void handleEvent(Event event) {
493                TreeItem item = (TreeItem) event.item;
494                Object obj = item.getData();
495
496                if (!(obj instanceof Group))
497                    return;
498
499                Group theGroup = (Group) item.getData();
500
501                if(theGroup.isRoot())
502                    return;
503
504                // Prevent graphical issues from happening by stopping
505                // tree from redrawing until all the items are created
506                tree.setRedraw(false);
507
508                if(item.getItemCount() > 0)
509                    item.setImage(theGroup.hasAttribute() ? folderOpenIconA : folderOpenIcon);
510
511                // Process any remaining SetData events and then allow
512                // the tree to redraw once all are finished
513                //                while(tree.getDisplay().readAndDispatch());
514
515                tree.setRedraw(true);
516            }
517        });
518
519        tree.addListener(SWT.Collapse, new Listener() {
520            @Override
521            public void handleEvent(Event event) {
522                TreeItem item = (TreeItem) event.item;
523                Object obj = item.getData();
524
525                if (!(obj instanceof Group))
526                    return;
527
528                Group theGroup = (Group) item.getData();
529
530                if(theGroup.isRoot())
531                    return;
532
533                item.setImage(theGroup.hasAttribute() ? folderCloseIconA : folderCloseIcon);
534            }
535        });
536
537        // When groups are expanded, populate TreeItems corresponding to file objects
538        // on demand.
539        tree.addListener(SWT.SetData, new Listener() {
540            @Override
541            public void handleEvent(Event event) {
542                TreeItem item = (TreeItem) event.item;
543                TreeItem parentItem = item.getParentItem();
544
545                int position = parentItem.indexOf(item);
546                HObject obj = ((Group) parentItem.getData()).getMember(position);
547
548                item.setData(obj);
549                item.setFont(curFont);
550                item.setText(obj.getName());
551                item.setImage(getObjectTypeImage(obj));
552
553                if(obj instanceof Group)
554                    item.setItemCount(((Group) obj).getMemberList().size());
555            }
556        });
557
558        tree.addDisposeListener(new DisposeListener() {
559            @Override
560            public void widgetDisposed(DisposeEvent e) {
561                if (curFont != null)
562                    curFont.dispose();
563            }
564        });
565    }
566
567    /** Creates a popup menu for a right mouse click on a data object */
568    private Menu createPopupMenu() {
569        Menu menu = new Menu(tree);
570        MenuItem item;
571
572        item = new MenuItem(menu, SWT.PUSH);
573        item.setText("&Open");
574        item.addSelectionListener(new SelectionAdapter() {
575            @Override
576            public void widgetSelected(SelectionEvent e) {
577                isDefaultDisplay = true;
578
579                try {
580                    loadDataThread = new LoadDataThread();
581                    loadDataThread.start();
582                }
583                catch (Exception err) {
584                    shell.getDisplay().beep();
585                    Tools.showError(shell, "Open", err.getMessage());
586                }
587            }
588        });
589
590        item = new MenuItem(menu, SWT.PUSH);
591        item.setText("Open &As");
592        item.addSelectionListener(new SelectionAdapter() {
593            @Override
594            public void widgetSelected(SelectionEvent e) {
595                isDefaultDisplay = false;
596
597                try {
598                    loadDataThread = new LoadDataThread();
599                    loadDataThread.start();
600                }
601                catch (Exception err) {
602                    shell.getDisplay().beep();
603                    err.printStackTrace();
604                    Tools.showError(shell, "Open", err.getMessage());
605                }
606            }
607        });
608
609        openVirtualFilesMenuItem = new MenuItem(menu, SWT.PUSH);
610        openVirtualFilesMenuItem.setText("Open Source Fi&les");
611        openVirtualFilesMenuItem.addSelectionListener(new SelectionAdapter() {
612            @Override
613            public void widgetSelected(SelectionEvent e) {
614                isDefaultDisplay = false;
615
616                log.trace("createPopupMenu(): selectedObject={}", selectedObject);
617                // If dataset is virtual - open source files. Only for HDF5
618                if (selectedObject != null) {
619                    log.trace("createPopupMenu(): selectedObject={} is dataset instance-{} of type H5 {}", selectedObject, selectedObject instanceof Dataset, selectedObject.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5)));
620                    if (selectedObject.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5)) &&
621                            (selectedObject instanceof Dataset)) {
622                        Dataset dataset = (Dataset) selectedObject;
623                        boolean isVirtual = dataset.isVirtual();
624                        log.trace("createPopupMenu(): isVirtual={}", isVirtual);
625                        if(isVirtual) {
626                            for(int ndx=0; ndx<dataset.getVirtualMaps(); ndx++) {
627                                try {
628                                    String theFile = selectedFile.getParentFile().getAbsolutePath() + File.separator + dataset.getVirtualFilename(ndx);
629                                    openFile(theFile, FileFormat.WRITE);
630                                }
631                                catch (Exception ex) {
632                                    shell.getDisplay().beep();
633                                    ex.printStackTrace();
634                                    Tools.showError(shell, "Open", ex.getMessage() + "\n" + dataset.getVirtualFilename(ndx));
635                                }
636                                log.trace("createPopupMenu(): virtualNameList[{}]={}", ndx, dataset.getVirtualFilename(ndx));
637                            }
638                        }
639                    }
640                }
641            }
642        });
643
644        new MenuItem(menu, SWT.SEPARATOR);
645
646        MenuItem newObjectMenuItem = new MenuItem(menu, SWT.CASCADE);
647        newObjectMenuItem.setText("New");
648        editGUIs.add(newObjectMenuItem);
649
650        new MenuItem(menu, SWT.SEPARATOR);
651
652        item = new MenuItem(menu, SWT.PUSH);
653        item.setText("Cu&t");
654        item.addSelectionListener(new SelectionAdapter() {
655            @Override
656            public void widgetSelected(SelectionEvent e) {
657                moveObject();
658            }
659        });
660        editGUIs.add(item);
661
662        item = new MenuItem(menu, SWT.PUSH);
663        item.setText("&Copy");
664        item.addSelectionListener(new SelectionAdapter() {
665            @Override
666            public void widgetSelected(SelectionEvent e) {
667                copyObject();
668            }
669        });
670
671        item = new MenuItem(menu, SWT.PUSH);
672        item.setText("&Paste");
673        item.addSelectionListener(new SelectionAdapter() {
674            @Override
675            public void widgetSelected(SelectionEvent e) {
676                pasteObject();
677            }
678        });
679        editGUIs.add(item);
680
681        item = new MenuItem(menu, SWT.PUSH);
682        item.setText("&Delete");
683        item.addSelectionListener(new SelectionAdapter() {
684            @Override
685            public void widgetSelected(SelectionEvent e) {
686                cutObject();
687            }
688        });
689        editGUIs.add(item);
690
691        exportDatasetMenuItem = new MenuItem(menu, SWT.CASCADE);
692        exportDatasetMenuItem.setText("Export Dataset");
693
694        new MenuItem(menu, SWT.SEPARATOR);
695
696        item = new MenuItem(menu, SWT.PUSH);
697        item.setText("&Save to");
698        item.addSelectionListener(new SelectionAdapter() {
699            @Override
700            public void widgetSelected(SelectionEvent e) {
701                TreeItem[] selectedItems = tree.getSelection();
702
703                if (selectedItems.length <= 0) return;
704
705                for (int i = 0; i < selectedItems.length; i++) {
706                    if (((HObject) selectedItems[i].getData() instanceof Group)
707                            && ((Group) selectedItems[i].getData()).isRoot()) {
708                        shell.getDisplay().beep();
709                        Tools.showError(shell, "Save", "Cannot save the root group.\nUse \"Save As\" from file menu to save the whole file");
710                        return;
711                    }
712                }
713
714                String filetype = FileFormat.FILE_TYPE_HDF4;
715                if (selectedObject.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5)))
716                    filetype = FileFormat.FILE_TYPE_HDF5;
717                else if (selectedObject.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_NC3)))
718                    filetype = FileFormat.FILE_TYPE_NC3;
719
720                String currentDir = selectedObject.getFileFormat().getParent();
721
722                if (currentDir != null)
723                    currentDir += File.separator;
724                else
725                    currentDir = "";
726
727                FileDialog fChooser = new FileDialog(shell, SWT.SAVE);
728
729                DefaultFileFilter filter = null;
730
731                if (filetype.equals(FileFormat.FILE_TYPE_HDF4)) {
732                    fChooser.setFileName(Tools.checkNewFile(currentDir, ".hdf").getName());
733                    filter = DefaultFileFilter.getFileFilterHDF4();
734                }
735                else if (filetype.equals(FileFormat.FILE_TYPE_NC3)) {
736                    fChooser.setFileName(Tools.checkNewFile(currentDir, ".nc").getName());
737                    filter = DefaultFileFilter.getFileFilterNetCDF3();
738                }
739                else {
740                    fChooser.setFileName(Tools.checkNewFile(currentDir, ".h5").getName());
741                    filter = DefaultFileFilter.getFileFilterHDF5();
742                }
743
744                fChooser.setFilterExtensions(new String[] {filter.getExtensions()});
745                fChooser.setFilterNames(new String[] {filter.getDescription()});
746                fChooser.setFilterIndex(0);
747
748                String filename = fChooser.open();
749
750                if(filename == null)
751                    return;
752
753                try {
754                    Tools.createNewFile(filename, currentDir, filetype, fileList);
755                }
756                catch (Exception ex) {
757                    Tools.showError(shell, "Save", ex.getMessage());
758                }
759
760                FileFormat dstFile = null;
761
762                try {
763                    dstFile = openFile(filename, FileFormat.WRITE);
764                }
765                catch (Exception ex) {
766                    shell.getDisplay().beep();
767                    Tools.showError(shell, "Save", ex.getMessage() + "\n" + filename);
768                }
769                if (dstFile != null)
770                    pasteObject(selectedItems, findTreeItem(dstFile.getRootObject()), dstFile);
771            }
772        });
773
774        item = new MenuItem(menu, SWT.PUSH);
775        item.setText("&Rename");
776        item.addSelectionListener(new SelectionAdapter() {
777            @Override
778            public void widgetSelected(SelectionEvent e) {
779                renameObject();
780            }
781        });
782        editGUIs.add(item);
783
784        new MenuItem(menu, SWT.SEPARATOR);
785
786        changeIndexItem = new MenuItem(menu, SWT.PUSH);
787        changeIndexItem.setText("Change file indexing");
788        changeIndexItem.addSelectionListener(new SelectionAdapter() {
789            @Override
790            public void widgetSelected(SelectionEvent e) {
791                ChangeIndexingDialog dialog = new ChangeIndexingDialog(shell, SWT.NONE, selectedFile);
792                dialog.open();
793                if (dialog.isReloadFile()) {
794                    try {
795                        selectedFile.setIndexType(dialog.getIndexType());
796                    }
797                    catch (Exception ex) {
798                        log.debug("ChangeIndexingDialog(): setIndexType failed: ", ex);
799                    }
800                    try {
801                        selectedFile.setIndexOrder(dialog.getIndexOrder());
802                    }
803                    catch (Exception ex) {
804                        log.debug("ChangeIndexingDialog(): setIndexOrder failed: ", ex);
805                    }
806
807                    try {
808                        reopenFile(selectedFile, -1);
809                    }
810                    catch (Exception ex) {
811                        log.debug("reload file {} failure after indexing change: ", selectedFile.getAbsolutePath(), ex);
812                        Tools.showError(shell, "File reload error", "Error reloading file " + selectedFile.getAbsolutePath() + " after changing indexing: " + ex.getMessage());
813                    }
814                }
815            }
816        });
817
818        new MenuItem(menu, SWT.SEPARATOR);
819
820        item = new MenuItem(menu, SWT.PUSH);
821        item.setText("&Find");
822        item.addSelectionListener(new SelectionAdapter() {
823            @Override
824            public void widgetSelected(SelectionEvent e) {
825                String findStr = currentSearchPhrase;
826                if (findStr == null)
827                    findStr = "";
828
829                findStr = (new InputDialog(shell, "Find Object by Name",
830                        "Find (e.g. O3Quality, O3*, or *Quality):", findStr)).open();
831
832                if (findStr != null && findStr.length() > 0)
833                    currentSearchPhrase = findStr;
834
835                find(currentSearchPhrase, selectedItem);
836            }
837        });
838
839        new MenuItem(menu, SWT.SEPARATOR);
840
841        item = new MenuItem(menu, SWT.PUSH);
842        item.setText("Expand All");
843        item.addSelectionListener(new SelectionAdapter() {
844            @Override
845            public void widgetSelected(SelectionEvent e) {
846                if(selectedItem != null)
847                    recursiveExpand(selectedItem, true);
848            }
849        });
850
851        item = new MenuItem(menu, SWT.PUSH);
852        item.setText("Collapse All");
853        item.addSelectionListener(new SelectionAdapter() {
854            @Override
855            public void widgetSelected(SelectionEvent e) {
856                if(selectedItem != null)
857                    recursiveExpand(selectedItem, false);
858            }
859        });
860
861        new MenuItem(menu, SWT.SEPARATOR);
862
863        item = new MenuItem(menu, SWT.PUSH);
864        item.setText("Close Fil&e");
865        item.addSelectionListener(new SelectionAdapter() {
866            @Override
867            public void widgetSelected(SelectionEvent e) {
868                try {
869                    ((HDFView) viewer).closeFile(selectedFile);
870                }
871                catch (Exception ex) {
872                    Tools.showError(shell, "Close", ex.getMessage());
873                }
874            }
875        });
876
877        item = new MenuItem(menu, SWT.PUSH);
878        item.setText("&Reload File");
879        item.addSelectionListener(new SelectionAdapter() {
880            @Override
881            public void widgetSelected(SelectionEvent e) {
882                try {
883                    reopenFile(selectedFile, -1);
884                }
885                catch (Exception ex) {
886                    log.debug("reload file {} failure: ", selectedFile.getAbsolutePath(), ex);
887                    Tools.showError(shell, "File reload error", "Error reloading file " + selectedFile.getAbsolutePath() + ": " + ex.getMessage());
888                }
889            }
890        });
891
892        item = new MenuItem(menu, SWT.CASCADE);
893        item.setText("Reload File As");
894
895        Menu reloadFileMenu = new Menu(item);
896        item.setMenu(reloadFileMenu);
897
898        item = new MenuItem(reloadFileMenu, SWT.PUSH);
899        item.setText("Read-Only");
900        item.addSelectionListener(new SelectionAdapter() {
901            @Override
902            public void widgetSelected(SelectionEvent e) {
903                try {
904                    reopenFile(selectedFile, FileFormat.READ);
905                }
906                catch (Exception ex) {
907                    log.debug("reload file {} as read-only failure: ", selectedFile.getAbsolutePath(), ex);
908                    Tools.showError(shell, "File reload error", "Error reloading file " + selectedFile.getAbsolutePath() + " read-only: " + ex.getMessage());
909                }
910            }
911        });
912
913        item = new MenuItem(reloadFileMenu, SWT.PUSH);
914        item.setText("SWMR Read-Only");
915        item.addSelectionListener(new SelectionAdapter() {
916            @Override
917            public void widgetSelected(SelectionEvent e) {
918                try {
919                    reopenFile(selectedFile, FileFormat.READ | FileFormat.MULTIREAD);
920                }
921                catch (Exception ex) {
922                    log.debug("reload file {} as SWMR read-only failure: ", selectedFile.getAbsolutePath(), ex);
923                    Tools.showError(shell, "File reload error", "Error reloading file " + selectedFile.getAbsolutePath() + " SWMR read-only: " + ex.getMessage());
924                }
925            }
926        });
927
928        item = new MenuItem(reloadFileMenu, SWT.PUSH);
929        item.setText("Read/Write");
930        item.addSelectionListener(new SelectionAdapter() {
931            @Override
932            public void widgetSelected(SelectionEvent e) {
933                try {
934                    reopenFile(selectedFile, FileFormat.WRITE);
935                }
936                catch (Exception ex) {
937                    log.debug("reload file {} as read/write failure: ", selectedFile.getAbsolutePath(), ex);
938                    Tools.showError(shell, "File reload error", "Error reloading file " + selectedFile.getAbsolutePath() + " read/write: " + ex.getMessage());
939                }
940            }
941        });
942
943        new MenuItem(menu, SWT.SEPARATOR);
944
945        setLibVerBoundsItem = new MenuItem(menu, SWT.NONE);
946        setLibVerBoundsItem.setText("Set Lib version bounds");
947        setLibVerBoundsItem.addSelectionListener(new SelectionAdapter() {
948            @Override
949            public void widgetSelected(SelectionEvent e) {
950                new ChangeLibVersionDialog(shell, SWT.NONE).open();
951            }
952        });
953
954
955        // Add new object menu
956        newObjectMenu = new Menu(menu);
957        newObjectMenuItem.setMenu(newObjectMenu);
958
959        item = new MenuItem(newObjectMenu, SWT.PUSH);
960        item.setText("Group");
961        item.setImage(ViewProperties.getFoldercloseIcon());
962        item.addSelectionListener(new SelectionAdapter() {
963            @Override
964            public void widgetSelected(SelectionEvent e) {
965                addNewObject(OBJECT_TYPE.GROUP);
966            }
967        });
968        editGUIs.add(item);
969
970        addDatasetMenuItem = new MenuItem(newObjectMenu, SWT.PUSH);
971        addDatasetMenuItem.setText("Dataset");
972        addDatasetMenuItem.setImage(ViewProperties.getDatasetIcon());
973        addDatasetMenuItem.addSelectionListener(new SelectionAdapter() {
974            @Override
975            public void widgetSelected(SelectionEvent e) {
976                addNewObject(OBJECT_TYPE.DATASET);
977            }
978        });
979        editGUIs.add(addDatasetMenuItem);
980
981        item = new MenuItem(newObjectMenu, SWT.PUSH);
982        item.setText("Image");
983        item.setImage(ViewProperties.getImageIcon());
984        item.addSelectionListener(new SelectionAdapter() {
985            @Override
986            public void widgetSelected(SelectionEvent e) {
987                addNewObject(OBJECT_TYPE.IMAGE);
988            }
989        });
990        editGUIs.add(item);
991
992        addTableMenuItem = new MenuItem(newObjectMenu, SWT.PUSH);
993        addTableMenuItem.setText("Compound DS");
994        addTableMenuItem.setImage(ViewProperties.getTableIcon());
995        addTableMenuItem.addSelectionListener(new SelectionAdapter() {
996            @Override
997            public void widgetSelected(SelectionEvent e) {
998                addNewObject(OBJECT_TYPE.TABLE);
999            }
1000        });
1001        editGUIs.add(addTableMenuItem);
1002
1003        addDatatypeMenuItem = new MenuItem(newObjectMenu, SWT.PUSH);
1004        addDatatypeMenuItem.setText("Datatype");
1005        addDatatypeMenuItem.setImage(ViewProperties.getDatatypeIcon());
1006        addDatatypeMenuItem.addSelectionListener(new SelectionAdapter() {
1007            @Override
1008            public void widgetSelected(SelectionEvent e) {
1009                addNewObject(OBJECT_TYPE.DATATYPE);
1010            }
1011        });
1012        editGUIs.add(addDatatypeMenuItem);
1013
1014        addLinkMenuItem = new MenuItem(newObjectMenu, SWT.PUSH);
1015        addLinkMenuItem.setText("Link");
1016        addLinkMenuItem.setImage(ViewProperties.getLinkIcon());
1017        addLinkMenuItem.addSelectionListener(new SelectionAdapter() {
1018            @Override
1019            public void widgetSelected(SelectionEvent e) {
1020                addNewObject(OBJECT_TYPE.LINK);
1021            }
1022        });
1023        editGUIs.add(addLinkMenuItem);
1024
1025
1026        // Add export dataset menu
1027        exportDatasetMenu = new Menu(menu);
1028        exportDatasetMenuItem.setMenu(exportDatasetMenu);
1029
1030        item = new MenuItem(exportDatasetMenu, SWT.PUSH);
1031        item.setText("Export Data to Text File");
1032        item.addSelectionListener(new SelectionAdapter() {
1033            @Override
1034            public void widgetSelected(SelectionEvent e) {
1035                binaryOrder = 99;
1036
1037                try {
1038                    saveDataAsFile();
1039                }
1040                catch (Exception ex) {
1041                    shell.getDisplay().beep();
1042                    Tools.showError(shell, "Export Dataset", ex.getMessage());
1043                }
1044            }
1045        });
1046
1047        item = new MenuItem(exportDatasetMenu, SWT.PUSH);
1048        item.setText("Export Data as Native Order");
1049        item.addSelectionListener(new SelectionAdapter() {
1050            @Override
1051            public void widgetSelected(SelectionEvent e) {
1052                binaryOrder = 1;
1053
1054                try {
1055                    saveDataAsFile();
1056                }
1057                catch (Exception ex) {
1058                    shell.getDisplay().beep();
1059                    Tools.showError(shell, "Export Dataset", ex.getMessage());
1060                }
1061            }
1062        });
1063
1064        item = new MenuItem(exportDatasetMenu, SWT.PUSH);
1065        item.setText("Export Data as Little Endian");
1066        item.addSelectionListener(new SelectionAdapter() {
1067            @Override
1068            public void widgetSelected(SelectionEvent e) {
1069                binaryOrder = 2;
1070
1071                try {
1072                    saveDataAsFile();
1073                }
1074                catch (Exception ex) {
1075                    shell.getDisplay().beep();
1076                    Tools.showError(shell, "Export Dataset", ex.getMessage());
1077                }
1078            }
1079        });
1080
1081        item = new MenuItem(exportDatasetMenu, SWT.PUSH);
1082        item.setText("Export Data as Big Endian");
1083        item.addSelectionListener(new SelectionAdapter() {
1084            @Override
1085            public void widgetSelected(SelectionEvent e) {
1086                binaryOrder = 3;
1087
1088                try {
1089                    saveDataAsFile();
1090                }
1091                catch (Exception ex) {
1092                    shell.getDisplay().beep();
1093                    Tools.showError(shell, "Export Dataset", ex.getMessage());
1094                }
1095            }
1096        });
1097
1098        // Add listener to dynamically enable/disable menu items based
1099        // on selection in tree
1100        menu.addMenuListener(new MenuAdapter() {
1101            @Override
1102            public void menuShown(MenuEvent e) {
1103                if (selectedItem == null || selectedObject == null || selectedFile == null) return;
1104
1105                boolean isReadOnly = selectedObject.getFileFormat().isReadOnly();
1106                boolean isWritable = !isReadOnly;
1107
1108                setEnabled(editGUIs, isWritable);
1109
1110                if (selectedObject instanceof Group) {
1111                    boolean state = !(((Group) selectedObject).isRoot());
1112
1113                    popupMenu.getItem(0).setEnabled(false); // "Open" menuitem
1114                    popupMenu.getItem(1).setEnabled(false); // "Open as" menuitem
1115                    popupMenu.getItem(2).setEnabled(false); // "Open Source Files" menuitem
1116                    popupMenu.getItem(6).setEnabled(
1117                            (selectedObject.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5)))
1118                            && state && isWritable); // "Cut" menuitem
1119                    popupMenu.getItem(7).setEnabled(state); // "Copy" menuitem
1120                    popupMenu.getItem(9).setEnabled(state && isWritable); // "Delete" menuitem
1121                    popupMenu.getItem(10).setEnabled(false); // "Export Dataset" menuitem
1122                    popupMenu.getItem(12).setEnabled(state && isWritable); // "Save to" menuitem
1123                    popupMenu.getItem(13).setEnabled(state && isWritable); // "Rename" menuitem
1124                }
1125                else {
1126                    popupMenu.getItem(0).setEnabled(true); // "Open" menuitem
1127                    popupMenu.getItem(1).setEnabled(true); // "Open as" menuitem
1128                    popupMenu.getItem(2).setEnabled(
1129                            (selectedObject.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5)))); // "Open Source Files" menuitem
1130                    popupMenu.getItem(6).setEnabled(
1131                            (selectedObject.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5)))
1132                            && isWritable); // "Cut" menuitem
1133                    popupMenu.getItem(7).setEnabled(true); // "Copy" menuitem
1134                    popupMenu.getItem(9).setEnabled(isWritable); // "Delete" menuitem
1135                    popupMenu.getItem(10).setEnabled(true); // "Export Dataset" menuitem
1136                    popupMenu.getItem(12).setEnabled(true); // "Save to" menuitem
1137                    popupMenu.getItem(13).setEnabled(isWritable); // "Rename" menuitem
1138                }
1139
1140                // Adding table is only supported by HDF5
1141                if ((selectedFile != null) && selectedFile.isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5))) {
1142                    //openVirtualFilesMenuItem.setEnabled(true); // Should be moved to createPopupMenu() since swt doesn't support MenuItem.setVisible
1143
1144                    boolean state = false;
1145                    if ((selectedObject instanceof Group)) {
1146                        state = (((Group) selectedObject).isRoot());
1147                        setLibVerBoundsItem.setEnabled(isWritable && state);
1148                    }
1149                    else {
1150                        setLibVerBoundsItem.setEnabled(false);
1151                    }
1152
1153                    changeIndexItem.setEnabled(state);
1154                }
1155                else {
1156                    addTableMenuItem.setEnabled(false);
1157                    addDatatypeMenuItem.setEnabled(false);
1158                    addLinkMenuItem.setEnabled(false);
1159                    //openVirtualFilesMenuItem.setEnabled(false);
1160                    setLibVerBoundsItem.setEnabled(false);
1161                    changeIndexItem.setEnabled(false);
1162                }
1163
1164                // Export table is only supported by HDF5
1165                if ((selectedObject != null) && selectedObject.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5))) {
1166                    if ((selectedObject instanceof Dataset)) {
1167                        Dataset dataset = (Dataset) selectedObject;
1168                        if ((dataset instanceof ScalarDS))
1169                            exportDatasetMenuItem.setEnabled(true);
1170                        openVirtualFilesMenuItem.setEnabled(true);
1171                    }
1172                    else {
1173                        exportDatasetMenuItem.setEnabled(false);
1174                        openVirtualFilesMenuItem.setEnabled(false);
1175                    }
1176                }
1177                else {
1178                    exportDatasetMenuItem.setEnabled(false);
1179                    openVirtualFilesMenuItem.setEnabled(false);
1180                }
1181            }
1182        });
1183
1184        return menu;
1185    }
1186
1187    /**
1188     * Creates a dialog for the user to select a type of new
1189     * object to be added to the TreeView, then passes the
1190     * result of the dialog on to addObject(HObject newObject, Group parentGroup)
1191     *
1192     * @param type
1193     *          The type (GROUP, DATASET, IMAGE, TABLE, DATATYPE, LINK) of object to add.
1194     */
1195    private void addNewObject(OBJECT_TYPE type) {
1196        if ((selectedObject == null) || (selectedItem == null)) return;
1197
1198        TreeItem parentItem = null;
1199        if(selectedObject instanceof Group)
1200            parentItem = selectedItem;
1201        else
1202            parentItem = selectedItem.getParentItem();
1203
1204        // Find the root item of the selected file
1205        TreeItem rootItem = selectedItem;
1206        while(rootItem.getParentItem() != null)
1207            rootItem = rootItem.getParentItem();
1208
1209        HObject obj = null;
1210
1211        switch(type) {
1212        case GROUP:
1213            NewGroupDialog groupDialog = new NewGroupDialog(shell, (Group) parentItem.getData(),
1214                    breadthFirstUserObjects(rootItem));
1215            groupDialog.open();
1216            obj = groupDialog.getObject();
1217            parentItem = findTreeItem(groupDialog.getParentGroup());
1218            break;
1219        case DATASET:
1220            NewDatasetDialog datasetDialog = new NewDatasetDialog(shell, (Group) parentItem.getData(), breadthFirstUserObjects(rootItem));
1221            datasetDialog.open();
1222            obj = datasetDialog.getObject();
1223            parentItem = findTreeItem(datasetDialog.getParentGroup());
1224            break;
1225        case IMAGE:
1226            NewImageDialog imageDialog = new NewImageDialog(shell, (Group) parentItem.getData(), breadthFirstUserObjects(rootItem));
1227            imageDialog.open();
1228            obj = imageDialog.getObject();
1229            parentItem = findTreeItem(imageDialog.getParentGroup());
1230            break;
1231        case TABLE:
1232            NewCompoundDatasetDialog tableDialog = new NewCompoundDatasetDialog(shell, (Group) parentItem.getData(), breadthFirstUserObjects(rootItem));
1233            tableDialog.open();
1234            obj = tableDialog.getObject();
1235            parentItem = findTreeItem(tableDialog.getParentGroup());
1236            break;
1237        case DATATYPE:
1238            NewDatatypeDialog datatypeDialog = new NewDatatypeDialog(shell, (Group) parentItem.getData(), breadthFirstUserObjects(rootItem));
1239            datatypeDialog.open();
1240            obj = datatypeDialog.getObject();
1241            parentItem = findTreeItem(datatypeDialog.getParentGroup());
1242            break;
1243        case LINK:
1244            NewLinkDialog linkDialog = new NewLinkDialog(shell, (Group) parentItem.getData(), breadthFirstUserObjects(rootItem), getCurrentFiles());
1245            linkDialog.open();
1246            obj = linkDialog.getObject();
1247            parentItem = findTreeItem(linkDialog.getParentGroup());
1248            break;
1249        }
1250
1251        if (obj == null)
1252            return;
1253
1254        try {
1255            this.insertObject(obj, parentItem);
1256        }
1257        catch (Exception ex) {
1258            shell.getDisplay().beep();
1259            Tools.showError(shell, "Create", ex.getMessage());
1260        }
1261    }
1262
1263    /**
1264     * Adds an already created HObject to the tree under the
1265     * TreeItem containing the specified parent group.
1266     *
1267     * @param obj
1268     *            the object to add.
1269     * @param parentGroup
1270     *            the parent group to add the object to.
1271     */
1272    @Override
1273    public TreeItem addObject(HObject obj, Group parentGroup) {
1274        if ((obj == null) || (parentGroup == null))
1275            return null;
1276
1277        return insertObject(obj, findTreeItem(parentGroup));
1278    }
1279
1280    /**
1281     * Insert an object into the tree as the last object
1282     * under parent item pobj.
1283     *
1284     * @param obj
1285     *            the object to insert.
1286     * @param pobj
1287     *            the parent TreeItem to insert the new object under.
1288     *            If null, inserts the object at the end of the Tree.
1289     *
1290     * @return the newly created TreeItem
1291     */
1292    private TreeItem insertObject(HObject obj, TreeItem pobj) {
1293        if ((obj == null))
1294            return null;
1295
1296        TreeItem item;
1297
1298        if(pobj != null) {
1299            item = new TreeItem(pobj, SWT.NONE, pobj.getItemCount());
1300            item.setFont(curFont);
1301            item.setText(obj.getName());
1302        }
1303        else {
1304            // Parent object was null, insert at end of tree as root object
1305            item = new TreeItem(tree, SWT.NONE, tree.getItemCount());
1306            item.setFont(curFont);
1307            item.setText(obj.getFileFormat().getName());
1308        }
1309
1310        item.setData(obj);
1311        item.setImage(getObjectTypeImage(obj));
1312
1313        return item;
1314    }
1315
1316    /** Move selected objects */
1317    private void moveObject() {
1318        objectsToCopy = tree.getSelection();
1319        moveFlag = true;
1320        currentSelectionsForMove = tree.getSelection();
1321    }
1322
1323    /** Copy selected objects */
1324    private void copyObject() {
1325        if (moveFlag)
1326            if(!Tools.showConfirm(shell, "Copy object", "Do you want to copy all the selected object(s) instead of move?"))
1327                return;
1328        moveFlag = false;
1329        currentSelectionsForMove = null;
1330        objectsToCopy = tree.getSelection();
1331    }
1332
1333    /** Delete selected objects */
1334    private void cutObject() {
1335        if (moveFlag)
1336            if(!Tools.showConfirm(shell, "Delete object", "Do you want to delete all the selected object(s) instead of move?"))
1337                return;
1338        moveFlag = false;
1339        currentSelectionsForMove = null;
1340        objectsToCopy = tree.getSelection();
1341        removeSelectedObjects();
1342    }
1343
1344    /** Paste selected objects */
1345    private void pasteObject() {
1346        if (moveFlag) {
1347            HObject theObj = null;
1348            for (int i = 0; i < currentSelectionsForMove.length; i++) {
1349                TreeItem currentItem = currentSelectionsForMove[i];
1350                theObj = (HObject) currentItem.getData();
1351
1352                if (isObjectOpen(theObj)) {
1353                    shell.getDisplay().beep();
1354                    Tools.showError(shell, "Move Objects", "Cannot move the selected object: " + theObj
1355                            + "\nThe dataset or dataset in the group is in use."
1356                            + "\n\nPlease close the dataset(s) and try again.\n");
1357
1358                    moveFlag = false;
1359                    currentSelectionsForMove = null;
1360                    objectsToCopy = null;
1361                    return;
1362                }
1363            }
1364        }
1365
1366        TreeItem pitem = selectedItem;
1367
1368        if ((objectsToCopy == null) || (objectsToCopy.length <= 0) || (pitem == null))
1369            return;
1370
1371        FileFormat srcFile = ((HObject) objectsToCopy[0].getData()).getFileFormat();
1372        FileFormat dstFile = getSelectedFile();
1373        FileFormat h5file = FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5);
1374        FileFormat h4file = FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4);
1375        FileFormat ncfile = FileFormat.getFileFormat(FileFormat.FILE_TYPE_NC3);
1376
1377        if (srcFile == null) {
1378            shell.getDisplay().beep();
1379            Tools.showError(shell, "Copy", "Source file is null.");
1380            return;
1381        }
1382        else if (dstFile == null) {
1383            shell.getDisplay().beep();
1384            Tools.showError(shell, "Copy", "Destination file is null.");
1385            return;
1386        }
1387        else if (srcFile.isThisType(h4file) && dstFile.isThisType(h5file)) {
1388            shell.getDisplay().beep();
1389            Tools.showError(shell, "Copy", "Unsupported operation: cannot copy HDF4 object to HDF5 file");
1390            return;
1391        }
1392        else if (srcFile.isThisType(h5file) && dstFile.isThisType(h4file)) {
1393            shell.getDisplay().beep();
1394            Tools.showError(shell, "Copy", "Unsupported operation: cannot copy HDF5 object to HDF4 file");
1395            return;
1396        }
1397        else if (srcFile.isThisType(ncfile) && dstFile.isThisType(ncfile)) {
1398            shell.getDisplay().beep();
1399            Tools.showError(shell, "Copy", "Unsupported operation: cannot copy NetCDF3 objects");
1400            return;
1401        }
1402
1403        if (moveFlag) {
1404            if (srcFile != dstFile) {
1405                shell.getDisplay().beep();
1406                Tools.showError(shell, "Move", "Cannot move the selected object to different file");
1407                moveFlag = false;
1408                currentSelectionsForMove = null;
1409                objectsToCopy = null;
1410                return;
1411            }
1412        }
1413
1414        /*
1415        if (pitem.getParentItem() != null) {
1416            pitem = pitem.getParentItem();
1417        }*/
1418
1419        Group pgroup = (Group) pitem.getData();
1420        String fullPath = pgroup.getPath() + pgroup.getName();
1421        if (pgroup.isRoot()) {
1422            fullPath = HObject.SEPARATOR;
1423        }
1424
1425        String msg = "";
1426        if (srcFile.isThisType(h4file))
1427            msg = "WARNING: object can not be deleted after it is copied.\n\n";
1428
1429        msg += "Do you want to copy the selected object(s) to \nGroup: " + fullPath + "\nFile: "
1430                + dstFile.getFilePath();
1431
1432        if (moveFlag) {
1433            String moveMsg = "Do you want to move the selected object(s) to \nGroup: " + fullPath + "\nFile: "
1434                    + dstFile.getFilePath();
1435            if(!Tools.showConfirm(shell, "Move Object", moveMsg))
1436                return;
1437        }
1438        else {
1439            if(!Tools.showConfirm(shell, "Copy object", msg))
1440                return;
1441        }
1442
1443        pasteObject(objectsToCopy, pitem, dstFile);
1444
1445        if (moveFlag) {
1446            removeSelectedObjects();
1447            moveFlag = false;
1448            currentSelectionsForMove = null;
1449            objectsToCopy = null;
1450        }
1451    }
1452
1453    /** Paste selected objects */
1454    private void pasteObject(TreeItem[] objList, TreeItem pobj, FileFormat dstFile) {
1455        if ((objList == null) || (objList.length <= 0) || (pobj == null)) return;
1456
1457        FileFormat srcFile = ((HObject) objList[0].getData()).getFileFormat();
1458        Group pgroup = (Group) pobj.getData();
1459
1460        HObject theObj = null;
1461        for (int i = 0; i < objList.length; i++) {
1462            theObj = (HObject) objList[i].getData();
1463
1464            if ((theObj instanceof Group) && ((Group) theObj).isRoot()) {
1465                shell.getDisplay().beep();
1466                Tools.showError(shell, "Paste", "Unsupported operation: cannot copy the root group");
1467                return;
1468            }
1469
1470            // Check if it creates infinite loop
1471            Group pg = pgroup;
1472            while (!pg.isRoot()) {
1473                if (theObj.equals(pg)) {
1474                    shell.getDisplay().beep();
1475                    Tools.showError(shell, "Paste", "Unsupported operation: cannot copy a group to itself.");
1476                    return;
1477                }
1478                pg = pg.getParent();
1479            }
1480
1481            try {
1482                log.trace("pasteObject(...): dstFile.copy({}, {}, null)", theObj, pgroup);
1483
1484                HObject newObj = null;
1485                if((newObj = srcFile.copy(theObj, pgroup, null)) != null) {
1486                    // Add the node to the tree
1487                    TreeItem newItem = insertObject(newObj, pobj);
1488
1489                    // If this is a group, add its first level child items
1490                    if(newObj instanceof Group) {
1491                        Iterator<HObject> children = ((Group) newObj).getMemberList().iterator();
1492                        while(children.hasNext())
1493                            insertObject(children.next(), newItem);
1494                    }
1495                }
1496            }
1497            catch (Exception ex) {
1498                shell.getDisplay().beep();
1499                Tools.showError(shell, "Paste", ex.getMessage());
1500            }
1501        } // (int i = 0; i < objList.length; i++)
1502    }
1503
1504    /**
1505     * Rename the currently selected object.
1506     */
1507    private void renameObject() {
1508        if (moveFlag) {
1509            if(!Tools.showConfirm(shell,  "Rename object", "Do you want to rename all the selected object(s) instead of move?"))
1510                return;
1511        }
1512        moveFlag = false;
1513        currentSelectionsForMove = null;
1514
1515        if (selectedObject == null)
1516            return;
1517
1518        if ((selectedObject instanceof Group) && ((Group) selectedObject).isRoot()) {
1519            shell.getDisplay().beep();
1520            Tools.showError(shell, "Rename", "Cannot rename the root.");
1521            return;
1522        }
1523
1524        boolean isH4 = selectedObject.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4));
1525        if (isH4) {
1526            shell.getDisplay().beep();
1527            Tools.showError(shell, "Rename", "Cannot rename HDF4 object.");
1528            return;
1529        }
1530
1531        boolean isN3 = selectedObject.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_NC3));
1532        if (isN3) {
1533            shell.getDisplay().beep();
1534            Tools.showError(shell, "Rename", "Cannot rename NetCDF3 object.");
1535            return;
1536        }
1537
1538        String oldName = selectedObject.getName();
1539        String newName = (new InputDialog(shell, "Rename Object",
1540                "Rename \"" + oldName + "\" to:", oldName)).open();
1541
1542        if (newName == null)
1543            return;
1544
1545        newName = newName.trim();
1546        if ((newName == null) || (newName.length() == 0) || newName.equals(oldName))
1547            return;
1548
1549        try {
1550            selectedObject.setName(newName);
1551        }
1552        catch (Exception ex) {
1553            shell.getDisplay().beep();
1554            Tools.showError(shell, "Rename Object", ex.getMessage());
1555        }
1556
1557        selectedItem.setText(newName);
1558    }
1559
1560    private void removeSelectedObjects() {
1561        FileFormat theFile = getSelectedFile();
1562        if (theFile.isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4))) {
1563            shell.getDisplay().beep();
1564            Tools.showError(shell, "Remove object", "Unsupported operation: cannot delete HDF4 object.");
1565            return;
1566        }
1567        if (theFile.isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_NC3))) {
1568            shell.getDisplay().beep();
1569            Tools.showError(shell, "Remove object", "Unsupported operation: cannot delete NetCDF3 object.");
1570            return;
1571        }
1572
1573        TreeItem[] currentSelections = tree.getSelection();
1574
1575        if (moveFlag)
1576            currentSelections = currentSelectionsForMove;
1577        if ((currentSelections == null) || (currentSelections.length <= 0))
1578            return;
1579
1580        if (!moveFlag) {
1581            if(!Tools.showConfirm(shell, "Remove object", "Do you want to remove all the selected object(s) ?"))
1582                return;
1583        }
1584
1585        HObject theObj = null;
1586        for (int i = 0; i < currentSelections.length; i++) {
1587            TreeItem currentItem = currentSelections[i];
1588            theObj = (HObject) currentItem.getData();
1589
1590            // Cannot delete a root object
1591            if (theObj instanceof Group && ((Group) theObj).isRoot()) {
1592                shell.getDisplay().beep();
1593                Tools.showError(shell, "Delete Objects", "Unsupported operation: cannot delete the file root.");
1594                return;
1595            }
1596
1597            if (!moveFlag) {
1598                if (isObjectOpen(theObj)) {
1599                    shell.getDisplay().beep();
1600                    Tools.showError(shell, "Delete Objects", "Cannot delete the selected object: " + theObj
1601                            + "\nThe dataset or dataset in the group is in use."
1602                            + "\n\nPlease close the dataset(s) and try again.\n");
1603                    continue;
1604                }
1605            }
1606
1607            try {
1608                theFile.delete(theObj);
1609            }
1610            catch (Exception ex) {
1611                shell.getDisplay().beep();
1612                Tools.showError(shell, "Delete Objects", ex.getMessage());
1613                continue;
1614            }
1615
1616            // When a TreeItem is disposed, it should be removed from its parent
1617            // items member list to prevent a bug when copying and deleting
1618            // groups/datasets
1619            ((Group) currentItem.getParentItem().getData()).removeFromMemberList(theObj);
1620
1621            if (currentItem.equals(selectedItem)) {
1622                selectedItem = null;
1623                selectedObject = null;
1624                selectedFile = null;
1625            }
1626
1627            currentItem.dispose();
1628        } // (int i=0; i < currentSelections.length; i++)
1629    }
1630
1631    /**
1632     * Populates the TreeView with TreeItems corresponding to
1633     * the top-level user objects in the specified file. The rest
1634     * of the user objects in the file are populated as TreeItems
1635     * on demand when the user expands groups.
1636     *
1637     * @return the root TreeItem created in the Tree corresponding
1638     * to the file object.
1639     */
1640    private TreeItem populateTree(FileFormat theFile) {
1641        if (theFile == null) {
1642            shell.getDisplay().beep();
1643            Tools.showError(shell, "Open File", "Error opening file");
1644            log.debug("Error populating tree, File object was null.");
1645            return null;
1646        }
1647        else if ((theFile.getFID() < 0) || (theFile.getRootObject() == null)) {
1648            if (theFile.isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_NC3))) {
1649                log.trace("populateTree(): FileID={} Null Root={}", theFile.getFID(), (theFile.getRootObject() == null));
1650            }
1651            //TODO: Update FitsFile and NC2File to have a fid other than -1
1652            // so this check isn't needed
1653            if (theFile.isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4)) ||
1654                    //theFile.isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_NC3)) ||
1655                    theFile.isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5))) {
1656                shell.getDisplay().beep();
1657                Tools.showError(shell, "Open File", "Error opening file " + theFile.getName());
1658                log.debug("Error populating tree for {}, File ID was wrong or File root object was null.", theFile.getFilePath());
1659                return null;
1660            }
1661        }
1662
1663        TreeItem rootItem = null;
1664
1665        try {
1666            rootItem = insertObject(theFile.getRootObject(), null);
1667            if (rootItem != null) {
1668                Iterator<HObject> it = ((Group) rootItem.getData()).getMemberList().iterator();
1669                while (it.hasNext()) {
1670                    TreeItem newItem = null;
1671                    HObject obj = it.next();
1672
1673                    newItem = insertObject(obj, rootItem);
1674
1675                    // Tell SWT how many members this group has so they can
1676                    // be populated when the group is expanded
1677                    if (obj instanceof Group) {
1678                        newItem.setItemCount(((Group) obj).getMemberList().size());
1679                        log.debug("populateTree(): group members size {}:", ((Group) obj).getMemberList().size());
1680                    }
1681                }
1682            }
1683        }
1684        catch (Exception ex) {
1685            log.debug("populateTree(): Error populating Tree with members of file {}:", theFile.getFilePath(), ex);
1686            if (rootItem != null)
1687                rootItem.dispose();
1688            shell.getDisplay().beep();
1689            Tools.showError(shell, "Open File", "Error opening file " + theFile.getName() + "\n\n" + ex.getMessage());
1690            return null;
1691        }
1692
1693        return rootItem;
1694    }
1695
1696    /**
1697     * Recursively expand/collapse a given selected TreeItem.
1698     *
1699     * @param item the selected tree item
1700     * @param expand
1701     *            Expands the TreeItem and its children if true.
1702     *            Collapse the TreeItem and its children if false.
1703     */
1704    //TODO: some groups dont get expanded right, likely due to SetData not being
1705    // able to catch up with a large number of expanding
1706    private void recursiveExpand(TreeItem item, boolean expand) {
1707        if(item == null || !(item.getData() instanceof Group))
1708            return;
1709
1710        TreeItem[] toExpand = item.getItems();
1711
1712        item.setExpanded(expand);
1713
1714        // Make sure the TreeItem's icon gets set appropriately by
1715        // notifying its Expand or Collapse listener
1716        Event event = new Event();
1717        event.item = item;
1718        tree.notifyListeners(expand ? SWT.Expand : SWT.Collapse, event);
1719
1720        // All SetData events for this group must be processed before any
1721        // child groups can be expanded, otherwise their data will be
1722        // null
1723        while(tree.getDisplay().readAndDispatch());
1724
1725        for(int i = 0; i < toExpand.length; i++)
1726            recursiveExpand(toExpand[i], expand);
1727    }
1728
1729    /**
1730     * Gets the Image to set on the TreeItem for the specified HObject,
1731     * based on the type of HObject it is.
1732     *
1733     * @param obj
1734     *
1735     * @return the image for the specified HObject
1736     */
1737    private Image getObjectTypeImage(HObject obj) {
1738        if (obj == null)
1739            return null;
1740
1741        // Should be safe to cast to a MetaDataContainer here because the
1742        // TreeView should never be able to select an object that does
1743        // not implement the MetaDataContainer interface
1744        boolean hasAttribute = ((MetaDataContainer) obj).hasAttribute();
1745
1746        if(obj instanceof Dataset) {
1747            if (obj instanceof ScalarDS) {
1748                ScalarDS sd = (ScalarDS) obj;
1749                Datatype dt = sd.getDatatype();
1750
1751                if (sd.isImage()) {
1752                    if (hasAttribute)
1753                        return imageIconA;
1754                    else
1755                        return imageIcon;
1756                }
1757                else if ((dt != null) && dt.isText()) {
1758                    if (hasAttribute)
1759                        return textIconA;
1760                    else
1761                        return textIcon;
1762                }
1763                else {
1764                    if (hasAttribute)
1765                        return datasetIconA;
1766                    else
1767                        return datasetIcon;
1768                }
1769            }
1770            else if (obj instanceof CompoundDS) {
1771                if (hasAttribute)
1772                    return tableIconA;
1773                else
1774                    return tableIcon;
1775            }
1776        }
1777        else if(obj instanceof Group) {
1778            if(((Group) obj).isRoot()) {
1779                FileFormat theFile = obj.getFileFormat();
1780
1781                if(theFile.isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_NC3))) {
1782                    if(theFile.isReadOnly())
1783                        return nc3IconR;
1784                    else
1785                        return nc3Icon;
1786                }
1787                else if(theFile.isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4))) {
1788                    if(theFile.isReadOnly())
1789                        return h4IconR;
1790                    else
1791                        return h4Icon;
1792                }
1793                else {
1794                    if(theFile.isReadOnly())
1795                        return h5IconR;
1796                    else
1797                        return h5Icon;
1798                }
1799            }
1800            else {
1801                if(hasAttribute)
1802                    return folderCloseIconA;
1803                else
1804                    return folderCloseIcon;
1805            }
1806        }
1807        else if(obj instanceof Datatype) {
1808            if(hasAttribute)
1809                return datatypeIconA;
1810            else
1811                return datatypeIcon;
1812        }
1813
1814        return questionIcon;
1815    }
1816
1817    /**
1818     * Checks if a file is already open.
1819     *
1820     * @param filename the file to query
1821     *
1822     * @return true if the file is open
1823     */
1824    private boolean isFileOpen(String filename) {
1825        boolean isOpen = false;
1826
1827        // Find the file by matching its file name from the list of open files
1828        FileFormat theFile = null;
1829        Iterator<FileFormat> iterator = fileList.iterator();
1830        while (iterator.hasNext()) {
1831            theFile = iterator.next();
1832            if (theFile.getFilePath().equals(filename)) {
1833                isOpen = true;
1834                break;
1835            }
1836        }
1837
1838        return isOpen;
1839    }
1840
1841    /**
1842     * Checks if an object is already open.
1843     */
1844    private boolean isObjectOpen(HObject obj) {
1845        boolean isOpen = false;
1846
1847        if (obj instanceof Group) {
1848            Group g = (Group) obj;
1849            List<?> members = g.getMemberList();
1850            if ((members == null) || members.isEmpty()) {
1851                return false;
1852            }
1853            else {
1854                int n = members.size();
1855                for (int i = 0; i < n; i++) {
1856                    HObject theObj = (HObject) members.get(i);
1857                    isOpen = (viewer.getDataView(theObj) != null);
1858                    if (isOpen)
1859                        break;
1860                }
1861            }
1862        }
1863        else {
1864            return (viewer.getDataView(obj) != null);
1865        }
1866
1867        return isOpen;
1868    }
1869
1870    /**
1871     * Returns a list that lists all TreeItems in the
1872     * current Tree that are children of the specified
1873     * TreeItem in a breadth-first manner.
1874     *
1875     * @param the current Tree item
1876     *
1877     * @return list of TreeItems
1878     */
1879    private ArrayList<TreeItem> getItemsBreadthFirst(TreeItem item) {
1880        if (item == null)
1881            return null;
1882
1883        ArrayList<TreeItem> allItems = new ArrayList<>();
1884        Queue<TreeItem> currentChildren = new LinkedList<>();
1885        TreeItem currentItem = item;
1886
1887        // Add all root items in the Tree to a Queue
1888        currentChildren.addAll(Arrays.asList(currentItem.getItems()));
1889
1890        // For every item in the queue, remove it from the head of the queue,
1891        // add it to the list of all items, then add all of its possible children
1892        // TreeItems to the end of the queue. This produces a breadth-first
1893        // ordering of the Tree's TreeItems.
1894        while(!currentChildren.isEmpty()) {
1895            currentItem = currentChildren.remove();
1896            allItems.add(currentItem);
1897
1898            if(currentItem.getItemCount() <= 0)
1899                continue;
1900
1901            currentChildren.addAll(Arrays.asList(currentItem.getItems()));
1902        }
1903
1904        return allItems;
1905    }
1906
1907    /**
1908     * Returns a list of all user objects that traverses the subtree rooted at
1909     * this item in breadth-first order..
1910     *
1911     * @param item
1912     *            the item to start with.
1913     */
1914    private final List<Object> breadthFirstUserObjects(TreeItem item) {
1915        if (item == null) return null;
1916
1917        ArrayList<Object> list = new ArrayList<>();
1918        list.add(item.getData()); // Add this item to the list first
1919
1920        Iterator<TreeItem> it = getItemsBreadthFirst(item).iterator();
1921        TreeItem theItem = null;
1922
1923        while (it.hasNext()) {
1924            theItem = it.next();
1925            list.add(theItem.getData());
1926        }
1927
1928        return list;
1929    }
1930
1931    /**
1932     * Find first object that is matched by name under the specified
1933     * TreeItem.
1934     *
1935     * @param objName
1936     *            -- the object name.
1937     * @return the object if found, otherwise, returns null.
1938     */
1939    private final HObject find(String objName, TreeItem parentItem) {
1940        if (objName == null || objName.length() <= 0 || parentItem == null) return null;
1941
1942        HObject retObj = null;
1943        boolean isFound = false;
1944        boolean isPrefix = false;
1945        boolean isSuffix = false;
1946        boolean isContain = false;
1947
1948        if (objName.equals("*"))
1949            return null;
1950
1951        if (objName.startsWith("*")) {
1952            isSuffix = true;
1953            objName = objName.substring(1, objName.length());
1954        }
1955
1956        if (objName.endsWith("*")) {
1957            isPrefix = true;
1958            objName = objName.substring(0, objName.length() - 1);
1959        }
1960
1961        if (isPrefix && isSuffix) {
1962            isContain = true;
1963            isPrefix = isSuffix = false;
1964        }
1965
1966        if (objName == null || objName.length() <= 0)
1967            return null;
1968
1969        HObject obj = null;
1970        String theName = null;
1971        TreeItem theItem = null;
1972        Iterator<TreeItem> it = getItemsBreadthFirst(parentItem).iterator();
1973        while (it.hasNext()) {
1974            theItem = it.next();
1975            obj = (HObject) theItem.getData();
1976            if (obj != null && (theName = obj.getName()) != null) {
1977                if (isPrefix)
1978                    isFound = theName.startsWith(objName);
1979                else if (isSuffix)
1980                    isFound = theName.endsWith(objName);
1981                else if (isContain)
1982                    isFound = theName.contains(objName);
1983                else
1984                    isFound = theName.equals(objName);
1985
1986                if (isFound) {
1987                    retObj = obj;
1988                    break;
1989                }
1990            }
1991        }
1992
1993        if (retObj != null) {
1994            tree.deselectAll();
1995            tree.setSelection(theItem);
1996            tree.showItem(theItem);
1997        }
1998
1999        return retObj;
2000    }
2001
2002    /**
2003     * Save the current file into a new HDF4 file. Since HDF4 does not
2004     * support packing, the source file is copied into the new file with
2005     * the exact same content.
2006     */
2007    private final void saveAsHDF4(FileFormat srcFile) {
2008        if (srcFile == null) {
2009            shell.getDisplay().beep();
2010            Tools.showError(shell, "Save", "Select a file to save.");
2011            return;
2012        }
2013
2014        HObject root = srcFile.getRootObject();
2015        if (root == null) {
2016            shell.getDisplay().beep();
2017            Tools.showError(shell, "Save", "The file is empty.");
2018            return;
2019        }
2020
2021        String currentDir = srcFile.getParent();
2022
2023        if (currentDir != null)
2024            currentDir += File.separator;
2025        else
2026            currentDir = "";
2027
2028        String filename = null;
2029        if (((HDFView) viewer).getTestState()) {
2030            filename = currentDir + File.separator + new InputDialog(shell, "Enter a file name", "").open();
2031        }
2032        else {
2033            FileDialog fChooser = new FileDialog(shell, SWT.SAVE);
2034            fChooser.setFileName(Tools.checkNewFile(currentDir, ".hdf").getName());
2035
2036            DefaultFileFilter filter = DefaultFileFilter.getFileFilterHDF4();
2037            fChooser.setFilterExtensions(new String[] {filter.getExtensions()});
2038            fChooser.setFilterNames(new String[] {filter.getDescription()});
2039            fChooser.setFilterIndex(0);
2040
2041            filename = fChooser.open();
2042        }
2043        if(filename == null)
2044            return;
2045
2046        try {
2047            Tools.createNewFile(filename, currentDir, FileFormat.FILE_TYPE_HDF4, fileList);
2048        }
2049        catch (Exception ex) {
2050            Tools.showError(shell, "Save", ex.getMessage());
2051        }
2052
2053        // Since cannot pack hdf4, simply copy the whole physical file
2054        int length = 0;
2055        int bsize = 512;
2056        byte[] buffer;
2057
2058        try (BufferedInputStream bi = new BufferedInputStream(new FileInputStream(srcFile.getFilePath()))) {
2059            try (BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream(filename))) {
2060                buffer = new byte[bsize];
2061                try {
2062                    length = bi.read(buffer, 0, bsize);
2063                }
2064                catch (Exception ex) {
2065                    length = 0;
2066                }
2067                while (length > 0) {
2068                    try {
2069                        bo.write(buffer, 0, length);
2070                        length = bi.read(buffer, 0, bsize);
2071                    }
2072                    catch (Exception ex) {
2073                        length = 0;
2074                    }
2075                }
2076
2077                try {
2078                    bo.flush();
2079                }
2080                catch (Exception ex) {
2081                    log.debug("Output file:", ex);
2082                }
2083            }
2084            catch (Exception ex) {
2085                shell.getDisplay().beep();
2086                Tools.showError(shell, "Save", ex.getMessage());
2087                return;
2088            }
2089        }
2090        catch (Exception ex) {
2091            shell.getDisplay().beep();
2092            Tools.showError(shell, "Save", ex.getMessage() + "\n" + filename);
2093            return;
2094        }
2095
2096        try {
2097            openFile(filename, FileFormat.WRITE);
2098        }
2099        catch (Exception ex) {
2100            shell.getDisplay().beep();
2101            Tools.showError(shell, "Save", ex.getMessage() + "\n" + filename);
2102        }
2103    }
2104
2105    /**
2106     * Copy the current file into a new HDF5 file. The new file does not include the
2107     * inaccessible objects. Values of reference dataset are not updated in the
2108     * new file.
2109     */
2110    private void saveAsHDF5(FileFormat srcFile) {
2111        if (srcFile == null) {
2112            shell.getDisplay().beep();
2113            Tools.showError(shell, "Save", "Select a file to save.");
2114            return;
2115        }
2116
2117        HObject root = srcFile.getRootObject();
2118        if (root == null) {
2119            shell.getDisplay().beep();
2120            Tools.showError(shell, "Save", "The file is empty.");
2121            return;
2122        }
2123
2124        String currentDir = srcFile.getParent();
2125
2126        if (currentDir != null)
2127            currentDir += File.separator;
2128        else
2129            currentDir = "";
2130
2131        String filename = null;
2132        if (((HDFView) viewer).getTestState()) {
2133            filename = currentDir + File.separator + new InputDialog(shell, "Enter a file name", "").open();
2134        }
2135        else {
2136            FileDialog fChooser = new FileDialog(shell, SWT.SAVE);
2137            fChooser.setFileName(Tools.checkNewFile(currentDir, ".h5").getName());
2138
2139            DefaultFileFilter filter = DefaultFileFilter.getFileFilterHDF5();
2140            fChooser.setFilterExtensions(new String[] {filter.getExtensions()});
2141            fChooser.setFilterNames(new String[] {filter.getDescription()});
2142            fChooser.setFilterIndex(0);
2143
2144            filename = fChooser.open();
2145        }
2146        if(filename == null)
2147            return;
2148
2149        try {
2150            Tools.createNewFile(filename, currentDir, FileFormat.FILE_TYPE_HDF5, fileList);
2151        }
2152        catch (Exception ex) {
2153            Tools.showError(shell, "Save", ex.getMessage());
2154        }
2155
2156        TreeItem rootItem = findTreeItem(root);
2157        int n = rootItem.getItemCount();
2158        ArrayList<TreeItem> objList = new ArrayList<>(n);
2159
2160        try {
2161            for (int i = 0; i < n; i++) objList.add(rootItem.getItem(i));
2162        }
2163        catch (Exception ex) {
2164            log.debug("saveAsHDF5() objList add failure: ", ex);
2165        }
2166
2167        FileFormat newFile = null;
2168        try {
2169            newFile = openFile(filename, FileFormat.WRITE);
2170        }
2171        catch (Exception ex) {
2172            shell.getDisplay().beep();
2173            Tools.showError(shell, "Save", ex.getMessage() + "\n" + filename);
2174            return;
2175        }
2176
2177        if (newFile == null)
2178            return;
2179
2180        HObject pitem = newFile.getRootObject();
2181
2182        pasteObject(objList.toArray(new TreeItem[0]), findTreeItem(pitem), newFile);
2183        objList.clear();
2184
2185        Group srcGroup = (Group) root;
2186        Group dstGroup = (Group) newFile.getRootObject();
2187        Object[] parameter = new Object[2];
2188        Class<?> classHOjbect = null;
2189        Class<?>[] parameterClass = new Class[2];
2190        Method method = null;
2191
2192        // Copy attributes of the root group
2193        try {
2194            parameter[0] = srcGroup;
2195            parameter[1] = dstGroup;
2196            classHOjbect = Class.forName("hdf.object.HObject");
2197            parameterClass[0] = parameterClass[1] = classHOjbect;
2198            method = newFile.getClass().getMethod("copyAttributes", parameterClass);
2199            method.invoke(newFile, parameter);
2200        }
2201        catch (Exception ex) {
2202            shell.getDisplay().beep();
2203            Tools.showError(shell, "Save", ex.getMessage());
2204        }
2205
2206        // Update reference datasets
2207        parameter[0] = srcGroup.getFileFormat();
2208        parameter[1] = newFile;
2209        parameterClass[0] = parameterClass[1] = parameter[0].getClass();
2210        try {
2211            method = newFile.getClass().getMethod("updateReferenceDataset", parameterClass);
2212            method.invoke(newFile, parameter);
2213        }
2214        catch (Exception ex) {
2215            shell.getDisplay().beep();
2216            Tools.showError(shell, "Save", ex.getMessage());
2217        }
2218    }
2219
2220    /** Save data as file.
2221     *
2222     * @throws Exception if a failure occurred
2223     */
2224    private void saveDataAsFile() throws Exception {
2225        if (!(selectedObject instanceof Dataset) || (selectedObject == null) || (selectedItem == null))
2226            return;
2227
2228        File chosenFile = null;
2229        String filename = null;
2230        Dataset dataset = (Dataset) selectedObject;
2231        String currentDir = dataset.getFile().substring(0, dataset.getFile().lastIndexOf(File.separator));
2232        String msgtext = null;
2233        if(binaryOrder == 99)
2234            msgtext = "Save Dataset Data To Text File --- " + dataset.getName();
2235        else
2236            msgtext = "Save Current Data To Binary File --- " + dataset.getName();
2237        if (((HDFView) viewer).getTestState()) {
2238            filename = currentDir + File.separator + new InputDialog(shell, msgtext, "").open();
2239        }
2240        else {
2241            FileDialog fChooser = new FileDialog(shell, SWT.SAVE);
2242            fChooser.setFilterPath(currentDir);
2243
2244            DefaultFileFilter filter = null;
2245
2246            if (binaryOrder == 99) {
2247                fChooser.setText(msgtext);
2248                fChooser.setFileName(dataset.getName() + ".txt");
2249                filter = DefaultFileFilter.getFileFilterText();
2250            }
2251            else {
2252                fChooser.setText(msgtext);
2253                fChooser.setFileName(dataset.getName() + ".bin");
2254                filter = DefaultFileFilter.getFileFilterBinary();
2255            }
2256
2257            fChooser.setFilterExtensions(new String[] {"*", filter.getExtensions()});
2258            fChooser.setFilterNames(new String[] {"All Files", filter.getDescription()});
2259            fChooser.setFilterIndex(1);
2260
2261            filename = fChooser.open();
2262        }
2263        if(filename == null)
2264            return;
2265
2266        // Check if the file is in use
2267        List<?> saveFileList = viewer.getTreeView().getCurrentFiles();
2268        if (saveFileList != null) {
2269            FileFormat theFile = null;
2270            Iterator<?> iterator = saveFileList.iterator();
2271            while (iterator.hasNext()) {
2272                theFile = (FileFormat) iterator.next();
2273                if (theFile.getFilePath().equals(filename)) {
2274                    shell.getDisplay().beep();
2275                    Tools.showError(shell, "Export Dataset", "Unable to save data to file \"" + filename + "\". \nThe file is being used.");
2276                    return;
2277                }
2278            }
2279        }
2280
2281        chosenFile = new File(filename);
2282
2283        if (chosenFile.exists()) {
2284            if(!Tools.showConfirm(shell, "Export Dataset", "File exists. Do you want to replace it?"))
2285                return;
2286        }
2287
2288        boolean isH4 = selectedObject.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4));
2289        if (isH4) {
2290            shell.getDisplay().beep();
2291            Tools.showError(shell, "Save", "Cannot export HDF4 object.");
2292            return;
2293        }
2294
2295        boolean isN3 = selectedObject.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_NC3));
2296        if (isN3) {
2297            shell.getDisplay().beep();
2298            Tools.showError(shell, "Save", "Cannot export netCDF3 object.");
2299            return;
2300        }
2301
2302        try {
2303            selectedObject.getFileFormat().exportDataset(filename, dataset, binaryOrder);
2304            viewer.showStatus("Data saved to: " + filename);
2305        }
2306        catch (Exception ex) {
2307            shell.getDisplay().beep();
2308            Tools.showError(shell, "Save", "Unable to export dataset: " + ex.getMessage());
2309        }
2310    }
2311
2312    /** enable/disable GUI components */
2313    private static void setEnabled(List<MenuItem> list, boolean b) {
2314        if (list == null)
2315            return;
2316
2317        Iterator<MenuItem> it = list.iterator();
2318        while (it.hasNext())
2319            it.next().setEnabled(b);
2320    }
2321
2322    /**
2323     * Opens a file and retrieves the file structure of the file. It also can be
2324     * used to create a new file by setting the accessID to FileFormat.CREATE.
2325     *
2326     * Subclasses must implement this function to take appropriate steps to open
2327     * a file.
2328     *
2329     * @param filename
2330     *            the name of the file to open.
2331     * @param accessID
2332     *            identifier for the file access. Valid value of accessID is:
2333     *            <ul>
2334     *            <li>FileFormat.READ --- allow read-only access to file.</li>
2335     *            <li>FileFormat.WRITE --- allow read and write access to file.</li>
2336     *            <li>FileFormat.CREATE --- create a new file.</li>
2337     *            </ul>
2338     *
2339     * @return the FileFormat of this file if successful; otherwise returns null.
2340     *
2341     * @throws Exception if a failure occurred
2342     */
2343    @Override
2344    public FileFormat openFile(String filename, int accessID) throws Exception {
2345        log.trace("openFile: {},{}", filename, accessID);
2346        FileFormat fileFormat = null;
2347        boolean isSWMRFile = (FileFormat.MULTIREAD == (accessID & FileFormat.MULTIREAD));
2348        log.trace("openFile: isSWMRFile={}", isSWMRFile);
2349        boolean isNewFile = (FileFormat.OPEN_NEW == (accessID & FileFormat.OPEN_NEW));
2350        if (isNewFile)
2351            accessID = accessID - FileFormat.OPEN_NEW; //strip OPEN_NEW
2352
2353        if (isFileOpen(filename)) {
2354            viewer.showStatus("File is in use.");
2355            return null;
2356        }
2357
2358        File tmpFile = new File(filename);
2359        if (!tmpFile.exists())
2360            throw new FileNotFoundException("File does not exist.");
2361
2362        if (!tmpFile.canWrite() && !isSWMRFile)
2363            accessID = FileFormat.READ;
2364
2365        Enumeration<?> keys = FileFormat.getFileFormatKeys();
2366
2367        String theKey = null;
2368        while (keys.hasMoreElements()) {
2369            theKey = (String) keys.nextElement();
2370            if (theKey.equals(FileFormat.FILE_TYPE_HDF4)) {
2371                log.trace("openFile: {} FILE_TYPE_HDF4", filename);
2372                try {
2373                    FileFormat h4format = FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4);
2374                    if ((h4format != null) && h4format.isThisType(filename)) {
2375                        fileFormat = h4format.createInstance(filename, accessID);
2376                        break;
2377                    }
2378                }
2379                catch (UnsatisfiedLinkError e) {
2380                    log.debug("openFile({}): HDF4 library link error:", filename, e);
2381                    viewer.showError("Unable to open file '" + filename + "': HDF4 library linking error");
2382                }
2383                catch (Exception err) {
2384                    log.debug("openFile: Error retrieving the file structure of {}:", filename, err);
2385                }
2386                continue;
2387            }
2388            else if (theKey.equals(FileFormat.FILE_TYPE_HDF5)) {
2389                log.trace("openFile: {} FILE_TYPE_HDF5", filename);
2390                try {
2391                    FileFormat h5format = FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5);
2392                    if ((h5format != null) && h5format.isThisType(filename)) {
2393                        fileFormat = h5format.createInstance(filename, accessID);
2394                        break;
2395                    }
2396                }
2397                catch (UnsatisfiedLinkError e) {
2398                    log.debug("openFile({}): HDF5 library link error:", filename, e);
2399                    viewer.showError("Unable to open file '" + filename + "': HDF5 library linking error");
2400                }
2401                catch (Exception err) {
2402                    log.debug("openFile: Error retrieving the file structure of {}:", filename, err);
2403                }
2404                continue;
2405            }
2406            else if (theKey.equals(FileFormat.FILE_TYPE_NC3)) {
2407                log.trace("openFile: {} FILE_TYPE_NC3", filename);
2408                try {
2409                    FileFormat nc3format = FileFormat.getFileFormat(FileFormat.FILE_TYPE_NC3);
2410                    if ((nc3format != null) && nc3format.isThisType(filename)) {
2411                        fileFormat = nc3format.createInstance(filename, accessID);
2412                        break;
2413                    }
2414                }
2415                catch (UnsatisfiedLinkError e) {
2416                    log.debug("openFile({}): NetCDF3 library link error:", filename, e);
2417                    viewer.showError("Unable to open file '" + filename + "': NetCDF3 library linking error");
2418                }
2419                catch (Exception err) {
2420                    log.debug("openFile: Error retrieving the file structure of {}:", filename, err);
2421                }
2422                continue;
2423            }
2424            else {
2425                log.trace("openFile: {} Other", filename);
2426                try {
2427                    FileFormat theformat = FileFormat.getFileFormat(theKey);
2428                    if (theformat.isThisType(filename)) {
2429                        fileFormat = theformat.createInstance(filename, accessID);
2430                        break;
2431                    }
2432                }
2433                catch (Exception err) {
2434                    log.debug("openFile: Error retrieving the file structure of {}:", filename, err);
2435                }
2436            }
2437        }
2438
2439        if (fileFormat == null)
2440            throw new java.io.IOException("Unsupported fileformat - " + filename);
2441
2442        if (fileFormat.isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5))) {
2443            if (tempIdxType >= 0) {
2444                fileFormat.setIndexType(tempIdxType);
2445
2446                // Reset the temporary index type
2447                tempIdxType = -1;
2448            }
2449            else
2450                fileFormat.setIndexType(fileFormat.getIndexType(ViewProperties.getIndexType()));
2451
2452            if (tempIdxOrder >= 0) {
2453                fileFormat.setIndexOrder(tempIdxOrder);
2454
2455                // Reset the temporary index order
2456                tempIdxOrder = -1;
2457            }
2458            else
2459                fileFormat.setIndexOrder(fileFormat.getIndexOrder(ViewProperties.getIndexOrder()));
2460        }
2461
2462        return initFile(fileFormat);
2463    }
2464
2465    /**
2466     * Initializes a FileFormat object by opening it and populating the file tree structure.
2467     *
2468     * @param fileFormat
2469     *            the file to open with an existing FileFormat instance.
2470     *
2471     * @return the initialized FileFormat of this file if successful; otherwise returns null.
2472     *
2473     * @throws Exception
2474     *             if a failure occurred
2475     */
2476    private FileFormat initFile(FileFormat fileFormat) throws Exception {
2477        log.trace("initFile[{}] - start", fileFormat.getAbsolutePath());
2478
2479        TreeItem fileRoot = null;
2480
2481        shell.setCursor(Display.getCurrent().getSystemCursor(SWT.CURSOR_WAIT));
2482
2483        try {
2484            fileFormat.setMaxMembers(ViewProperties.getMaxMembers());
2485            fileFormat.setStartMembers(ViewProperties.getStartMembers());
2486
2487            fileFormat.open();
2488
2489            fileRoot = populateTree(fileFormat);
2490
2491            if (fileRoot != null) {
2492                /* Expand top level items of root object */
2493                int currentRowCount = tree.getItemCount();
2494                if (currentRowCount > 0)
2495                    tree.getItem(currentRowCount - 1).setExpanded(true);
2496
2497                fileList.add(fileFormat);
2498            }
2499
2500            tree.setItemCount(fileList.size());
2501
2502            log.trace("initFile[{}] - fileList items={}", fileFormat.getAbsolutePath(), fileList.size());
2503        }
2504        catch (Exception ex) {
2505            log.debug("initFile: FileFormat init error:", ex);
2506            fileFormat = null;
2507        }
2508        finally {
2509            shell.setCursor(null);
2510        }
2511
2512        return fileFormat;
2513    }
2514
2515    @Override
2516    public FileFormat reopenFile(FileFormat fileFormat, int newFileAccessMode) throws Exception {
2517        String fileFormatName = fileFormat.getAbsolutePath();
2518
2519        // Make sure to reload the file using the file's current indexing options
2520        tempIdxType = fileFormat.getIndexType(null);
2521        tempIdxOrder = fileFormat.getIndexOrder(null);
2522
2523        closeFile(fileFormat);
2524        ((HDFView) viewer).showMetaData(null);
2525
2526        if (newFileAccessMode < 0) {
2527            if (ViewProperties.isReadOnly())
2528                return openFile(fileFormatName, FileFormat.READ);
2529            else if (ViewProperties.isReadSWMR())
2530                return openFile(fileFormatName, FileFormat.READ | FileFormat.MULTIREAD);
2531            else
2532                return openFile(fileFormatName, FileFormat.WRITE);
2533        }
2534        else
2535            return openFile(fileFormatName, newFileAccessMode);
2536    }
2537
2538    /**
2539     * Close a file
2540     *
2541     * @param file
2542     *            the file to close
2543     *
2544     * @throws Exception if a failure occurred
2545     */
2546    @Override
2547    public void closeFile(FileFormat file) throws Exception {
2548        if (file == null) return;
2549
2550        // Find the file item in the tree and remove it
2551        FileFormat theFile = null;
2552        TreeItem[] openFiles = tree.getItems(); // Returns the top-level items of the tree
2553
2554        for (int i = 0; i < openFiles.length; i++) {
2555            theFile = ((Group) openFiles[i].getData()).getFileFormat();
2556
2557            if (theFile.equals(file)) {
2558                // Remove TreeItem from the view
2559                openFiles[i].dispose();
2560                log.trace("dispose({}):", theFile.getFilePath());
2561
2562                try {
2563                    theFile.close();
2564                }
2565                catch (Exception ex) {
2566                    log.debug("closeFile({}):", theFile.getFilePath(), ex);
2567                }
2568
2569                fileList.remove(theFile);
2570                if (theFile.equals(selectedFile)) {
2571                    selectedFile = null;
2572                    selectedObject = null;
2573                    selectedItem = null;
2574                }
2575
2576                break;
2577            }
2578        }
2579    }
2580
2581    /**
2582     * Save a file
2583     *
2584     * @param file
2585     *            the file to save
2586     *
2587     * @throws Exception if a failure occurred
2588     */
2589    @Override
2590    public void saveFile(FileFormat file) throws Exception {
2591        if (file == null) {
2592            shell.getDisplay().beep();
2593            Tools.showError(shell, "Save", "Select a file to save.");
2594            return;
2595        }
2596
2597        boolean isH4 = file.isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4));
2598        boolean isH5 = file.isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5));
2599
2600        if (!(isH4 || isH5)) {
2601            shell.getDisplay().beep();
2602            Tools.showError(shell, "Save", "Saving file is not supported for this file type");
2603            return;
2604        }
2605
2606        // Write the change of the data into the file before saving the file
2607        ((HDFView) viewer).writeDataToFile(file);
2608
2609        if (isH5)
2610            saveAsHDF5(file);
2611        else if (isH4)
2612            saveAsHDF4(file);
2613    }
2614
2615    /**
2616     * Returns the tree item that contains the given data object.
2617     */
2618    @Override
2619    public TreeItem findTreeItem(HObject obj) {
2620        if (obj == null)
2621            return null;
2622
2623        if (obj.getFileFormat().getRootObject() == null)
2624            return null;
2625
2626        // Locate the item's file root to save on search time in large files
2627        TreeItem[] fileRoots = tree.getItems();
2628        TreeItem rootItem = null;
2629        HObject rootObject = null;
2630        for (int i = 0; i < fileRoots.length; i++) {
2631            rootItem = fileRoots[i];
2632            rootObject = (HObject) rootItem.getData();
2633
2634            if (rootObject == null)
2635                continue;
2636
2637            if (rootObject.getFileFormat().equals(obj.getFileFormat())) {
2638                // If the object being looked for is a file root, return
2639                // this found TreeItem
2640                if (obj instanceof Group && ((Group) obj).isRoot())
2641                    return rootItem;
2642
2643                // Else the file root for the object being looked for has
2644                // been found, continue the search through only this TreeItem's
2645                // members
2646                break;
2647            }
2648        }
2649
2650        TreeItem theItem = null;
2651        HObject theObj = null;
2652        List<TreeItem> breadthFirstItems = getItemsBreadthFirst(rootItem);
2653
2654        if (breadthFirstItems != null) {
2655            Iterator<TreeItem> it = getItemsBreadthFirst(rootItem).iterator();
2656
2657            while (it.hasNext()) {
2658                theItem = it.next();
2659                theObj = (HObject) theItem.getData();
2660
2661                if (theObj == null)
2662                    continue;
2663
2664                if (theObj.equals(obj))
2665                    return theItem;
2666            }
2667        }
2668
2669        return null;
2670    }
2671
2672    /**
2673     * change the display option.
2674     */
2675    @Override
2676    public void setDefaultDisplayMode(boolean displaymode) {
2677        isDefaultDisplay = displaymode;
2678    }
2679
2680    /**
2681     * Gets the selected file. When multiple files are open, we need to know
2682     * which file is currently selected.
2683     *
2684     * @return the FileFormat of the currently selected file.
2685     */
2686    @Override
2687    public FileFormat getSelectedFile() {
2688        return selectedFile;
2689    }
2690
2691    /**
2692     * @return the currently selected object in the tree.
2693     */
2694    @Override
2695    public HObject getCurrentObject() {
2696        return selectedObject;
2697    }
2698
2699    /**
2700     * @return the Tree which holds the file structure.
2701     */
2702    @Override
2703    public Tree getTree() {
2704        return tree;
2705    }
2706
2707    /**
2708     * @return the list of currently open files.
2709     */
2710    @Override
2711    public List<FileFormat> getCurrentFiles() {
2712        return fileList;
2713    }
2714
2715    /**
2716     * Display the content of a data object.
2717     *
2718     * @param dataObject
2719     *            the data object
2720     *
2721     * @return the DataView that displays the data content
2722     *
2723     * @throws Exception if a failure occurred
2724     */
2725    @Override
2726    public DataView showDataContent(HObject dataObject) throws Exception {
2727        /* Can only display objects with data */
2728        if ((dataObject == null) || !(dataObject instanceof DataFormat))
2729            return null;
2730
2731        log.trace("showDataContent({}): start", dataObject.getName());
2732
2733        /* Set up the default display properties passed to the DataView instance */
2734        DataView theView = null;
2735        DataFormat d = (DataFormat) dataObject;
2736        HashMap<DATA_VIEW_KEY, Serializable> map = new HashMap<>(8);
2737
2738        if (!d.isInited())
2739            d.init();
2740
2741        boolean isImage = ((d instanceof ScalarDS) && ((ScalarDS) d).isImage());
2742        boolean isDisplayTypeChar = false;
2743        boolean isTransposed = false;
2744        boolean isIndexBase1 = ViewProperties.isIndexBase1();
2745        BitSet bitmask = null;
2746        String dataViewName = null;
2747
2748        if (isDefaultDisplay) { /* Displaying a data object using the default display options */
2749            DataView existingView = viewer.getDataView((HObject) d);
2750
2751            /*
2752             * Check to make sure this data object isn't already opened in an existing
2753             * DataView. If it is, just bring that DataView to focus.
2754             */
2755            if (existingView != null) {
2756                Shell[] shells = Display.getDefault().getShells();
2757
2758                if (shells.length >= 1) {
2759                    for (int i = 0; i < shells.length; i++) {
2760                        DataView view = (DataView) shells[i].getData();
2761
2762                        if (view != null) {
2763                            if (view.equals(existingView)) {
2764                                shells[i].forceActive();
2765
2766                                log.trace("showDataContent(): found existing DataView for data object {}", dataObject.getName());
2767
2768                                return view;
2769                            }
2770                        }
2771                    }
2772                }
2773            }
2774        }
2775        else { /* Open Dialog to allow user to choose different data display options */
2776            DataOptionDialog dialog = new DataOptionDialog(shell, d);
2777            dialog.open();
2778
2779            if (dialog.isCancelled())
2780                return null;
2781
2782            isImage = dialog.isImageDisplay();
2783            isDisplayTypeChar = dialog.isDisplayTypeChar();
2784            dataViewName = dialog.getDataViewName();
2785            isTransposed = dialog.isTransposed();
2786            bitmask = dialog.getBitmask();
2787            isIndexBase1 = dialog.isIndexBase1();
2788            isApplyBitmaskOnly = dialog.isApplyBitmaskOnly();
2789        }
2790
2791        map.put(ViewProperties.DATA_VIEW_KEY.OBJECT, dataObject);
2792        map.put(ViewProperties.DATA_VIEW_KEY.VIEW_NAME, dataViewName);
2793        map.put(ViewProperties.DATA_VIEW_KEY.CHAR, isDisplayTypeChar);
2794        map.put(ViewProperties.DATA_VIEW_KEY.TRANSPOSED, isTransposed);
2795        map.put(ViewProperties.DATA_VIEW_KEY.INDEXBASE1, isIndexBase1);
2796        map.put(ViewProperties.DATA_VIEW_KEY.BITMASK, bitmask);
2797        if (isApplyBitmaskOnly)
2798            map.put(ViewProperties.DATA_VIEW_KEY.BITMASKOP, ViewProperties.BITMASK_OP.AND);
2799
2800        log.trace(
2801                "showDataContent(): object={} dataViewName={} isDisplayTypeChar={} isTransposed={} isIndexBase1={} bitmask={}",
2802                dataObject, dataViewName, isDisplayTypeChar, isTransposed, isIndexBase1, bitmask);
2803
2804        shell.setCursor(Display.getCurrent().getSystemCursor(SWT.CURSOR_WAIT));
2805
2806        if (isImage) {
2807            DataViewFactory imageViewFactory = null;
2808            try {
2809                imageViewFactory = DataViewFactoryProducer.getFactory(DataViewType.IMAGE);
2810            }
2811            catch (Exception ex) {
2812                log.debug("showDataContent(): error occurred while instantiating ImageView factory class", ex);
2813                viewer.showError("Error occurred while instantiating ImageView factory class");
2814                return null;
2815            }
2816
2817            if (imageViewFactory == null) {
2818                log.debug("showDataContent(): ImageView factory is null");
2819                return null;
2820            }
2821
2822            try {
2823                theView = imageViewFactory.getImageView(viewer, map);
2824
2825                if (theView == null) {
2826                    log.debug("showDataContent(): error occurred while instantiating ImageView class");
2827                    viewer.showError("Error occurred while instantiating ImageView class");
2828                    Tools.showError(shell, "Show Data", "Error occurred while instantiating ImageView class");
2829                }
2830            }
2831            catch (ClassNotFoundException ex) {
2832                log.debug("showDataContent(): no suitable ImageView class found");
2833                viewer.showError("Unable to find suitable ImageView class for object '" + dataObject.getName() + "'");
2834                Tools.showError(shell, "Show Data", "Unable to find suitable ImageView class for object '" + dataObject.getName() + "'");
2835                theView = null;
2836            }
2837        }
2838        else {
2839            DataViewFactory tableViewFactory = null;
2840            try {
2841                tableViewFactory = DataViewFactoryProducer.getFactory(DataViewType.TABLE);
2842            }
2843            catch (Exception ex) {
2844                log.debug("showDataContent(): error occurred while instantiating TableView factory class", ex);
2845                viewer.showError("Error occurred while instantiating TableView factory class");
2846                return null;
2847            }
2848
2849            if (tableViewFactory == null) {
2850                log.debug("showDataContent(): TableView factory is null");
2851                return null;
2852            }
2853
2854            try {
2855                theView = tableViewFactory.getTableView(viewer, map);
2856
2857                if (theView == null) {
2858                    log.debug("showDataContent(): error occurred while instantiating TableView class");
2859                    viewer.showError("Error occurred while instantiating TableView class");
2860                    Tools.showError(shell, "Show Data", "Error occurred while instantiating TableView class");
2861                }
2862            }
2863            catch (ClassNotFoundException ex) {
2864                log.debug("showDataContent(): no suitable TableView class found");
2865                viewer.showError("Unable to find suitable TableView class for object '" + dataObject.getName() + "'");
2866                Tools.showError(shell, "Show Data", "Unable to find suitable TableView class for object '" + dataObject.getName() + "'");
2867                theView = null;
2868            }
2869        }
2870
2871        if (!shell.isDisposed())
2872            shell.setCursor(null);
2873
2874        return theView;
2875    }
2876
2877    /**
2878     * Updates the current font.
2879     *
2880     * @param font
2881     *           the new font
2882     */
2883    public void updateFont(Font font) {
2884        if (curFont != null)
2885            curFont.dispose();
2886
2887        curFont = font;
2888
2889        tree.setFont(font);
2890    }
2891
2892    /**
2893     * Updates the icon for the TreeItem representing the given HObject. Used
2894     * to change the icon after a status update, such as adding an attribute to
2895     * an object.
2896     *
2897     * @param obj
2898     *           the object to update the icon for
2899     */
2900    public void updateItemIcon(HObject obj) {
2901        if (obj == null) {
2902            log.debug("updateItemIcon(): object is null");
2903            return;
2904        }
2905
2906        TreeItem theItem = findTreeItem(obj);
2907
2908        if (theItem == null) {
2909            log.debug("updateItemIcon(): could not find TreeItem for HObject");
2910        }
2911        else {
2912            if (obj instanceof Group && !(((Group) obj).isRoot())) {
2913                if (theItem.getExpanded()) {
2914                    if (((MetaDataContainer) obj).hasAttribute())
2915                        theItem.setImage(folderOpenIconA);
2916                    else
2917                        theItem.setImage(folderOpenIcon);
2918                }
2919                else {
2920                    if (((MetaDataContainer) obj).hasAttribute())
2921                        theItem.setImage(folderCloseIconA);
2922                    else
2923                        theItem.setImage(folderCloseIcon);
2924                }
2925            }
2926            else {
2927                theItem.setImage(getObjectTypeImage(obj));
2928            }
2929        }
2930    }
2931
2932    /**
2933     * ChangeIndexingDialog displays file index options.
2934     */
2935    private class ChangeIndexingDialog extends Dialog
2936    {
2937        private Button checkIndexByName;
2938        private Button checkIndexIncrements;
2939        private Button checkIndexNative;
2940
2941        private boolean reloadFile;
2942
2943        private FileFormat selectedFile;
2944        private int indexType;
2945        private int indexOrder;
2946
2947        private ChangeIndexingDialog(Shell parent, int style, FileFormat viewSelectedFile) {
2948            super(parent, style);
2949
2950            selectedFile = viewSelectedFile;
2951            try {
2952                indexType = selectedFile.getIndexType(null);
2953            }
2954            catch (Exception ex) {
2955                log.debug("ChangeIndexingDialog(): getIndexType failed: ", ex);
2956            }
2957            try {
2958                indexOrder = selectedFile.getIndexOrder(null);
2959            }
2960            catch (Exception ex) {
2961                log.debug("ChangeIndexingDialog(): getIndexOrder failed: ", ex);
2962            }
2963            reloadFile = false;
2964        }
2965
2966        private void setIndexOptions() {
2967            try {
2968                if (checkIndexByName.getSelection())
2969                    indexType = selectedFile.getIndexType("H5_INDEX_NAME");
2970                else
2971                    indexType = selectedFile.getIndexType("H5_INDEX_CRT_ORDER");
2972            }
2973            catch (Exception ex) {
2974                log.debug("setIndexOptions(): getIndexType failed: ", ex);
2975            }
2976
2977            try {
2978                if (checkIndexIncrements.getSelection())
2979                    indexOrder = selectedFile.getIndexOrder("H5_ITER_INC");
2980                else if (checkIndexNative.getSelection())
2981                    indexOrder = selectedFile.getIndexOrder("H5_ITER_NATIVE");
2982                else
2983                    indexOrder = selectedFile.getIndexOrder("H5_ITER_DEC");
2984            }
2985            catch (Exception ex) {
2986                log.debug("setIndexOptions(): getIndexOrder failed: ", ex);
2987            }
2988
2989            reloadFile = true;
2990        }
2991
2992        /** @return the current value of the index type. */
2993        public int getIndexType() {
2994            return indexType;
2995        }
2996
2997        /** @return the current value of the index order. */
2998        public int getIndexOrder() {
2999            return indexOrder;
3000        }
3001
3002        /** @return the current value of the reloadFile. */
3003        public boolean isReloadFile() {
3004            return reloadFile;
3005        }
3006
3007        /** open the ChangeIndexingDialog for setting the indexing values. */
3008        public void open() {
3009            Shell parent = getParent();
3010            final Shell openShell = new Shell(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL);
3011            openShell.setFont(curFont);
3012            openShell.setText("Indexing options");
3013            openShell.setImages(ViewProperties.getHdfIcons());
3014            openShell.setLayout(new GridLayout(1, true));
3015
3016            // Create main content region
3017            Composite content = new Composite(openShell, SWT.NONE);
3018            content.setLayout(new GridLayout(1, true));
3019            content.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
3020
3021            org.eclipse.swt.widgets.Group indexingTypeGroup = new org.eclipse.swt.widgets.Group(content, SWT.NONE);
3022            indexingTypeGroup.setFont(curFont);
3023            indexingTypeGroup.setText("Indexing Type");
3024            indexingTypeGroup.setLayout(new GridLayout(2, true));
3025            indexingTypeGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
3026
3027            int initIndexType = 0;
3028            try {
3029                initIndexType = selectedFile.getIndexType("H5_INDEX_NAME");
3030            }
3031            catch (Exception ex) {
3032                log.debug("open(): getIndexType failed: ", ex);
3033            }
3034            checkIndexByName = new Button(indexingTypeGroup, SWT.RADIO);
3035            checkIndexByName.setFont(curFont);
3036            checkIndexByName.setText("By Name");
3037            checkIndexByName.setSelection((indexType) == initIndexType);
3038            checkIndexByName.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, false, false));
3039
3040            try {
3041                initIndexType = selectedFile.getIndexType("H5_INDEX_CRT_ORDER");
3042            }
3043            catch (Exception ex) {
3044                log.debug("open(): getIndexType failed: ", ex);
3045            }
3046            Button byOrder = new Button(indexingTypeGroup, SWT.RADIO);
3047            byOrder.setFont(curFont);
3048            byOrder.setText("By Creation Order");
3049            byOrder.setSelection((indexType) == initIndexType);
3050            byOrder.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, false, false));
3051
3052            org.eclipse.swt.widgets.Group indexingOrderGroup = new org.eclipse.swt.widgets.Group(content, SWT.NONE);
3053            indexingOrderGroup.setFont(curFont);
3054            indexingOrderGroup.setText("Indexing Order");
3055            indexingOrderGroup.setLayout(new GridLayout(3, true));
3056            indexingOrderGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
3057
3058            int initIndexOrder = 0;
3059            try {
3060                initIndexOrder = selectedFile.getIndexOrder("H5_ITER_INC");
3061            }
3062            catch (Exception ex) {
3063                log.debug("open(): getIndexOrder failed: ", ex);
3064            }
3065            checkIndexIncrements = new Button(indexingOrderGroup, SWT.RADIO);
3066            checkIndexIncrements.setFont(curFont);
3067            checkIndexIncrements.setText("Increments");
3068            checkIndexIncrements.setSelection((indexOrder) == initIndexOrder);
3069            checkIndexIncrements.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, false, false));
3070
3071            try {
3072                initIndexOrder = selectedFile.getIndexOrder("H5_ITER_DEC");
3073            }
3074            catch (Exception ex) {
3075                log.debug("open(): getIndexOrder failed: ", ex);
3076            }
3077            Button decrements = new Button(indexingOrderGroup, SWT.RADIO);
3078            decrements.setFont(curFont);
3079            decrements.setText("Decrements");
3080            decrements.setSelection((indexOrder) == initIndexOrder);
3081            decrements.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, false, false));
3082
3083            try {
3084                initIndexOrder = selectedFile.getIndexOrder("H5_ITER_NATIVE");
3085            }
3086            catch (Exception ex) {
3087                log.debug("open(): getIndexOrder failed: ", ex);
3088            }
3089            checkIndexNative = new Button(indexingOrderGroup, SWT.RADIO);
3090            checkIndexNative.setFont(curFont);
3091            checkIndexNative.setText("Native");
3092            checkIndexNative.setSelection((indexOrder) == initIndexOrder);
3093            checkIndexNative.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, false, false));
3094
3095
3096            // Create Ok/Cancel button region
3097            Composite buttonComposite = new Composite(openShell, SWT.NONE);
3098            buttonComposite.setLayout(new GridLayout(2, true));
3099            buttonComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
3100
3101            Button okButton = new Button(buttonComposite, SWT.PUSH);
3102            okButton.setFont(curFont);
3103            okButton.setText("   &Reload File   ");
3104            okButton.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
3105            okButton.addSelectionListener(new SelectionAdapter() {
3106                @Override
3107                public void widgetSelected(SelectionEvent e) {
3108                    setIndexOptions();
3109                    openShell.dispose();
3110                }
3111            });
3112
3113            Button cancelButton = new Button(buttonComposite, SWT.PUSH);
3114            cancelButton.setFont(curFont);
3115            cancelButton.setText("     &Cancel     ");
3116            cancelButton.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, true, false));
3117            cancelButton.addSelectionListener(new SelectionAdapter() {
3118                @Override
3119                public void widgetSelected(SelectionEvent e) {
3120                    reloadFile = false;
3121                    openShell.dispose();
3122                }
3123            });
3124
3125            openShell.pack();
3126
3127            Point minimumSize = openShell.computeSize(SWT.DEFAULT, SWT.DEFAULT);
3128
3129            openShell.setMinimumSize(minimumSize);
3130
3131            Rectangle parentBounds = parent.getBounds();
3132            Point shellSize = openShell.getSize();
3133            openShell.setLocation((parentBounds.x + (parentBounds.width / 2)) - (shellSize.x / 2),
3134                    (parentBounds.y + (parentBounds.height / 2)) - (shellSize.y / 2));
3135
3136            openShell.open();
3137
3138            Display display = parent.getDisplay();
3139            while (!openShell.isDisposed())
3140                if (!display.readAndDispatch())
3141                    display.sleep();
3142        }
3143    }
3144
3145    private class ChangeLibVersionDialog extends Dialog
3146    {
3147        public ChangeLibVersionDialog(Shell parent, int style) {
3148            super(parent, style);
3149        }
3150
3151        public void open() {
3152            Shell parent = getParent();
3153            final Shell openShell = new Shell(parent, SWT.SHELL_TRIM | SWT.APPLICATION_MODAL);
3154            openShell.setFont(curFont);
3155            openShell.setText("Set the library version bounds: ");
3156            openShell.setImages(ViewProperties.getHdfIcons());
3157            openShell.setLayout(new GridLayout(1, true));
3158
3159            String[] lowValues = { "Earliest", "V18", "V110", "V112", "V114", "Latest" };
3160            int[] lowConstants = { HDF5Constants.H5F_LIBVER_EARLIEST, HDF5Constants.H5F_LIBVER_V18, HDF5Constants.H5F_LIBVER_V110, HDF5Constants.H5F_LIBVER_V112, HDF5Constants.H5F_LIBVER_V114, HDF5Constants.H5F_LIBVER_LATEST };
3161            String[] highValues = { "V18", "V110", "V112", "V114", "Latest" };
3162            int[] highConstants = { HDF5Constants.H5F_LIBVER_V18, HDF5Constants.H5F_LIBVER_V110, HDF5Constants.H5F_LIBVER_V112, HDF5Constants.H5F_LIBVER_V114, HDF5Constants.H5F_LIBVER_LATEST };
3163
3164            // Try to retrieve the existing version bounds
3165            int[] current = null;
3166            try {
3167                current = selectedObject.getFileFormat().getLibBounds();
3168            }
3169            catch (Exception err) {
3170                openShell.getDisplay().beep();
3171                Tools.showError(openShell, "Version bounds", "Error when getting lib version bounds, using default");
3172                current = new int[]{HDF5Constants.H5F_LIBVER_EARLIEST, HDF5Constants.H5F_LIBVER_LATEST};
3173            }
3174            int lowidx = 0;
3175            for(int i = 0; i < lowConstants.length; i++) {
3176                if(lowConstants[i] == current[0]){
3177                    lowidx = i;
3178                    break;
3179                }
3180            }
3181            int highidx = 0;
3182            for(int i = 0; i < highConstants.length; i++) {
3183                if(highConstants[i] == current[1]){
3184                    highidx = i;
3185                    break;
3186                }
3187            }
3188
3189            // Dummy label
3190            new Label(openShell, SWT.LEFT);
3191
3192            Label label = new Label(openShell, SWT.LEFT);
3193            label.setFont(curFont);
3194            label.setText("Earliest Version: ");
3195
3196            final Combo earliestCombo = new Combo(openShell, SWT.SINGLE | SWT.BORDER | SWT.READ_ONLY);
3197            earliestCombo.setFont(curFont);
3198            earliestCombo.setItems(lowValues);
3199            earliestCombo.select(lowidx);
3200            earliestCombo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
3201
3202            label = new Label(openShell, SWT.LEFT);
3203            label.setFont(curFont);
3204            label.setText("Latest Version: ");
3205
3206            final Combo latestCombo = new Combo(openShell, SWT.SINGLE | SWT.BORDER | SWT.READ_ONLY);
3207            latestCombo.setFont(curFont);
3208            latestCombo.setItems(highValues);
3209            latestCombo.select(highidx);
3210            latestCombo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
3211
3212            // Dummy label to consume remain space after resizing
3213            new Label(openShell, SWT.LEFT).setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
3214
3215            // Create Ok/Cancel button region
3216            Composite buttonComposite = new Composite(openShell, SWT.NONE);
3217            buttonComposite.setLayout(new GridLayout(2, true));
3218            buttonComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
3219
3220            Button okButton = new Button(buttonComposite, SWT.PUSH);
3221            okButton.setFont(curFont);
3222            okButton.setText("   &OK   ");
3223            okButton.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
3224            okButton.addSelectionListener(new SelectionAdapter() {
3225                @Override
3226                public void widgetSelected(SelectionEvent e) {
3227                    try {
3228                        selectedObject.getFileFormat().setLibBounds(earliestCombo.getItem(earliestCombo.getSelectionIndex()), latestCombo.getItem(latestCombo.getSelectionIndex()));
3229                    }
3230                    catch (Exception err) {
3231                        openShell.getDisplay().beep();
3232                        Tools.showError(openShell, "Version bounds", "Error when setting lib version bounds");
3233                        return;
3234                    }
3235
3236                    openShell.dispose();
3237
3238                    ((HDFView) viewer).showMetaData(selectedObject);
3239                }
3240            });
3241
3242            Button cancelButton = new Button(buttonComposite, SWT.PUSH);
3243            cancelButton.setFont(curFont);
3244            cancelButton.setText(" &Cancel ");
3245            cancelButton.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, true, false));
3246            cancelButton.addSelectionListener(new SelectionAdapter() {
3247                @Override
3248                public void widgetSelected(SelectionEvent e) {
3249                    openShell.dispose();
3250                }
3251            });
3252
3253            openShell.pack();
3254
3255            Point minimumSize = openShell.computeSize(SWT.DEFAULT, SWT.DEFAULT);
3256
3257            openShell.setMinimumSize(minimumSize);
3258            openShell.setSize(minimumSize.x + 50, minimumSize.y);
3259
3260            Rectangle parentBounds = parent.getBounds();
3261            Point shellSize = openShell.getSize();
3262            openShell.setLocation((parentBounds.x + (parentBounds.width / 2)) - (shellSize.x / 2),
3263                    (parentBounds.y + (parentBounds.height / 2)) - (shellSize.y / 2));
3264
3265            openShell.open();
3266
3267            Display display = parent.getDisplay();
3268            while (!openShell.isDisposed())
3269                if (!display.readAndDispatch())
3270                    display.sleep();
3271        }
3272    }
3273
3274    private class LoadDataThread extends Thread
3275    {
3276        LoadDataThread() {
3277            super();
3278            setDaemon(true);
3279        }
3280
3281        @Override
3282        public void run() {
3283            try {
3284                Display.getDefault().syncExec(new Runnable() {
3285                    @Override
3286                    public void run() {
3287                        try {
3288                            showDataContent(selectedObject);
3289                        }
3290                        catch (Exception ex) {
3291                            log.debug("showDataContent failure: ", ex);
3292                        }
3293                    }
3294                });
3295            }
3296            catch (Exception e) {
3297                log.debug("showDataContent loading manually interrupted");
3298            }
3299        }
3300    }
3301}