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