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