001/*****************************************************************************
002 * Copyright by The HDF Group.                                               *
003 * Copyright by the Board of Trustees of the University of Illinois.         *
004 * All rights reserved.                                                      *
005 *                                                                           *
006 * This file is part of the HDF Java Products distribution.                  *
007 * The full copyright notice, including terms governing use, modification,   *
008 * and redistribution, is contained in the COPYING file, which can be found  *
009 * at the root of the source code distribution tree,                         *
010 * or in https://www.hdfgroup.org/licenses.                                  *
011 * If you do not have access to either file, you may request a copy from     *
012 * help@hdfgroup.org.                                                        *
013 ****************************************************************************/
014
015package hdf.view;
016
017import java.io.BufferedInputStream;
018import java.io.BufferedOutputStream;
019import java.io.File;
020import java.io.FileOutputStream;
021import java.net.URL;
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.Enumeration;
025import java.util.Iterator;
026import java.util.List;
027
028import hdf.HDFVersions;
029import hdf.object.DataFormat;
030import hdf.object.FileFormat;
031import hdf.object.HObject;
032import hdf.view.DataView.DataView;
033import hdf.view.DataView.DataViewFactory;
034import hdf.view.DataView.DataViewFactoryProducer;
035import hdf.view.DataView.DataViewManager;
036import hdf.view.HelpView.HelpView;
037import hdf.view.MetaDataView.MetaDataView;
038import hdf.view.TableView.TableView;
039import hdf.view.TreeView.DefaultTreeView;
040import hdf.view.TreeView.TreeView;
041import hdf.view.ViewProperties.DataViewType;
042import hdf.view.dialog.ImageConversionDialog;
043import hdf.view.dialog.InputDialog;
044import hdf.view.dialog.UserOptionsDialog;
045import hdf.view.dialog.UserOptionsGeneralPage;
046import hdf.view.dialog.UserOptionsHDFPage;
047import hdf.view.dialog.UserOptionsNode;
048import hdf.view.dialog.UserOptionsViewModulesPage;
049
050import hdf.hdf5lib.H5;
051
052import org.slf4j.Logger;
053import org.slf4j.LoggerFactory;
054
055import org.eclipse.jface.preference.PreferenceManager;
056import org.eclipse.swt.SWT;
057import org.eclipse.swt.custom.SashForm;
058import org.eclipse.swt.custom.ScrolledComposite;
059import org.eclipse.swt.dnd.DND;
060import org.eclipse.swt.dnd.DropTarget;
061import org.eclipse.swt.dnd.DropTargetEvent;
062import org.eclipse.swt.dnd.DropTargetListener;
063import org.eclipse.swt.dnd.FileTransfer;
064import org.eclipse.swt.dnd.Transfer;
065import org.eclipse.swt.events.DisposeEvent;
066import org.eclipse.swt.events.DisposeListener;
067import org.eclipse.swt.events.KeyAdapter;
068import org.eclipse.swt.events.KeyEvent;
069import org.eclipse.swt.events.SelectionAdapter;
070import org.eclipse.swt.events.SelectionEvent;
071import org.eclipse.swt.graphics.Font;
072import org.eclipse.swt.graphics.Image;
073import org.eclipse.swt.graphics.Point;
074import org.eclipse.swt.graphics.Rectangle;
075import org.eclipse.swt.layout.FillLayout;
076import org.eclipse.swt.layout.GridData;
077import org.eclipse.swt.layout.GridLayout;
078import org.eclipse.swt.layout.RowLayout;
079import org.eclipse.swt.widgets.Button;
080import org.eclipse.swt.widgets.Combo;
081import org.eclipse.swt.widgets.Composite;
082import org.eclipse.swt.widgets.Control;
083import org.eclipse.swt.widgets.Dialog;
084import org.eclipse.swt.widgets.Display;
085import org.eclipse.swt.widgets.Event;
086import org.eclipse.swt.widgets.FileDialog;
087import org.eclipse.swt.widgets.Label;
088import org.eclipse.swt.widgets.Listener;
089import org.eclipse.swt.widgets.Menu;
090import org.eclipse.swt.widgets.MenuItem;
091import org.eclipse.swt.widgets.Monitor;
092import org.eclipse.swt.widgets.Shell;
093import org.eclipse.swt.widgets.Text;
094import org.eclipse.swt.widgets.ToolBar;
095import org.eclipse.swt.widgets.ToolItem;
096
097/**
098 * HDFView is the main class of this HDF visual tool. It is used to layout the graphical components of the
099 * hdfview. The major GUI components of the HDFView include Menubar, Toolbar, TreeView, ContentView, and
100 * MessageArea.
101 *
102 * The HDFView is designed in such a way that it does not have direct access to the HDF library. All the HDF
103 * library access is done through HDF objects. Therefore, the HDFView package depends on the object package
104 * but not the library package. The source code of the view package (hdf.view) should be compiled with the
105 * library package (hdf.hdflib and hdf.hdf5lib).
106 *
107 * @author Jordan T. Henderson
108 * @version 2.4 2015
109 */
110public class HDFView implements DataViewManager {
111    private static final Logger log = LoggerFactory.getLogger(HDFView.class);
112
113    private static Display display;
114    private static Shell mainWindow;
115
116    /* Determines whether HDFView is being executed for GUI testing */
117    private boolean isTesting = false;
118
119    /* The directory where HDFView is installed */
120    private String rootDir;
121
122    /* The initial directory where HDFView looks for files */
123    private String startDir;
124
125    /* The current working directory */
126    private String currentDir;
127
128    /* The current working file */
129    private String currentFile = null;
130
131    /* The view properties */
132    private ViewProperties props;
133
134    /* A list of tree view implementations. */
135    private static List<String> treeViews;
136
137    /* A list of image view implementations. */
138    private static List<String> imageViews;
139
140    /* A list of tree table implementations. */
141    private static List<?> tableViews;
142
143    /* A list of metadata view implementations. */
144    private static List<?> metaDataViews;
145
146    /* A list of palette view implementations. */
147    private static List<?> paletteViews;
148
149    /* A list of help view implementations. */
150    private static List<?> helpViews;
151
152    /* The list of GUI components related to NetCDF3 */
153    private final List<MenuItem> n3GUIs = new ArrayList<>();
154
155    /* The list of GUI components related to HDF4 */
156    private final List<MenuItem> h4GUIs = new ArrayList<>();
157
158    /* The list of GUI components related to HDF5 */
159    private final List<MenuItem> h5GUIs = new ArrayList<>();
160
161    /* The list of GUI components related to editing */
162    // private final List<?>            editGUIs;
163
164    /* GUI component: the TreeView */
165    private TreeView treeView = null;
166
167    private static final String JAVA_VERSION    = HDFVersions.getPropertyVersionJava();
168    private static final String HDF4_VERSION    = HDFVersions.getPropertyVersionHDF4();
169    private static final String HDF5_VERSION    = HDFVersions.getPropertyVersionHDF5();
170    private static final String HDFVIEW_VERSION = HDFVersions.getPropertyVersionView();
171    private static final String HDFVIEW_USERSGUIDE_URL =
172        "https://portal.hdfgroup.org/display/HDFVIEW/HDFView+3.x+User%27s+Guide";
173    private static final String JAVA_COMPILER = "jdk " + JAVA_VERSION;
174    private static final String JAVA_VER_INFO =
175        "Compiled at " + JAVA_COMPILER + "\nRunning at " + System.getProperty("java.version");
176
177    private static final String ABOUT_HDFVIEW = "HDF Viewer, "
178                                                + "Version " + ViewProperties.VERSION + "\n"
179                                                + "For " + System.getProperty("os.name") + "\n\n"
180                                                + "Copyright " + '\u00a9' + " 2006 The HDF Group.\n"
181                                                + "All rights reserved.";
182
183    /* GUI component: The toolbar for open, close, help and hdf4 and hdf5 library information */
184    private ToolBar toolBar;
185
186    /* GUI component: The text area for showing status messages */
187    private Text status;
188
189    /* GUI component: The area for object view */
190    private ScrolledComposite treeArea;
191
192    /* GUI component: The area for quick general view */
193    private ScrolledComposite generalArea;
194
195    /* GUI component: To add and display URLs */
196    private Combo urlBar;
197
198    private Button recentFilesButton;
199    private Button clearTextButton;
200
201    /* GUI component: A list of current data windows */
202    private Menu windowMenu;
203
204    /* GUI component: File menu on the menubar */
205    // private final Menu               fileMenu;
206
207    /* The font to be used for display text on all Controls */
208    private Font currentFont;
209
210    private UserOptionsDialog userOptionDialog;
211
212    /** State of refresh. */
213    public boolean viewerState = false;
214
215    /** Timer for refresh functions. */
216    private final Runnable timer = new Runnable() {
217        public void run()
218        {
219            // refresh each table displaying data
220            Shell[] shellList = display.getShells();
221            if (shellList != null) {
222                for (int i = 0; i < shellList.length; i++) {
223                    if (shellList[i].equals(mainWindow))
224                        showMetaData(treeView.getCurrentObject());
225                    else {
226                        DataView view = (DataView)shellList[i].getData();
227                        if ((view != null) && (view instanceof TableView)) {
228                            HObject obj = view.getDataObject();
229                            if (obj == null || obj.getFileFormat() == null || !(obj instanceof DataFormat))
230                                continue;
231
232                            FileFormat file = obj.getFileFormat();
233                            if (file.isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5)))
234                                ((TableView)view).refreshDataTable();
235                        }
236                    }
237                }
238            }
239            log.trace("viewerState = {}", viewerState);
240            if (viewerState)
241                display.timerExec(ViewProperties.getTimerRefresh(), timer);
242            else
243                display.timerExec(-1, timer);
244        }
245    };
246
247    /**
248     * Constructs HDFView with a given root directory, where the HDFView is
249     * installed, and opens the given files in the viewer.
250     *
251     * @param root
252     *            the directory where the HDFView is installed.
253     * @param start_dir
254     *            the starting directory for file searches
255     */
256    public HDFView(String root, String start_dir)
257    {
258        log.debug("Root is {}", root);
259
260        if (display == null || display.isDisposed())
261            display = new Display();
262
263        rootDir  = root;
264        startDir = start_dir;
265
266        // editGUIs = new Vector<Object>();
267
268        props = new ViewProperties(rootDir, startDir);
269        try {
270            props.load();
271        }
272        catch (Exception ex) {
273            log.debug("Failed to load View Properties from {}", rootDir);
274        }
275
276        ViewProperties.loadIcons();
277
278        String workDir = System.getProperty("hdfview.workdir");
279        if (workDir != null)
280            currentDir = workDir;
281        else
282            currentDir = ViewProperties.getWorkDir();
283
284        if (currentDir == null)
285            currentDir = System.getProperty("user.dir");
286
287        log.info("Current directory is {}", currentDir);
288
289        try {
290            currentFont =
291                new Font(display, ViewProperties.getFontType(), ViewProperties.getFontSize(), SWT.NORMAL);
292        }
293        catch (Exception ex) {
294            currentFont = null;
295        }
296
297        if (FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5) != null)
298            ViewProperties.loadPluginPaths();
299
300        treeViews     = ViewProperties.getTreeViewList();
301        metaDataViews = ViewProperties.getMetaDataViewList();
302        tableViews    = ViewProperties.getTableViewList();
303        imageViews    = ViewProperties.getImageViewList();
304        paletteViews  = ViewProperties.getPaletteViewList();
305        helpViews     = ViewProperties.getHelpViewList();
306
307        log.debug("Constructor exit");
308    }
309
310    /**
311     * Creates HDFView with a given size, and opens the given files in the viewer.
312     *
313     * @param flist
314     *            a list of files to open.
315     * @param width
316     *            the width of the app in pixels
317     * @param height
318     *            the height of the app in pixels
319     * @param x
320     *            the coord x of the app in pixels
321     * @param y
322     *            the coord y of the app in pixels
323     *
324     * @return
325     *            the newly-created HDFView Shell
326     */
327    public Shell openMainWindow(List<File> flist, int width, int height, int x, int y)
328    {
329        log.debug("openMainWindow enter current directory is {}", currentDir);
330
331        // Initialize all GUI components
332        mainWindow = createMainWindow();
333
334        try {
335            Font font    = null;
336            String fType = ViewProperties.getFontType();
337            int fSize    = ViewProperties.getFontSize();
338
339            try {
340                font = new Font(display, fType, fSize, SWT.NORMAL);
341            }
342            catch (Exception ex) {
343                log.debug("Failed to load font");
344                font = null;
345            }
346
347            if (font != null)
348                updateFont(font);
349        }
350        catch (Exception ex) {
351            log.debug("Failed to load Font properties");
352        }
353
354        // Make sure all GUI components are in place before
355        // opening any files
356        mainWindow.pack();
357
358        int nfiles   = flist.size();
359        File theFile = null;
360        for (int i = 0; i < nfiles; i++) {
361            theFile = flist.get(i);
362
363            if (theFile.isFile()) {
364                currentDir  = theFile.getParentFile().getAbsolutePath();
365                currentFile = theFile.getAbsolutePath();
366
367                try {
368                    int access_mode = FileFormat.WRITE;
369                    if (ViewProperties.isReadOnly())
370                        access_mode = FileFormat.READ;
371                    else if (ViewProperties.isReadSWMR())
372                        access_mode = FileFormat.READ | FileFormat.MULTIREAD;
373                    treeView.openFile(currentFile, access_mode);
374
375                    try {
376                        urlBar.remove(currentFile);
377                    }
378                    catch (Exception ex) {
379                    }
380
381                    // first entry is always the workdir
382                    urlBar.add(currentFile, 1);
383                    urlBar.select(1);
384                }
385                catch (Exception ex) {
386                    showError(ex.toString());
387                }
388            }
389            else {
390                currentDir = theFile.getAbsolutePath();
391            }
392
393            log.info("CurrentDir is {}", currentDir);
394        }
395
396        if (FileFormat.getFileFormat(FileFormat.FILE_TYPE_NC3) == null)
397            setEnabled(n3GUIs, false);
398
399        if (FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4) == null)
400            setEnabled(h4GUIs, false);
401
402        if (FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5) == null)
403            setEnabled(h5GUIs, false);
404
405        // Set size of main window
406        // float inset = 0.17f; // for UG only.
407        float inset  = 0.04f;
408        Point winDim = new Point(width, height);
409
410        // If given height and width are too small, adjust accordingly
411        if (height <= 300)
412            winDim.y = (int)((1 - 2 * inset) * mainWindow.getSize().y);
413
414        if (width <= 300)
415            winDim.x = (int)(0.9 * mainWindow.getSize().y);
416
417        mainWindow.setLocation(x, y);
418        mainWindow.setSize(winDim.x + 200, winDim.y);
419
420        // Display the window
421        mainWindow.open();
422        log.debug("openMainWindow exit");
423        return mainWindow;
424    }
425
426    /** switch processing to the main application window */
427    public void runMainWindow()
428    {
429        log.debug("runMainWindow enter");
430
431        while (!mainWindow.isDisposed()) {
432            // ===================================================
433            // Wrap each event dispatch in an exception handler
434            // so that if any event causes an exception it does
435            // not break the main UI loop
436            // ===================================================
437            try {
438                if (!display.readAndDispatch())
439                    display.sleep();
440            }
441            catch (Exception e) {
442                e.printStackTrace();
443            }
444        }
445
446        if (!isTesting)
447            display.dispose();
448        log.debug("runMainWindow exit");
449    }
450
451    /**
452     * Creates and lays out GUI components.
453     *
454     * <pre>
455     * ||=========||=============================||
456     * ||         ||                             ||
457     * ||         ||                             ||
458     * || TreeView||       ContentPane           ||
459     * ||         ||                             ||
460     * ||=========||=============================||
461     * ||            Message Area                ||
462     * ||========================================||
463     * </pre>
464     */
465    private Shell createMainWindow()
466    {
467        // Create a new display window
468        final Shell shell = new Shell(display);
469        shell.setImages(ViewProperties.getHdfIcons());
470        shell.setFont(currentFont);
471        shell.setText("HDFView " + HDFVIEW_VERSION);
472        shell.setLayout(new GridLayout(3, false));
473        shell.addDisposeListener(new DisposeListener() {
474            @Override
475            public void widgetDisposed(DisposeEvent e)
476            {
477                ViewProperties.setRecentFiles(new ArrayList<>(Arrays.asList(urlBar.getItems())));
478
479                try {
480                    props.save();
481                }
482                catch (Exception ex) {
483                }
484
485                closeAllWindows();
486
487                // Close all open files
488                try {
489                    List<FileFormat> filelist = treeView.getCurrentFiles();
490
491                    if ((filelist != null) && !filelist.isEmpty()) {
492                        Object[] files = filelist.toArray();
493
494                        for (int i = 0; i < files.length; i++) {
495                            try {
496                                treeView.closeFile((FileFormat)files[i]);
497                            }
498                            catch (Exception ex) {
499                                continue;
500                            }
501                        }
502                    }
503                }
504                catch (Exception ex) {
505                }
506
507                if (currentFont != null)
508                    currentFont.dispose();
509            }
510        });
511
512        createMenuBar(shell);
513        createToolbar(shell);
514        createUrlToolbar(shell);
515        createContentArea(shell);
516
517        log.info("Main Window created");
518
519        return shell;
520    }
521
522    private void createMenuBar(final Shell shell)
523    {
524        Menu menu = new Menu(shell, SWT.BAR);
525        shell.setMenuBar(menu);
526
527        MenuItem menuItem = new MenuItem(menu, SWT.CASCADE);
528        menuItem.setText("&File");
529
530        Menu fileMenu = new Menu(menuItem);
531        menuItem.setMenu(fileMenu);
532
533        MenuItem item = new MenuItem(fileMenu, SWT.PUSH);
534        item.setText("&Open\tCtrl-O");
535        item.setAccelerator(SWT.MOD1 + 'O');
536        item.addSelectionListener(new SelectionAdapter() {
537            @Override
538            public void widgetSelected(SelectionEvent e)
539            {
540                openLocalFile(null, -1);
541            }
542        });
543
544        item = new MenuItem(fileMenu, SWT.CASCADE);
545        item.setText("Open As");
546
547        Menu openAsMenu = new Menu(item);
548        item.setMenu(openAsMenu);
549
550        item = new MenuItem(openAsMenu, SWT.PUSH);
551        item.setText("Read-Only");
552        item.addSelectionListener(new SelectionAdapter() {
553            @Override
554            public void widgetSelected(SelectionEvent e)
555            {
556                openLocalFile(null, FileFormat.READ);
557            }
558        });
559
560        item = new MenuItem(openAsMenu, SWT.PUSH);
561        item.setText("SWMR Read-Only");
562        item.addSelectionListener(new SelectionAdapter() {
563            @Override
564            public void widgetSelected(SelectionEvent e)
565            {
566                openLocalFile(null, FileFormat.READ | FileFormat.MULTIREAD);
567            }
568        });
569
570        item = new MenuItem(openAsMenu, SWT.PUSH);
571        item.setText("Read/Write");
572        item.addSelectionListener(new SelectionAdapter() {
573            @Override
574            public void widgetSelected(SelectionEvent e)
575            {
576                openLocalFile(null, FileFormat.WRITE);
577            }
578        });
579
580        new MenuItem(fileMenu, SWT.SEPARATOR);
581
582        MenuItem fileNewMenu = new MenuItem(fileMenu, SWT.CASCADE);
583        fileNewMenu.setText("New");
584
585        Menu newMenu = new Menu(fileNewMenu);
586        fileNewMenu.setMenu(newMenu);
587
588        item = new MenuItem(newMenu, SWT.PUSH);
589        item.setText("HDF&4");
590        h4GUIs.add(item);
591        item.addSelectionListener(new SelectionAdapter() {
592            @Override
593            public void widgetSelected(SelectionEvent e)
594            {
595                if (currentDir != null)
596                    currentDir += File.separator;
597                else
598                    currentDir = "";
599
600                String filename = null;
601
602                if (!isTesting) {
603                    FileDialog fChooser = new FileDialog(shell, SWT.SAVE);
604                    fChooser.setFileName(Tools.checkNewFile(currentDir, ".hdf").getName());
605
606                    DefaultFileFilter filter = DefaultFileFilter.getFileFilterHDF4();
607                    fChooser.setFilterExtensions(new String[] {filter.getExtensions()});
608                    fChooser.setFilterNames(new String[] {filter.getDescription()});
609                    fChooser.setFilterIndex(0);
610
611                    filename = fChooser.open();
612                }
613                else {
614                    // Prepend test file directory to filename
615                    filename = currentDir.concat(new InputDialog(mainWindow, "Enter a file name", "").open());
616                }
617
618                if (filename == null)
619                    return;
620
621                try {
622                    log.trace("HDFView create hdf4 file");
623                    FileFormat theFile = Tools.createNewFile(filename, currentDir, FileFormat.FILE_TYPE_HDF4,
624                                                             getTreeView().getCurrentFiles());
625
626                    if (theFile == null)
627                        return;
628
629                    currentDir = theFile.getParent();
630                }
631                catch (Exception ex) {
632                    Tools.showError(mainWindow, "New", ex.getMessage());
633                    return;
634                }
635
636                try {
637                    treeView.openFile(filename, FileFormat.WRITE);
638                    currentFile = filename;
639
640                    try {
641                        urlBar.remove(filename);
642                    }
643                    catch (Exception ex) {
644                        log.debug("unable to remove {} from urlBar", filename);
645                    }
646
647                    // first entry is always the workdir
648                    urlBar.add(filename, 1);
649                    urlBar.select(1);
650                }
651                catch (Exception ex) {
652                    display.beep();
653                    Tools.showError(mainWindow, "New", ex.getMessage() + "\n" + filename);
654                }
655            }
656        });
657
658        item = new MenuItem(newMenu, SWT.PUSH);
659        item.setText("HDF&5");
660        h5GUIs.add(item);
661        item.addSelectionListener(new SelectionAdapter() {
662            @Override
663            public void widgetSelected(SelectionEvent e)
664            {
665                if (currentDir != null)
666                    currentDir += File.separator;
667                else
668                    currentDir = "";
669
670                String filename = null;
671
672                if (!isTesting) {
673                    FileDialog fChooser = new FileDialog(shell, SWT.SAVE);
674                    fChooser.setFileName(Tools.checkNewFile(currentDir, ".h5").getName());
675
676                    DefaultFileFilter filter = DefaultFileFilter.getFileFilterHDF5();
677                    fChooser.setFilterExtensions(new String[] {filter.getExtensions()});
678                    fChooser.setFilterNames(new String[] {filter.getDescription()});
679                    fChooser.setFilterIndex(0);
680
681                    filename = fChooser.open();
682                }
683                else {
684                    // Prepend test file directory to filename
685                    filename = currentDir.concat(new InputDialog(mainWindow, "Enter a file name", "").open());
686                }
687
688                if (filename == null)
689                    return;
690
691                try {
692                    log.trace("HDFView create hdf5 file");
693                    FileFormat theFile = Tools.createNewFile(filename, currentDir, FileFormat.FILE_TYPE_HDF5,
694                                                             getTreeView().getCurrentFiles());
695
696                    if (theFile == null)
697                        return;
698
699                    currentDir = theFile.getParent();
700                }
701                catch (Exception ex) {
702                    Tools.showError(mainWindow, "New", ex.getMessage());
703                    return;
704                }
705
706                try {
707                    treeView.openFile(filename, FileFormat.WRITE);
708                    currentFile = filename;
709
710                    try {
711                        urlBar.remove(filename);
712                    }
713                    catch (Exception ex) {
714                        log.debug("unable to remove {} from urlBar", filename);
715                    }
716
717                    // first entry is always the workdir
718                    urlBar.add(filename, 1);
719                    urlBar.select(1);
720                }
721                catch (Exception ex) {
722                    display.beep();
723                    Tools.showError(mainWindow, "New", ex.getMessage() + "\n" + filename);
724                }
725            }
726        });
727
728        new MenuItem(fileMenu, SWT.SEPARATOR);
729
730        item = new MenuItem(fileMenu, SWT.PUSH);
731        item.setText("&Close");
732        item.addSelectionListener(new SelectionAdapter() {
733            @Override
734            public void widgetSelected(SelectionEvent e)
735            {
736                closeFile(treeView.getSelectedFile());
737            }
738        });
739
740        item = new MenuItem(fileMenu, SWT.PUSH);
741        item.setText("Close &All");
742        item.addSelectionListener(new SelectionAdapter() {
743            @Override
744            public void widgetSelected(SelectionEvent e)
745            {
746                closeAllWindows();
747
748                List<FileFormat> files = treeView.getCurrentFiles();
749                while (!files.isEmpty()) {
750                    try {
751                        treeView.closeFile(files.get(0));
752                    }
753                    catch (Exception ex) {
754                        log.trace("unable to close {} in treeView", files.get(0));
755                    }
756                }
757
758                currentFile = null;
759
760                for (Control control : generalArea.getChildren())
761                    control.dispose();
762                generalArea.setContent(null);
763
764                urlBar.setText("");
765            }
766        });
767
768        new MenuItem(fileMenu, SWT.SEPARATOR);
769
770        item = new MenuItem(fileMenu, SWT.PUSH);
771        item.setText("&Save");
772        item.addSelectionListener(new SelectionAdapter() {
773            @Override
774            public void widgetSelected(SelectionEvent e)
775            {
776                if (treeView.getCurrentFiles().isEmpty()) {
777                    Tools.showError(mainWindow, "Save", "No files currently open.");
778                    return;
779                }
780
781                if (treeView.getSelectedFile() == null) {
782                    Tools.showError(mainWindow, "Save", "No files currently selected.");
783                    return;
784                }
785
786                // Save what has been changed in memory into file
787                writeDataToFile(treeView.getSelectedFile());
788            }
789        });
790
791        item = new MenuItem(fileMenu, SWT.PUSH);
792        item.setText("S&ave As");
793        item.addSelectionListener(new SelectionAdapter() {
794            @Override
795            public void widgetSelected(SelectionEvent e)
796            {
797                if (treeView.getCurrentFiles().isEmpty()) {
798                    Tools.showError(mainWindow, "Save", "No files currently open.");
799                    return;
800                }
801
802                if (treeView.getSelectedFile() == null) {
803                    Tools.showError(mainWindow, "Save", "No files currently selected.");
804                    return;
805                }
806
807                try {
808                    treeView.saveFile(treeView.getSelectedFile());
809                }
810                catch (Exception ex) {
811                    display.beep();
812                    Tools.showError(mainWindow, "Save", ex.getMessage());
813                }
814            }
815        });
816
817        new MenuItem(fileMenu, SWT.SEPARATOR);
818
819        item = new MenuItem(fileMenu, SWT.PUSH);
820        item.setText("E&xit\tCtrl-Q");
821        item.setAccelerator(SWT.MOD1 + 'Q');
822        item.addSelectionListener(new SelectionAdapter() {
823            @Override
824            public void widgetSelected(SelectionEvent e)
825            {
826                mainWindow.dispose();
827            }
828        });
829
830        menuItem = new MenuItem(menu, SWT.CASCADE);
831        menuItem.setText("&Window");
832
833        windowMenu = new Menu(menuItem);
834        menuItem.setMenu(windowMenu);
835
836        item = new MenuItem(windowMenu, SWT.PUSH);
837        item.setText("&Cascade");
838        item.addSelectionListener(new SelectionAdapter() {
839            @Override
840            public void widgetSelected(SelectionEvent e)
841            {
842                cascadeWindows();
843            }
844        });
845
846        item = new MenuItem(windowMenu, SWT.PUSH);
847        item.setText("&Tile");
848        item.addSelectionListener(new SelectionAdapter() {
849            @Override
850            public void widgetSelected(SelectionEvent e)
851            {
852                tileWindows();
853            }
854        });
855
856        new MenuItem(windowMenu, SWT.SEPARATOR);
857
858        item = new MenuItem(windowMenu, SWT.PUSH);
859        item.setText("Close &All");
860        item.addSelectionListener(new SelectionAdapter() {
861            @Override
862            public void widgetSelected(SelectionEvent e)
863            {
864                closeAllWindows();
865            }
866        });
867
868        new MenuItem(windowMenu, SWT.SEPARATOR);
869
870        menuItem = new MenuItem(menu, SWT.CASCADE);
871        menuItem.setText("&Tools");
872
873        Menu toolsMenu = new Menu(menuItem);
874        menuItem.setMenu(toolsMenu);
875
876        MenuItem convertMenuItem = new MenuItem(toolsMenu, SWT.CASCADE);
877        convertMenuItem.setText("Convert Image To");
878
879        Menu convertMenu = new Menu(convertMenuItem);
880        convertMenuItem.setMenu(convertMenu);
881
882        item = new MenuItem(convertMenu, SWT.PUSH);
883        item.setText("HDF4");
884        item.addSelectionListener(new SelectionAdapter() {
885            @Override
886            public void widgetSelected(SelectionEvent e)
887            {
888                convertFile(Tools.FILE_TYPE_IMAGE, FileFormat.FILE_TYPE_HDF4);
889            }
890        });
891        h4GUIs.add(item);
892
893        item = new MenuItem(convertMenu, SWT.PUSH);
894        item.setText("HDF5");
895        item.addSelectionListener(new SelectionAdapter() {
896            @Override
897            public void widgetSelected(SelectionEvent e)
898            {
899                convertFile(Tools.FILE_TYPE_IMAGE, FileFormat.FILE_TYPE_HDF5);
900            }
901        });
902        h5GUIs.add(item);
903
904        new MenuItem(toolsMenu, SWT.SEPARATOR);
905
906        item = new MenuItem(toolsMenu, SWT.PUSH);
907        item.setText("User &Options");
908        item.addSelectionListener(new SelectionAdapter() {
909            @Override
910            public void widgetSelected(SelectionEvent e)
911            {
912                // Create the preference manager
913                PreferenceManager mgr = new PreferenceManager();
914
915                // Create the nodes
916                UserOptionsNode one   = new UserOptionsNode("general", new UserOptionsGeneralPage());
917                UserOptionsNode two   = new UserOptionsNode("hdf", new UserOptionsHDFPage());
918                UserOptionsNode three = new UserOptionsNode("modules", new UserOptionsViewModulesPage());
919
920                // Add the nodes
921                mgr.addToRoot(one);
922                mgr.addToRoot(two);
923                mgr.addToRoot(three);
924
925                // Create the preferences dialog
926                userOptionDialog = new UserOptionsDialog(shell, mgr, rootDir);
927
928                // Set the preference store
929                userOptionDialog.setPreferenceStore(props);
930                userOptionDialog.create();
931
932                // Open the dialog
933                userOptionDialog.open();
934
935                // TODO: this functionality is currently broken because isWorkDirChanged() is not exposed
936                // correctly. if (userOptionDialog.isWorkDirChanged()) this will always overwrite the
937                // currentDir until isWorkDirChanged() is fixed
938                currentDir = ViewProperties.getWorkDir();
939
940                // if (userOptionDialog.isFontChanged()) {
941                Font font = null;
942
943                try {
944                    font = new Font(display, ViewProperties.getFontType(), ViewProperties.getFontSize(),
945                                    SWT.NORMAL);
946                }
947                catch (Exception ex) {
948                    font = null;
949                }
950
951                log.trace("update fonts");
952                updateFont(font);
953            }
954        });
955
956        new MenuItem(toolsMenu, SWT.SEPARATOR);
957
958        item = new MenuItem(toolsMenu, SWT.PUSH);
959        item.setText("&Register File Format");
960        item.addSelectionListener(new SelectionAdapter() {
961            @Override
962            public void widgetSelected(SelectionEvent e)
963            {
964                registerFileFormat();
965            }
966        });
967
968        item = new MenuItem(toolsMenu, SWT.PUSH);
969        item.setText("&Unregister File Format");
970        item.addSelectionListener(new SelectionAdapter() {
971            @Override
972            public void widgetSelected(SelectionEvent e)
973            {
974                unregisterFileFormat();
975            }
976        });
977
978        menuItem = new MenuItem(menu, SWT.CASCADE);
979        menuItem.setText("&Help");
980
981        Menu helpMenu = new Menu(menuItem);
982        menuItem.setMenu(helpMenu);
983
984        item = new MenuItem(helpMenu, SWT.PUSH);
985        item.setText("&User's Guide");
986        item.addSelectionListener(new SelectionAdapter() {
987            @Override
988            public void widgetSelected(SelectionEvent e)
989            {
990                org.eclipse.swt.program.Program.launch(HDFVIEW_USERSGUIDE_URL);
991            }
992        });
993
994        if ((helpViews != null) && !helpViews.isEmpty()) {
995            int n = helpViews.size();
996            for (int i = 0; i < n; i++) {
997                HelpView theView = (HelpView)helpViews.get(i);
998                item             = new MenuItem(helpMenu, SWT.PUSH);
999                item.setText(theView.getLabel());
1000                // item.setActionCommand(theView.getActionCommand());
1001            }
1002        }
1003
1004        new MenuItem(helpMenu, SWT.SEPARATOR);
1005
1006        item = new MenuItem(helpMenu, SWT.PUSH);
1007        item.setText("HDF&4 Library Version");
1008        h4GUIs.add(item);
1009        item.addSelectionListener(new SelectionAdapter() {
1010            @Override
1011            public void widgetSelected(SelectionEvent e)
1012            {
1013                new LibraryVersionDialog(shell, FileFormat.FILE_TYPE_HDF4).open();
1014            }
1015        });
1016
1017        item = new MenuItem(helpMenu, SWT.PUSH);
1018        item.setText("HDF&5 Library Version");
1019        h5GUIs.add(item);
1020        item.addSelectionListener(new SelectionAdapter() {
1021            @Override
1022            public void widgetSelected(SelectionEvent e)
1023            {
1024                new LibraryVersionDialog(shell, FileFormat.FILE_TYPE_HDF5).open();
1025            }
1026        });
1027
1028        item = new MenuItem(helpMenu, SWT.PUSH);
1029        item.setText("&Java Version");
1030        item.addSelectionListener(new SelectionAdapter() {
1031            @Override
1032            public void widgetSelected(SelectionEvent e)
1033            {
1034                new JavaVersionDialog(mainWindow).open();
1035            }
1036        });
1037
1038        new MenuItem(helpMenu, SWT.SEPARATOR);
1039
1040        item = new MenuItem(helpMenu, SWT.PUSH);
1041        item.setText("Supported Fi&le Formats");
1042        item.addSelectionListener(new SelectionAdapter() {
1043            @Override
1044            public void widgetSelected(SelectionEvent e)
1045            {
1046                new SupportedFileFormatsDialog(mainWindow).open();
1047            }
1048        });
1049
1050        new MenuItem(helpMenu, SWT.SEPARATOR);
1051
1052        item = new MenuItem(helpMenu, SWT.PUSH);
1053        item.setText("&About...");
1054        item.addSelectionListener(new SelectionAdapter() {
1055            @Override
1056            public void widgetSelected(SelectionEvent e)
1057            {
1058                new AboutDialog(mainWindow).open();
1059            }
1060        });
1061
1062        setEnabled(Arrays.asList(windowMenu.getItems()), false);
1063
1064        log.info("Menubar created");
1065    }
1066
1067    private void createToolbar(final Shell shell)
1068    {
1069        toolBar = new ToolBar(shell, SWT.HORIZONTAL | SWT.RIGHT);
1070        toolBar.setFont(Display.getCurrent().getSystemFont());
1071        toolBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1));
1072
1073        ToolItem openItem = new ToolItem(toolBar, SWT.PUSH);
1074        openItem.setToolTipText("Open");
1075        openItem.setImage(ViewProperties.getFileopenIcon());
1076        openItem.addSelectionListener(new SelectionAdapter() {
1077            @Override
1078            public void widgetSelected(SelectionEvent e)
1079            {
1080                openLocalFile(null, -1);
1081            }
1082        });
1083
1084        new ToolItem(toolBar, SWT.SEPARATOR).setWidth(4);
1085
1086        ToolItem closeItem = new ToolItem(toolBar, SWT.PUSH);
1087        closeItem.setImage(ViewProperties.getFilecloseIcon());
1088        closeItem.setToolTipText("Close");
1089        closeItem.addSelectionListener(new SelectionAdapter() {
1090            @Override
1091            public void widgetSelected(SelectionEvent e)
1092            {
1093                closeFile(treeView.getSelectedFile());
1094            }
1095        });
1096
1097        new ToolItem(toolBar, SWT.SEPARATOR).setWidth(20);
1098
1099        ToolItem helpItem = new ToolItem(toolBar, SWT.PUSH);
1100        helpItem.setImage(ViewProperties.getHelpIcon());
1101        helpItem.setToolTipText("Help");
1102        helpItem.addSelectionListener(new SelectionAdapter() {
1103            @Override
1104            public void widgetSelected(SelectionEvent e)
1105            {
1106                String ugPath = ViewProperties.getUsersGuide();
1107
1108                if (ugPath == null || !ugPath.startsWith("http://")) {
1109                    String sep   = File.separator;
1110                    File tmpFile = new File(ugPath);
1111
1112                    if (!(tmpFile.exists())) {
1113                        ugPath  = rootDir + sep + "UsersGuide" + sep + "index.html";
1114                        tmpFile = new File(ugPath);
1115
1116                        if (!(tmpFile.exists()))
1117                            ugPath = HDFVIEW_USERSGUIDE_URL;
1118
1119                        ViewProperties.setUsersGuide(ugPath);
1120                    }
1121                }
1122
1123                try {
1124                    org.eclipse.swt.program.Program.launch(ugPath);
1125                }
1126                catch (Exception ex) {
1127                    Tools.showError(shell, "Help", ex.getMessage());
1128                }
1129            }
1130        });
1131
1132        new ToolItem(toolBar, SWT.SEPARATOR).setWidth(4);
1133
1134        ToolItem hdf4Item = new ToolItem(toolBar, SWT.PUSH);
1135        hdf4Item.setImage(ViewProperties.getH4Icon());
1136        hdf4Item.setToolTipText("HDF4 Library Version");
1137        hdf4Item.addSelectionListener(new SelectionAdapter() {
1138            @Override
1139            public void widgetSelected(SelectionEvent e)
1140            {
1141                new LibraryVersionDialog(shell, FileFormat.FILE_TYPE_HDF4).open();
1142            }
1143        });
1144
1145        if (FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4) == null)
1146            hdf4Item.setEnabled(false);
1147
1148        new ToolItem(toolBar, SWT.SEPARATOR).setWidth(4);
1149
1150        ToolItem hdf5Item = new ToolItem(toolBar, SWT.PUSH);
1151        hdf5Item.setImage(ViewProperties.getH5Icon());
1152        hdf5Item.setToolTipText("HDF5 Library Version");
1153        hdf5Item.addSelectionListener(new SelectionAdapter() {
1154            @Override
1155            public void widgetSelected(SelectionEvent e)
1156            {
1157                new LibraryVersionDialog(shell, FileFormat.FILE_TYPE_HDF5).open();
1158            }
1159        });
1160
1161        if (FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5) == null)
1162            hdf5Item.setEnabled(false);
1163
1164        // Make the toolbar as wide as the window and as
1165        // tall as the buttons
1166        toolBar.setSize(shell.getClientArea().width, openItem.getBounds().height);
1167        toolBar.setLocation(0, 0);
1168
1169        log.info("Toolbar created");
1170    }
1171
1172    private void createUrlToolbar(final Shell shell)
1173    {
1174        // Recent Files button
1175        recentFilesButton = new Button(shell, SWT.PUSH);
1176        recentFilesButton.setFont(currentFont);
1177        recentFilesButton.setText("Recent Files");
1178        recentFilesButton.setToolTipText("List of recent files");
1179        recentFilesButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
1180        recentFilesButton.addSelectionListener(new SelectionAdapter() {
1181            @Override
1182            public void widgetSelected(SelectionEvent e)
1183            {
1184                urlBar.setListVisible(true);
1185            }
1186        });
1187
1188        // Recent files combo box
1189        urlBar = new Combo(shell, SWT.BORDER | SWT.SINGLE);
1190        urlBar.setFont(currentFont);
1191        urlBar.setItems(ViewProperties.getMRF().toArray(new String[0]));
1192        urlBar.setVisibleItemCount(ViewProperties.MAX_RECENT_FILES);
1193        urlBar.deselectAll();
1194        urlBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
1195        urlBar.addKeyListener(new KeyAdapter() {
1196            @Override
1197            public void keyPressed(KeyEvent e)
1198            {
1199                if (e.keyCode == SWT.CR) {
1200                    String filename = urlBar.getText();
1201                    if (filename == null || filename.length() < 1 || filename.equals(currentFile))
1202                        return;
1203
1204                    if (!(filename.startsWith("http://") || filename.startsWith("https://") ||
1205                          filename.startsWith("ftp://"))) {
1206                        openLocalFile(filename, -1);
1207                    }
1208                    else {
1209                        String remoteFile = openRemoteFile(filename);
1210
1211                        if (remoteFile != null)
1212                            openLocalFile(remoteFile, -1);
1213                    }
1214                }
1215            }
1216        });
1217        urlBar.addSelectionListener(new SelectionAdapter() {
1218            @Override
1219            public void widgetSelected(SelectionEvent e)
1220            {
1221                String filename = urlBar.getText();
1222                if (filename == null || filename.length() < 1 || filename.equals(currentFile)) {
1223                    return;
1224                }
1225
1226                if (!(filename.startsWith("http://") || filename.startsWith("https://") ||
1227                      filename.startsWith("ftp://"))) {
1228                    openLocalFile(filename, -1);
1229                }
1230                else {
1231                    String remoteFile = openRemoteFile(filename);
1232
1233                    if (remoteFile != null)
1234                        openLocalFile(remoteFile, -1);
1235                }
1236            }
1237        });
1238
1239        clearTextButton = new Button(shell, SWT.PUSH);
1240        clearTextButton.setToolTipText("Clear current selection");
1241        clearTextButton.setFont(currentFont);
1242        clearTextButton.setText("Clear Text");
1243        clearTextButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
1244        clearTextButton.addSelectionListener(new SelectionAdapter() {
1245            @Override
1246            public void widgetSelected(SelectionEvent e)
1247            {
1248                urlBar.setText("");
1249                urlBar.deselectAll();
1250            }
1251        });
1252
1253        log.info("URL Toolbar created");
1254    }
1255
1256    private void createContentArea(final Shell shell)
1257    {
1258        SashForm content = new SashForm(shell, SWT.VERTICAL);
1259        content.setSashWidth(10);
1260        content.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1));
1261
1262        // Add Data content area and Status Area to main window
1263        Composite container = new Composite(content, SWT.NONE);
1264        container.setLayout(new FillLayout());
1265
1266        Composite statusArea = new Composite(content, SWT.NONE);
1267        statusArea.setLayout(new FillLayout(SWT.HORIZONTAL));
1268
1269        final SashForm contentArea = new SashForm(container, SWT.HORIZONTAL);
1270        contentArea.setSashWidth(10);
1271
1272        // Add TreeView and DataView to content area pane
1273        treeArea = new ScrolledComposite(contentArea, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
1274        treeArea.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE));
1275        treeArea.setExpandHorizontal(true);
1276        treeArea.setExpandVertical(true);
1277
1278        generalArea = new ScrolledComposite(contentArea, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
1279        generalArea.setExpandHorizontal(true);
1280        generalArea.setExpandVertical(true);
1281        generalArea.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW));
1282        generalArea.setMinHeight(contentArea.getSize().y - 2);
1283
1284        // Create status area for displaying messages and metadata
1285        status = new Text(statusArea, SWT.V_SCROLL | SWT.MULTI | SWT.BORDER);
1286        status.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW));
1287        status.setEditable(false);
1288        status.setFont(currentFont);
1289
1290        contentArea.addListener(SWT.Resize, new Listener() {
1291            @Override
1292            public void handleEvent(Event arg0)
1293            {
1294                generalArea.setMinHeight(contentArea.getSize().y - 2);
1295            }
1296        });
1297
1298        // Add drag and drop support for opening files
1299        DropTarget target               = new DropTarget(treeArea, DND.DROP_COPY);
1300        final FileTransfer fileTransfer = FileTransfer.getInstance();
1301        target.setTransfer(new Transfer[] {fileTransfer});
1302        target.addDropListener(new DropTargetListener() {
1303            @Override
1304            public void dragEnter(DropTargetEvent e)
1305            {
1306                e.detail = DND.DROP_COPY;
1307            }
1308            @Override
1309            public void dragOver(DropTargetEvent e)
1310            {
1311                // Intentional
1312            }
1313            @Override
1314            public void dragOperationChanged(DropTargetEvent e)
1315            {
1316                // Intentional
1317            }
1318            @Override
1319            public void dragLeave(DropTargetEvent e)
1320            {
1321                // Intentional
1322            }
1323            @Override
1324            public void dropAccept(DropTargetEvent e)
1325            {
1326                // Intentional
1327            }
1328            @Override
1329            public void drop(DropTargetEvent e)
1330            {
1331                if (fileTransfer.isSupportedType(e.currentDataType)) {
1332                    String[] files = (String[])e.data;
1333                    for (int i = 0; i < files.length; i++)
1334                        openLocalFile(files[i], -1);
1335                }
1336            }
1337        });
1338
1339        showStatus("HDFView root - " + rootDir);
1340        showStatus("User property file - " + ViewProperties.getPropertyFile());
1341
1342        content.setWeights(new int[] {9, 1});
1343        contentArea.setWeights(new int[] {1, 3});
1344
1345        DataViewFactory treeViewFactory = null;
1346        try {
1347            treeViewFactory = DataViewFactoryProducer.getFactory(DataViewType.TREEVIEW);
1348        }
1349        catch (Exception ex) {
1350            log.debug("createContentArea(): error occurred while instantiating TreeView factory class", ex);
1351            this.showError("Error occurred while instantiating TreeView factory class");
1352            return;
1353        }
1354
1355        if (treeViewFactory == null) {
1356            log.debug("createContentArea(): TreeView factory is null");
1357            return;
1358        }
1359
1360        try {
1361            treeView = treeViewFactory.getTreeView(treeArea, this);
1362
1363            if (treeView == null) {
1364                log.debug("createContentArea(): error occurred while instantiating TreeView class");
1365                this.showError("Error occurred while instantiating TreeView class");
1366                return;
1367            }
1368        }
1369        catch (ClassNotFoundException ex) {
1370            log.debug("createContentArea(): no suitable TreeView class found");
1371            this.showError("Unable to find suitable TreeView class");
1372            return;
1373        }
1374
1375        treeArea.setContent(treeView.getTree());
1376
1377        log.info("Content Area created");
1378    }
1379
1380    /**
1381     * Get a list of treeview implementations.
1382     *
1383     * @return a list of treeview implementations.
1384     */
1385    public static final List<String> getListOfTreeViews() { return treeViews; }
1386
1387    /**
1388     * Get a list of imageview implementations.
1389     *
1390     * @return a list of imageview implementations.
1391     */
1392    public static final List<String> getListOfImageViews() { return imageViews; }
1393
1394    /**
1395     * Get a list of tableview implementations.
1396     *
1397     * @return a list of tableview implementations.
1398     */
1399    public static final List<?> getListOfTableViews() { return tableViews; }
1400
1401    /**
1402     * Get a list of metaDataview implementations.
1403     *
1404     * @return a list of metaDataview implementations.
1405     */
1406    public static final List<?> getListOfMetaDataViews() { return metaDataViews; }
1407
1408    /**
1409     * Get a list of paletteview implementations.
1410     *
1411     * @return a list of paletteview implementations.
1412     */
1413    public static final List<?> getListOfPaletteViews() { return paletteViews; }
1414
1415    @Override
1416    public TreeView getTreeView()
1417    {
1418        return treeView;
1419    }
1420
1421    /**
1422     * Get the combobox associated with a URL entry.
1423     *
1424     * @return the combobox associated with a URL entry.
1425     */
1426    public Combo getUrlBar() { return urlBar; }
1427
1428    /**
1429     * Start stop a timer.
1430     *
1431     * @param toggleTimer
1432     *            -- true: start timer, false stop timer.
1433     */
1434    @Override
1435    public final void executeTimer(boolean toggleTimer)
1436    {
1437        showStatus("toggleTimer: " + toggleTimer);
1438        viewerState = toggleTimer;
1439        if (viewerState)
1440            display.timerExec(ViewProperties.getTimerRefresh(), timer);
1441        else
1442            display.timerExec(-1, timer);
1443    }
1444
1445    /**
1446     * Display feedback message.
1447     *
1448     * @param msg
1449     *            the message to display.
1450     */
1451    @Override
1452    public void showStatus(String msg)
1453    {
1454        if (status == null) {
1455            log.debug("showStatus(): status area is null");
1456            return;
1457        }
1458
1459        status.append(msg);
1460        status.append("\n");
1461    }
1462
1463    /**
1464     * Display error message
1465     *
1466     * @param errMsg
1467     *            the error message to display
1468     */
1469    @Override
1470    public void showError(String errMsg)
1471    {
1472        if (status == null) {
1473            log.debug("showError(): status area is null");
1474            return;
1475        }
1476
1477        status.append(" *** ");
1478        status.append(errMsg);
1479        if (log.isDebugEnabled())
1480            status.append(" - see log for more info");
1481        status.append(" *** ");
1482        status.append("\n");
1483    }
1484
1485    /**
1486     * Display the metadata view for an object
1487     *
1488     * @param obj
1489     *            the object containing the metadata to show
1490     */
1491    public void showMetaData(final HObject obj)
1492    {
1493        for (Control control : generalArea.getChildren())
1494            control.dispose();
1495        generalArea.setContent(null);
1496
1497        if (obj == null)
1498            return;
1499
1500        DataViewFactory metaDataViewFactory = null;
1501        try {
1502            metaDataViewFactory = DataViewFactoryProducer.getFactory(DataViewType.METADATA);
1503        }
1504        catch (Exception ex) {
1505            log.debug("showMetaData(): error occurred while instantiating MetaDataView factory class", ex);
1506            this.showError("Error occurred while instantiating MetaDataView factory class");
1507            return;
1508        }
1509
1510        if (metaDataViewFactory == null) {
1511            log.debug("showMetaData(): MetaDataView factory is null");
1512            return;
1513        }
1514
1515        MetaDataView theView;
1516        try {
1517            theView = metaDataViewFactory.getMetaDataView(generalArea, this, obj);
1518
1519            if (theView == null) {
1520                log.debug("showMetaData(): error occurred while instantiating MetaDataView class");
1521                this.showError("Error occurred while instantiating MetaDataView class");
1522                return;
1523            }
1524        }
1525        catch (ClassNotFoundException ex) {
1526            log.debug("showMetaData(): no suitable MetaDataView class found");
1527            this.showError("Unable to find suitable MetaDataView class");
1528            return;
1529        }
1530    }
1531
1532    /**
1533     * close the file currently selected in the application
1534     *
1535     * @param theFile
1536     *        the file selected or specified
1537     */
1538    public void closeFile(FileFormat theFile)
1539    {
1540        if (theFile == null) {
1541            display.beep();
1542            Tools.showError(mainWindow, "Close", "Select a file to close");
1543            return;
1544        }
1545
1546        // Close all the data windows of this file
1547        Shell[] views = display.getShells();
1548        if (views != null) {
1549            for (int i = 0; i < views.length; i++) {
1550                Object shellData = views[i].getData();
1551
1552                if (!(shellData instanceof DataView))
1553                    continue;
1554
1555                if ((DataView)shellData != null) {
1556                    HObject obj = ((DataView)shellData).getDataObject();
1557
1558                    if (obj == null || obj.getFileFormat() == null)
1559                        continue;
1560
1561                    if (obj.getFileFormat().equals(theFile)) {
1562                        views[i].dispose();
1563                        views[i] = null;
1564                    }
1565                }
1566            }
1567        }
1568
1569        int index = urlBar.getSelectionIndex();
1570        if (index >= 0) {
1571            String fName = urlBar.getItem(urlBar.getSelectionIndex());
1572            if (theFile.getFilePath().equals(fName)) {
1573                currentFile = null;
1574                urlBar.setText("");
1575            }
1576        }
1577
1578        try {
1579            treeView.closeFile(theFile);
1580        }
1581        catch (Exception ex) {
1582            // Intentional
1583        }
1584
1585        for (Control control : generalArea.getChildren())
1586            control.dispose();
1587        generalArea.setContent(null);
1588
1589        System.gc();
1590    }
1591
1592    /**
1593     * Write the change of data to the given file.
1594     *
1595     * @param theFile
1596     *           The file to be updated.
1597     */
1598    public void writeDataToFile(FileFormat theFile)
1599    {
1600        try {
1601            Shell[] openShells = display.getShells();
1602
1603            if (openShells != null) {
1604                for (int i = 0; i < openShells.length; i++) {
1605                    DataView theView = (DataView)openShells[i].getData();
1606
1607                    if (theView instanceof TableView) {
1608                        TableView tableView = (TableView)theView;
1609                        FileFormat file     = tableView.getDataObject().getFileFormat();
1610                        if (file.equals(theFile))
1611                            tableView.updateValueInFile();
1612                    }
1613                }
1614            }
1615        }
1616        catch (Exception ex) {
1617            display.beep();
1618            Tools.showError(mainWindow, "Save", ex.getMessage());
1619        }
1620    }
1621
1622    @Override
1623    public void addDataView(DataView dataView)
1624    {
1625        if (dataView == null || dataView instanceof MetaDataView)
1626            return;
1627
1628        // Check if the data content is already displayed
1629        Shell[] shellList = display.getShells();
1630        if (shellList != null) {
1631            for (int i = 0; i < shellList.length; i++) {
1632                if (dataView.equals(shellList[i].getData()) && shellList[i].isVisible()) {
1633                    showWindow(shellList[i]);
1634                    return;
1635                }
1636            }
1637        }
1638
1639        // First window being added
1640        if (shellList != null && shellList.length == 2)
1641            setEnabled(Arrays.asList(windowMenu.getItems()), true);
1642
1643        HObject obj = dataView.getDataObject();
1644        String fullPath =
1645            ((obj.getPath() == null) ? "" : obj.getPath()) + ((obj.getName() == null) ? "" : obj.getName());
1646
1647        MenuItem item = new MenuItem(windowMenu, SWT.PUSH);
1648        item.setText(fullPath);
1649        item.addSelectionListener(new SelectionAdapter() {
1650            @Override
1651            public void widgetSelected(SelectionEvent e)
1652            {
1653                Shell[] sList = display.getShells();
1654
1655                for (int i = 0; i < sList.length; i++) {
1656                    DataView view = (DataView)sList[i].getData();
1657
1658                    if (view != null) {
1659                        HObject obj = view.getDataObject();
1660
1661                        if (obj.getFullName().equals(((MenuItem)e.widget).getText()))
1662                            showWindow(sList[i]);
1663                    }
1664                }
1665            }
1666        });
1667
1668        mainWindow.setCursor(null);
1669    }
1670
1671    @Override
1672    public void removeDataView(DataView dataView)
1673    {
1674        if (mainWindow.isDisposed())
1675            return;
1676
1677        HObject obj = dataView.getDataObject();
1678        if (obj == null)
1679            return;
1680
1681        MenuItem[] items = windowMenu.getItems();
1682        for (int i = 0; i < items.length; i++) {
1683            if (items[i].getText().equals(obj.getFullName()))
1684                items[i].dispose();
1685        }
1686
1687        // Last window being closed
1688        if (display.getShells().length == 2)
1689            for (MenuItem item : windowMenu.getItems())
1690                item.setEnabled(false);
1691    }
1692
1693    @Override
1694    public DataView getDataView(HObject dataObject)
1695    {
1696        Shell[] openShells             = display.getShells();
1697        DataView view                  = null;
1698        HObject currentObj             = null;
1699        FileFormat currentDataViewFile = null;
1700
1701        for (int i = 0; i < openShells.length; i++) {
1702            view = (DataView)openShells[i].getData();
1703
1704            if (view != null) {
1705                currentObj = view.getDataObject();
1706                if (currentObj == null)
1707                    continue;
1708
1709                currentDataViewFile = currentObj.getFileFormat();
1710
1711                if (currentObj.equals(dataObject) && currentDataViewFile.equals(dataObject.getFileFormat()))
1712                    return view;
1713            }
1714        }
1715
1716        return null;
1717    }
1718
1719    /**
1720     * Set the testing state that determines if HDFView
1721     * is being executed for GUI testing.
1722     *
1723     * @param testing
1724     *           Provides SWTBot native dialog compatibility
1725     *           workarounds if set to true.
1726     */
1727    public void setTestState(boolean testing) { isTesting = testing; }
1728
1729    /**
1730     * Get the testing state that determines if HDFView
1731     * is being executed for GUI testing.
1732     *
1733     * @return true if HDFView is being executed for GUI testing.
1734     */
1735    public boolean getTestState() { return isTesting; }
1736
1737    /**
1738     * Set default UI fonts.
1739     */
1740    private void updateFont(Font font)
1741    {
1742        if (currentFont != null)
1743            currentFont.dispose();
1744
1745        log.trace("updateFont():");
1746        currentFont = font;
1747
1748        mainWindow.setFont(font);
1749        recentFilesButton.setFont(font);
1750        recentFilesButton.requestLayout();
1751        urlBar.setFont(font);
1752        urlBar.requestLayout();
1753        clearTextButton.setFont(font);
1754        clearTextButton.requestLayout();
1755        status.setFont(font);
1756
1757        // On certain platforms the url_bar items don't update their size after
1758        // a font change. Removing and replacing them fixes this.
1759        for (String item : urlBar.getItems()) {
1760            urlBar.remove(item);
1761            urlBar.add(item);
1762        }
1763
1764        treeArea.setFont(font);
1765        treeArea.requestLayout();
1766        for (Control control : treeArea.getChildren()) {
1767            control.setFont(font);
1768            control.requestLayout();
1769        }
1770
1771        generalArea.setFont(font);
1772        generalArea.requestLayout();
1773        for (Control control : generalArea.getChildren()) {
1774            control.setFont(font);
1775            control.requestLayout();
1776        }
1777
1778        if (treeView.getSelectedFile() != null)
1779            urlBar.select(0);
1780
1781        if (treeView instanceof DefaultTreeView)
1782            ((DefaultTreeView)treeView).updateFont(font);
1783
1784        Shell[] shellList = display.getShells();
1785        if (shellList != null) {
1786            for (int i = 0; i < shellList.length; i++) {
1787                shellList[i].setFont(font);
1788                shellList[i].requestLayout();
1789            }
1790        }
1791
1792        mainWindow.requestLayout();
1793    }
1794
1795    /**
1796     * Bring window to the front.
1797     *
1798     * @param name
1799     *               the name of the window to show.
1800     */
1801    private void showWindow(final Shell shell)
1802    {
1803        shell.getDisplay().asyncExec(new Runnable() {
1804            @Override
1805            public void run()
1806            {
1807                shell.forceActive();
1808            }
1809        });
1810    }
1811
1812    /**
1813     * Cascade all windows.
1814     */
1815    private void cascadeWindows()
1816    {
1817        Shell[] sList = display.getShells();
1818
1819        // Return if main window (shell) is the only open shell
1820        if (sList.length <= 1)
1821            return;
1822
1823        Shell shell = null;
1824
1825        Rectangle bounds = Display.getCurrent().getPrimaryMonitor().getClientArea();
1826        int w            = Math.max(50, bounds.width - 100);
1827        int h            = Math.max(50, bounds.height - 100);
1828
1829        int x = bounds.x;
1830        int y = bounds.y;
1831
1832        for (int i = 0; i < sList.length; i++) {
1833            shell = sList[i];
1834            shell.setBounds(x, y, w, h);
1835            shell.setActive();
1836            x += 20;
1837            y += 20;
1838        }
1839    }
1840
1841    /**
1842     * Tile all windows.
1843     */
1844    private void tileWindows()
1845    {
1846        Shell[] sList = display.getShells();
1847
1848        // Return if main window (shell) is the only open shell
1849        if (sList.length <= 1)
1850            return;
1851
1852        int x       = 0;
1853        int y       = 0;
1854        int idx     = 0;
1855        Shell shell = null;
1856
1857        int n    = sList.length;
1858        int cols = (int)Math.sqrt(n);
1859        int rows = (int)Math.ceil((double)n / (double)cols);
1860
1861        Rectangle bounds = Display.getCurrent().getPrimaryMonitor().getClientArea();
1862        int w            = bounds.width / cols;
1863        int h            = bounds.height / rows;
1864
1865        y = bounds.y;
1866        for (int i = 0; i < rows; i++) {
1867            x = bounds.x;
1868
1869            for (int j = 0; j < cols; j++) {
1870                idx = i * cols + j;
1871                if (idx >= n)
1872                    return;
1873
1874                shell = sList[idx];
1875                shell.setBounds(x, y, w, h);
1876                shell.setActive();
1877                x += w;
1878            }
1879
1880            y += h;
1881        }
1882    }
1883
1884    /**
1885     * Closes all windows.
1886     */
1887    private void closeAllWindows()
1888    {
1889        Shell[] sList = display.getShells();
1890
1891        for (int i = 0; i < sList.length; i++) {
1892            if (sList[i].equals(mainWindow))
1893                continue;
1894            sList[i].dispose();
1895        }
1896    }
1897
1898    /* Enable and disable GUI components */
1899    private static void setEnabled(List<MenuItem> list, boolean b)
1900    {
1901        Iterator<MenuItem> it = list.iterator();
1902
1903        while (it.hasNext())
1904            it.next().setEnabled(b);
1905    }
1906
1907    /** Open local file */
1908    private void openLocalFile(String filename, int fileAccessID)
1909    {
1910        log.trace("openLocalFile {},{}", filename, fileAccessID);
1911
1912        /*
1913         * If given a specific access mode, use it without changing it. If not given a
1914         * specific access mode, check the current status of the "is read only" property
1915         * to determine how to open the file. This is to allow one time overrides of the
1916         * default file access mode when opening a file.
1917         */
1918        int accessMode = fileAccessID;
1919        if (accessMode < 0) {
1920            if (ViewProperties.isReadOnly())
1921                accessMode = FileFormat.READ;
1922            else if (ViewProperties.isReadSWMR())
1923                accessMode = FileFormat.READ | FileFormat.MULTIREAD;
1924            else
1925                accessMode = FileFormat.WRITE;
1926        }
1927
1928        String[] selectedFilenames = null;
1929        File[] chosenFiles         = null;
1930
1931        if (filename != null) {
1932            File file = new File(filename);
1933            if (!file.exists()) {
1934                Tools.showError(mainWindow, "Open", "File " + filename + " does not exist.");
1935                return;
1936            }
1937
1938            if (file.isDirectory()) {
1939                currentDir = filename;
1940                openLocalFile(null, -1);
1941            }
1942            else {
1943                currentFile = filename;
1944
1945                try {
1946                    treeView.openFile(filename, accessMode);
1947                }
1948                catch (Exception ex) {
1949                    try {
1950                        treeView.openFile(filename, FileFormat.READ);
1951                    }
1952                    catch (Exception ex2) {
1953                        display.beep();
1954                        urlBar.deselectAll();
1955                        Tools.showError(mainWindow, "Open", "Failed to open file " + filename + "\n" + ex2);
1956                        currentFile = null;
1957                    }
1958                }
1959            }
1960
1961            try {
1962                urlBar.remove(filename);
1963            }
1964            catch (Exception ex) {
1965                log.trace("unable to remove {} from urlBar", filename);
1966            }
1967
1968            // first entry is always the workdir
1969            urlBar.add(filename, 1);
1970            urlBar.select(1);
1971        }
1972        else {
1973            if (!isTesting) {
1974                log.trace("openLocalFile filename is null");
1975                FileDialog fChooser = new FileDialog(mainWindow, SWT.OPEN | SWT.MULTI);
1976                String modeStr      = "Read/Write";
1977                boolean isSWMRFile  = (FileFormat.MULTIREAD == (accessMode & FileFormat.MULTIREAD));
1978                if (isSWMRFile)
1979                    modeStr = "SWMR Read-only";
1980                else if (accessMode == FileFormat.READ)
1981                    modeStr = "Read-only";
1982                fChooser.setText(mainWindow.getText() + " - Open File " + modeStr);
1983                fChooser.setFilterPath(currentDir);
1984
1985                DefaultFileFilter filter = DefaultFileFilter.getFileFilter();
1986                fChooser.setFilterExtensions(new String[] {"*", filter.getExtensions()});
1987                fChooser.setFilterNames(new String[] {"All Files", filter.getDescription()});
1988                fChooser.setFilterIndex(1);
1989
1990                fChooser.open();
1991
1992                selectedFilenames = fChooser.getFileNames();
1993                if (selectedFilenames.length <= 0)
1994                    return;
1995
1996                chosenFiles = new File[selectedFilenames.length];
1997                for (int i = 0; i < chosenFiles.length; i++) {
1998                    log.trace("openLocalFile selectedFilenames[{}]: {}", i, selectedFilenames[i]);
1999                    chosenFiles[i] =
2000                        new File(fChooser.getFilterPath() + File.separator + selectedFilenames[i]);
2001
2002                    if (!chosenFiles[i].exists()) {
2003                        Tools.showError(mainWindow, "Open",
2004                                        "File " + chosenFiles[i].getName() + " does not exist.");
2005                        continue;
2006                    }
2007
2008                    if (chosenFiles[i].isDirectory())
2009                        currentDir = chosenFiles[i].getPath();
2010                    else
2011                        currentDir = chosenFiles[i].getParent();
2012
2013                    try {
2014                        urlBar.remove(chosenFiles[i].getAbsolutePath());
2015                    }
2016                    catch (Exception ex) {
2017                        log.trace("unable to remove {} from urlBar", chosenFiles[i].getAbsolutePath());
2018                    }
2019
2020                    // first entry is always the workdir
2021                    urlBar.add(chosenFiles[i].getAbsolutePath(), 1);
2022                    urlBar.select(1);
2023
2024                    log.trace("openLocalFile treeView.openFile(accessMode={} chosenFiles[{}]: {}", accessMode,
2025                              i, chosenFiles[i].getAbsolutePath());
2026                    try {
2027                        treeView.openFile(chosenFiles[i].getAbsolutePath(), accessMode + FileFormat.OPEN_NEW);
2028                    }
2029                    catch (Exception ex) {
2030                        try {
2031                            treeView.openFile(chosenFiles[i].getAbsolutePath(), FileFormat.READ);
2032                        }
2033                        catch (Exception ex2) {
2034                            display.beep();
2035                            urlBar.deselectAll();
2036                            Tools.showError(mainWindow, "Open",
2037                                            "Failed to open file " + selectedFilenames[i] + "\n" + ex2);
2038                            currentFile = null;
2039                        }
2040                    }
2041                }
2042
2043                currentFile = chosenFiles[0].getAbsolutePath();
2044            }
2045            else {
2046                // Prepend test file directory to filename
2047                String fName =
2048                    currentDir + File.separator + new InputDialog(mainWindow, "Enter a file name", "").open();
2049
2050                File chosenFile = new File(fName);
2051
2052                if (!chosenFile.exists()) {
2053                    Tools.showError(mainWindow, "Open", "File " + chosenFile.getName() + " does not exist.");
2054                    return;
2055                }
2056
2057                if (chosenFile.isDirectory())
2058                    currentDir = chosenFile.getPath();
2059                else
2060                    currentDir = chosenFile.getParent();
2061
2062                try {
2063                    urlBar.remove(chosenFile.getAbsolutePath());
2064                }
2065                catch (Exception ex) {
2066                    log.trace("unable to remove {} from urlBar", chosenFile.getAbsolutePath());
2067                }
2068
2069                // first entry is always the workdir
2070                urlBar.add(chosenFile.getAbsolutePath(), 1);
2071                urlBar.select(1);
2072
2073                log.trace("openLocalFile treeView.openFile(chosenFile[{}]: {}", chosenFile.getAbsolutePath(),
2074                          accessMode + FileFormat.OPEN_NEW);
2075                try {
2076                    treeView.openFile(chosenFile.getAbsolutePath(), accessMode + FileFormat.OPEN_NEW);
2077                }
2078                catch (Exception ex) {
2079                    try {
2080                        treeView.openFile(chosenFile.getAbsolutePath(), FileFormat.READ);
2081                    }
2082                    catch (Exception ex2) {
2083                        display.beep();
2084                        urlBar.deselectAll();
2085                        Tools.showError(mainWindow, "Open", "Failed to open file " + chosenFile + "\n" + ex2);
2086                        currentFile = null;
2087                    }
2088                }
2089
2090                currentFile = chosenFile.getAbsolutePath();
2091            }
2092        }
2093    }
2094
2095    /** Load remote file and save it to local temporary directory */
2096    private String openRemoteFile(String urlStr)
2097    {
2098        if (urlStr == null)
2099            return null;
2100
2101        String localFile = null;
2102
2103        if (urlStr.startsWith("http://"))
2104            localFile = urlStr.substring(7);
2105        else if (urlStr.startsWith("https://"))
2106            localFile = urlStr.substring(8);
2107        else if (urlStr.startsWith("ftp://"))
2108            localFile = urlStr.substring(6);
2109        else
2110            return null;
2111
2112        localFile = localFile.replace('/', '@');
2113        localFile = localFile.replace('\\', '@');
2114
2115        // Search the local file cache
2116        String tmpDir = System.getProperty("java.io.tmpdir");
2117
2118        File tmpFile = new File(tmpDir);
2119        if (!tmpFile.canWrite())
2120            tmpDir = System.getProperty("user.home");
2121
2122        localFile = tmpDir + File.separator + localFile;
2123
2124        tmpFile = new File(localFile);
2125        if (tmpFile.exists())
2126            return localFile;
2127
2128        URL url = null;
2129
2130        try {
2131            url = new URL(urlStr);
2132        }
2133        catch (Exception ex) {
2134            url = null;
2135            display.beep();
2136            Tools.showError(mainWindow, "Open", ex.getMessage());
2137            return null;
2138        }
2139
2140        try (BufferedInputStream in = new BufferedInputStream(url.openStream())) {
2141            try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(tmpFile))) {
2142                mainWindow.setCursor(display.getSystemCursor(SWT.CURSOR_WAIT));
2143                byte[] buff = new byte[512]; // set default buffer size to 512
2144                int n       = 0;
2145                while ((n = in.read(buff)) > 0)
2146                    out.write(buff, 0, n);
2147            }
2148            catch (Exception ex) {
2149                log.debug("Remote file: ", ex);
2150                throw ex;
2151            }
2152        }
2153        catch (Exception ex) {
2154            display.beep();
2155            Tools.showError(mainWindow, "Open", ex.getMessage());
2156            // Want to call setCursor always
2157            localFile = null;
2158        }
2159
2160        mainWindow.setCursor(null);
2161
2162        return localFile;
2163    }
2164
2165    private void convertFile(String typeFrom, String typeTo)
2166    {
2167        ImageConversionDialog dialog =
2168            new ImageConversionDialog(mainWindow, typeFrom, typeTo, currentDir, treeView.getCurrentFiles());
2169        dialog.open();
2170
2171        if (dialog.isFileConverted()) {
2172            String filename = dialog.getConvertedFile();
2173            File theFile    = new File(filename);
2174
2175            if (!theFile.exists())
2176                return;
2177
2178            currentDir  = theFile.getParentFile().getAbsolutePath();
2179            currentFile = theFile.getAbsolutePath();
2180
2181            try {
2182                treeView.openFile(filename, FileFormat.WRITE);
2183
2184                try {
2185                    urlBar.remove(filename);
2186                }
2187                catch (Exception ex) {
2188                    log.trace("unable to remove {} from urlBar", filename);
2189                }
2190
2191                // first entry is always the workdir
2192                urlBar.add(filename, 1);
2193                urlBar.select(1);
2194            }
2195            catch (Exception ex) {
2196                showError(ex.toString());
2197            }
2198        }
2199    }
2200
2201    private void registerFileFormat()
2202    {
2203        String msg = "Register a new file format by \nKEY:FILE_FORMAT:FILE_EXTENSION\n"
2204                     + "where, KEY: the unique identifier for the file format"
2205                     + "\n           FILE_FORMAT: the full class name of the file format"
2206                     + "\n           FILE_EXTENSION: the file extension for the file format"
2207                     + "\n\nFor example, "
2208                     + "\n\t to add NetCDF, \"NetCDF:hdf.object.nc2.NC2File:nc\""
2209                     + "\n\t to add FITS, \"FITS:hdf.object.fits.FitsFile:fits\"\n\n";
2210
2211        // TODO:Add custom HDFLarge icon to dialog
2212        InputDialog dialog = new InputDialog(mainWindow, "Register a file format", msg, SWT.ICON_INFORMATION);
2213
2214        String str = dialog.open();
2215
2216        if ((str == null) || (str.length() < 1))
2217            return;
2218
2219        int idx1 = str.indexOf(':');
2220        int idx2 = str.lastIndexOf(':');
2221
2222        if ((idx1 < 0) || (idx2 <= idx1)) {
2223            Tools.showError(mainWindow, "Register File Format",
2224                            "Failed to register " + str +
2225                                "\n\nMust in the form of KEY:FILE_FORMAT:FILE_EXTENSION");
2226            return;
2227        }
2228
2229        String key       = str.substring(0, idx1);
2230        String className = str.substring(idx1 + 1, idx2);
2231        String extension = str.substring(idx2 + 1);
2232
2233        // Check if the file format has been registered or the key is taken.
2234        String theKey            = null;
2235        String theClassName      = null;
2236        Enumeration<?> localEnum = FileFormat.getFileFormatKeys();
2237        while (localEnum.hasMoreElements()) {
2238            theKey = (String)localEnum.nextElement();
2239            if (theKey.endsWith(key)) {
2240                Tools.showError(mainWindow, "Register File Format", "Invalid key: " + key + " is taken.");
2241                return;
2242            }
2243
2244            theClassName = FileFormat.getFileFormat(theKey).getClass().getName();
2245            if (theClassName.endsWith(className)) {
2246                Tools.showError(mainWindow, "Register File Format",
2247                                "The file format has already been registered: " + className);
2248                return;
2249            }
2250        }
2251
2252        // Enables use of JHDF5 in JNLP (Web Start) applications, the system
2253        // class loader with reflection first.
2254        Class<?> theClass = null;
2255        try {
2256            theClass = Class.forName(className);
2257        }
2258        catch (Exception ex) {
2259            try {
2260                theClass = ViewProperties.loadExtClass().loadClass(className);
2261            }
2262            catch (Exception ex2) {
2263                theClass = null;
2264            }
2265        }
2266
2267        if (theClass == null)
2268            return;
2269
2270        try {
2271            Object theObject = theClass.newInstance();
2272            if (theObject instanceof FileFormat)
2273                FileFormat.addFileFormat(key, (FileFormat)theObject);
2274        }
2275        catch (Exception ex) {
2276            Tools.showError(mainWindow, "Register File Format", "Failed to register " + str + "\n\n" + ex);
2277            return;
2278        }
2279
2280        if ((extension != null) && (extension.length() > 0)) {
2281            extension  = extension.trim();
2282            String ext = ViewProperties.getFileExtension();
2283            ext += ", " + extension;
2284            ViewProperties.setFileExtension(ext);
2285        }
2286    }
2287
2288    private void unregisterFileFormat()
2289    {
2290        Enumeration<?> keys       = FileFormat.getFileFormatKeys();
2291        ArrayList<Object> keyList = new ArrayList<>();
2292
2293        while (keys.hasMoreElements())
2294            keyList.add(keys.nextElement());
2295
2296        String theKey = new UnregisterFileFormatDialog(mainWindow, SWT.NONE, keyList).open();
2297
2298        if (theKey == null)
2299            return;
2300
2301        FileFormat.removeFileFormat(theKey);
2302    }
2303
2304    private class LibraryVersionDialog extends Dialog {
2305        private String message;
2306
2307        public LibraryVersionDialog(Shell parent, String libType)
2308        {
2309            super(parent, SWT.APPLICATION_MODAL | SWT.DIALOG_TRIM);
2310
2311            if (libType.equals(FileFormat.FILE_TYPE_HDF4))
2312                setMessage("HDF " + HDF4_VERSION);
2313            else if (libType.equals(FileFormat.FILE_TYPE_HDF5))
2314                setMessage("HDF5 " + HDF5_VERSION);
2315        }
2316
2317        public void setMessage(String message) { this.message = message; }
2318
2319        public void open()
2320        {
2321            Shell dialog = new Shell(getParent(), getStyle());
2322            dialog.setFont(currentFont);
2323            dialog.setText("HDF Library Version");
2324
2325            createContents(dialog);
2326
2327            dialog.pack();
2328
2329            Point computedSize = dialog.computeSize(SWT.DEFAULT, SWT.DEFAULT);
2330            dialog.setSize(computedSize.x + 50, computedSize.y + 50);
2331
2332            // Center the window relative to the main HDFView window
2333            Point winCenter = new Point(mainWindow.getBounds().x + (mainWindow.getBounds().width / 2),
2334                                        mainWindow.getBounds().y + (mainWindow.getBounds().height / 2));
2335
2336            dialog.setLocation(winCenter.x - (dialog.getSize().x / 2),
2337                               winCenter.y - (dialog.getSize().y / 2));
2338
2339            dialog.open();
2340
2341            Display display = getParent().getDisplay();
2342            while (!dialog.isDisposed()) {
2343                if (!display.readAndDispatch())
2344                    display.sleep();
2345            }
2346        }
2347
2348        private void createContents(final Shell shell)
2349        {
2350            shell.setLayout(new GridLayout(2, false));
2351
2352            Image hdfImage = ViewProperties.getHDFViewIcon();
2353
2354            Label imageLabel = new Label(shell, SWT.CENTER);
2355            imageLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
2356            imageLabel.setImage(hdfImage);
2357
2358            Label versionLabel = new Label(shell, SWT.CENTER);
2359            versionLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false));
2360            versionLabel.setFont(currentFont);
2361            versionLabel.setText(message);
2362
2363            // Draw HDF Icon and Version string
2364            Composite buttonComposite = new Composite(shell, SWT.NONE);
2365            buttonComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));
2366            RowLayout buttonLayout = new RowLayout();
2367            buttonLayout.center    = true;
2368            buttonLayout.justify   = true;
2369            buttonLayout.type      = SWT.HORIZONTAL;
2370            buttonComposite.setLayout(buttonLayout);
2371
2372            Button okButton = new Button(buttonComposite, SWT.PUSH);
2373            okButton.setFont(currentFont);
2374            okButton.setText("   &OK   ");
2375            shell.setDefaultButton(okButton);
2376            okButton.addSelectionListener(new SelectionAdapter() {
2377                @Override
2378                public void widgetSelected(SelectionEvent e)
2379                {
2380                    shell.dispose();
2381                }
2382            });
2383        }
2384    }
2385
2386    private class JavaVersionDialog extends Dialog {
2387        public JavaVersionDialog(Shell parent) { super(parent, SWT.APPLICATION_MODAL | SWT.DIALOG_TRIM); }
2388
2389        public void open()
2390        {
2391            final Shell dialog = new Shell(getParent(), getStyle());
2392            dialog.setFont(currentFont);
2393            dialog.setText("HDFView Java Version");
2394            dialog.setLayout(new GridLayout(2, false));
2395
2396            Image hdfImage = ViewProperties.getHDFViewIcon();
2397
2398            Label imageLabel = new Label(dialog, SWT.CENTER);
2399            imageLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
2400            imageLabel.setImage(hdfImage);
2401
2402            Label versionLabel = new Label(dialog, SWT.CENTER);
2403            versionLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false));
2404            versionLabel.setFont(currentFont);
2405            versionLabel.setText(JAVA_VER_INFO);
2406
2407            Composite buttonComposite = new Composite(dialog, SWT.NONE);
2408            buttonComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));
2409            RowLayout buttonLayout = new RowLayout();
2410            buttonLayout.center    = true;
2411            buttonLayout.justify   = true;
2412            buttonLayout.type      = SWT.HORIZONTAL;
2413            buttonComposite.setLayout(buttonLayout);
2414
2415            Button okButton = new Button(buttonComposite, SWT.PUSH);
2416            okButton.setFont(currentFont);
2417            okButton.setText("   &OK   ");
2418            dialog.setDefaultButton(okButton);
2419            okButton.addSelectionListener(new SelectionAdapter() {
2420                @Override
2421                public void widgetSelected(SelectionEvent e)
2422                {
2423                    dialog.dispose();
2424                }
2425            });
2426
2427            dialog.pack();
2428
2429            Point computedSize = dialog.computeSize(SWT.DEFAULT, SWT.DEFAULT);
2430            dialog.setSize(computedSize.x + 50, computedSize.y + 50);
2431
2432            // Center the window relative to the main HDFView window
2433            Point winCenter = new Point(mainWindow.getBounds().x + (mainWindow.getBounds().width / 2),
2434                                        mainWindow.getBounds().y + (mainWindow.getBounds().height / 2));
2435
2436            dialog.setLocation(winCenter.x - (dialog.getSize().x / 2),
2437                               winCenter.y - (dialog.getSize().y / 2));
2438
2439            dialog.open();
2440
2441            Display openDisplay = getParent().getDisplay();
2442            while (!dialog.isDisposed()) {
2443                if (!openDisplay.readAndDispatch())
2444                    openDisplay.sleep();
2445            }
2446        }
2447    }
2448
2449    private class SupportedFileFormatsDialog extends Dialog {
2450        public SupportedFileFormatsDialog(Shell parent)
2451        {
2452            super(parent, SWT.APPLICATION_MODAL | SWT.DIALOG_TRIM);
2453        }
2454
2455        public void open()
2456        {
2457            final Shell dialog = new Shell(getParent(), getStyle());
2458            dialog.setFont(currentFont);
2459            dialog.setText("Supported File Formats");
2460            dialog.setLayout(new GridLayout(2, false));
2461
2462            Image hdfImage = ViewProperties.getHDFViewIcon();
2463
2464            Label imageLabel = new Label(dialog, SWT.CENTER);
2465            imageLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
2466            imageLabel.setImage(hdfImage);
2467
2468            Enumeration<?> formatKeys = FileFormat.getFileFormatKeys();
2469
2470            StringBuilder formats = new StringBuilder("\nSupported File Formats: \n");
2471            while (formatKeys.hasMoreElements())
2472                formats.append("    ").append(formatKeys.nextElement()).append("\n");
2473            formats.append("\n");
2474
2475            Label formatsLabel = new Label(dialog, SWT.LEFT);
2476            formatsLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false));
2477            formatsLabel.setFont(currentFont);
2478            formatsLabel.setText(formats.toString());
2479
2480            Composite buttonComposite = new Composite(dialog, SWT.NONE);
2481            buttonComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));
2482            RowLayout buttonLayout = new RowLayout();
2483            buttonLayout.center    = true;
2484            buttonLayout.justify   = true;
2485            buttonLayout.type      = SWT.HORIZONTAL;
2486            buttonComposite.setLayout(buttonLayout);
2487
2488            Button okButton = new Button(buttonComposite, SWT.PUSH);
2489            okButton.setFont(currentFont);
2490            okButton.setText("   &OK   ");
2491            dialog.setDefaultButton(okButton);
2492            okButton.addSelectionListener(new SelectionAdapter() {
2493                @Override
2494                public void widgetSelected(SelectionEvent e)
2495                {
2496                    dialog.dispose();
2497                }
2498            });
2499
2500            dialog.pack();
2501
2502            Point computedSize = dialog.computeSize(SWT.DEFAULT, SWT.DEFAULT);
2503            dialog.setSize(computedSize.x + 50, computedSize.y + 50);
2504
2505            // Center the window relative to the main HDFView window
2506            Point winCenter = new Point(mainWindow.getBounds().x + (mainWindow.getBounds().width / 2),
2507                                        mainWindow.getBounds().y + (mainWindow.getBounds().height / 2));
2508
2509            dialog.setLocation(winCenter.x - (dialog.getSize().x / 2),
2510                               winCenter.y - (dialog.getSize().y / 2));
2511
2512            dialog.open();
2513
2514            Display openDisplay = getParent().getDisplay();
2515            while (!dialog.isDisposed()) {
2516                if (!openDisplay.readAndDispatch())
2517                    openDisplay.sleep();
2518            }
2519        }
2520    }
2521
2522    private class AboutDialog extends Dialog {
2523        public AboutDialog(Shell parent) { super(parent, SWT.APPLICATION_MODAL | SWT.DIALOG_TRIM); }
2524
2525        public void open()
2526        {
2527            final Shell dialog = new Shell(getParent(), getStyle());
2528            dialog.setFont(currentFont);
2529            dialog.setText("About HDFView");
2530            dialog.setLayout(new GridLayout(2, false));
2531
2532            Image hdfImage = ViewProperties.getHDFViewIcon();
2533
2534            Label imageLabel = new Label(dialog, SWT.CENTER);
2535            imageLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
2536            imageLabel.setImage(hdfImage);
2537
2538            Label aboutLabel = new Label(dialog, SWT.LEFT);
2539            aboutLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false));
2540            aboutLabel.setFont(currentFont);
2541            aboutLabel.setText(ABOUT_HDFVIEW);
2542
2543            Composite buttonComposite = new Composite(dialog, SWT.NONE);
2544            buttonComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));
2545            RowLayout buttonLayout = new RowLayout();
2546            buttonLayout.center    = true;
2547            buttonLayout.justify   = true;
2548            buttonLayout.type      = SWT.HORIZONTAL;
2549            buttonComposite.setLayout(buttonLayout);
2550
2551            Button okButton = new Button(buttonComposite, SWT.PUSH);
2552            okButton.setFont(currentFont);
2553            okButton.setText("   &OK   ");
2554            dialog.setDefaultButton(okButton);
2555            okButton.addSelectionListener(new SelectionAdapter() {
2556                @Override
2557                public void widgetSelected(SelectionEvent e)
2558                {
2559                    dialog.dispose();
2560                }
2561            });
2562
2563            dialog.pack();
2564
2565            Point computedSize = dialog.computeSize(SWT.DEFAULT, SWT.DEFAULT);
2566            dialog.setSize(computedSize.x + 50, computedSize.y + 50);
2567
2568            // Center the window relative to the main HDFView window
2569            Point winCenter = new Point(mainWindow.getBounds().x + (mainWindow.getBounds().width / 2),
2570                                        mainWindow.getBounds().y + (mainWindow.getBounds().height / 2));
2571
2572            dialog.setLocation(winCenter.x - (dialog.getSize().x / 2),
2573                               winCenter.y - (dialog.getSize().y / 2));
2574
2575            dialog.open();
2576
2577            Display openDisplay = getParent().getDisplay();
2578            while (!dialog.isDisposed()) {
2579                if (!openDisplay.readAndDispatch())
2580                    openDisplay.sleep();
2581            }
2582        }
2583    }
2584
2585    private class UnregisterFileFormatDialog extends Dialog {
2586        private List<Object> keyList;
2587        private String formatChoice = null;
2588
2589        public UnregisterFileFormatDialog(Shell parent, int style, List<Object> keyList)
2590        {
2591            super(parent, style);
2592
2593            this.keyList = keyList;
2594        }
2595
2596        public String open()
2597        {
2598            Shell parent      = getParent();
2599            final Shell shell = new Shell(parent, SWT.APPLICATION_MODAL | SWT.DIALOG_TRIM);
2600            shell.setFont(currentFont);
2601            shell.setText("Unregister a file format");
2602            shell.setLayout(new GridLayout(2, false));
2603
2604            Image hdfImage = ViewProperties.getHDFViewIcon();
2605
2606            Label imageLabel = new Label(shell, SWT.CENTER);
2607            imageLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
2608            imageLabel.setImage(hdfImage);
2609
2610            final Combo formatChoiceCombo = new Combo(shell, SWT.SINGLE | SWT.DROP_DOWN | SWT.READ_ONLY);
2611            formatChoiceCombo.setFont(currentFont);
2612            formatChoiceCombo.setItems(keyList.toArray(new String[0]));
2613            formatChoiceCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
2614            formatChoiceCombo.select(0);
2615            formatChoiceCombo.addSelectionListener(new SelectionAdapter() {
2616                @Override
2617                public void widgetSelected(SelectionEvent e)
2618                {
2619                    formatChoice = formatChoiceCombo.getItem(formatChoiceCombo.getSelectionIndex());
2620                }
2621            });
2622
2623            Composite buttonComposite = new Composite(shell, SWT.NONE);
2624            buttonComposite.setLayout(new GridLayout(2, true));
2625            buttonComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));
2626
2627            Button okButton = new Button(buttonComposite, SWT.PUSH);
2628            okButton.setFont(currentFont);
2629            okButton.setText("   &OK   ");
2630            okButton.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
2631            okButton.addSelectionListener(new SelectionAdapter() {
2632                @Override
2633                public void widgetSelected(SelectionEvent e)
2634                {
2635                    shell.dispose();
2636                }
2637            });
2638
2639            Button cancelButton = new Button(buttonComposite, SWT.PUSH);
2640            cancelButton.setFont(currentFont);
2641            cancelButton.setText(" &Cancel ");
2642            cancelButton.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, true, false));
2643            cancelButton.addSelectionListener(new SelectionAdapter() {
2644                @Override
2645                public void widgetSelected(SelectionEvent e)
2646                {
2647                    shell.dispose();
2648                }
2649            });
2650
2651            shell.pack();
2652
2653            Point computedSize = shell.computeSize(SWT.DEFAULT, SWT.DEFAULT);
2654            shell.setSize(computedSize.x + 50, computedSize.y + 50);
2655
2656            Rectangle parentBounds = parent.getBounds();
2657            Point shellSize        = shell.getSize();
2658            shell.setLocation((parentBounds.x + (parentBounds.width / 2)) - (shellSize.x / 2),
2659                              (parentBounds.y + (parentBounds.height / 2)) - (shellSize.y / 2));
2660
2661            shell.open();
2662
2663            Display openDisplay = parent.getDisplay();
2664            while (!shell.isDisposed()) {
2665                if (!openDisplay.readAndDispatch())
2666                    openDisplay.sleep();
2667            }
2668
2669            return formatChoice;
2670        }
2671    }
2672
2673    /**
2674     * The starting point of this application.
2675     *
2676     * <pre>
2677     * Usage: java(w)
2678     *        -Dhdf.hdf5lib.H5.hdf5lib="your HDF5 library path"
2679     *        -Dhdf.hdflib.HDFLibrary.hdflib="your HDF4 library path"
2680     *        -root "the directory where the HDFView is installed"
2681     *        -start "the directory HDFView searches for files"
2682     *        -geometry or -g "the preferred window size as WIDTHxHEIGHT+XOFF+YOFF"
2683     *        -java.version "show the version of jave used to build the HDFView and exit"
2684     *        [filename] "the file to open"
2685     * </pre>
2686     *
2687     * @param args  the command line arguments
2688     */
2689    public static void main(String[] args)
2690    {
2691        if (display == null || display.isDisposed())
2692            display = new Display();
2693
2694        String rootDir = System.getProperty("hdfview.root");
2695        if (rootDir == null)
2696            rootDir = System.getProperty("user.dir");
2697        String startDir = System.getProperty("user.dir");
2698        log.trace("main: rootDir = {}  startDir = {}", rootDir, startDir);
2699
2700        File tmpFile           = null;
2701        Monitor primaryMonitor = display.getPrimaryMonitor();
2702        Point margin = new Point(primaryMonitor.getBounds().width, primaryMonitor.getBounds().height);
2703
2704        int j = args.length;
2705        int W = margin.x / 2;
2706        int H = margin.y;
2707        int X = 0;
2708        int Y = 0;
2709
2710        for (int i = 0; i < args.length; i++) {
2711            if ("-root".equalsIgnoreCase(args[i])) {
2712                j--;
2713                try {
2714                    j--;
2715                    tmpFile = new File(args[++i]);
2716
2717                    if (tmpFile.isDirectory())
2718                        rootDir = tmpFile.getPath();
2719                    else if (tmpFile.isFile())
2720                        rootDir = tmpFile.getParent();
2721                }
2722                catch (Exception ex) {
2723                }
2724            }
2725            else if ("-start".equalsIgnoreCase(args[i])) {
2726                j--;
2727                try {
2728                    j--;
2729                    tmpFile = new File(args[++i]);
2730
2731                    if (tmpFile.isDirectory())
2732                        startDir = tmpFile.getPath();
2733                    else if (tmpFile.isFile())
2734                        startDir = tmpFile.getParent();
2735                }
2736                catch (Exception ex) {
2737                }
2738            }
2739            else if ("-g".equalsIgnoreCase(args[i]) || "-geometry".equalsIgnoreCase(args[i])) {
2740                j--;
2741                // -geometry WIDTHxHEIGHT+XOFF+YOFF
2742                try {
2743                    String geom = args[++i];
2744                    j--;
2745
2746                    int idx  = 0;
2747                    int idx2 = geom.lastIndexOf('-');
2748                    int idx3 = geom.lastIndexOf('+');
2749
2750                    idx = Math.max(idx2, idx3);
2751                    if (idx > 0) {
2752                        Y = Integer.parseInt(geom.substring(idx + 1));
2753
2754                        if (idx == idx2)
2755                            Y = -Y;
2756
2757                        geom = geom.substring(0, idx);
2758                        idx2 = geom.lastIndexOf('-');
2759                        idx3 = geom.lastIndexOf('+');
2760                        idx  = Math.max(idx2, idx3);
2761
2762                        if (idx > 0) {
2763                            X = Integer.parseInt(geom.substring(idx + 1));
2764
2765                            if (idx == idx2)
2766                                X = -X;
2767
2768                            geom = geom.substring(0, idx);
2769                        }
2770                    }
2771
2772                    idx = geom.indexOf('x');
2773
2774                    if (idx > 0) {
2775                        W = Integer.parseInt(geom.substring(0, idx));
2776                        H = Integer.parseInt(geom.substring(idx + 1));
2777                    }
2778                }
2779                catch (Exception ex) {
2780                    ex.printStackTrace();
2781                }
2782            }
2783            else if ("-java.version".equalsIgnoreCase(args[i])) {
2784                /* Set icon to ViewProperties.getLargeHdfIcon() */
2785                Tools.showInformation(mainWindow, "Java Version", JAVA_VER_INFO);
2786                System.exit(0);
2787            }
2788        }
2789
2790        ArrayList<File> fList = new ArrayList<>();
2791
2792        if (j >= 0) {
2793            for (int i = args.length - j; i < args.length; i++) {
2794                tmpFile = new File(args[i]);
2795                if (!tmpFile.isAbsolute())
2796                    tmpFile = new File(rootDir, args[i]);
2797                log.trace("main: filelist - file = {} ", tmpFile.getAbsolutePath());
2798                log.trace("main: filelist - add file = {} exists={} isFile={} isDir={}", tmpFile,
2799                          tmpFile.exists(), tmpFile.isFile(), tmpFile.isDirectory());
2800                if (tmpFile.exists() && (tmpFile.isFile() || tmpFile.isDirectory())) {
2801                    log.trace("main: flist - add file = {}", tmpFile.getAbsolutePath());
2802                    fList.add(new File(tmpFile.getAbsolutePath()));
2803                }
2804            }
2805        }
2806
2807        final ArrayList<File> theFileList = fList;
2808        final String the_rootDir          = rootDir;
2809        final String the_startDir         = startDir;
2810        final int the_X = X, the_Y = Y, the_W = W, the_H = H;
2811
2812        display.syncExec(new Runnable() {
2813            @Override
2814            public void run()
2815            {
2816                HDFView app = new HDFView(the_rootDir, the_startDir);
2817
2818                // TODO: Look for a better solution to native dialog problem
2819                app.setTestState(false);
2820
2821                app.openMainWindow(theFileList, the_W, the_H, the_X, the_Y);
2822                app.runMainWindow();
2823            }
2824        });
2825    }
2826}