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