001/*****************************************************************************
002 * Copyright by The HDF Group.                                               *
003 * Copyright by the Board of Trustees of the University of Illinois.         *
004 * All rights reserved.                                                      *
005 *                                                                           *
006 * This file is part of the HDF Java Products distribution.                  *
007 * The full copyright notice, including terms governing use, modification,   *
008 * and redistribution, is contained in the COPYING file, which can be found  *
009 * at the root of the source code distribution tree,                         *
010 * or in https://www.hdfgroup.org/licenses.                                  *
011 * If you do not have access to either file, you may request a copy from     *
012 * help@hdfgroup.org.                                                        *
013 ****************************************************************************/
014
015package hdf.view.MetaDataView;
016
017import java.io.File;
018import java.lang.reflect.Array;
019import java.math.BigInteger;
020import java.util.ArrayList;
021import java.util.Iterator;
022import java.util.List;
023import java.util.StringTokenizer;
024
025import hdf.object.Attribute;
026import hdf.object.CompoundDS;
027import hdf.object.Dataset;
028import hdf.object.Datatype;
029import hdf.object.FileFormat;
030import hdf.object.Group;
031import hdf.object.HObject;
032import hdf.object.MetaDataContainer;
033import hdf.object.ScalarDS;
034import hdf.object.h5.H5ReferenceType;
035import hdf.object.h5.H5ReferenceType.H5ReferenceData;
036import hdf.view.DataView.DataViewManager;
037import hdf.view.DefaultFileFilter;
038import hdf.view.Tools;
039import hdf.view.TreeView.DefaultTreeView;
040import hdf.view.TreeView.TreeView;
041import hdf.view.ViewProperties;
042import hdf.view.dialog.InputDialog;
043import hdf.view.dialog.NewDataObjectDialog;
044import hdf.view.dialog.NewScalarAttributeDialog;
045import hdf.view.dialog.NewStringAttributeDialog;
046
047import hdf.hdf5lib.H5;
048import hdf.hdf5lib.HDF5Constants;
049
050import org.slf4j.Logger;
051import org.slf4j.LoggerFactory;
052
053import org.eclipse.jface.dialogs.MessageDialog;
054import org.eclipse.swt.SWT;
055import org.eclipse.swt.custom.ScrolledComposite;
056import org.eclipse.swt.events.MenuAdapter;
057import org.eclipse.swt.events.MenuEvent;
058import org.eclipse.swt.events.SelectionAdapter;
059import org.eclipse.swt.events.SelectionEvent;
060import org.eclipse.swt.graphics.Font;
061import org.eclipse.swt.graphics.Point;
062import org.eclipse.swt.graphics.Rectangle;
063import org.eclipse.swt.layout.GridData;
064import org.eclipse.swt.layout.GridLayout;
065import org.eclipse.swt.widgets.Button;
066import org.eclipse.swt.widgets.Combo;
067import org.eclipse.swt.widgets.Composite;
068import org.eclipse.swt.widgets.Dialog;
069import org.eclipse.swt.widgets.Display;
070import org.eclipse.swt.widgets.Event;
071import org.eclipse.swt.widgets.FileDialog;
072import org.eclipse.swt.widgets.Label;
073import org.eclipse.swt.widgets.Listener;
074import org.eclipse.swt.widgets.Menu;
075import org.eclipse.swt.widgets.MenuItem;
076import org.eclipse.swt.widgets.Shell;
077import org.eclipse.swt.widgets.TabFolder;
078import org.eclipse.swt.widgets.TabItem;
079import org.eclipse.swt.widgets.Table;
080import org.eclipse.swt.widgets.TableColumn;
081import org.eclipse.swt.widgets.TableItem;
082import org.eclipse.swt.widgets.Text;
083// import hdf.view.dialog.NewCompoundAttributeDialog;
084
085/**
086 * DefaultBaseMetaDataView is a default implementation of the MetaDataView which
087 * is used to show data properties of an object. Data properties include
088 * attributes and general object information such as the object type, data type
089 * and data space.
090 *
091 * This base class is responsible for displaying an object's general information
092 * and attributes, since these are not object-specific. Subclasses of this class
093 * are responsible for displaying any extra object-specific content by
094 * overriding the addObjectSpecificContent() method.
095 *
096 * @author Jordan T. Henderson
097 * @version 1.0 4/20/2018
098 */
099public abstract class DefaultBaseMetaDataView implements MetaDataView {
100
101    private static final Logger log = LoggerFactory.getLogger(DefaultBaseMetaDataView.class);
102
103    /** The default display */
104    protected final Display display = Display.getDefault();
105
106    /** The view manger reference */
107    protected final DataViewManager viewManager;
108
109    private final Composite parent;
110
111    /** The metadata container */
112    protected final TabFolder contentTabFolder;
113
114    /** The attribute metadata pane */
115    protected final Composite attributeInfoPane;
116
117    /** The general metadata pane */
118    protected final Composite generalObjectInfoPane;
119
120    /** The current font */
121    protected Font curFont;
122
123    /** The HDF data object */
124    protected HObject dataObject;
125
126    /* The table to hold the list of attributes attached to the HDF object */
127    private Table attrTable;
128
129    private Label attrNumberLabel;
130
131    private List<?> attrList;
132
133    private int numAttributes;
134
135    /** The HDF data object is hdf5 type */
136    protected boolean isH5;
137    /** The HDF data object is hdf4 type */
138    protected boolean isH4;
139    /** The HDF data object is netcdf type */
140    protected boolean isN3;
141
142    private static final String[] attrTableColNames = {"Name", "Type", "Array Size", "Value[50](...)"};
143
144    private static final int ATTR_TAB_INDEX    = 0;
145    private static final int GENERAL_TAB_INDEX = 1;
146
147    /**
148     *The metadata view interface for displaying metadata information
149     *
150     * @param parentComposite
151     *        the parent visual object
152     * @param viewer
153     *        the viewer to use
154     * @param theObj
155     *        the object to display the metadata info
156     */
157    public DefaultBaseMetaDataView(Composite parentComposite, DataViewManager viewer, HObject theObj)
158    {
159        this.parent      = parentComposite;
160        this.viewManager = viewer;
161        this.dataObject  = theObj;
162
163        numAttributes = 0;
164
165        isH5 = dataObject.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5));
166        isH4 = dataObject.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4));
167        isN3 = dataObject.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_NC3));
168
169        try {
170            curFont =
171                new Font(display, ViewProperties.getFontType(), ViewProperties.getFontSize(), SWT.NORMAL);
172        }
173        catch (Exception ex) {
174            curFont = null;
175        }
176
177        /* Get the metadata information before adding GUI components */
178        try {
179            attrList = ((MetaDataContainer)dataObject).getMetadata();
180            if (attrList != null)
181                numAttributes = attrList.size();
182        }
183        catch (Exception ex) {
184            attrList = null;
185            log.debug("Error retrieving metadata of object '" + dataObject.getName() + "':", ex);
186        }
187        for (int i = 0; i < numAttributes; i++) {
188            Attribute attr = (Attribute)attrList.get(i);
189            Datatype atype = attr.getAttributeDatatype();
190            if (isH5 && atype.isRef()) {
191                H5ReferenceType rtype = (H5ReferenceType)atype;
192                try {
193                    List<H5ReferenceData> refdata = (List)rtype.getData();
194                    for (int r = 0; r < (int)rtype.getRefSize(); r++) {
195                        H5ReferenceData rf = refdata.get(r);
196                        log.trace("constructor: refdata {}", rf.ref_array);
197                    }
198                }
199                catch (Exception ex) {
200                    log.trace("Error retrieving H5ReferenceData of object ", ex);
201                }
202            }
203        }
204
205        log.trace("dataObject={} isN3={} isH4={} isH5={} numAttributes={}", dataObject, isN3, isH4, isH5,
206                  numAttributes);
207
208        contentTabFolder = new TabFolder(parent, SWT.NONE);
209        contentTabFolder.addSelectionListener(new SelectionAdapter() {
210            @Override
211            public void widgetSelected(SelectionEvent e)
212            {
213                switch (contentTabFolder.getSelectionIndex()) {
214                case ATTR_TAB_INDEX:
215                    parent.setData("MetaDataView.LastTabIndex", ATTR_TAB_INDEX);
216                    break;
217                case GENERAL_TAB_INDEX:
218                default:
219                    parent.setData("MetaDataView.LastTabIndex", GENERAL_TAB_INDEX);
220                    break;
221                }
222            }
223        });
224
225        attributeInfoPane = createAttributeInfoPane(contentTabFolder, dataObject);
226        if (attributeInfoPane != null) {
227            TabItem attributeInfoItem = new TabItem(contentTabFolder, SWT.None, ATTR_TAB_INDEX);
228            attributeInfoItem.setText("Object Attribute Info");
229            attributeInfoItem.setControl(attributeInfoPane);
230        }
231
232        generalObjectInfoPane = createGeneralObjectInfoPane(contentTabFolder, dataObject);
233        if (generalObjectInfoPane != null) {
234            TabItem generalInfoItem = new TabItem(contentTabFolder, SWT.None, GENERAL_TAB_INDEX);
235            generalInfoItem.setText("General Object Info");
236            generalInfoItem.setControl(generalObjectInfoPane);
237        }
238
239        /* Add any extra information depending on the object type */
240        try {
241            addObjectSpecificContent();
242        }
243        catch (UnsupportedOperationException ex) {
244        }
245
246        if (parent instanceof ScrolledComposite)
247            ((ScrolledComposite)parent).setContent(contentTabFolder);
248
249        /*
250         * If the MetaDataView.LastTabIndex key data exists in the parent
251         * composite, retrieve its value to determine which remembered
252         * tab to select.
253         */
254        Object lastTabObject = parent.getData("MetaDataView.LastTabIndex");
255        if (lastTabObject != null) {
256            contentTabFolder.setSelection((int)lastTabObject);
257        }
258    }
259
260    /**
261     * Additional metadata to display
262     */
263    protected abstract void addObjectSpecificContent();
264
265    private Composite createAttributeInfoPane(Composite parent, final HObject dataObject)
266    {
267        if (parent == null || dataObject == null)
268            return null;
269
270        org.eclipse.swt.widgets.Group attributeInfoGroup = null;
271
272        attributeInfoGroup = new org.eclipse.swt.widgets.Group(parent, SWT.NONE);
273        attributeInfoGroup.setFont(curFont);
274        attributeInfoGroup.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW));
275        attributeInfoGroup.setLayout(new GridLayout(3, false));
276        attributeInfoGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
277
278        if (isH5) {
279            StringBuilder objCreationStr = new StringBuilder("Creation Order NOT Tracked");
280            long ocplID                  = -1;
281            long objid                   = -1;
282            int creationOrder            = 0;
283            try {
284                objid = dataObject.open();
285                if (objid >= 0) {
286                    if (dataObject instanceof Group) {
287                        ocplID = H5.H5Gget_create_plist(objid);
288                    }
289                    else if (dataObject instanceof Dataset) {
290                        ocplID = H5.H5Dget_create_plist(objid);
291                    }
292                    if (ocplID >= 0) {
293                        creationOrder = H5.H5Pget_attr_creation_order(ocplID);
294                        log.trace("createAttributeInfoPane(): creationOrder={}", creationOrder);
295                        if ((creationOrder & HDF5Constants.H5P_CRT_ORDER_TRACKED) > 0) {
296                            objCreationStr.setLength(0);
297                            objCreationStr.append("Creation Order Tracked");
298                            if ((creationOrder & HDF5Constants.H5P_CRT_ORDER_INDEXED) > 0)
299                                objCreationStr.append(" and Indexed");
300                        }
301                    }
302                }
303            }
304            finally {
305                H5.H5Pclose(ocplID);
306                dataObject.close(objid);
307            }
308
309            /* Creation order section */
310            Label label;
311            label = new Label(attributeInfoGroup, SWT.LEFT);
312            label.setFont(curFont);
313            label.setText("Attribute Creation Order: ");
314            label.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, false, false));
315
316            Text text;
317            text = new Text(attributeInfoGroup, SWT.SINGLE | SWT.BORDER);
318            text.setEditable(false);
319            text.setFont(curFont);
320            text.setText(objCreationStr.toString());
321            text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));
322        }
323
324        log.trace("createAttributeInfoPane(): numAttributes={}", numAttributes);
325
326        attrNumberLabel = new Label(attributeInfoGroup, SWT.RIGHT);
327        attrNumberLabel.setFont(curFont);
328        attrNumberLabel.setText("Number of attributes = 0");
329        attrNumberLabel.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, false, false));
330
331        Button addButton = new Button(attributeInfoGroup, SWT.PUSH);
332        addButton.setFont(curFont);
333        addButton.setText("Add Attribute");
334        addButton.setEnabled(!(dataObject.getFileFormat().isReadOnly()));
335        addButton.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
336        addButton.addSelectionListener(new SelectionAdapter() {
337            @Override
338            public void widgetSelected(SelectionEvent e)
339            {
340                addAttribute(dataObject);
341            }
342        });
343
344        /* Deleting attributes is not supported by HDF4 */
345        Button delButton = new Button(attributeInfoGroup, SWT.PUSH);
346        delButton.setFont(curFont);
347        delButton.setText("Delete Attribute");
348        delButton.setEnabled(isH5 && !(dataObject.getFileFormat().isReadOnly()));
349        delButton.setLayoutData(new GridData(SWT.END, SWT.FILL, false, false));
350        delButton.addSelectionListener(new SelectionAdapter() {
351            @Override
352            public void widgetSelected(SelectionEvent e)
353            {
354                deleteAttribute(dataObject);
355            }
356        });
357
358        attrTable =
359            new Table(attributeInfoGroup, SWT.FULL_SELECTION | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
360        attrTable.setLinesVisible(true);
361        attrTable.setHeaderVisible(true);
362        attrTable.setFont(curFont);
363        attrTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1));
364
365        Menu attrPopupMenu = createAttributePopupMenu(attrTable);
366        attrTable.setMenu(attrPopupMenu);
367
368        /*
369         * Add a double-click listener for editing attribute values in a separate
370         * TableView
371         */
372        attrTable.addListener(SWT.MouseDoubleClick, new Listener() {
373            @Override
374            public void handleEvent(Event arg0)
375            {
376                int selectionIndex = attrTable.getSelectionIndex();
377                if (selectionIndex < 0) {
378                    Tools.showError(Display.getDefault().getShells()[0], "Select", "No Attribute selected");
379                    return;
380                }
381
382                final TableItem item = attrTable.getItem(selectionIndex);
383
384                viewManager.getTreeView().setDefaultDisplayMode(true);
385
386                try {
387                    Display.getDefault().syncExec(new Runnable() {
388                        @Override
389                        public void run()
390                        {
391                            try {
392                                HObject selectedObject = (HObject)item.getData();
393                                if ((selectedObject instanceof Dataset) &&
394                                    !((Dataset)selectedObject).isNULL()) {
395                                    viewManager.getTreeView().showDataContent(selectedObject);
396                                }
397                                else {
398                                    Tools.showInformation(
399                                        Display.getDefault().getShells()[0], "Open",
400                                        "No data to display in an object with a NULL dataspace.");
401                                }
402                            }
403                            catch (Exception ex) {
404                                log.debug("Attribute showDataContent failure: ", ex);
405                            }
406                        }
407                    });
408                }
409                catch (Exception e) {
410                    log.debug("Attribute showDataContent loading manually interrupted");
411                }
412            }
413        });
414
415        /*
416         * Add a right-click listener for showing a menu that has options for renaming
417         * an attribute, editing an attribute, or deleting an attribute
418         */
419        attrTable.addListener(SWT.MenuDetect, new Listener() {
420            @Override
421            public void handleEvent(Event arg0)
422            {
423                int index = attrTable.getSelectionIndex();
424                if (index < 0)
425                    return;
426
427                attrTable.getMenu().setVisible(true);
428            }
429        });
430
431        for (int i = 0; i < attrTableColNames.length; i++) {
432            TableColumn column = new TableColumn(attrTable, SWT.NONE);
433            column.setText(attrTableColNames[i]);
434            column.setMoveable(false);
435
436            /*
437             * Make sure all columns show even when the object in question has no attributes
438             */
439            if (i == attrTableColNames.length - 1)
440                column.setWidth(200);
441            else
442                column.setWidth(50);
443        }
444
445        if (attrList != null) {
446            attrNumberLabel.setText("Number of attributes = " + numAttributes);
447
448            Attribute attr = null;
449            for (int i = 0; i < numAttributes; i++) {
450                attr = (Attribute)attrList.get(i);
451
452                log.trace("createAttributeInfoPane(): attr[{}] is {} of type {}", i, attr.getAttributeName(),
453                          attr.getAttributeDatatype().getDescription());
454
455                addAttributeTableItem(attrTable, attr);
456            }
457        }
458
459        for (int i = 0; i < attrTableColNames.length; i++) {
460            attrTable.getColumn(i).pack();
461        }
462
463        // Prevent attributes with many values, such as array types, from making
464        // the window too wide
465        attrTable.getColumn(3).setWidth(200);
466
467        return attributeInfoGroup;
468    }
469
470    private Composite createGeneralObjectInfoPane(Composite parent, final HObject dataObject)
471    {
472        if (parent == null || dataObject == null)
473            return null;
474
475        FileFormat theFile = dataObject.getFileFormat();
476        boolean isRoot     = ((dataObject instanceof Group) && ((Group)dataObject).isRoot());
477        String objTypeStr  = "Unknown";
478        Label label;
479        Text text;
480
481        /* Add an SWT Group to encompass all of the GUI components */
482        org.eclipse.swt.widgets.Group generalInfoGroup = new org.eclipse.swt.widgets.Group(parent, SWT.NONE);
483        generalInfoGroup.setFont(curFont);
484        generalInfoGroup.setLayout(new GridLayout(2, false));
485        generalInfoGroup.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW));
486
487        /* Object name section */
488        label = new Label(generalInfoGroup, SWT.LEFT);
489        label.setFont(curFont);
490        label.setText("Name: ");
491
492        text = new Text(generalInfoGroup, SWT.SINGLE | SWT.BORDER);
493        text.setEditable(false);
494        text.setFont(curFont);
495        text.setText(dataObject.getName());
496        text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
497
498        /* Object Path section */
499        label = new Label(generalInfoGroup, SWT.LEFT);
500        label.setFont(curFont);
501        label.setText("Path: ");
502
503        text = new Text(generalInfoGroup, SWT.SINGLE | SWT.BORDER);
504        text.setEditable(false);
505        text.setFont(curFont);
506        text.setText(
507            dataObject.getPath() == null
508                ? "/"
509                : dataObject.getPath()); /* TODO: temporary workaround until Object Library is fixed */
510        text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
511
512        /* Object Type section */
513        label = new Label(generalInfoGroup, SWT.LEFT);
514        label.setFont(curFont);
515        label.setText("Type: ");
516
517        if (isH5) {
518            if (dataObject instanceof Group) {
519                objTypeStr = "HDF5 Group";
520            }
521            else if (dataObject instanceof ScalarDS) {
522                objTypeStr = "HDF5 Dataset";
523            }
524            else if (dataObject instanceof CompoundDS) {
525                objTypeStr = "HDF5 Dataset";
526            }
527            else if (dataObject instanceof Datatype) {
528                objTypeStr = "HDF5 Named Datatype";
529            }
530            else {
531                log.debug("createGeneralObjectInfoPane(): unknown HDF5 dataObject");
532            }
533        }
534        else if (isH4) {
535            if (dataObject instanceof Group) {
536                objTypeStr = "HDF4 Group";
537            }
538            else if (dataObject instanceof ScalarDS) {
539                ScalarDS ds = (ScalarDS)dataObject;
540                if (ds.isImage()) {
541                    objTypeStr = "HDF4 Raster Image";
542                }
543                else {
544                    objTypeStr = "HDF4 SDS";
545                }
546            }
547            else if (dataObject instanceof CompoundDS) {
548                objTypeStr = "HDF4 Vdata";
549            }
550            else {
551                log.debug("createGeneralObjectInfoPane(): unknown HDF4 dataObject");
552            }
553        }
554        else if (isN3) {
555            if (dataObject instanceof Group) {
556                objTypeStr = "netCDF3 Group";
557            }
558            else if (dataObject instanceof ScalarDS) {
559                objTypeStr = "netCDF3 Dataset";
560            }
561            else {
562                log.debug("createGeneralObjectInfoPane(): unknown netCDF3 dataObject");
563            }
564        }
565        else {
566            if (dataObject instanceof Group) {
567                objTypeStr = "Group";
568            }
569            else if (dataObject instanceof ScalarDS) {
570                objTypeStr = "Dataset";
571            }
572            else if (dataObject instanceof CompoundDS) {
573                objTypeStr = "Dataset";
574            }
575            else {
576                log.debug("createGeneralObjectInfoPane(): unknown dataObject");
577            }
578        }
579
580        text = new Text(generalInfoGroup, SWT.SINGLE | SWT.BORDER);
581        text.setEditable(false);
582        text.setFont(curFont);
583        text.setText(objTypeStr);
584        text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
585
586        /* Object ID section */
587
588        // bug #926 to remove the OID, put it back on Nov. 20, 2008, --PC
589        String oidStr = null;
590        long[] oID    = dataObject.getOID();
591        if (oID != null) {
592            oidStr = String.valueOf(oID[0]);
593            if (isH4)
594                oidStr += ", " + oID[1];
595
596            if (isH5) {
597                label = new Label(generalInfoGroup, SWT.LEFT);
598                label.setFont(curFont);
599                label.setText("Object Ref:       ");
600            }
601            else {
602                label = new Label(generalInfoGroup, SWT.LEFT);
603                label.setFont(curFont);
604                label.setText("Tag, Ref:        ");
605            }
606
607            text = new Text(generalInfoGroup, SWT.SINGLE | SWT.BORDER);
608            text.setEditable(false);
609            text.setFont(curFont);
610            text.setText(oidStr);
611            text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
612        }
613
614        /*
615         * If this is the root group, add some special extra info, such as the Library
616         * Version bounds set for the file.
617         */
618        if (isRoot) {
619            /* Get the file's size */
620            long fileSize = 0;
621            try {
622                fileSize = (new File(dataObject.getFile())).length();
623            }
624            catch (Exception ex) {
625                fileSize = -1;
626            }
627            fileSize /= 1024;
628
629            /* Retrieve the number of subgroups and datasets in the root group */
630            HObject root         = theFile.getRootObject();
631            HObject theObj       = null;
632            Iterator<HObject> it = ((Group)root).depthFirstMemberList().iterator();
633            int groupCount       = 0;
634            int datasetCount     = 0;
635
636            while (it.hasNext()) {
637                theObj = it.next();
638
639                if (theObj instanceof Group)
640                    groupCount++;
641                else
642                    datasetCount++;
643            }
644
645            /* Append all of the file's information to the general object info pane */
646            String fileInfo = "";
647
648            fileInfo = "size=" + fileSize + "K,  groups=" + groupCount + ",  datasets=" + datasetCount;
649
650            /* File name section */
651            label = new Label(generalInfoGroup, SWT.LEFT);
652            label.setFont(curFont);
653            label.setText("File Name: ");
654
655            text = new Text(generalInfoGroup, SWT.SINGLE | SWT.BORDER);
656            text.setEditable(false);
657            text.setFont(curFont);
658            text.setText(dataObject.getFileFormat().getName());
659            text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
660
661            /* File Path section */
662            label = new Label(generalInfoGroup, SWT.LEFT);
663            label.setFont(curFont);
664            label.setText("File Path: ");
665
666            text = new Text(generalInfoGroup, SWT.SINGLE | SWT.BORDER);
667            text.setEditable(false);
668            text.setFont(curFont);
669            text.setText((new File(dataObject.getFile())).getParent());
670            text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
671
672            label = new Label(generalInfoGroup, SWT.LEFT);
673            label.setFont(curFont);
674            label.setText("File Type: ");
675
676            if (isH5)
677                objTypeStr = "HDF5,  " + fileInfo;
678            else if (isH4)
679                objTypeStr = "HDF4,  " + fileInfo;
680            else if (isN3)
681                objTypeStr = "netCDF3,  " + fileInfo;
682            else
683                objTypeStr = fileInfo;
684
685            text = new Text(generalInfoGroup, SWT.SINGLE | SWT.BORDER);
686            text.setEditable(false);
687            text.setFont(curFont);
688            text.setText(objTypeStr);
689            text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
690
691            if (isH5) {
692                log.trace("createGeneralObjectInfoPane(): get Library Version bounds info");
693                String libversion = "";
694                try {
695                    libversion = dataObject.getFileFormat().getLibBoundsDescription();
696                }
697                catch (Exception ex) {
698                    log.debug("Get Library Bounds Description failure: ", ex);
699                }
700
701                if (libversion.length() > 0) {
702                    label = new Label(generalInfoGroup, SWT.LEFT);
703                    label.setFont(curFont);
704                    label.setText("Library version bounds: ");
705
706                    text = new Text(generalInfoGroup, SWT.SINGLE | SWT.BORDER);
707                    text.setEditable(false);
708                    text.setFont(curFont);
709                    text.setText(libversion);
710                    text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
711                }
712
713                Button userBlockButton = new Button(generalInfoGroup, SWT.PUSH);
714                userBlockButton.setText("Show User Block");
715                userBlockButton.addSelectionListener(new SelectionAdapter() {
716                    @Override
717                    public void widgetSelected(SelectionEvent e)
718                    {
719                        new UserBlockDialog(display.getShells()[0], SWT.NONE, dataObject).open();
720                    }
721                });
722            }
723        }
724
725        /* Add a dummy label to take up some vertical space between sections */
726        label = new Label(generalInfoGroup, SWT.LEFT);
727        label.setText("");
728        label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));
729
730        return generalInfoGroup;
731    }
732
733    @Override
734    public HObject getDataObject()
735    {
736        return dataObject;
737    }
738
739    private Menu createAttributePopupMenu(final Table table)
740    {
741        final Menu menu = new Menu(table);
742        MenuItem item;
743
744        item = new MenuItem(menu, SWT.PUSH);
745        item.setText("Rename Attribute");
746        item.addSelectionListener(new SelectionAdapter() {
747            @Override
748            public void widgetSelected(SelectionEvent e)
749            {
750                int selectionIndex = table.getSelectionIndex();
751                if (selectionIndex < 0) {
752                    Tools.showError(Display.getDefault().getShells()[0], "Select", "No Attribute selected");
753                    return;
754                }
755
756                HObject itemObj = (HObject)table.getItem(selectionIndex).getData();
757                String result =
758                    new InputDialog(Display.getDefault().getShells()[0],
759                                    Display.getDefault().getShells()[0].getText() + " - Rename Attribute",
760                                    "New Attribute Name", itemObj.getName())
761                        .open();
762
763                if ((result == null) || ((result = result.trim()) == null) || (result.length() < 1)) {
764                    return;
765                }
766
767                Attribute attr = (Attribute)attrTable.getItem(selectionIndex).getData();
768                renameAttribute(attr, result);
769            }
770        });
771
772        item = new MenuItem(menu, SWT.PUSH);
773        item.setText("View/Edit Attribute Value");
774        item.addSelectionListener(new SelectionAdapter() {
775            @Override
776            public void widgetSelected(SelectionEvent e)
777            {
778                int selectionIndex = attrTable.getSelectionIndex();
779                if (selectionIndex < 0) {
780                    Tools.showError(Display.getDefault().getShells()[0], "Select", "No Attribute selected");
781                    return;
782                }
783
784                final TableItem item = attrTable.getItem(selectionIndex);
785
786                viewManager.getTreeView().setDefaultDisplayMode(true);
787
788                try {
789                    Display.getDefault().syncExec(new Runnable() {
790                        @Override
791                        public void run()
792                        {
793                            try {
794                                HObject selectedObject = (HObject)item.getData();
795                                if ((selectedObject instanceof Dataset) &&
796                                    !((Dataset)selectedObject).isNULL()) {
797                                    viewManager.getTreeView().showDataContent(selectedObject);
798                                }
799                                else {
800                                    Tools.showInformation(
801                                        Display.getDefault().getShells()[0], "Open",
802                                        "No data to display in an object with a NULL dataspace.");
803                                }
804                            }
805                            catch (Exception ex) {
806                                log.debug("Attribute showDataContent failure: ", ex);
807                            }
808                        }
809                    });
810                }
811                catch (Exception ex) {
812                    log.debug("Attribute showDataContent loading manually interrupted");
813                }
814            }
815        });
816
817        item = new MenuItem(menu, SWT.PUSH);
818        item.setText("Delete Attribute");
819        item.addSelectionListener(new SelectionAdapter() {
820            @Override
821            public void widgetSelected(SelectionEvent e)
822            {
823                int selectionIndex = attrTable.getSelectionIndex();
824                if (selectionIndex < 0) {
825                    Tools.showError(Display.getDefault().getShells()[0], "Select", "No Attribute selected");
826                    return;
827                }
828
829                deleteAttribute(dataObject);
830            }
831        });
832
833        menu.addMenuListener(new MenuAdapter() {
834            @Override
835            public void menuShown(MenuEvent e)
836            {
837                /* 'Rename Attribute' MenuItem */
838                menu.getItem(0).setEnabled(!dataObject.getFileFormat().isReadOnly() && !isH4);
839
840                /* 'Delete Attribute' MenuItem */
841                menu.getItem(2).setEnabled(!dataObject.getFileFormat().isReadOnly() && !isH4);
842            }
843        });
844
845        return menu;
846    }
847
848    @Override
849    public Attribute addAttribute(HObject obj)
850    {
851        if (obj == null)
852            return null;
853
854        HObject root   = obj.getFileFormat().getRootObject();
855        Attribute attr = null;
856        if (isH5) {
857            NewScalarAttributeDialog dialog = new NewScalarAttributeDialog(
858                display.getShells()[0], obj, ((Group)root).breadthFirstMemberList());
859            dialog.open();
860            attr = dialog.getAttribute();
861        }
862        else {
863            NewStringAttributeDialog dialog = new NewStringAttributeDialog(
864                display.getShells()[0], obj, ((Group)root).breadthFirstMemberList());
865            dialog.open();
866            attr = dialog.getAttribute();
867        }
868
869        if (attr == null) {
870            log.debug("addAttribute(): attr is null");
871            return null;
872        }
873
874        addAttributeTableItem(attrTable, attr);
875
876        numAttributes++;
877        attrNumberLabel.setText("Number of attributes = " + numAttributes);
878
879        if (viewManager.getTreeView() instanceof DefaultTreeView)
880            ((DefaultTreeView)viewManager.getTreeView()).updateItemIcon(obj);
881
882        return attr;
883    }
884
885    @Override
886    public Attribute deleteAttribute(HObject obj)
887    {
888        if (obj == null)
889            return null;
890
891        int idx = attrTable.getSelectionIndex();
892        if (idx < 0) {
893            log.debug("deleteAttribute(): no attribute is selected");
894            Tools.showError(display.getShells()[0], "Delete", "No attribute is selected.");
895            return null;
896        }
897
898        int answer = SWT.NO;
899        if (Tools.showConfirm(display.getShells()[0], "Delete",
900                              "Do you want to delete the selected attribute?"))
901            answer = SWT.YES;
902        if (answer == SWT.NO) {
903            log.trace("deleteAttribute(): attribute deletion cancelled");
904            return null;
905        }
906
907        if (attrList == null) {
908            log.debug("deleteAttribute(): Attribute list was null; can't delete an attribute from it");
909            return null;
910        }
911
912        Attribute attr = (Attribute)attrList.get(idx);
913
914        log.trace("deleteAttribute(): Attribute selected for deletion: {}", attr.getAttributeName());
915
916        try {
917            ((MetaDataContainer)obj).removeMetadata(attr);
918        }
919        catch (Exception ex) {
920            log.debug("deleteAttribute(): attribute deletion failed for object '{}': ", obj.getName(), ex);
921        }
922
923        attrTable.remove(idx);
924        numAttributes--;
925
926        attrNumberLabel.setText("Number of attributes = " + numAttributes);
927
928        if (viewManager.getTreeView() instanceof DefaultTreeView)
929            ((DefaultTreeView)viewManager.getTreeView()).updateItemIcon(obj);
930
931        return attr;
932    }
933
934    private void renameAttribute(Attribute attr, String newName)
935    {
936        if ((attr == null) || (newName == null) || (newName = newName.trim()) == null ||
937            (newName.length() < 1)) {
938            log.debug("renameAttribute(): Attribute is null or Attribute's new name is null");
939            return;
940        }
941
942        String attrName = attr.getAttributeName();
943
944        log.trace("renameAttribute(): oldName={} newName={}", attrName, newName);
945
946        if (isH5) {
947            try {
948                dataObject.getFileFormat().renameAttribute(dataObject, attrName, newName);
949            }
950            catch (Exception ex) {
951                log.debug("renameAttribute(): renaming failure:", ex);
952                Tools.showError(display.getShells()[0], "Delete", ex.getMessage());
953            }
954
955            /* Update the attribute table */
956            int selectionIndex = attrTable.getSelectionIndex();
957            if (selectionIndex < 0) {
958                Tools.showError(Display.getDefault().getShells()[0], "Delete", "No Attribute selected");
959                return;
960            }
961
962            attrTable.getItem(selectionIndex).setText(0, newName);
963        }
964        else {
965            log.debug("renameAttribute(): renaming attributes is only allowed for HDF5 files");
966        }
967
968        if (dataObject instanceof MetaDataContainer) {
969            try {
970                ((MetaDataContainer)dataObject).updateMetadata(attr);
971            }
972            catch (Exception ex) {
973                log.debug("renameAttribute(): updateMetadata() failure:", ex);
974                Tools.showError(display.getShells()[0], "Delete", ex.getMessage());
975            }
976        }
977    }
978
979    /**
980     * Update an attribute's value. Currently can only update a single data point.
981     *
982     * @param attr
983     *            the selected attribute.
984     * @param newValue
985     *            the string of the new value.
986     */
987    private void updateAttributeValue(Attribute attr, String newValue)
988    {
989        if ((attr == null) || (newValue == null) || (newValue = newValue.trim()) == null ||
990            (newValue.length() < 1)) {
991            log.debug("updateAttributeValue(): Attribute is null or Attribute's new value is null");
992            return;
993        }
994
995        String attrName = attr.getAttributeName();
996        Object data;
997
998        log.trace("updateAttributeValue(): changing value of attribute '{}'", attrName);
999
1000        try {
1001            data = attr.getAttributeData();
1002        }
1003        catch (Exception ex) {
1004            log.debug("updateAttributeValue(): getData() failure:", ex);
1005            return;
1006        }
1007
1008        if (data == null) {
1009            log.debug("updateAttributeValue(): attribute's data was null");
1010            return;
1011        }
1012
1013        int arrayLength    = Array.getLength(data);
1014        StringTokenizer st = new StringTokenizer(newValue, ",");
1015        if (st.countTokens() < arrayLength) {
1016            log.debug("updateAttributeValue(): More data values needed: {}", newValue);
1017            Tools.showError(display.getShells()[0], "Update", "More data values needed: " + newValue);
1018            return;
1019        }
1020
1021        char cNT     = ' ';
1022        String cName = data.getClass().getName();
1023        int cIndex   = cName.lastIndexOf('[');
1024        if (cIndex >= 0) {
1025            cNT = cName.charAt(cIndex + 1);
1026        }
1027        boolean isUnsigned = attr.getAttributeDatatype().isUnsigned();
1028
1029        log.trace("updateAttributeValue(): array_length={} cName={} NT={} isUnsigned={}", arrayLength, cName,
1030                  cNT, isUnsigned);
1031
1032        double d        = 0;
1033        String theToken = null;
1034        long max        = 0;
1035        long min        = 0;
1036        for (int i = 0; i < arrayLength; i++) {
1037            max = min = 0;
1038            theToken  = st.nextToken().trim();
1039            try {
1040                if (!(Array.get(data, i) instanceof String)) {
1041                    d = Double.parseDouble(theToken);
1042                }
1043            }
1044            catch (NumberFormatException ex) {
1045                log.debug("updateAttributeValue(): NumberFormatException: ", ex);
1046                Tools.showError(display.getShells()[0], "Update", ex.getMessage());
1047                return;
1048            }
1049
1050            if (isUnsigned && (d < 0)) {
1051                log.debug("updateAttributeValue(): Negative value for unsigned integer: {}", theToken);
1052                Tools.showError(display.getShells()[0], "Update",
1053                                "Negative value for unsigned integer: " + theToken);
1054                return;
1055            }
1056
1057            switch (cNT) {
1058            case 'B': {
1059                if (isUnsigned) {
1060                    min = 0;
1061                    max = 255;
1062                }
1063                else {
1064                    min = Byte.MIN_VALUE;
1065                    max = Byte.MAX_VALUE;
1066                }
1067
1068                if ((d > max) || (d < min)) {
1069                    Tools.showError(display.getShells()[0], "Update",
1070                                    "Data is out of range[" + min + ", " + max + "]: " + theToken);
1071                }
1072                else {
1073                    Array.setByte(data, i, (byte)d);
1074                }
1075                break;
1076            }
1077            case 'S': {
1078                if (isUnsigned) {
1079                    min = 0;
1080                    max = 65535;
1081                }
1082                else {
1083                    min = Short.MIN_VALUE;
1084                    max = Short.MAX_VALUE;
1085                }
1086
1087                if ((d > max) || (d < min)) {
1088                    Tools.showError(display.getShells()[0], "Update",
1089                                    "Data is out of range[" + min + ", " + max + "]: " + theToken);
1090                }
1091                else {
1092                    Array.setShort(data, i, (short)d);
1093                }
1094                break;
1095            }
1096            case 'I': {
1097                if (isUnsigned) {
1098                    min = 0;
1099                    max = 4294967295L;
1100                }
1101                else {
1102                    min = Integer.MIN_VALUE;
1103                    max = Integer.MAX_VALUE;
1104                }
1105
1106                if ((d > max) || (d < min)) {
1107                    Tools.showError(display.getShells()[0], "Update",
1108                                    "Data is out of range[" + min + ", " + max + "]: " + theToken);
1109                }
1110                else {
1111                    Array.setInt(data, i, (int)d);
1112                }
1113                break;
1114            }
1115            case 'J':
1116                long lvalue = 0;
1117                if (isUnsigned) {
1118                    if (theToken != null) {
1119                        String theValue = theToken;
1120                        BigInteger maxJ = new BigInteger("18446744073709551615");
1121                        BigInteger big  = new BigInteger(theValue);
1122                        if ((big.compareTo(maxJ) > 0) || (big.compareTo(BigInteger.ZERO) < 0)) {
1123                            Tools.showError(display.getShells()[0], "Update",
1124                                            "Data is out of range[" + min + ", " + max + "]: " + theToken);
1125                        }
1126                        lvalue = big.longValue();
1127                        log.trace("updateAttributeValue(): big.longValue={}", lvalue);
1128                        Array.setLong(data, i, lvalue);
1129                    }
1130                    else
1131                        Array.set(data, i, theToken);
1132                }
1133                else {
1134                    min = Long.MIN_VALUE;
1135                    max = Long.MAX_VALUE;
1136                    if ((d > max) || (d < min)) {
1137                        Tools.showError(display.getShells()[0], "Update",
1138                                        "Data is out of range[" + min + ", " + max + "]: " + theToken);
1139                    }
1140                    lvalue = (long)d;
1141                    log.trace("updateAttributeValue(): longValue={}", lvalue);
1142                    Array.setLong(data, i, lvalue);
1143                }
1144                break;
1145            case 'F':
1146                Array.setFloat(data, i, (float)d);
1147                break;
1148            case 'D':
1149                Array.setDouble(data, i, d);
1150                break;
1151            default:
1152                Array.set(data, i, theToken);
1153                break;
1154            }
1155        }
1156
1157        try {
1158            dataObject.getFileFormat().writeAttribute(dataObject, attr, true);
1159        }
1160        catch (Exception ex) {
1161            log.debug("updateAttributeValue(): writeAttribute failure: ", ex);
1162            Tools.showError(display.getShells()[0], "Update", ex.getMessage());
1163            return;
1164        }
1165
1166        /* Update the attribute table */
1167        int selectionIndex = attrTable.getSelectionIndex();
1168        if (selectionIndex < 0) {
1169            Tools.showError(Display.getDefault().getShells()[0], "Update", "No Attribute selected");
1170            return;
1171        }
1172
1173        attrTable.getItem(selectionIndex).setText(3, attr.toAttributeString(", "));
1174
1175        if (dataObject instanceof MetaDataContainer) {
1176            try {
1177                ((MetaDataContainer)dataObject).updateMetadata(attr);
1178            }
1179            catch (Exception ex) {
1180                log.debug("updateAttributeValue(): updateMetadata() failure:", ex);
1181                Tools.showError(display.getShells()[0], "Update", ex.getMessage());
1182            }
1183        }
1184    }
1185
1186    private void addAttributeTableItem(Table table, Attribute attr)
1187    {
1188        if (table == null || attr == null) {
1189            log.debug("addAttributeTableItem(): table or attribute is null");
1190            return;
1191        }
1192
1193        String attrName        = attr.getAttributeName();
1194        String attrType        = attr.getAttributeDatatype().getDescription();
1195        StringBuilder attrSize = new StringBuilder();
1196        String attrValue       = attr.toAttributeString(", ", 50);
1197        String[] rowData       = new String[attrTableColNames.length];
1198
1199        if (attrName == null)
1200            attrName = "null";
1201        if (attrType == null)
1202            attrType = "null";
1203        if (attrValue == null)
1204            attrValue = "null";
1205
1206        TableItem item = new TableItem(attrTable, SWT.NONE);
1207        item.setFont(curFont);
1208        item.setData(attr);
1209
1210        if (attr.getProperty("field") != null) {
1211            rowData[0] = attrName + " {Field: " + attr.getProperty("field") + "}";
1212        }
1213        else {
1214            rowData[0] = attrName;
1215        }
1216
1217        if (attr.isAttributeNULL()) {
1218            attrSize.append("NULL");
1219        }
1220        else if (attr.isAttributeScalar()) {
1221            attrSize.append("Scalar");
1222        }
1223        else {
1224            long[] dims = attr.getAttributeDims();
1225            attrSize.append(String.valueOf(dims[0]));
1226            for (int j = 1; j < dims.length; j++) {
1227                attrSize.append(" x ").append(dims[j]);
1228            }
1229        }
1230
1231        rowData[1] = attrType;
1232        rowData[2] = attrSize.toString();
1233        if (attr.isAttributeNULL())
1234            rowData[3] = "NULL";
1235        else
1236            rowData[3] = attrValue;
1237
1238        item.setText(rowData);
1239    }
1240
1241    /**
1242     * Updates the current font.
1243     *
1244     * @param font the new font
1245     */
1246    public void updateFont(Font font)
1247    {
1248        if (curFont != null)
1249            curFont.dispose();
1250
1251        log.trace("updateFont():");
1252        curFont = font;
1253
1254        attributeInfoPane.setFont(font);
1255        attributeInfoPane.pack();
1256        attributeInfoPane.requestLayout();
1257
1258        generalObjectInfoPane.setFont(font);
1259        generalObjectInfoPane.pack();
1260        generalObjectInfoPane.requestLayout();
1261    }
1262
1263    private class UserBlockDialog extends Dialog {
1264        private Shell shell;
1265
1266        private final HObject obj;
1267
1268        private byte[] userBlock;
1269
1270        private final String[] displayChoices = {"Text", "Binary", "Octal", "Hexadecimal", "Decimal"};
1271
1272        private Button jamButton;
1273        private Text userBlockArea;
1274
1275        public UserBlockDialog(Shell parent, int style, HObject obj)
1276        {
1277            super(parent, style);
1278
1279            this.obj = obj;
1280
1281            userBlock = Tools.getHDF5UserBlock(obj.getFile());
1282        }
1283
1284        public void open()
1285        {
1286            Shell openParent = getParent();
1287            shell            = new Shell(openParent, SWT.DIALOG_TRIM | SWT.RESIZE);
1288            shell.setFont(curFont);
1289            shell.setText("User Block - " + obj);
1290            shell.setLayout(new GridLayout(5, false));
1291
1292            Label label = new Label(shell, SWT.RIGHT);
1293            label.setFont(curFont);
1294            label.setText("Display As: ");
1295
1296            Combo userBlockDisplayChoice = new Combo(shell, SWT.SINGLE | SWT.READ_ONLY);
1297            userBlockDisplayChoice.setFont(curFont);
1298            userBlockDisplayChoice.setItems(displayChoices);
1299            userBlockDisplayChoice.select(0);
1300            userBlockDisplayChoice.addSelectionListener(new SelectionAdapter() {
1301                @Override
1302                public void widgetSelected(SelectionEvent e)
1303                {
1304                    Combo source = (Combo)e.widget;
1305                    int type     = 0;
1306
1307                    String typeName = source.getItem(source.getSelectionIndex());
1308
1309                    jamButton.setEnabled(false);
1310                    userBlockArea.setEditable(false);
1311
1312                    if (typeName.equalsIgnoreCase("Text")) {
1313                        type = 0;
1314                        jamButton.setEnabled(true);
1315                        userBlockArea.setEditable(true);
1316                    }
1317                    else if (typeName.equalsIgnoreCase("Binary")) {
1318                        type = 2;
1319                    }
1320                    else if (typeName.equalsIgnoreCase("Octal")) {
1321                        type = 8;
1322                    }
1323                    else if (typeName.equalsIgnoreCase("Hexadecimal")) {
1324                        type = 16;
1325                    }
1326                    else if (typeName.equalsIgnoreCase("Decimal")) {
1327                        type = 10;
1328                    }
1329
1330                    showUserBlockAs(type);
1331                }
1332            });
1333
1334            Label dummyLabel = new Label(shell, SWT.RIGHT);
1335            dummyLabel.setFont(curFont);
1336            dummyLabel.setText("");
1337            dummyLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
1338
1339            Label sizeLabel = new Label(shell, SWT.RIGHT);
1340            sizeLabel.setFont(curFont);
1341            sizeLabel.setText("Header Size (Bytes): 0");
1342
1343            jamButton = new Button(shell, SWT.PUSH);
1344            jamButton.setFont(curFont);
1345            jamButton.setText("Save User Block");
1346            jamButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
1347            jamButton.addSelectionListener(new SelectionAdapter() {
1348                @Override
1349                public void widgetSelected(SelectionEvent e)
1350                {
1351                    writeUserBlock();
1352                }
1353            });
1354
1355            ScrolledComposite userBlockScroller = new ScrolledComposite(shell, SWT.V_SCROLL | SWT.BORDER);
1356            userBlockScroller.setExpandHorizontal(true);
1357            userBlockScroller.setExpandVertical(true);
1358            userBlockScroller.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 5, 1));
1359
1360            userBlockArea = new Text(userBlockScroller, SWT.MULTI | SWT.WRAP);
1361            userBlockArea.setEditable(true);
1362            userBlockArea.setFont(curFont);
1363            userBlockScroller.setContent(userBlockArea);
1364
1365            Button closeButton = new Button(shell, SWT.CENTER);
1366            closeButton.setFont(curFont);
1367            closeButton.setText(" &Close ");
1368            closeButton.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, true, false, 5, 1));
1369            closeButton.addSelectionListener(new SelectionAdapter() {
1370                @Override
1371                public void widgetSelected(SelectionEvent e)
1372                {
1373                    shell.dispose();
1374                }
1375            });
1376
1377            if (userBlock != null) {
1378                int headSize = showUserBlockAs(0);
1379                sizeLabel.setText("Header Size (Bytes): " + headSize);
1380            }
1381            else {
1382                userBlockDisplayChoice.setEnabled(false);
1383            }
1384
1385            shell.pack();
1386
1387            Rectangle parentBounds = openParent.getBounds();
1388
1389            Point shellSize = new Point((int)(0.5 * parentBounds.width), (int)(0.5 * parentBounds.height));
1390            shell.setSize(shellSize);
1391
1392            shell.setLocation((parentBounds.x + (parentBounds.width / 2)) - (shellSize.x / 2),
1393                              (parentBounds.y + (parentBounds.height / 2)) - (shellSize.y / 2));
1394
1395            shell.open();
1396
1397            Display openDisplay = openParent.getDisplay();
1398            while (!shell.isDisposed()) {
1399                if (!openDisplay.readAndDispatch())
1400                    openDisplay.sleep();
1401            }
1402        }
1403
1404        private int showUserBlockAs(int radix)
1405        {
1406            if (userBlock == null)
1407                return 0;
1408
1409            int headerSize = 0;
1410
1411            String userBlockInfo = null;
1412            if ((radix == 2) || (radix == 8) || (radix == 16) || (radix == 10)) {
1413                StringBuilder sb = new StringBuilder();
1414                for (headerSize = 0; headerSize < userBlock.length; headerSize++) {
1415                    int intValue = userBlock[headerSize];
1416                    if (intValue < 0) {
1417                        intValue += 256;
1418                    }
1419                    else if (intValue == 0) {
1420                        break; // null end
1421                    }
1422
1423                    sb.append(Integer.toString(intValue, radix)).append(" ");
1424                }
1425                userBlockInfo = sb.toString();
1426            }
1427            else {
1428                userBlockInfo = new String(userBlock).trim();
1429                if (userBlockInfo != null) {
1430                    headerSize = userBlockInfo.length();
1431                }
1432            }
1433
1434            userBlockArea.setText(userBlockInfo);
1435
1436            return headerSize;
1437        }
1438
1439        private void writeUserBlock()
1440        {
1441            if (!obj.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5))) {
1442                return;
1443            }
1444
1445            int blkSize0 = 0;
1446            if (userBlock != null) {
1447                blkSize0 = userBlock.length;
1448                // The super block space is allocated by offset 0, 512, 1024, 2048, etc
1449                if (blkSize0 > 0) {
1450                    int offset = 512;
1451                    while (offset < blkSize0) {
1452                        offset *= 2;
1453                    }
1454                    blkSize0 = offset;
1455                }
1456            }
1457
1458            int blkSize1        = 0;
1459            String userBlockStr = userBlockArea.getText();
1460            if (userBlockStr == null) {
1461                if (blkSize0 <= 0) {
1462                    return; // nothing to write
1463                }
1464                else {
1465                    userBlockStr = " "; // want to wipe out old userblock content
1466                }
1467            }
1468            byte[] buf = null;
1469            buf        = userBlockStr.getBytes();
1470
1471            blkSize1 = buf.length;
1472            if (blkSize1 <= blkSize0) {
1473                java.io.RandomAccessFile raf = null;
1474                try {
1475                    raf = new java.io.RandomAccessFile(obj.getFile(), "rw");
1476                }
1477                catch (Exception ex) {
1478                    Tools.showError(shell, "Save", "Can't open output file: " + obj.getFile());
1479                    return;
1480                }
1481
1482                try {
1483                    raf.seek(0);
1484                    raf.write(buf, 0, buf.length);
1485                    raf.seek(buf.length);
1486                    if (blkSize0 > buf.length) {
1487                        byte[] padBuf = new byte[blkSize0 - buf.length];
1488                        raf.write(padBuf, 0, padBuf.length);
1489                    }
1490                }
1491                catch (Exception ex) {
1492                    log.debug("raf write:", ex);
1493                }
1494                try {
1495                    raf.close();
1496                }
1497                catch (Exception ex) {
1498                    log.debug("raf close:", ex);
1499                }
1500
1501                Tools.showInformation(shell, "Save", "Saving user block is successful.");
1502            }
1503            else {
1504                // must rewrite the whole file
1505                MessageDialog confirm = new MessageDialog(
1506                    shell, "Save", null,
1507                    "The user block to write is " + blkSize1 + " (bytes),\n"
1508                        + "which is larger than the user block space in file " + blkSize0 + " (bytes).\n"
1509                        + "To expand the user block, the file must be rewritten.\n\n"
1510                        + "Do you want to replace the current file? Click "
1511                        + "\n\"Yes\" to replace the current file,"
1512                        + "\n\"No\" to save to a different file, "
1513                        + "\n\"Cancel\" to quit without saving the change.\n\n ",
1514                    MessageDialog.QUESTION_WITH_CANCEL, new String[] {"Yes", "No", "Cancel"}, 0);
1515                int op = confirm.open();
1516
1517                if (op == 2)
1518                    return;
1519
1520                String fin = obj.getFile();
1521
1522                String fout = fin + "~copy.h5";
1523                if (fin.endsWith(".h5")) {
1524                    fout = fin.substring(0, fin.length() - 3) + "~copy.h5";
1525                }
1526                else if (fin.endsWith(".hdf5")) {
1527                    fout = fin.substring(0, fin.length() - 5) + "~copy.h5";
1528                }
1529
1530                File outFile = null;
1531
1532                if (op == 1) {
1533                    FileDialog fChooser = new FileDialog(shell, SWT.SAVE);
1534                    fChooser.setFileName(fout);
1535
1536                    DefaultFileFilter filter = DefaultFileFilter.getFileFilterHDF5();
1537                    fChooser.setFilterExtensions(new String[] {"*", filter.getExtensions()});
1538                    fChooser.setFilterNames(new String[] {"All Files", filter.getDescription()});
1539                    fChooser.setFilterIndex(1);
1540
1541                    if (fChooser.open() == null)
1542                        return;
1543
1544                    File chosenFile = new File(fChooser.getFileName());
1545
1546                    outFile = chosenFile;
1547                    fout    = outFile.getAbsolutePath();
1548                }
1549                else {
1550                    outFile = new File(fout);
1551                }
1552
1553                if (!outFile.exists()) {
1554                    try {
1555                        if (!outFile.createNewFile())
1556                            log.debug("Error creating file {}", fout);
1557                    }
1558                    catch (Exception ex) {
1559                        Tools.showError(shell, "Save", "Failed to write user block into file.");
1560                        return;
1561                    }
1562                }
1563
1564                // close the file
1565                TreeView view = viewManager.getTreeView();
1566
1567                try {
1568                    view.closeFile(view.getSelectedFile());
1569                }
1570                catch (Exception ex) {
1571                    log.debug("Error closing file {}", fin);
1572                }
1573
1574                if (Tools.setHDF5UserBlock(fin, fout, buf)) {
1575                    if (op == 1) {
1576                        fin = fout; // open the new file
1577                    }
1578                    else {
1579                        File oldFile   = new File(fin);
1580                        boolean status = oldFile.delete();
1581                        if (status) {
1582                            if (!outFile.renameTo(oldFile))
1583                                log.debug("Error renaming file {}", fout);
1584                        }
1585                        else {
1586                            Tools.showError(
1587                                shell, "Save",
1588                                "Cannot replace the current file.\nPlease save to a different file.");
1589                            outFile.delete();
1590                        }
1591                    }
1592                }
1593                else {
1594                    Tools.showError(shell, "Save", "Failed to write user block into file.");
1595                    outFile.delete();
1596                }
1597
1598                // reopen the file
1599                shell.dispose();
1600
1601                try {
1602                    int access_mode = FileFormat.WRITE;
1603                    if (ViewProperties.isReadOnly())
1604                        access_mode = FileFormat.READ;
1605                    else if (ViewProperties.isReadSWMR())
1606                        access_mode = FileFormat.READ | FileFormat.MULTIREAD;
1607                    view.openFile(fin, access_mode);
1608                }
1609                catch (Exception ex) {
1610                    log.debug("Error opening file {}", fin);
1611                }
1612            }
1613        }
1614    }
1615}