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