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