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