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