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