HDF5 Browser

this is what I have stumbled upon when I was exploring into my automation problems regarding the HDF5 files

#pragma rtGlobals=1     // Use modern global access method.

// The routines in this file implement the HDF5 Browser control panel.

// Version 1.00, May 9, 2005: Initial release.

// Version 1.01, May 10, 2005: Revamped preferences code.

// Version 1.02, May 14, 2005: Revamped preferences code again using
//  the LoadPackagePrefs and SavePackagePrefs operations. This requires
// Igor Pro 5.04B07 or later.

// Version 1.03, January 15, 2010: Now uses HDF5ListGroup/R=2 to deal with
// files that use hard or soft links to include the same group more than one time.
// This version of this procedure file requires HDF5XOP 1.09 or later.

// Version 1.04, May 21, 2015: Defines HDF5 library constants such as
// H5T_ORDER_LE and H5T_ORDER_BE. This version of this procedure file requires
// HDF5XOP 1.14 or earlier.

#pragma version = 1.04

#include <WaveSelectorWidget>

#pragma moduleName=HDF5Browser

Menu "Load Waves"
    "New HDF5 Browser", /Q, CreateNewHDF5Browser()
End

Constant H5S_MAX_RANK = 32

// Constants for HDF5DataInfo datatype_order field and HDF5DatatypeInfo order field
Constant H5T_ORDER_ERROR = -1       // Error
Constant H5T_ORDER_LE = 0           // Little endian
Constant H5T_ORDER_BE = 1           // Big endian
Constant H5T_ORDER_VAX = 2          // VAX mixed endian
// The following is valid only with HDF5 library 1.8.5 or before because this value was changed in 1.8.6.
#if (Exists("HDF5LibraryInfo") == 3)
    // This is just to create a compile error if you are running with a newer HDF5 XOP
    // and therefore should be using a new "HDF5 Browser.ipf" file.
    #define *** You are using a newer HDF5 XOP which uses a newer HDF5 library. You need a newer "HDF5 Browser.ipf" file - version 1.20 or later. ***
#endif
Constant H5T_ORDER_NONE = 3     // No particular order (strings, bits,..)

// Constants for HDF5DataInfo datatype_sign field and HDF5DatatypeInfo sign field
Constant H5T_SGN_ERROR = -1         // Error
Constant H5T_SGN_NONE = 0           // Unsigned
Constant H5T_SGN_2 = 1              // Two's complement

// Constants for HDF5DataInfo datatype_class field and HDF5DatatypeInfo type_class field
Constant H5T_NO_CLASS = -1          // Error
Constant H5T_INTEGER = 0            // Integer types
Constant H5T_FLOAT = 1          // Floating-point types
Constant H5T_TIME = 2               // Date and time types
Constant H5T_STRING = 3         // Character string types
Constant H5T_BITFIELD = 4           // Bit field types
Constant H5T_OPAQUE = 5         // Opaque types
Constant H5T_COMPOUND = 6           // Compound types
Constant H5T_REFERENCE = 7          // Reference types
Constant H5T_ENUM = 8           // Enumeration types
Constant H5T_VLEN = 9               // Variable-Length types
Constant H5T_ARRAY = 10         // Array types

// Constants for HDF5DataInfo dataspace_type field
Constant H5S_NO_CLASS = -1          // Error
Constant H5S_SCALAR = 0         // Scalar variable
Constant H5S_SIMPLE = 1         // Constant simple data space
Constant H5S_NULL = 2               // Constant null data space

// Constants for HDF5DatatypeInfo strpad field
Constant H5T_STR_ERROR = -1         // Error
Constant H5T_STR_NULLTERM = 0       // Null terminate like in C
Constant H5T_STR_NULLPAD = 1        // Ppad with nulls
Constant H5T_STR_SPACEPAD = 2       // Pad with spaces like in Fortran

// Constants for HDF5DatatypeInfo cset field
Constant H5T_CSET_ERROR = -1            // Error
Constant H5T_CSET_ASCII = 0         // US ASCII
Constant H5T_CSET_UTF8  = 1         // UTF-8 Unicode encoding

Structure HDF5BrowserData
    SVAR browserName

    Wave/T groupsList
    Wave/T groupAttributesList
    Wave/T datasetsList
    Wave/T datasetAttributesList
   
    Wave/T groupFullPaths
    SVAR groupPath

    SVAR hyperSelectionWavePath
   
    NVAR fileID
    SVAR fileName
    SVAR path
    SVAR fullPath
    NVAR readOnly
   
    SVAR datasetInfo            // Panel readout info for currently-selected dataset
EndStructure

static Function CreateHDF5BrowserGlobals(newBrowserName)
    String newBrowserName
   
    // Create globals that apply to this browser window.
   
    String savedDataFolder = SetBrowserDataFolder(newBrowserName)
   
    String/G browserName = newBrowserName
   
    Make/O/T/N=0 groupsList
    Make/O/T/N=0 groupAttributesList
    Make/O/T/N=0 datasetsList
    Make/O/T/N=0 datasetAttributesList

    Make/O/T/N=0 groupFullPaths                                 // For each group, contains the corresponding full path to that group.
    String/G groupPath = "/"            // Path to currently selected group

    String/G hyperSelectionWavePath     // Path to hyperselection wave, if any.
   
    Variable/G fileID = 0                           // 0 means no file is open for this browser.

    String/G fileName = ""
    String/G path = ""
    String/G fullPath = ""
    Variable/G readOnly = 1
   
    String/G datasetInfo
   
    SetDataFolder savedDataFolder
End

Function/S SetBrowserDataFolder(browserName)        // Pass "" for browserName to set to master browser folder.
    String browserName
   
    String savedDataFolder = GetDataFolder(1)
   
    NewDataFolder/O/S root:Packages
    NewDataFolder/O/S HDF5Browser
   
    if (strlen(browserName) > 0)
        NewDataFolder/O/S $browserName
    endif
   
    return savedDataFolder
End

static Function SetHDF5BrowserData(browserName, bd)
    String browserName
    STRUCT HDF5BrowserData &bd
   
    String savedDataFolder = SetBrowserDataFolder(browserName)
   
    // These statements set the structure fields to reference the corresponding waves and variables in the data folder.
   
    SVAR bd.browserName
   
    Wave/T bd.groupsList
    Wave/T bd.groupAttributesList
    Wave/T bd.datasetsList
    Wave/T bd.datasetAttributesList
   
    Wave/T bd.groupFullPaths
    SVAR bd.groupPath

    SVAR bd.hyperSelectionWavePath
   
    NVAR bd.fileID
    SVAR bd.fileName
    SVAR bd.path
    SVAR bd.fullPath
    NVAR bd.readOnly
   
    SVAR bd.datasetInfo
   
    SetDataFolder savedDataFolder
End

static Function CountSlashes(item)
    String item
   
    Variable slashes = 0
    Variable pos = 0
   
    do
        pos = strsearch(item, "/", pos)
        if (pos < 0)
            break
        endif
        slashes += 1
        pos += 1
    while (1)

    return slashes
End

Function/S GetGroupHierarchy(fileID, startPath, level, mode)
    Variable fileID
    String startPath
    Variable level
    Variable mode       // 0 to just get group names; 1 to get full path to each group.

    String result = ""

    String indent = ""          // Used only for mode 0.
    Variable i, j
   
    // This gives full hierarchy with full paths
    // The /R=2 flag requires HDF5 1.09 or later. If you get an error here, you need to activate a more recent version of HDF5XOP.
    HDF5ListGroup /F /R=2 /TYPE=1 fileID, startPath // REQUIRES HDF5XOP 1.09 or later
    result = S_HDF5ListGroup
   
    if (mode == 0)              // Want just names, not full paths
        result = ""
        Variable numItems = ItemsInList(S_HDF5ListGroup)
        for(i=0; i<numItems; i+=1)
            String item = StringFromList(i, S_HDF5ListGroup)
            level = CountSlashes(item)
            indent = ""
            for(j=0; j<level; j+=1)
                indent += "    "
            endfor
            item = ParseFilePath(0, item, "/", 1, 0)            // Get last element
            item = indent + item                // Prepend indentation
            result += item + ";"       
        endfor 
    endif
   
    return result
End

static Function/S GetFileHierarchy(bd, mode)
    STRUCT HDF5BrowserData &bd
    Variable mode       // 0 to just get group names; 1 to get full path to each group.
   
    if (bd.fileID == 0)                         // No file is open?
        return ""
    endif
   
    String hierarchy, rootName
    if (mode == 1)
        rootName = "/"      // For purposes of storing full path, use true root name
    else
        rootName = "root"       // For display purposes, use "root" as root name
    endif
    hierarchy = rootName + ";" + GetGroupHierarchy(bd.fileID, "/", 1, mode)
   
    return hierarchy
End

static Function ResetListSelections(bd, resetGroupsList, resetGroupAttributesList, resetDatasetsList, resetDatasetAttributesList)
    STRUCT HDF5BrowserData &bd
    Variable resetGroupsList, resetGroupAttributesList, resetDatasetsList, resetDatasetAttributesList

    if (resetGroupsList)
        ListBox GroupsList, win=$bd.browserName, selRow=0
    endif
    if (resetGroupAttributesList)
        ListBox GroupAttributesList, win=$bd.browserName, selRow=0
    endif
    if (resetDatasetsList)
        ListBox DatasetsList, win=$bd.browserName, selRow=0
    endif
    if (resetDatasetAttributesList)
        ListBox DatasetAttributesList, win=$bd.browserName, selRow=0
    endif
End

Function/S HDF5GetObjectFullPath(groupPath, objectName)
    String groupPath                    // Path to parent group
    String objectName               // Name of dataset or group in parent group
   
    String fullPath
   
    if (CmpStr(groupPath, "/") == 0)
        fullPath = "/" + objectName
    else
        fullPath = groupPath + "/" + objectName
    endif
   
    return fullPath
End

Function/S GetTextPreviewString(tw)
    Wave/T tw                   // tw has already been flattened to 1D.
   
    String preview, temp
    Variable len
   
    Variable row, numRows, totalLength
   
    totalLength = 0

    numRows = numpnts(tw)
    if (numRows == 0)
        return ""
    endif
   
    preview = ""
    row = 0
    do
        temp = tw[row]
        temp = ReplaceString("\r", temp, "<CR>", 1) // Convert CR to "\r"
        temp = ReplaceString("\n", temp, "<LF>", 1) // Convert LF to "\n"

        len = strlen(temp)
        if (len > 128)
            if (numRows == 1)
                sprintf preview, "\"%s\" . . . (%d characters total)", temp[0,127], len
            else
                preview += " . . . <Strings too long to display here>"
            endif
           
            row = numRows               // To prevent extra . . .
            break
        endif

        preview += "\"" + temp + "\""
        row += 1
        totalLength += len

        if (row >= numRows)
            break
        endif
        if (totalLength >= 256)             // Enough is enough
            break
        endif

        preview += ", "
    while(1)

    if (row < numRows)
        preview += " . . ."
    endif

    return preview
End

Function/S GetNumericPreviewString(w)
    Wave w                      // w has already been flattened to 1D.

    String preview, temp
   
    Variable row, numRows, totalLength
   
    totalLength = 0

    numRows = numpnts(w)
    if (numRows == 0)
        return ""
    endif
   
    preview = ""
    row = 0
    do
        sprintf temp, "%g", w[row]
        preview += temp
        row += 1
        totalLength += strlen(temp)

        if (row >= numRows)
            break
        endif
        if (totalLength >= 256)                 // Enough is enough
            break
        endif

        preview += ", "
    while(1)

    if (row < numRows)
        preview += " . . ."
    endif
   
    return preview
End

Function/S GetPreviewString(locationID, objectType, di, fullPath, attributeName)
    Variable locationID
    Variable objectType                     // 1 = group, 2 = dataset
    STRUCT HDF5DataInfo &di
    String fullPath                     // Full path to group or dataset
    String attributeName                // "" if this is a dataset, not an attribute
   
    String value = "<Can't display here>"
    String temp
   
    Variable rank = di.ndims
    Variable dim, numElements
   
    if (rank == 0)
        numElements = 1
    else
        numElements = di.dims[0]
        for(dim=1; dim<rank; dim+=1)
            numElements *= di.dims[dim]    
        endfor
    endif

    strswitch(di.datatype_class_str)
        case "H5T_INTEGER":
        case "H5T_FLOAT":
        case "H5T_ENUM":
        case "H5T_OPAQUE":
        case "H5T_BITFIELD":
            if (numElements > 100)
                value = "<Too big to display here>"// It would take too long to load.
                break
            endif
           
            HDF5LoadData /A=attributeName /N=tempNumericAttributeWave /O /Q /TYPE=(objectType) /VAR=0 /Z locationID, fullPath
            if (V_flag != 0)
                value = "ERROR!"
            else
                Wave tempNumericAttributeWave
               
                if (rank > 1)
                    // So we can treat multi-dimensional wave as one big row.
                    Redimension/N=(numElements)/E=1 tempNumericAttributeWave
                endif
               
                value = GetNumericPreviewString(tempNumericAttributeWave)
            endif
            KillWaves/Z tempNumericAttributeWave
            break              

        case "H5T_REFERENCE":
            if (numElements > 10)
                value = "<Too big to display here>" // It would take too long to load.
                break
            endif
       
            HDF5LoadData /A=attributeName /N=tempTextAttributeWave /O /Q /TYPE=(objectType) /VAR=0 /Z locationID, fullPath
            if (V_flag != 0)
                value = "ERROR!"
            else
                if (rank > 1)
                    // So we can treat multi-dimensional wave as one big row.
                    Redimension/N=(numElements)/E=1 tempTextAttributeWave
                endif
               
                // Remove the prefix (e.g., "D:" for a dataset, which is there for
                // programmatic use but would confuse in a preview.
                Wave/T references = tempTextAttributeWave           // Created by HDF5LoadData
                String tmp
                Variable npnts=numpnts(references), len
                Variable i
                for(i=0; i<npnts; i+=1)
                    tmp = references[i]
                    len = strlen(tmp)
                    references[i] = tmp[2,len]
                endfor
               
                value = GetTextPreviewString(references)
            endif
            KillWaves/Z tempTextAttributeWave
            break              

        case "H5T_STRING":
            if (numElements > 100)
                value = "<Too big to display here>" // It would take too long to load.
                break
            endif
       
            HDF5LoadData /A=attributeName /N=tempTextAttributeWave /O /Q /TYPE=(objectType) /VAR=0 /Z locationID, fullPath
            if (V_flag != 0)
                value = "ERROR!"
            else
                if (rank > 1)
                    // So we can treat multi-dimensional wave as one big row.
                    Redimension/N=(numElements)/E=1 tempTextAttributeWave
                endif
                value = GetTextPreviewString(tempTextAttributeWave)
            endif
            KillWaves/Z tempTextAttributeWave
            break              
           
        case "H5T_TIME":
        case "H5T_COMPOUND":
        case "H5T_VLEN":
        case "H5T_ARRAY":
            value = "<Can't display this type here>"
            break
    endswitch
   
    return value
End

static Function FillDatasetsList(bd)
    STRUCT HDF5BrowserData &bd

    HDF5ListGroup /TYPE=2 bd.fileID, bd.groupPath
    Variable numItemsInList = ItemsInList(S_HDF5ListGroup)
   
    if (numItemsInList == 0)
        Redimension/N=0 bd.datasetsList
    else
        Redimension/N=(numItemsInList, 5) bd.datasetsList
        SetDimLabel 1, 0, Dataset, bd.datasetsList
        SetDimLabel 1, 1, Rank, bd.datasetsList
        SetDimLabel 1, 2, 'Dim Sizes', bd.datasetsList
        SetDimLabel 1, 3, Type, bd.datasetsList
        SetDimLabel 1, 4, Value, bd.datasetsList
        bd.datasetsList[][0] = StringFromList(p, S_HDF5ListGroup)
   
        String dataset
        Variable i, numDatasets
        numDatasets = ItemsInList(S_HDF5ListGroup)
        for(i=0; i<numDatasets; i+=1)
            dataset = StringFromList(i, S_HDF5ListGroup)
            String fullPath = HDF5GetObjectFullPath(bd.groupPath, dataset)
            STRUCT HDF5DataInfo di
            InitHDF5DataInfo(di)            // Set input fields.
            HDF5DatasetInfo(bd.fileID, fullPath, 0, di)
           
            Variable rank = di.ndims
            bd.datasetsList[i][1] = num2istr(rank)
           
            String dimsStr=""
            Variable dim
            for(dim=0; dim<rank; dim+=1)
                dimsStr += num2istr(di.dims[dim]) + ";"
            endfor
            bd.datasetsList[i][2] = dimsStr

            bd.datasetsList[i][3] = di.datatype_str

            String preview = GetPreviewString(bd.fileID, 2, di, fullPath, "")
            bd.datasetsList[i][4] = preview
        endfor
    endif
End
       
static Function FillGroupAttributesList(bd)
    STRUCT HDF5BrowserData &bd

    Variable numAttributes = 0
    String groupPath = bd.groupPath
    if (strlen(groupPath) > 0)
        HDF5ListAttributes/TYPE=1 bd.fileID, groupPath
        numAttributes = ItemsInList(S_HDF5ListAttributes)
    endif
   
    if (numAttributes == 0)
        Redimension/N=0 bd.groupAttributesList
    else
        Redimension/N=(numAttributes, 5) bd.groupAttributesList
        SetDimLabel 1, 0, Attribute,bd.groupAttributesList
        SetDimLabel 1, 1, Rank,bd.groupAttributesList
        SetDimLabel 1, 2, 'Dim Sizes',bd.groupAttributesList
        SetDimLabel 1, 3, Type,bd.groupAttributesList
        SetDimLabel 1, 4, Value,bd.groupAttributesList
        bd.groupAttributesList[][0] = StringFromList(p, S_HDF5ListAttributes)
   
        String attribute
        Variable i
        for(i=0; i<numAttributes; i+=1)
            String attributeName
            attributeName = StringFromList(i, S_HDF5ListAttributes)
            attribute = attributeName

            STRUCT HDF5DataInfo di
            InitHDF5DataInfo(di)            // Set input fields.
            HDF5AttributeInfo(bd.fileID, groupPath, 1, attribute, 0, di)
           
            Variable rank = di.ndims
            bd.groupAttributesList[i][1] = num2istr(rank)
           
            String dimsStr=""
            Variable dim
            for(dim=0; dim<rank; dim+=1)
                dimsStr += num2istr(di.dims[dim]) + ";"
            endfor
            bd.groupAttributesList[i][2] = dimsStr

            bd.groupAttributesList[i][3] = di.datatype_str

            String preview = GetPreviewString(bd.fileID, 1, di, groupPath, attributeName)
            bd.groupAttributesList[i][4] = preview
        endfor
    endif
End
       
static Function FillDatasetAttributesList(bd)
    STRUCT HDF5BrowserData &bd

    Variable numAttributes = 0
    String datasetPath = SelectedDatasetPath(bd)
    if (strlen(datasetPath) > 0)
        HDF5ListAttributes/TYPE=2 bd.fileID, datasetPath
        numAttributes = ItemsInList(S_HDF5ListAttributes)
    endif
   
    if (numAttributes == 0)
        Redimension/N=0 bd.datasetAttributesList
    else
        Redimension/N=(numAttributes, 5) bd.datasetAttributesList
        SetDimLabel 1, 0, Attribute,bd.datasetAttributesList
        SetDimLabel 1, 1, Rank,bd.datasetAttributesList
        SetDimLabel 1, 2, 'Dim Sizes',bd.datasetAttributesList
        SetDimLabel 1, 3, Type,bd.datasetAttributesList
        SetDimLabel 1, 4, Value,bd.datasetAttributesList
        bd.datasetAttributesList[][0] = StringFromList(p, S_HDF5ListAttributes)
   
        String attribute
        Variable i
        for(i=0; i<numAttributes; i+=1)
            String attributeName
            attributeName = StringFromList(i, S_HDF5ListAttributes)
            attribute = attributeName

            STRUCT HDF5DataInfo di
            InitHDF5DataInfo(di)            // Set input fields.
            HDF5AttributeInfo(bd.fileID, datasetPath, 2, attribute, 0, di)
           
            Variable rank = di.ndims
            bd.datasetAttributesList[i][1] = num2istr(rank)
           
            String dimsStr=""
            Variable dim
            for(dim=0; dim<rank; dim+=1)
                dimsStr += num2istr(di.dims[dim]) + ";"
            endfor
            bd.datasetAttributesList[i][2] = dimsStr

            bd.datasetAttributesList[i][3] = di.datatype_str

            String preview = GetPreviewString(bd.fileID, 2, di, datasetPath, attributeName)
            bd.datasetAttributesList[i][4] = preview
        endfor
    endif
End
   
static Function FillLists(bd)
    STRUCT HDF5BrowserData &bd

    if (bd.fileID == 0)                         // No file is open?
        Redimension/N=(0) bd.groupsList
        Redimension/N=(0) bd.groupAttributesList
        Redimension/N=(0) bd.datasetsList
        Redimension/N=(0) bd.datasetAttributesList
        return -1
    endif
   
    Variable numItemsInList
    String hierarchy
   
    // Show entire hierarchy in Groups list.
    hierarchy = GetFileHierarchy(bd, 0)
    numItemsInList = ItemsInList(hierarchy)
    Redimension/N=(numItemsInList) bd.groupsList
    bd.groupsList = StringFromList(p, hierarchy)
   
    // The groupFullPaths wave stores the full path to each group
    hierarchy = GetFileHierarchy(bd, 1)
    numItemsInList = ItemsInList(hierarchy)
    Redimension/N=(numItemsInList) bd.groupFullPaths
    bd.groupFullPaths = StringFromList(p, hierarchy)
   
    // Show datasets in current group in Datasets list.
    FillDatasetsList(bd)
   
    // Show attributes of currently-selected group.
    FillGroupAttributesList(bd)
   
    // Show attributes of currently-selected dataset.
    FillDatasetAttributesList(bd)
End

Function/S SelectedGroupName(bd)
    STRUCT HDF5BrowserData &bd
   
    if (numpnts(bd.groupsList) == 0)
        return ""
    endif

    ControlInfo/W=$bd.browserName GroupsList
    Variable selRow = V_value
    String groupName = bd.groupsList[selRow]

    // Group names may have leading spaces at this point. The spaces are used to create
    // indentation in the list to show the hierarchy. We must remove the leading spaces.
    sscanf groupName, " %s", groupName

    if (strlen(groupName) > 0)
        return groupName
    endif

    return ""
End

Function/S SelectedGroupPath(bd)
    STRUCT HDF5BrowserData &bd
   
    String groupPath = bd.groupPath

    return groupPath
End

Function/S SelectedDatasetName(bd)
    STRUCT HDF5BrowserData &bd
   
    if (numpnts(bd.datasetsList) == 0)
        return ""
    endif

    ControlInfo/W=$bd.browserName DatasetsList
    Variable selRow = V_value
    String datasetName = bd.datasetsList[selRow][0]
    if (strlen(datasetName) > 0)
        return datasetName
    endif

    return ""
End

Function/S SelectedDatasetPath(bd)
    STRUCT HDF5BrowserData &bd

    String datasetName = SelectedDatasetName(bd)
    if (strlen(datasetName) == 0)
        return ""                       // Nothing is selected
    endif
   
    String datasetPath = bd.groupPath
    if (CmpStr(datasetPath[strlen(datasetPath)-1],"/") != 0)
        datasetPath += "/"
    endif
    datasetPath += datasetName

    return datasetPath
End

Function/S SelectedAttributeName(bd, isGroupAttribute)
    STRUCT HDF5BrowserData &bd
    Variable isGroupAttribute

    String controlName
    if (isGroupAttribute)
        controlName = "GroupAttributesList"
        Wave/T list = bd.groupAttributesList
    else
        controlName = "DatasetAttributesList"
        Wave/T list = bd.datasetAttributesList
    endif
   
    if (numpnts(list) == 0)
        return ""
    endif

    ControlInfo/W=$bd.browserName $controlName
    Variable selRow = V_value
    String attributeName = list[selRow][0]
   
    if (strlen(attributeName) > 0)
        return attributeName
    endif

    return ""
End

Function/S SelectedAttributePath(bd, isGroupAttribute)
    STRUCT HDF5BrowserData &bd
    Variable isGroupAttribute

    String attributeName = SelectedAttributeName(bd, isGroupAttribute)
    if (strlen(attributeName) == 0)
        return ""                       // Nothing is selected
    endif
   
    String path
    if (isGroupAttribute)
        path = SelectedGroupPath(bd)
    else
        path = SelectedDatasetPath(bd)
    endif
   
    path += "/" + attributeName

    return path
End

StrConstant kLoadAllMembersString = "_Load_All_Members_"

static Function SetMembersPopupMenu(bd)
    STRUCT HDF5BrowserData &bd
   
    Variable hideMembers
    String memberList

    hideMembers = 1
    memberList = kLoadAllMembersString + ";"
   
    if (bd.fileID != 0)             // File is open for this browser?
        String fullPath
       
        fullPath = SelectedDatasetPath(bd)
        if (strlen(fullPath) > 0)
            STRUCT HDF5DataInfo di
            InitHDF5DataInfo(di)
            HDF5DatasetInfo(bd.fileID, fullPath, 0, di)
            if (CmpStr(di.datatype_class_str, "H5T_COMPOUND") == 0)
                hideMembers = 0
               
                ControlInfo /W=$bd.browserName Members
                Variable selectedItemNumber = V_Value
                STRUCT HDF5DatatypeInfo dti
                InitHDF5DatatypeInfo(dti)
                if (HDF5TypeInfo(bd.fileID, fullPath, "", "", 1, dti))
                    memberList += "HDF5TypeInfo Error!"
                else
                    memberList += dti.names
                    if (selectedItemNumber > dti.nmembers+1)    // +1 because of "_Load_All_Members_" item.
                        selectedItemNumber = 1      // Force menu selection to be in bounds.
                    endif
                endif
                PopupMenu Members, win=$bd.browserName, mode=selectedItemNumber
            endif
        endif
    endif
   
    PopupMenu Members, win=$bd.browserName, disable=hideMembers
   
    String cmd              // What a pain. Can't use local variable with PopupMenu value=
    sprintf cmd, "PopupMenu Members, win=%s, value=\"%s\"", bd.browserName, memberList
    Execute cmd
End

Function HDF5GetReadOnlySetting(browserName)
    String browserName
   
    Variable result
    ControlInfo /W=$browserName ReadOnly
    result = V_value
    return result
End

Function HDF5GetCompLoadInfo(bd, isCompound, compMode, memberName)
    STRUCT HDF5BrowserData &bd
    Variable &isCompound                    // Output: If non-zero, a compound dataset is selected.
    Variable &compMode                      // Output: Value suitable for passing to HDF5LoadData /COMP flag.
    String &memberName                      // Output: If "" load all members.
   
    isCompound = 0
    memberName = ""
   
    ControlInfo /W=$bd.browserName Members
    if (V_disable == 0)
        isCompound = 1
        memberName = S_value
        if (CmpStr(memberName, kLoadAllMembersString) == 0)
            memberName = ""
        endif
    endif

    compMode = isCompound && strlen(memberName)>0
End

Function HDF5GetTranspose2DSetting(browserName)
    String browserName
   
    Variable result
    ControlInfo /W=$browserName Transpose2DDatasets
    result = V_value
    return result
End

static Function HDF5GetLoadDatasetOptions(browserName, tableDisplayMode, graphDisplayMode)
    String browserName
    Variable &tableDisplayMode, &graphDisplayMode
   
    ControlInfo /W=$browserName DisplayInTable
    tableDisplayMode = V_value - 1                      // 0=no; 1=display; 2=append
   
    ControlInfo /W=$browserName DisplayInGraph
    graphDisplayMode = V_value - 1                      // 0=no; 1=display; 2=append
   
    return tableDisplayMode || graphDisplayMode
End

static Function MembersPopupProc(ctrlName,popNum,popStr) : PopupMenuControl
    String ctrlName
    Variable popNum
    String popStr
   
    String browserName = HDF5GetTopBrowserName()
   
    STRUCT HDF5BrowserData bd
    SetHDF5BrowserData(browserName, bd)
   
    HDF5DisplaySelectedDataset(bd)
End

static Function SetButtonStates(bd)
    STRUCT HDF5BrowserData &bd
   
    if (bd.fileID == 0)             // No file is open for this browser?
        Button CreateFile, win=$bd.browserName, disable=0       // Enable Create
        Button OpenFile, win=$bd.browserName, disable=0     // Enable Open
        Button CloseFile, win=$bd.browserName, disable=2        // Disable Close
        Button LoadGroup, win=$bd.browserName, disable=2        // Disable Load Group
        Button SaveDataFolder, win=$bd.browserName, disable=2   // Disable Save Data Folder
        Button LoadDataset, win=$bd.browserName, disable=2  // Disable Load Dataset
        Button SaveWaves, win=$bd.browserName, disable=2        // Disable Save Waves
    else
        Button CreateFile, win=$bd.browserName, disable=2       // Disable Create
        Button OpenFile, win=$bd.browserName, disable=2     // Disable Open
        Button CloseFile, win=$bd.browserName, disable=0        // Enable Close
       
        String groupName = SelectedGroupName(bd)
        Variable code = strlen(groupName) > 0 ? 0:2
        Button LoadGroup, win=$bd.browserName, disable=code // Enable Load Group
        code = bd.readOnly == 0 ? 0:2
        Button SaveDataFolder, win=$bd.browserName, disable=code    // Enable Save Data Folder
       
        String datasetName = SelectedDatasetName(bd)
        code = strlen(datasetName) > 0 ? 0:2
        Button LoadDataset, win=$bd.browserName, disable=code   // Enable Load Dataset
        code = bd.readOnly == 0 ? 0:2
        Button SaveWaves, win=$bd.browserName, disable=code     // Enable Save Waves
    endif
    SetMembersPopupMenu(bd)
    SetGraphButtonTitle(bd.browserName)
    SetTableButtonTitle(bd.browserName)
    SetDumpButtonTitle(bd.browserName)
    SetVariable HyperSelectionWave, win=$bd.browserName, value= bd.hyperSelectionWavePath
End

static Function DrawFilePath(bd)
    STRUCT HDF5BrowserData &bd
   
    // TitleBox FilePath, win=$bd.browserName, title=bd.fullPath            // This is limited to 63 characters.
    TitleBox FilePath, win=$bd.browserName, variable=bd.fullPath
End

static Function DrawGroupPath(bd)
    STRUCT HDF5BrowserData &bd
   
    TitleBox GroupPath, win=$bd.browserName, variable=bd.groupPath
End

static Function DrawDatasetInfo(bd)
    STRUCT HDF5BrowserData &bd
   
    TitleBox Dataset, win=$bd.browserName, variable=bd.datasetInfo
End

static Function UpdateAfterFileCreateOrOpen(isCreate, browserName, fileID, path, fileName)
    Variable isCreate
    String browserName
    Variable fileID
    String path, fileName
   
    STRUCT HDF5BrowserData bd
    SetHDF5BrowserData(browserName, bd)

    ResetListSelections(bd, 1, 1, 1, 1)

    bd.fileID = fileID
    bd.fileName = fileName
    bd.path = path
    bd.fullPath = path + fileName
    DrawFilePath(bd)
   
    bd.readOnly = isCreate ? 0 : HDF5GetReadOnlySetting(browserName)

    bd.groupPath = "/"
    DrawGroupPath(bd)

    SetButtonStates(bd)

    FillLists(bd)
   
    UpdateAfterGroupSelected(bd, "/")
   
    String datasetName = SelectedDatasetName(bd)
    if (strlen(datasetName) > 0)
        SelectDataset(bd, datasetName)
    endif
End

static Function CreateFileButtonProc(ctrlName) : ButtonControl
    String ctrlName
   
    String browserName = HDF5GetTopBrowserName()

    STRUCT HDF5BrowserData bd
    SetHDF5BrowserData(browserName, bd)
   
    Variable fileID
   
    HDF5CreateFile /I /O fileID  as ""
    if (V_flag == 0)        // Create OK?
        UpdateAfterFileCreateOrOpen(1, browserName, fileID, S_path, S_fileName)
    endif
End

static Function OpenFileButtonProc(ctrlName) : ButtonControl
    String ctrlName
   
    String browserName = HDF5GetTopBrowserName()
   
    Variable readOnly = HDF5GetReadOnlySetting(browserName)
   
    Variable locFileID
   
    if (readOnly)
        HDF5OpenFile/R locFileID as ""
    else
        HDF5OpenFile locFileID as ""
    endif
   
    if (V_flag == 0)                    // Open OK?
        UpdateAfterFileCreateOrOpen(0, browserName, locFileID, S_path, S_fileName)
    endif
End

// This detects if the file is no longer open, such as if you save the experiment, quit Igor and then reopen the experiment.
Function FileWasUnexpectedlyClosed(bd)
    STRUCT HDF5BrowserData &bd

    if (bd.fileID == 0)
        return 0                // File is closed but not unexpectedly.
    endif
   
    HDF5ListAttributes/Q /TYPE=1 /Z bd.fileID , "/"     // Try to list the attributes of the root of the file.
    if (V_flag != 0)
        return 1                                // Error: Assume file was closed.
    endif
   
    return 0
End

static Function FileWasClosed(bd)           // Does cleanup after a file is closed.
    STRUCT HDF5BrowserData &bd

    bd.fileID = 0
    Redimension/N=(0) bd.groupFullPaths
    bd.groupPath = ""
    bd.fileName = ""
    bd.path = ""
    bd.fullPath = ""
    bd.datasetInfo = ""

    DrawFilePath(bd)
    SetButtonStates(bd)
    FillLists(bd)
End

static Function CloseFileButtonProc(ctrlName) : ButtonControl
    String ctrlName
   
    String browserName = HDF5GetTopBrowserName()

    STRUCT HDF5BrowserData bd
    SetHDF5BrowserData(browserName, bd)

    HDF5CloseFile bd.fileID
    CloseSavePanels()
    FileWasClosed(bd)
End

static Function LoadDatasetButtonProc(ctrlName) : ButtonControl
    String ctrlName

    String browserName = HDF5GetTopBrowserName()

    STRUCT HDF5BrowserData bd
    SetHDF5BrowserData(browserName, bd)
   
    String datasetPath = SelectedDatasetPath(bd)

    String slabWaveStr = ""
    ControlInfo /W=$bd.browserName UseHyperSelection
    if (V_value)                                // Use Hyperselection is checked?
        slabWaveStr = bd.hyperSelectionWavePath
    endif
    WAVE/Z slabWave = $slabWaveStr          // It is OK if wave does not exist and slabWave is NULL. HDF5LoadData will simply ignore /SLAB.
   
    Variable isCompound
    String memberName
    Variable compMode
    HDF5GetCompLoadInfo(bd, isCompound, compMode, memberName)

    // If isFormalImage is true, we are loading an image written
    // according to the HDF5 Image and Palette Specification.
    Variable isFormalImage = 0
    if (!isCompound)
        String savedDataFolder = SetBrowserDataFolder("")           // tempClassAttribute goes in master HDF5Browser data folder
        HDF5LoadData /Z /O /N=tempClassAttribute /A="CLASS" /Q /VAR=1 bd.fileID, datasetPath
        if (V_flag == 0)
            WAVE/T tempClassAttribute           // HDF5LoadData will have created this string
            if (CmpStr(tempClassAttribute[0],"IMAGE") == 0)
                isFormalImage = 1
            endif  
            KillWaves/Z tempClassAttribute
        endif
        SetDataFolder savedDataFolder
    endif
   
    Variable tableDisplayMode, graphDisplayMode     // 0=no; 1=display; 2=append
    HDF5GetLoadDatasetOptions(bd.browserName, tableDisplayMode, graphDisplayMode)
   
    if (isFormalImage)
        HDF5LoadImage /O /GRPH=(graphDisplayMode) /T=(tableDisplayMode) bd.fileID, datasetPath
    else
        Variable transpose2D = HDF5GetTranspose2DSetting(bd.browserName)
        HDF5LoadData /O /SLAB=slabWave /TRAN=(transpose2D) /COMP={compMode,memberName} /GRPH=(graphDisplayMode) /T=(tableDisplayMode) bd.fileID, datasetPath
    endif
End

static Function LoadGroupButtonProc(ctrlName) : ButtonControl
    String ctrlName

    String browserName = HDF5GetTopBrowserName()

    STRUCT HDF5BrowserData bd
    SetHDF5BrowserData(browserName, bd)
   
    String groupPath = SelectedGroupPath(bd)

    ControlInfo /W=$bd.browserName LoadGroupsRecursively
    if (V_value)                                    // Use LoadGroupsRecursively is checked?
        HDF5LoadGroup /O /R /T /IMAG=1 :, bd.fileID, groupPath
    else
        HDF5LoadGroup /O /T /IMAG=1 :, bd.fileID, groupPath
    endif

    // For debugging
    Variable numItems
    Variable debug = 1
    if (debug)
        Wave/Z/T groupPaths = root:groupPaths
        if (WaveExists(groupPaths))
            numItems = ItemsInList(S_groupPaths)
            Redimension/N=(numItems) groupPaths
            groupPaths = StringFromList(p, S_groupPaths)
        endif

        Wave/Z/T dataFolderPaths = root:dataFolderPaths
        if (WaveExists(dataFolderPaths))
            numItems = ItemsInList(S_dataFolderPaths)
            Redimension/N=(numItems) dataFolderPaths
            dataFolderPaths = StringFromList(p, S_dataFolderPaths)
        endif

        Wave/Z/T wavePaths = root:wavePaths
        if (WaveExists(wavePaths))
            numItems = ItemsInList(S_objectPaths)
            Redimension/N=(numItems) wavePaths
            wavePaths = StringFromList(p, S_objectPaths)
        endif
    endif
End

Function AttachListWaves(bd)
    STRUCT HDF5BrowserData &bd
   
    ListBox GroupsList win=$bd.browserName, listWave=bd.groupsList
    ListBox GroupAttributesList win=$bd.browserName, listWave=bd.groupAttributesList
    ListBox DatasetsList win=$bd.browserName, listWave=bd.datasetsList
    ListBox DatasetAttributesList win=$bd.browserName, listWave=bd.datasetAttributesList
End

Function HDF5BrowserPanelHook(infoStr)
    String infoStr

    String browserName= StringByKey("WINDOW",infoStr)
    String event= StringByKey("EVENT",infoStr)

    STRUCT HDF5BrowserData bd
    SetHDF5BrowserData(browserName, bd)

    strswitch(event)
        case "activate":                // We do not get this on Windows when the panel is first created.
            // This detects if the file is no longer open, such as if you save the experiment, quit Igor and then reopen the experiment.
            if (FileWasUnexpectedlyClosed(bd))
                Printf "The file \"%s\" is no longer open.\r", bd.fileName
                FileWasClosed(bd)
            endif
           
            SetGraphButtonTitle(browserName)
            SetTableButtonTitle(browserName)
            SetDumpButtonTitle(browserName)
            break

        case "resize":
            HDF5ResizeBrowser(browserName)
            break
           
        case "moved":           // This message was added in Igor Pro 5.04B07.
            // If this is the last HDF5 browser, save the browser window size and position.
            if (strlen(HDF5GetIndexedBrowserName(1)) == 0)
                SetPrefWindowCoords(browserName)
            endif
            break
           
        case "kill":
            if (bd.fileID != 0)
                HDF5CloseFile bd.fileID
                bd.fileID = 0
                CloseSavePanels()
            endif
            KillDataFolder root:Packages:HDF5Browser:$browserName
            break
    endswitch
   
    return 0
End

Function SelectDataset(bd, datasetName)
    STRUCT HDF5BrowserData &bd
    String datasetName

    String info

    if (strlen(datasetName) == 0)
        info = ""
    else
        String fullPath
        fullPath = HDF5GetObjectFullPath(bd.groupPath, datasetName)
        STRUCT HDF5DataInfo di
        InitHDF5DataInfo(di)            // Set input fields.
        HDF5DatasetInfo(bd.fileID, fullPath, 0, di)
        // Print s
        sprintf info, "%s, class=%s", datasetName, di.datatype_class_str
    endif
    bd.datasetInfo = info
    DrawDatasetInfo(bd)
    SetButtonStates(bd)
    FillDatasetAttributesList(bd)
End

Function UpdateAfterGroupSelected(bd, fullGroupPath)
    STRUCT HDF5BrowserData &bd
    String fullGroupPath
   
    Variable selectedGroupChanged = CmpStr(bd.groupPath, fullGroupPath) != 0

    bd.groupPath = fullGroupPath
    DrawGroupPath(bd)
    FillGroupAttributesList(bd)
    FillDatasetsList(bd)

    if (selectedGroupChanged)
        ResetListSelections(bd, 0, 1, 1, 1)
        String datasetName = ""
        if (numpnts(bd.datasetsList) > 0)
            datasetName = bd.datasetsList[0][0]
        endif
        SelectDataset(bd, datasetName)
    endif
    SetButtonStates(bd)
End

static Function GroupsListActionProc(s, bd) : ListboxControl
    STRUCT WMListboxAction &s
    STRUCT HDF5BrowserData &bd
   
    String browserName = s.win
    Variable result = 0                                 // As of now, the return value must always be zero.
   
    switch(s.eventCode)
        case 4:                     // Cell selection
            String fullGroupPath = bd.groupFullPaths[s.row]
            UpdateAfterGroupSelected(bd, fullGroupPath)
            // Printf "Row=%d, column=%d, path=%s\r", s.row, s.col, fullGroupPath
            if (HDF5BrowserDumpIsVisible())
                HDF5DisplayDumpOfSelectedGroup(bd)
            endif
            break  
    endswitch

    return result
End

static Function GroupAttributesListActionProc(s, bd) : ListboxControl
    STRUCT WMListboxAction &s
    STRUCT HDF5BrowserData &bd
   
    String browserName = s.win
    Variable result = 0                                 // As of now, the return value must always be zero.
   
    switch(s.eventCode)
        case 3:                     // Double-click
            break;
           
        case 4:                     // Cell selection
            // Printf "Row=%d, column=%d\r", s.row, s.col
            HDF5DisplaySelectedAttribute(bd,  1)                // Update various windows if they are displayed
            break  
    endswitch

    return result
End

Function HandleDatasetDoubleClick(s, bd)
    STRUCT WMListboxAction &s
    STRUCT HDF5BrowserData &bd

    String datasetPath = SelectedDatasetPath(bd)
    if (strlen(datasetPath) == 0)
        return -1
    endif
   
    STRUCT HDF5DataInfo di
    InitHDF5DataInfo(di)
    HDF5DatasetInfo(bd.fileID, datasetPath, 0, di)
    switch(di.datatype_class)
        default:
            // Load dataset here.
            break  
    endswitch
End

static Function DatasetsListActionProc(s, bd) : ListboxControl
    STRUCT WMListboxAction &s
    STRUCT HDF5BrowserData &bd
   
    String browserName = s.win
    Variable result = 0                                 // As of now, the return value must always be zero.
   
    switch(s.eventCode)
        case 3:                     // Double-click
            HandleDatasetDoubleClick(s, bd)
            break;
           
        case 4:                     // Cell selection
            String name = bd.datasetsList[s.row][0]
            SelectDataset(bd, name)
            // Printf "Row=%d, column=%d, name=%s\r", s.row, s.col, name
            HDF5DisplaySelectedDataset(bd)                  // Update various windows if they are displayed
            break  
    endswitch
   
    return result
End

static Function DatasetAttributesListActionProc(s, bd) : ListboxControl
    STRUCT WMListboxAction &s
    STRUCT HDF5BrowserData &bd
   
    String browserName = s.win
    Variable result = 0                                 // As of now, the return value must always be zero.
   
    switch(s.eventCode)
        case 3:                     // Double-click
            break;
           
        case 4:                     // Cell selection
            // Printf "Row=%d, column=%d\r", s.row, s.col
            HDF5DisplaySelectedAttribute(bd,  0)                // Update various windows if they are displayed
            break  
    endswitch
   
    return result
End

static Function ListBoxActionProc(s) : ListboxControl
    STRUCT WMListboxAction &s

    String browserName = s.win

    STRUCT HDF5BrowserData bd
    SetHDF5BrowserData(browserName, bd)
   
    Variable result = 0                         // As of now, the return value must always be zero.
   
    strswitch(s.ctrlName)
        case "GroupsList":
            result = GroupsListActionProc(s, bd)
            break
           
        case "GroupAttributesList":
            result = GroupAttributesListActionProc(s, bd)
            break
           
        case "DatasetsList":
            result = DatasetsListActionProc(s, bd)
            break
       
        case "DatasetAttributesList":
            result = DatasetAttributesListActionProc(s, bd)
            break
    endswitch
   
    return result
End

static Function SetGraphButtonTitle(browserName)
    String browserName
   
    if (HDF5BrowserGraphIsVisible())
        Button Graph, win=$browserName, title="Hide Graph"
    else
        Button Graph, win=$browserName, title="Show Graph"
    endif  
End

static Function GraphButtonProc(ctrlName) : ButtonControl
    String ctrlName

    String browserName = HDF5GetTopBrowserName()
   
    if (HDF5BrowserGraphIsVisible())
        DoWindow/K HDF5BrowserGraph
    else
        HDF5CreateBrowserGraph()                // Create if it does not exist.
    endif
    SetGraphButtonTitle(browserName)
End

static Function SetTableButtonTitle(browserName)
    String browserName
   
    if (HDF5BrowserTableIsVisible())
        Button Table, win=$browserName, title="Hide Table"
    else
        Button Table, win=$browserName, title="Show Table"
    endif  
End

static Function TableButtonProc(ctrlName) : ButtonControl
    String ctrlName

    String browserName = HDF5GetTopBrowserName()
   
    if (HDF5BrowserTableIsVisible())
        DoWindow/K HDF5BrowserTable
    else
        HDF5CreateBrowserTable()                // Create if it does not exist.
    endif
    SetTableButtonTitle(browserName)
End

static Function SetDumpButtonTitle(browserName)
    String browserName
   
    if (HDF5BrowserDumpIsVisible())
        Button Dump, win=$browserName, title="Hide Dump"
    else
        Button Dump, win=$browserName, title="Show Dump"
    endif  
End

static Function DumpButtonProc(ctrlName) : ButtonControl
    String ctrlName

    String browserName = HDF5GetTopBrowserName()
   
    if (HDF5BrowserDumpIsVisible())
        Notebook HDF5DumpNotebook, visible=0
    else
        HDF5CreateDumpWindow()                      // Create if it does not exist.
        DoWindow/F HDF5DumpNotebook         // Show it.
    endif
    SetDumpButtonTitle(browserName)
End

static Function HelpButtonProc(ctrlName) : ButtonControl
    String ctrlName

    DisplayHelpTopic "The HDF5 Browser"
End

static Function CreateHDF5BrowserPanel(browserName)
    String browserName
   
    Variable isMacintosh = 0
    if (CmpStr(IgorInfo(2),"Macintosh") == 0)
        isMacintosh = 1
    endif

    // Determine panel size and position
    Variable left, top, right, bottom
    GetPrefWindowCoords("HDF5Browser", left, top, right, bottom)    // See if prefs set.
    if (right-left<200 || bottom-top<200)
        // These values are calculated to fill a typical 17 inch screen (832x624) on Macintosh.
        left = 5
        top = 50
        right = 820
        bottom = 625
    endif
   
    Variable readOnly, loadGroupsRecursively, transpose2DDatasets
    GetPrefBrowserSettings(readOnly, loadGroupsRecursively, transpose2DDatasets)

    NewPanel /W=(left, top, right, bottom)/K=1 as "HDF5 Browser"
   
    DoWindow/C $browserName
    DoWindow/T $browserName, browserName
   
    // This marks this control panel as an HDF5 browser.
    SetWindow kwTopWin, userdata(HDF5BrowserName)=browserName
   
    SetDrawLayer ProgBack

    SetDrawEnv fstyle= 1
    DrawText 18,75,"File:"

    SetDrawEnv fstyle= 1
    DrawText 18,103,"Selected Group:"

    SetDrawEnv fstyle= 1
    DrawText 18,130,"Selected Dataset:"

    TitleBox FilePath,pos={55,57},size={706,21}

    left = isMacintosh ? 150 : 125
    TitleBox GroupPath,pos={left,86},size={658,20}
    TitleBox Dataset,pos={left,113},size={13,21}

    CheckBox UseHyperSelection,pos={15,155},size={110,14},title="Use Hyperselection",value= 0
    CheckBox UseHyperSelection,help={"For experts only. Allows loading a subset of a dataset."}

    SetVariable HyperSelectionWave,pos={140,155},size={390,16},title="Hyper Selection Wave:"
    SetVariable HyperSelectionWave,help={"Enter full path to wave containing hyperselection information. See HDF5LoadData /SLAB keyword help."}

    CheckBox LoadGroupsRecursively,pos={15,181},size={137,14},title="Load Groups Recursively",value=loadGroupsRecursively
    CheckBox LoadGroupsRecursively,proc=HDF5BrowserPrefCheckboxProc,help={"When checked, the Load Group button loads subgroups."}
   
    Button CreateFile,pos={15,8},size={125,20},proc=HDF5Browser#CreateFileButtonProc,title="Create HDF5 File"
    Button OpenFile,pos={159,8},size={125,20},proc=HDF5Browser#OpenFileButtonProc,title="Open HDF5 File"
    Button CloseFile,pos={296,8},size={125,20},proc=HDF5Browser#CloseFileButtonProc,title="Close HDF5 File"
    Button Help,pos={435,8},size={50,20},proc=HDF5Browser#HelpButtonProc,title="Help"
    CheckBox ReadOnly,pos={186,32},size={68,14},title="Read Only",proc=HDF5BrowserPrefCheckboxProc,value=readOnly

    // Start Preview

    Button Graph,pos={556,27},size={90,20},proc=HDF5Browser#GraphButtonProc,title="Show Graph"
    Button Graph help={"Shows or hides a graph which displays the last dataset or attribute that you selected."}

    Button Table,pos={672,27},size={90,20},proc=HDF5Browser#TableButtonProc,title="Show Table"
    Button Table help={"Shows or hides a table which displays the last dataset or attribute that you selected."}

    Button Dump,pos={556,59},size={90,20},proc=HDF5Browser#DumpButtonProc,title="Show Dump"
    Button Dump help={"Shows or hides a window which displays a dump of the last dataset or attribute that you selected."}

    CheckBox ShowAttributesInDump,pos={653,71},size={100,14},title="Show Attributes In Dump"
    CheckBox ShowAttributesInDump help={"Check to display the dataset's attributes in the dump window."}

    CheckBox ShowDataInDump,pos={653,56},size={114,14},title="Show Data In Dump"
    CheckBox ShowDataInDump,help={"Check to display data in the dump window. For large datasets this can take a long time."}

    GroupBox PreviewOptions,pos={543,5},size={258,87},title="Preview Options"

    // End Preview

    TitleBox GroupsTitle,pos={15,230},size={50,16},disable=2,title="Groups",fSize=14
    TitleBox GroupsTitle,frame=0,fStyle=1

    ListBox GroupsList,pos={15,250},size={306,170}, mode=2, proc=HDF5Browser#ListBoxActionProc
    ListBox GroupsList,fSize=14

    Button LoadGroup,pos={80,224},size={100,20},proc=HDF5Browser#LoadGroupButtonProc,title="Load Group"
    Button LoadGroup,help={"Loads the currently selected group into the current data folder."}

    Button SaveDataFolder,pos={194,224},size={120,20},proc=HDF5Browser#SaveDataFolderButtonProc,title="Save Data Folder"
    Button SaveDataFolder,help={"Saves a data folder in the currently selected group. Available if the current HDF5 file is open for read/write."}

    TitleBox GroupAttributesTitle,pos={15,435},size={111,16},disable=2,title="Group Attributes"
    TitleBox GroupAttributesTitle,fSize=14,frame=0,fStyle=1

    ListBox GroupAttributesList,pos={15,455},size={306,109}, mode=2, proc=HDF5Browser#ListBoxActionProc
    ListBox GroupAttributesList, widths={175,40,80,120,1000}, userColumnResize= 1   // userColumnResize requires Igor Pro 5.02.
    ListBox GroupAttributesList,fSize=14

    TitleBox DatasetsTitle,pos={341,230},size={62,16},disable=2,title="Datasets"
    TitleBox DatasetsTitle,fSize=14,frame=0,fStyle=1

    ListBox DatasetsList,pos={341,250},size={459,170}, mode=2, proc=HDF5Browser#ListBoxActionProc
    ListBox DatasetsList, widths={175,40,80,120,1000}, userColumnResize= 1  // userColumnResize requires Igor Pro 5.02.
    ListBox DatasetsList,fSize=14

    TitleBox DatasetAttributesTitle,pos={341,435},size={123,16},disable=2,title="Dataset Attributes"
    TitleBox DatasetAttributesTitle,fSize=14,frame=0,fStyle=1

    ListBox DatasetAttributesList,pos={341,455},size={459,109}, mode=2, proc=HDF5Browser#ListBoxActionProc
    ListBox DatasetAttributesList, widths={175,40,80,120,1000}, userColumnResize= 1 // userColumnResize requires Igor Pro 5.02.
    ListBox DatasetAttributesList,fSize=14

    Button LoadDataset,pos={415,224},size={100,20},proc=HDF5Browser#LoadDatasetButtonProc,title="Load Dataset"
    Button LoadDataset,help={"Loads the currently selected dataset into the current data folder."}

    Button SaveWaves,pos={529,224},size={100,20},proc=HDF5Browser#SaveWavesButtonProc,title="Save Waves"
    Button SaveWaves,help={"Saves waves in the currently selected group. Available if the current HDF5 file is open for read/write."}

    CheckBox Transpose2DDatasets,pos={189,181},size={130,14},title="Transpose 2D Datasets",value=transpose2DDatasets
    CheckBox Transpose2DDatasets,proc=HDF5BrowserPrefCheckboxProc,help={"When checked, 2D datasets are transposed so that Igor image plots will match HDFView."}

    PopupMenu Members,pos={342,194},size={216,24},title="Members"
    PopupMenu Members,mode=1,value= #"\"Load All Members\""
    PopupMenu Members proc=HDF5Browser#MembersPopupProc
    PopupMenu Members,help={"Choose the compound member to preview or load."}

    // Load Dataset Options
    PopupMenu DisplayInTable,pos={565,123},size={200,24},title="Table:"
    PopupMenu DisplayInTable,mode=2,value= #"\"No Table;Display in New Table;Append to Top Table\""
    PopupMenu DisplayInGraph,pos={563,154},size={203,24},title="Graph:"
    PopupMenu DisplayInGraph,mode=2,value= #"\"No Graph;Display in New Graph;Append to Top Graph\""
    GroupBox LoadDatasetOptions,pos={542,100},size={258,87},title="Load Dataset Options"
   
    HDF5ResizeBrowser(browserName)      // Needed because we used preferred browser size.
   
    SetWindow kwTopWin,hook=HDF5BrowserPanelHook
EndMacro

Function HDF5BrowserPrefCheckboxProc(ctrlName,checked) : CheckBoxControl
    String ctrlName
    Variable checked
   
    STRUCT HDF5BrowserPrefs prefs
   
    HDF5BrowserLoadPackagePrefs(prefs)
    strswitch(ctrlName)
        case "ReadOnly":
            prefs.readOnly = checked
            break
       
        case "LoadGroupsRecursively":
            prefs.loadGroupsRecursively = checked
            break
       
        case "Transpose2DDatasets":
            prefs.transpose2DDatasets = checked
            break
           
        case "SaveGroupsRecursively":
            prefs.saveGroupsRecursively = checked
            break
           
        case "IncludeIgorAttributes":
            prefs.includeIgorAttributes = checked
            break
       
    endswitch
    HDF5BrowserSavePackagePrefs(prefs)
End

Function CreateNewHDF5Browser()
    if (Exists("HDF5LoadData") != 4)
        String message
        message = "The HDF5XOP is not activated. Please see the HDF5XOP Help file for instructions."
        DoAlert 0, message
        DisplayHelpTopic "HDF5XOP"
        return -1  
    endif

    String browserName = UniqueName("HDF5Browser", 9, 0)
   
    CreateHDF5BrowserGlobals(browserName)
   
    CreateHDF5BrowserPanel(browserName)

    STRUCT HDF5BrowserData bd
    SetHDF5BrowserData(browserName, bd)
   
    AttachListWaves(bd)
    SetButtonStates(bd)
End

Static Function IsHDF5Browser(name)
    String name         // Name of a window
   
    if (WinType(name) != 7)
        return 0                // Not a control panel window
    endif
   
    String data = GetUserData(name, "", "HDF5BrowserName")  // HDF5BrowserName property is set by CreateHDF5BrowserPanel
    if (CmpStr(data,name) == 0)         // Is this an HDF5Browser?
        return 1
    endif
   
    return 0
End

Function/S HDF5GetIndexedBrowserName(index)
    Variable index
   
    if (index < 0)
        return ""               // Bad index
    endif
   
    String panelName
   
    Variable i = 0
    do
        panelName = WinName(i, 64)
        if (strlen(panelName) == 0)
            break
        endif
       
        if (IsHDF5Browser(panelName))       // Is this an HDF5Browser?
            if (index == 0)
                return panelName
            endif
            index -= 1
        endif
       
        i += 1
    while(1)
   
    return ""       // No HDF5 browser with that index
End

Function/S HDF5GetTopBrowserName()
    String browserName = HDF5GetIndexedBrowserName(0)
    return browserName
End

Function HDF5AreAnyBrowsersOpen()
    String browserName = HDF5GetIndexedBrowserName(0)
    if (strlen(browserName) > 0)
        return 1
    endif
    return 0
End

// FixCloseHDF5FileButtons()
// If experiment was saved with an HDF5 file open, it is no longer open but the panel's Open
// and Close buttons will be out-of-sync. This fixes that.
static Function FixCloseHDF5FileButtons()

    STRUCT HDF5BrowserData bd
    String browserName
   
    Variable index = 0
    do
        browserName = HDF5GetIndexedBrowserName(index)
        if (strlen(browserName) == 0)
            break
        endif
       
        SetHDF5BrowserData(browserName, bd)
        if (FileWasUnexpectedlyClosed(bd))
            FileWasClosed(bd)
        endif
       
        index += 1
    while(1)
End

static Function AfterFileOpenHook(refNum,file,pathName,type,creator,kind)
    Variable refNum,kind
    String file,pathName,type,creator

    // DoAlert 0, "AfterFileOpenHook"       // For debugging
   
    switch (kind)
        case 1:                                     // Packed experiment
        case 2:                                     // Unpacked experiment
            FixCloseHDF5FileButtons()       // If experiment was saved with an HDF5 file open, it is no longer open
        break
    endswitch

    return 0
End

// ************* Start of HDF5 Browser Display Routines ***************

static Function WaveRank(w)
    Wave w
   
    Variable dimension
    for(dimension=3; dimension>=0; dimension-=1)
        if (DimSize(w, dimension) > 0)
            return dimension+1
        endif
    endfor
   
    return 0
End

// *** DISPLAY IN DUMP WINDOW ***

Function HDF5BrowserDumpIsVisible() // Returns true if dump window exists and is visible.
                                    // Returns false if it does not exist or is invisible.
    DoWindow  HDF5DumpNotebook
    if (V_flag == 0)
        return 0                    // Does not exist
    endif
   
    String name
    Variable index = 0
    do
        name = WinName(index, 16, 1)
        if (strlen(name) == 0)
            return 0                // Did not find HDF5DumpNotebook among visible notebooks.
        endif
        if (CmpStr(name, "HDF5DumpNotebook") == 0)
            return 1                // Found HDF5DumpNotebook among visible notebooks
        endif
        index += 1
    while(1)
   
    return 0                        // This will never execute.                        
End

Function HDF5BrowserDumpHook(infoStr)
    String infoStr

    String event= StringByKey("EVENT",infoStr)

    strswitch(event)
        case "activate":                // We do not get this on Windows when the panel is first created.
            break
           
        case "resize":
        case "moved":                   // This message was added in Igor Pro 5.04B07.
            SetPrefWindowCoords("HDF5DumpNotebook")
            break
    endswitch
   
    return 0
End

Function HDF5CreateDumpWindow()
    DoWindow HDF5DumpNotebook
    if (V_flag == 0)
        Variable left, top, right, bottom
        GetPrefWindowCoords("HDF5DumpNotebook", left, top, right, bottom)
        if (right > left)                                           // Were prefs ever set?
            NewNotebook /F=0 /N=HDF5DumpNotebook/K=1 /W=(left, top, right, bottom)
        else
            NewNotebook/F=0/N=HDF5DumpNotebook/K=1
        endif
        SetWindow HDF5DumpNotebook,hook=HDF5BrowserDumpHook
        if (NumType(FontSizeHeight("Courier New", 12, 0)) == 0)     // Courier New exists?
            Notebook HDF5DumpNotebook font="Courier New", fSize=12
        endif
        Notebook HDF5DumpNotebook text="A dump will appear here when you click a dataset.\r"
    endif
End

// CleanupDump(dump)
// Removes nulls, converts \n to CR, etc. Dump of NASA strings can contain lots of such "garbage".
// For an example, see the attribute /StructMetadata.O_GLOSDS in the NASA sample file MISRAERO.h5.
static Function/S CleanupDump(dump)
    String dump
   
    // Convert literal string "\000" to "". Null characters are represented in dump by "\000"
    dump = ReplaceString("\\000", dump, "", 1)
   
    // Convert literal string "\r\n" to CR
    dump = ReplaceString("\\r\\n", dump, "\r", 1)
   
    // Convert literal string "\r" to CR
    dump = ReplaceString("\\r", dump, "\r", 1)
   
    // Convert literal string "\n" to CR
    dump = ReplaceString("\\n", dump, "\r", 1)
   
    // Convert literal string "\t" to tab
    dump = ReplaceString("\\t", dump, "\t", 1)
   
    // Convert CRLF to CR
    dump = ReplaceString("\r\n", dump, "\r", 1)
   
    // Convert LF to CR
    dump = ReplaceString("\n", dump, "\r", 1)
   
    return dump
End

Function HDF5DisplayDumpOfSelectedGroup(bd)
    STRUCT HDF5BrowserData &bd
   
    String path = SelectedGroupPath(bd)
    if (strlen(path) == 0)
        return -1
    endif
   
    ControlInfo /W=$bd.browserName ShowAttributesInDump
    Variable showAttributes = V_value
    HDF5Dump /Q /H=1 /ATTR=(showAttributes) /G=path bd.fullPath                 // This sets S_HDF5Dump.
    S_HDF5Dump = CleanupDump(S_HDF5Dump)   
   
    HDF5CreateDumpWindow()
   
    Notebook HDF5DumpNotebook selection={startOfFile, endOfFile}
    Notebook HDF5DumpNotebook text=S_HDF5Dump
    Notebook HDF5DumpNotebook selection={startOfFile, startOfFile}, findText={"",1}
End

static Function DisplayDumpOfSelectedDataset(bd)
    STRUCT HDF5BrowserData &bd
   
    String datasetPath = SelectedDatasetPath(bd)
    if (strlen(datasetPath) == 0)
        return -1
    endif
   
    ControlInfo /W=$bd.browserName ShowAttributesInDump
    Variable showAttributes = V_value
    ControlInfo /W=$bd.browserName ShowDataInDump
    Variable showData = V_value
    HDF5Dump /Q /ATTR=(showAttributes) /H=(!showData) /D=datasetPath bd.fullPath            // This sets S_HDF5Dump.
    S_HDF5Dump = CleanupDump(S_HDF5Dump)   
   
    HDF5CreateDumpWindow()
   
    Notebook HDF5DumpNotebook selection={startOfFile, endOfFile}
    Notebook HDF5DumpNotebook text=S_HDF5Dump
    Notebook HDF5DumpNotebook selection={startOfFile, startOfFile}, findText={"",1}
End

static Function DisplayDumpOfSelectedAttribute(bd, isGroupAttribute)
    STRUCT HDF5BrowserData &bd
    Variable isGroupAttribute
   
    String path = SelectedAttributePath(bd, isGroupAttribute)
    if (strlen(path) == 0)
        return -1
    endif
   
    HDF5Dump /Q /A=path bd.fullPath                 // This sets S_HDF5Dump.
    S_HDF5Dump = CleanupDump(S_HDF5Dump)   
   
    HDF5CreateDumpWindow()
   
    Notebook HDF5DumpNotebook selection={startOfFile, endOfFile}
    Notebook HDF5DumpNotebook text=S_HDF5Dump
    Notebook HDF5DumpNotebook selection={startOfFile, startOfFile}, findText={"",1}
End

// *** DISPLAY IN GRAPH ***

Function HDF5BrowserGraphIsVisible()        // Returns true if dump window exists and is visible.
                                        // Returns false if it does not exist or is invisible.
    DoWindow  HDF5BrowserGraph
    if (V_flag == 0)
        return 0                    // Does not exist
    endif
   
    // Graphs are always visible so we don't need to check that.
   
    return 1                        // This will never execute.                        
End

Function HDF5BrowserGraphHook(infoStr)
    String infoStr

    String event= StringByKey("EVENT",infoStr)

    strswitch(event)
        case "activate":                // We do not get this on Windows when the panel is first created.
            break
           
        case "resize":
        case "moved":                   // This message was added in Igor Pro 5.04B07.
            SetPrefWindowCoords("HDF5BrowserGraph")
            break
    endswitch
   
    return 0
End

Function HDF5CreateBrowserGraph()
    DoWindow HDF5BrowserGraph
    if (V_flag == 0)
        Variable left, top, right, bottom
        GetPrefWindowCoords("HDF5BrowserGraph", left, top, right, bottom)
        if (right > left)                                   // Were prefs ever set?
            Display /K=1 /W=(left, top, right, bottom)
        else
            Display /K=1
        endif
        DoWindow/C HDF5BrowserGraph
        SetWindow HDF5BrowserGraph,hook=HDF5BrowserGraphHook
    endif
End

static Function SetImageLayer(ctrlName,varNum,varStr,varName) : SetVariableControl
    String ctrlName
    Variable varNum
    String varStr
    String varName

    NVAR imageLayer = root:Packages:HDF5Browser:imageLayer

    ModifyImage /W=HDF5BrowserGraph BrowserWave, plane=varNum
End

static Function DisplayGraphOfSelectedData(bd, isAttribute, objectType, listOfWavesLoaded)      // The data is already loaded into waves specified by listOfWavesLoaded
    STRUCT HDF5BrowserData &bd
    Variable isAttribute
    Variable objectType             // Host object type of attribute. 1=group, 2=dataset
    String listOfWavesLoaded

    HDF5CreateBrowserGraph()

    String firstWaveLoaded = StringFromList(0, listOfWavesLoaded)
    Wave browserWave = root:Packages:HDF5Browser:$firstWaveLoaded
    Variable newDataIsText = WaveType(browserWave) == 0

    Variable oldRank = 0, newRank = 0
    if (strlen(TraceNameList("HDF5BrowserGraph", ";", 1)) > 0)
        oldRank = 1                
    endif
    if (strlen(ImageNameList("HDF5BrowserGraph", ";")) > 0)
        oldRank = 2
    endif
    newRank = WaveRank(browserWave) // Will be zero for zero-point wave

    String savedDataFolder = SetBrowserDataFolder("")                   // browserWave goes in master HDF5Browser data folder

    Variable displayedDimensionalityChanged = (oldRank <= 1) != (newRank <= 1)  // Switching between 1D and >1D ?
    Variable index, browserWaveIsDisplayed
    String waveLoaded, nameOfGraphWave
   
    // Remove any waves in graph not in listOfWavesLoaded or all waves if dimensionality changed
    index = 0
    do
        if (oldRank == 1)
            Wave/Z graphWave = WaveRefIndexed("HDF5BrowserGraph", index, 1)
            if (!WaveExists(graphWave))
                break
            endif
            nameOfGraphWave = NameOfWave(graphWave)
        else
            nameOfGraphWave = StringFromList(index, ImageNameList("HDF5BrowserGraph", ";" ))
            if (strlen(nameOfGraphWave) == 0)
                break
            endif
        endif
       
        Variable waveIsInListOfLoadedWaves = WhichListItem(nameOfGraphWave, listOfWavesLoaded) >= 0
        if (displayedDimensionalityChanged || !waveIsInListOfLoadedWaves)
            if (oldRank == 1)
                RemoveFromGraph /W=HDF5BrowserGraph $nameOfGraphWave
            endif
            if (oldRank > 1)
                RemoveImage /W=HDF5BrowserGraph $nameOfGraphWave
            endif
            index -= 1
        endif
        if (!waveIsInListOfLoadedWaves)
            KillWaves/Z $nameOfGraphWave
        endif
       
        index += 1
    while(1)

    // Append any waves to graph in listOfWavesLoaded
    index = 0
    do
        waveLoaded = StringFromList(index, listOfWavesLoaded)
        if (strlen(waveLoaded) == 0)
            break
        endif
        Wave browserWave = root:Packages:HDF5Browser:$waveLoaded
        Variable browserWaveType = WaveType(browserWave)
        nameOfGraphWave = waveLoaded

        CheckDisplayed /W=HDF5BrowserGraph browserWave
        browserWaveIsDisplayed = V_flag
       
        if (!newDataIsText)
            if (browserWaveType != 0)               // When loading compound data we can get a mix of numeric and non-numeric.
                if (browserWaveIsDisplayed == 0)
                    if (newRank <= 1)
                        AppendToGraph /W=HDF5BrowserGraph browserWave
                        if (displayedDimensionalityChanged)
                            SetAxis/A left
                        endif
                    else
                        AppendImage /W=HDF5BrowserGraph browserWave
                        if (displayedDimensionalityChanged)
                            SetAxis/A/R left                                    // Reverse left axis like NewImage does.
                        endif
                    endif
                endif
   
                if (newRank >= 2)
                    NVAR formalImageType = root:Packages:HDF5Browser:formalImageType
                    switch (formalImageType)                            // browserPalette would be created by HDF5LoadImage
                        case 0:                                             // Not a formal image.
                        case 1:                                             // No palette wave loaded.
                            ModifyImage /W=HDF5BrowserGraph $nameOfGraphWave ctab= {*,*,Grays,0}
                            break
                           
                        case 2:                                             // Palette wave was loaded.
                            Wave browserPalette = root:Packages:HDF5Browser:browserPalette
                            ModifyImage /W=HDF5BrowserGraph $nameOfGraphWave, cindex=browserPalette
                            break
                    endswitch
                endif
            endif
        endif

        if (newDataIsText)                      // Display a snippet of the text wave.
            if (browserWaveType == 0)       // When loading compound data we can get a mix of numeric and non-numeric.
                String text
                Wave/T w = root:Packages:HDF5Browser:$nameOfGraphWave
                if (numpnts(w) > 0)
                    text = CleanupDump(w[0])
                else
                    text = ""
                endif
                if ( (strlen(text) > 256) || (numpnts(w)>1) )
                    text = "A snippet of the text:\r\r" + text[0,255]          
                endif          
                TextBox/C/N=browserTextbox/W=HDF5BrowserGraph/A=LT text
            endif
        else
            TextBox/K/N=browserTextbox/W=HDF5BrowserGraph
        endif
       
        index += 1
    while(1)
   
    // Show Image Layer control if displaying a stack of images
    Variable numDims = WaveDims(browserWave)
    Variable isStack = 0
    if (!newDataIsText && newRank>2)
        if (DimSize(browserWave,2) == 3)
            // Igor assumes that this is an RGB wave using direct color.
            if (numDims > 3)
                isStack = 1         // This is a stack of RGB images.
            endif
        else
            isStack = 1         // This is a stack of indexed color images.
        endif
    endif
   
    if (isStack)
        Variable/G root:Packages:HDF5Browser:imageLayer = 0
        Variable dim, numLayers
       
        numLayers = 1
        for(dim=2; dim<numDims; dim+=1)
            numLayers *= DimSize(browserWave, dim)
        endfor
       
        ControlBar/W=HDF5BrowserGraph 25
        SetVariable ImageLayer win=HDF5BrowserGraph, title="Layer",size={100,20},format="%d"
        SetVariable ImageLayer win=HDF5BrowserGraph, proc=HDF5Browser#SetImageLayer
        SetVariable ImageLayer win=HDF5BrowserGraph, value=imageLayer, limits={0,numLayers-1,1}
    else
        KillControl/W=HDF5BrowserGraph ImageLayer
        ControlBar/W=HDF5BrowserGraph 0
    endif
       
    String title = "HDF5 Preview - "
    if (isAttribute)
        title += SelectedAttributeName(bd, objectType==1)
    else
        title += SelectedDatasetName(bd)
    endif
    title = title[0,39]             // Unfortunately titles are limited to 40 characters.
    DoWindow /T HDF5BrowserGraph, title
   
    SetDataFolder savedDataFolder
End

// *** DISPLAY IN TABLE ***

Function HDF5BrowserTableIsVisible()        // Returns true if dump window exists and is visible.
                                        // Returns false if it does not exist or is invisible.
    DoWindow  HDF5BrowserTable
    if (V_flag == 0)
        return 0                    // Does not exist
    endif
   
    // Tables are always visible so we don't need to check that.
   
    return 1                        // This will never execute.                        
End

Function HDF5BrowserTableHook(infoStr)
    String infoStr

    String event= StringByKey("EVENT",infoStr)

    strswitch(event)
        case "activate":                // We do not get this on Windows when the panel is first created.
            break
           
        case "resize":
        case "moved":                   // This message was added in Igor Pro 5.04B07.
            SetPrefWindowCoords("HDF5BrowserTable")
            break
    endswitch
   
    return 0
End

Function HDF5CreateBrowserTable()
    DoWindow HDF5BrowserTable
    if (V_flag == 0)
        Variable left, top, right, bottom
        GetPrefWindowCoords("HDF5BrowserTable", left, top, right, bottom)
        if (right > left)                                   // Were prefs ever set?
            Edit /K=1 /W=(left, top, right, bottom)
        else
            Edit /K=1
        endif
        DoWindow/C HDF5BrowserTable
        SetWindow HDF5BrowserTable,hook=HDF5BrowserTableHook
    endif
End

static Function DisplayTableOfSelectedData(bd, isAttribute, objectType, listOfWavesLoaded)      // The data is already loaded into waves listed by listOfWavesLoaded
    STRUCT HDF5BrowserData &bd
    Variable isAttribute
    Variable objectType             // Host object type of attribute. 1=group, 2=dataset
    String listOfWavesLoaded
   
    HDF5CreateBrowserTable()

    String waveLoaded
    Variable index
   
    // Remove any waves in table not in listOfWavesLoaded
    index = 0
    do
        Wave/Z tableWave = WaveRefIndexed("HDF5BrowserTable", index, 3)
        if (!WaveExists(tableWave))
            break
        endif
       
        String nameOfTableWave = NameOfWave(tableWave)
        if (WhichListItem(nameOfTableWave, listOfWavesLoaded) < 0)
            RemoveFromTable /W=HDF5BrowserTable tableWave
            KillWaves/Z tableWave
            index -= 1
        endif
       
        index += 1
    while(1)

    // Append any waves to table in listOfWavesLoaded
    index = 0
    do
        waveLoaded = StringFromList(index, listOfWavesLoaded)
        if (strlen(waveLoaded) == 0)
            break
        endif
        Wave browserWave = root:Packages:HDF5Browser:$waveLoaded
   
        CheckDisplayed /W=HDF5BrowserTable browserWave
        if (V_flag == 0)
            AppendToTable /W=HDF5BrowserTable browserWave
        endif
       
        index += 1
    while(1)

    String title = "HDF5 Preview - "
    if (isAttribute)
        title += SelectedAttributeName(bd, objectType==1)
    else
        title += SelectedDatasetName(bd)
    endif
    title = title[0,39]             // Unfortunately titles are limited to 40 characters.
    DoWindow /T HDF5BrowserTable, title
End

static Function RemoveFromGraphAndTable(w)
    Wave w
   
    String name = NameOfWave(w)
   
    if (HDF5BrowserGraphIsVisible())
        CheckDisplayed /W=HDF5BrowserGraph w
        if (V_flag != 0)
            Variable isImage = strlen(ImageNameList("HDF5BrowserGraph", ";")) > 0
            if (isImage)
                RemoveImage /W=HDF5BrowserGraph $name
            else
                RemoveFromGraph /W=HDF5BrowserGraph $name
            endif
        endif
    endif
   
    if (HDF5BrowserTableIsVisible())
        CheckDisplayed /W=HDF5BrowserTable w
        if (V_flag != 0)
            RemoveFromTable /W=HDF5BrowserTable w
        endif
    endif
End

//  KillConflictingBrowserWaves(new_class_str, enumMode)
//  "Conflicting" means that a wave is text and we are going
// to use the same name to load a numeric wave or vice versa.
// This function removes conflicting waves from the browser graph
// and table and then kills them.
static Function KillConflictingBrowserWaves(new_class_str, enumMode)
    String new_class_str                // Class of data we are about to load.
    Variable enumMode

    String browserDF = "root:Packages:HDF5Browser"
    Variable numWaves = CountObjects(browserDF, 1)
    Variable i

    for(i=0; i<numWaves; i+=1)
        String name = GetIndexedObjName(browserDF, 1, i)
        if (strlen(name)>=11 && CmpStr(name[0,11],"browserWave")==0)    // Is this a browser display wave?
            Wave w = $(browserDF + ":" + name)

            Variable oldDataIsText
            oldDataIsText = WaveType(w) == 0
   
            Variable newDataIsText
           
            newDataIsText = 0
            strswitch(new_class_str)
                case "H5T_STRING":
                case "H5T_REFERENCE":
                    newDataIsText = 1
                    break
                   
                case "H5T_ENUM":
                    if (enumMode == 1)
                        newDataIsText = 1
                    endif
                    break
                   
                case "H5T_COMPOUND":
                    // If we are loading all members of a compound dataset
                    // at this point we need to know the class of the compound
                    // member corresponding to the current wave. However, this
                    // is very complicated to do and I decided not to attempt it.
                    // The result is that, if we have an existing brower_<member>
                    // wave whose type is string and we try to load a numeric wave
                    // with the same name, HDF5LoadWave will get a "Can't overwrite
                    // text with numeric and vice versa" error.
                    break
            endswitch
           
            if (newDataIsText != oldDataIsText)
                RemoveFromGraphAndTable(w)
                KillWaves w
                // Printf "Killed %s\r", name
            endif
        endif
    endfor
End

static Function LoadSelectedDataForDisplay(bd, isAttribute, objectType, listOfWavesLoaded, errorMessage)
    STRUCT HDF5BrowserData &bd
    Variable isAttribute
    Variable objectType                 // Host object type of attribute. 1=group, 2=dataset
    String &listOfWavesLoaded       // Output: List of waves loaded.
    String &errorMessage                // Output: Error message or ""
   
    Variable err = 0
    errorMessage = ""

    Wave/Z browserWave = root:Packages:HDF5Browser:browserWave
   
    String path
    if (isAttribute)
        if (objectType == 1)
            path = SelectedGroupPath(bd)
        else
            path = SelectedDatasetPath(bd)
        endif
    else
        path = SelectedDatasetPath(bd)
    endif
    if (strlen(path) == 0)
        return -1
    endif
   
    String attributeName = ""
    if (isAttribute)
        attributeName = SelectedAttributeName(bd, objectType==1)
        if (strlen(attributeName) == 0)
            return -1
        endif
    endif

    STRUCT HDF5DataInfo di
    InitHDF5DataInfo(di)
    if (isAttribute)
        HDF5AttributeInfo(bd.fileID, path, objectType, attributeName, 0, di)
    else
        HDF5DatasetInfo(bd.fileID, path, 0, di)
    endif
   
    String datatype_class_str = di.datatype_class_str

    STRUCT HDF5DatatypeInfo dti
    InitHDF5DatatypeInfo(dti)                   // Sets input fields.

    Variable isCompound = 0
    Variable compMode = 0
    String memberName = ""
    if (!isAttribute)           // We support viewing members only for datasets, not for attributes.
        HDF5GetCompLoadInfo(bd, isCompound, compMode, memberName)
    endif
   
    if (compMode != 0)          // Are we loading a member of a compound datatype?
        err = HDF5TypeInfo(bd.fileID, path, "", memberName, 1, dti)
        if (err != 0)
            return err
        endif
        datatype_class_str = dti.type_class_str
    else
        // If array, we need to know about the base datatype of the array
        if (CmpStr(datatype_class_str, "H5T_ARRAY") == 0)
            if (isAttribute)
                HDF5AttributeInfo(bd.fileID, path, objectType, attributeName, 2, di)        // 2 means get info on base datatype of array
            else
                HDF5DatasetInfo(bd.fileID, path, 2, di) // 2 means get info on base datatype of array
            endif
        endif
        datatype_class_str = di.datatype_class_str
    endif

    err = HDF5CheckDataClass(datatype_class_str, errorMessage)
    if (err != 0)
        if (WaveExists(browserWave))
            Redimension/N=(0) browserWave       // Don't leave browserWave around which a user might
        endif                                               // think goes with the selected item in the control panel
        return err
    endif
   
    String savedDataFolder = SetBrowserDataFolder("")                   // tempClassAttribute goes in master HDF5Browser data folder
   
    // If isFormalImage is true, we are loading an image written
    // according to the HDF5 Image and Palette Specification.
    Variable isFormalImage = 0
    if (!isAttribute && !isCompound)
        HDF5LoadData /Z /O /N=tempClassAttribute /A="CLASS" /Q /VAR=1 bd.fileID, path
        if (V_flag == 0)
            WAVE/T tempClassAttribute           // HDF5LoadData will have created this string
            if (CmpStr(tempClassAttribute[0],"IMAGE") == 0)
                isFormalImage = 1
            endif  
            KillWaves/Z tempClassAttribute
        endif
    endif

    SetDataFolder savedDataFolder
   
    Variable enumMode = 1                       // 0: Load enum into numeric wave; 1: load enum into text wave.
   
    // If browserWave exists and we are switching from a text wave to a numeric wave
    // or vice-versa then we must remove browserWave from the graph and table and
    // kill it. Otherwise we will get an error when HDF5LoadData tries to overwrite
    // a text wave with numeric or vice versa.
    KillConflictingBrowserWaves(datatype_class_str, enumMode)   // Also removes them from graph and table.
   
    savedDataFolder = SetBrowserDataFolder("")                  // browser waves go in master HDF5Browser data folder

    String slabWaveStr = ""
    ControlInfo /W=$bd.browserName UseHyperSelection
    if (V_value)                                // Use Hyperselection is checked?
        slabWaveStr = bd.hyperSelectionWavePath
    endif
    WAVE/Z slabWave = $slabWaveStr          // It is OK if wave does not exist and slabWave is NULL. HDF5LoadData will simply ignore /SLAB.

    if (isFormalImage)
        String browserPaletteName = "browserPalette"
        HDF5LoadImage /O /N=browserWave /PALN=browserPaletteName /Q bd.fileID, path
        Variable/G formalImageType = 1      // Means formal image with no palette
        listOfWavesLoaded = StringFromList(0, S_waveNames)  // We count only the image as loaded, not the palette.
        if (WhichListItem(browserPaletteName, S_waveNames) >= 0)
            formalImageType = 2                 // Means formal image with palette
        endif
    else
        KillWaves/Z browserPalette

        Variable transpose2D = HDF5GetTranspose2DSetting(bd.browserName)

        // Note that when loading all members of a compound dataset this
        // will create a family of waves named browserWave_<member>.
        // Also when loading a VLEN dataset it will create a family of
        // waves named browserWave<digit>. Otherwise it just creates
        // one wave named browserWave.
        HDF5LoadData /O /IGOR=-1 /N=browserWave /COMP={compMode,memberName} /TRAN=(transpose2D) /A=attributeName /TYPE=(objectType) /ENUM=(enumMode) /SLAB=slabWave /Q /VAR=0 bd.fileID, path

        Variable/G formalImageType = 0      // Not a formal image
        listOfWavesLoaded = S_waveNames
    endif
   
    errorMessage = GetRTErrMessage()
    err = GetRTError(1)

    SetDataFolder savedDataFolder
    return err
End

Function HDF5DisplaySelectedDataset(bd)
    STRUCT HDF5BrowserData &bd

    if (HDF5BrowserDumpIsVisible())
        DisplayDumpOfSelectedDataset(bd)
    endif

    String listOfWavesLoaded = ""  
   
    Variable needToLoadData = HDF5BrowserGraphIsVisible() || HDF5BrowserTableIsVisible()
    if (needToLoadData)
        String errorMessage
        if (LoadSelectedDataForDisplay(bd, 0, 2, listOfWavesLoaded, errorMessage) != 0)
            DoAlert 0, errorMessage
            return -1
        endif
    endif

    if (HDF5BrowserGraphIsVisible())
        DisplayGraphOfSelectedData(bd, 0, 1, listOfWavesLoaded)
    endif

    if (HDF5BrowserTableIsVisible())
        DisplayTableOfSelectedData(bd, 0, 1, listOfWavesLoaded)
    endif
End

Function HDF5DisplaySelectedAttribute(bd,  isGroupAttribute)
    STRUCT HDF5BrowserData &bd
    Variable isGroupAttribute
   
    if (HDF5BrowserDumpIsVisible())
        DisplayDumpOfSelectedAttribute(bd, isGroupAttribute)
    endif
   
    Variable objectType = isGroupAttribute ? 1:2
   
    String listOfWavesLoaded = ""
   
    Variable needToLoadData = HDF5BrowserGraphIsVisible() || HDF5BrowserTableIsVisible()
    if (needToLoadData)
        String errorMessage
        if (LoadSelectedDataForDisplay(bd, 1, objectType, listOfWavesLoaded, errorMessage))
            DoAlert 0, errorMessage
            return -1
        endif
    endif

    if (HDF5BrowserGraphIsVisible())
        DisplayGraphOfSelectedData(bd, 1, objectType, listOfWavesLoaded)
    endif

    if (HDF5BrowserTableIsVisible())
        DisplayTableOfSelectedData(bd, 1, objectType, listOfWavesLoaded)
    endif
End

// ************* End of HDF5 Browser Display Routines ***************

// ************* Start of HDF5 Browser Resize Routines ***************

static Function MinWindowSize(winName,minwidth,minheight)
    String winName
    Variable minwidth,minheight

    GetWindow $winName wsize
    Variable width= max(V_right-V_left,minwidth)
    Variable height= max(V_bottom-V_top,minheight)
    MoveWindow/W=$winName V_left, V_top, V_left+width, V_top+height
End

Function PositionControlRelative(panelName, control, masterControl, xMode, yMode, dx, dy)
    String panelName
    String control, masterControl       // Positions control relative to masterControl.
    Variable xMode      // 0 = relative to master left, 1 = relative to master right, 2 = do not set x position.
    Variable yMode      // 0 = relative to master top, 1 = relative to master bottom, 2 = do not set y position.
    Variable dx, dy
   
    ControlInfo/W=$panelName $masterControl
    Variable masterLeft = V_left, masterTop = V_top
    Variable masterRight = masterLeft+V_width, masterBottom=masterTop+V_height
   
    ControlInfo/W=$panelName $control
    Variable controlLeft = V_left, controlTop = V_top
   
    Variable left, top
   
    switch(xMode)
        case 0:
            left = masterLeft + dx
            break
   
        case 1:
            left = masterRight + dx
            break
       
        case 2:
            left = controlLeft
            break
    endswitch
   
    switch(yMode)
        case 0:
            top = masterTop + dy
            break
   
        case 1:
            top = masterBottom + dy
            break
       
        case 2:
            top = controlTop
            break
    endswitch

    ModifyControl $control, win=$panelName, pos={left, top}
End

Function OffsetControls(panelName, controlList, dx, dy)
    String panelName
    String controlList  // Semicolon-separated list of control names
    Variable dx, dy
   
    String name
    Variable index = 0
    do
        name = StringFromList(index, controlList)
        if (strlen(name) == 0)
            break
        endif
       
        ControlInfo/W=$panelName $name
        ModifyControl $name, win=$panelName, pos={V_left+dx,V_top+dy}

        index += 1
    while(1)
End

Function HDF5ResizeBrowser(browserName)
    String browserName

    Variable statusCode= 0

    String win = browserName

    GetWindow $browserName wsizeDC
    Variable winLeft=V_left, winTop=V_top, winRight=V_right, winBottom=V_bottom
    Variable winWidth = winRight - winLeft, winHeight = winBottom - winTop

    if (winWidth<600 || winHeight<500)
        return 0                        // Too small.
    endif
   
    // Set preferred browser window size. We would like to also do this
    // when the browser is moved without resizing but we get no message
    // from Igor when a window is moved.
    SetPrefWindowCoords(browserName)
   
    Variable leftBorder=15, hSpaceBetweenLists=20, rightBorder=15
    Variable listsTop, vSpaceBetweenLists = 30, bottomBorder = 10
   
    ControlInfo/W=$browserName GroupsList
    listsTop = V_top
   
    Variable hSpaceForLists = winRight - winLeft - leftBorder - hSpaceBetweenLists - rightBorder
    Variable vSpaceForLists = winBottom - listsTop - vSpaceBetweenLists - bottomBorder
   
    Variable groupListsWidth = .4 * hSpaceForLists
    Variable datasetListsWidth = .6 * hSpaceForLists
   
    Variable groupListsHeight = .65 * vSpaceForLists
    Variable attributeListsHeight = .35 * vSpaceForLists
   
    Variable left, top
   
    // Set Groups list coordinates
    left = leftBorder
    top = listsTop
    ListBox GroupsList, win=$browserName, pos={left, top}, size={groupListsWidth, groupListsHeight}
   
    // Set Group Attributes list coordinates
    left = leftBorder
    top = listsTop + groupListsHeight + vSpaceBetweenLists
    ListBox GroupAttributesList, win=$browserName, pos={left, top}, size={groupListsWidth, attributeListsHeight}
   
    top -= 20
    TitleBox GroupAttributesTitle, win=$browserName, pos={left, top}

    // Remember where DatasetsList is. it is used to position other control.
    ControlInfo/W=$browserName DatasetsList
    Variable oldDatasetsListRight = V_Left + V_Width
   
    // Set Datasets list coordinates
    left = leftBorder + groupListsWidth + hSpaceBetweenLists
    top = listsTop
    ListBox DatasetsList, win=$browserName, pos={left, top}, size={datasetListsWidth, groupListsHeight}
   
    // Determine how DatasetsList right edge changed. This is used to position other control.
    ControlInfo/W=$browserName DatasetsList
    Variable changeInDatsetsListRight = (V_Left + V_Width) - oldDatasetsListRight
   
    // Set Datasets Title
    top -= 20
    TitleBox DatasetsTitle, win=$browserName, pos={left, top}
   
    // Set Load Dataset Button
    PositionControlRelative(browserName, "LoadDataset", "DatasetsTitle", 1, 2, 20, 0)

    // Set Save Waves Button
    PositionControlRelative(browserName, "SaveWaves", "LoadDataset", 1, 2, 20, 0)

    // Set Members popup menu
    PositionControlRelative(browserName, "Members", "DatasetsTitle", 0, 2, 0, 0)
   
    // Set Dataset Attributes list coordinates
    left = leftBorder + groupListsWidth + hSpaceBetweenLists
    top = listsTop + groupListsHeight + vSpaceBetweenLists
    ListBox DatasetAttributesList, win=$browserName, pos={left, top}, size={datasetListsWidth, attributeListsHeight}
   
    top -= 20
    TitleBox DatasetAttributesTitle, win=$browserName, pos={left, top}

    // Set Preview Options
    String list = "PreviewOptions;Graph;Table;Dump;ShowAttributesInDump;ShowDataInDump;"
    OffsetControls(browserName, list, changeInDatsetsListRight, 0)

    // Set Load Dataset Options
    list = "LoadDatasetOptions;DisplayInTable;DisplayInGraph;"
    OffsetControls(browserName, list, changeInDatsetsListRight, 0)
   
    statusCode=1

    return statusCode   // 0 if nothing done, else 1 or 2
End

// ************* End of HDF5 Browser Resize Routines ***************

// ************* Start of HDF5 Browser Prefs Routines ***************

// HDF5 Browser preferences are stored on disk in the Packages directory
// in Igor's preferences directory. They are temporarily loaded into an
// HDF5BrowserPrefs data structure but are not stored permanently in memory.

// When a preference value has to be changed, the prefs data structure
// is loaded into memory, the value is changed and the data structure is
// immediately written back out to disk.
// To see where prefs data is changed, search for HDF5BrowserSavePackagePrefs.

static StrConstant kPackageName = "HDF5Browser" // NOTE: Package name must be distinctive!
static Constant kCurrentPrefsVersion = 100          // Changes to hundreds digit means incompatible prefs
static StrConstant kPrefFileName = "Preferences.bin"
static Constant kPrefRecordID = 0

Structure HDF5BrowserPrefs
    uint32 prefsVersion         // Preferences structure version number. 100 means 1.00.

    // Preview graph location in points. 0 means default.
    float graphLeft
    float graphTop
    float graphRight
    float graphBottom
       
    // Preview table location in points. 0 means default.
    float tableLeft
    float tableTop
    float tableRight
    float tableBottom
       
    // Dump notebook location in points. 0 means default.
    float dumpLeft
    float dumpTop
    float dumpRight
    float dumpBottom
           
    // Save Waves and Save Data Folder panel location in pixels. 0 means default.
    float savePanelLeft
    float savePanelTop
    float savePanelRight
    float savePanelBottom

    // HDF5 browser location in points. 0 means default.
    float browserLeft
    float browserTop
    float browserRight
    float browserBottom

    // Overall prefs
    uchar readOnly                      // Open file read only
    uchar reservedOverall[15]
   
    // Load prefs
    uchar loadGroupsRecursively     // Controls Load Group button
    uchar transpose2DDatasets       // Controls Load Dataset and Load Group buttons
    uchar reservedLoad[14]
   
    // Save prefs
    uchar saveGroupsRecursively     // Affects Save Data Folder
    uchar includeIgorAttributes     // Affects Save Waves and Save Data Folder
    uchar reservedSave[14]

    uint32 reserved[100]    // Reserved for future use
EndStructure

Function HDF5BrowserLoadPackagePrefs(prefs)
    STRUCT HDF5BrowserPrefs &prefs

    Variable i

    // This loads preferences from disk if they exist on disk.
    LoadPackagePreferences kPackageName, kPrefFileName, kPrefRecordID, prefs

    // If prefs not loaded or not valid, initialize them.
    if (V_flag!=0 || prefs.prefsVersion!=kCurrentPrefsVersion)
        prefs.prefsVersion = kCurrentPrefsVersion
       
        prefs.graphLeft = 0
        prefs.graphTop = 0
        prefs.graphRight = 0
        prefs.graphBottom = 0
           
        // Preview table location in points. 0 means default.
        prefs.tableLeft = 0
        prefs.tableTop = 0
        prefs.tableRight = 0
        prefs.tableBottom = 0
           
        // Dump notebook location in points. 0 means default.
        prefs.dumpLeft = 0
        prefs.dumpTop = 0
        prefs.dumpRight = 0
        prefs.dumpBottom = 0
               
        // Save Waves and Save Data Folder panel location in pixels. 0 means default.
        prefs.savePanelLeft = 0
        prefs.savePanelTop = 0
        prefs.savePanelRight = 0
        prefs.savePanelBottom = 0
   
        // HDF5 browser location in points. 0 means default.
        prefs.browserLeft = 0
        prefs.browserTop = 0
        prefs.browserRight = 0
        prefs.browserBottom = 0
   
        // Overall prefs
        prefs.readOnly = 1
        for(i=0; i<15; i+=1)
            prefs.reservedOverall[i] = 0
        endfor
       
        // Load prefs
        prefs.loadGroupsRecursively = 1
        prefs.transpose2DDatasets = 0
        for(i=0; i<14; i+=1)
            prefs.reservedLoad[i] = 0
        endfor
       
        // Save prefs
        prefs.saveGroupsRecursively = 1
        prefs.includeIgorAttributes = 1
        for(i=0; i<14; i+=1)
            prefs.reservedSave[i] = 0
        endfor
   
        for(i=0; i<100; i+=1)
            prefs.reserved[i] = 0
        endfor
       
        HDF5BrowserSavePackagePrefs(prefs)      // Create default prefs file.
    endif
End

Function HDF5BrowserSavePackagePrefs(prefs)
    STRUCT HDF5BrowserPrefs &prefs

    SavePackagePreferences kPackageName, kPrefFileName, kPrefRecordID, prefs
End

static Function GetPrefWindowCoords(windowName, left, top, right, bottom)
    String windowName
    Variable &left, &top, &right, &bottom

    STRUCT HDF5BrowserPrefs prefs
   
    HDF5BrowserLoadPackagePrefs(prefs)
   
    strswitch(windowName)
        case "HDF5BrowserGraph":
            left = prefs.graphLeft
            top = prefs.graphTop
            right = prefs.graphRight
            bottom = prefs.graphBottom
            break
       
        case "HDF5BrowserTable":
            left = prefs.tableLeft
            top = prefs.tableTop
            right = prefs.tableRight
            bottom = prefs.tableBottom
            break
       
        case "HDF5DumpNotebook":
            left = prefs.dumpLeft
            top = prefs.dumpTop
            right = prefs.dumpRight
            bottom = prefs.dumpBottom
            break
       
        case "HDF5SaveWavesPanel":
        case "HDF5SaveDataFolderPanel":
            left = prefs.savePanelLeft
            top = prefs.savePanelTop
            right = prefs.savePanelRight
            bottom = prefs.savePanelBottom
            break

        default:        // We want to get preferred coords for a new HDF5 browser.
            left = prefs.browserLeft
            top = prefs.browserTop
            right = prefs.browserRight
            bottom = prefs.browserBottom
            break
    endswitch
End

#if (Exists("PanelResolution") != 3)                // Igor7 has a PanelResolution function that Igor6 lacks
Static Function PanelResolution(wName)          // For compatibility with Igor 7
    String wName
    return 72
End
#endif

static Function SetPrefWindowCoords(windowName)
    String windowName
   
    STRUCT HDF5BrowserPrefs prefs
   
    HDF5BrowserLoadPackagePrefs(prefs)
   
    GetWindow $windowName wSize

    // NewPanel uses device coordinates. We therefore need to scale from
    // points (returned by GetWindow) to device units for windows created
    // by NewPanel.
    Variable scale = PanelResolution(windowName) / 72
   
    strswitch(windowName)
        case "HDF5BrowserGraph":
            prefs.graphLeft = V_Left
            prefs.graphTop = V_Top
            prefs.graphRight = V_Right
            prefs.graphBottom = V_Bottom
            break
       
        case "HDF5BrowserTable":
            prefs.tableLeft = V_left
            prefs.tableTop = V_top
            prefs.tableRight = V_Right
            prefs.tableBottom = V_Bottom
            break
       
        case "HDF5DumpNotebook":
            prefs.dumpLeft = V_left
            prefs.dumpTop = V_top
            prefs.dumpRight = V_Right
            prefs.dumpBottom = V_Bottom
            break
           
        case "HDF5SaveWavesPanel":
        case "HDF5SaveDataFolderPanel":
            prefs.savePanelLeft = V_left * scale
            prefs.savePanelTop = V_top * scale
            prefs.savePanelRight = V_Right * scale
            prefs.savePanelBottom = V_Bottom * scale
            break

        default:        // We want to set preferred coords for a new HDF5 browser.
            prefs.browserLeft = V_left * scale
            prefs.browserTop = V_top * scale
            prefs.browserRight = V_Right * scale
            prefs.browserBottom = V_Bottom * scale
            break
    endswitch

    HDF5BrowserSavePackagePrefs(prefs)
End

static Function GetPrefBrowserSettings(readOnly, loadGroupsRecursively, transpose2DDatasets)
    Variable &readOnly
    Variable &loadGroupsRecursively
    Variable &transpose2DDatasets

    STRUCT HDF5BrowserPrefs prefs
   
    HDF5BrowserLoadPackagePrefs(prefs)

    readOnly = prefs.readOnly
    loadGroupsRecursively = prefs.loadGroupsRecursively
    transpose2DDatasets = prefs.transpose2DDatasets
End

// ************* End of HDF5 Browser Prefs Routines ***************

// ************* Start of HDF5 Utility Routines ***************

Function HDF5CheckDataClass(dataClassStr, errorMessage)
    String dataClassStr
    String &errorMessage
   
    Variable err = 0
    errorMessage = ""

    strswitch(dataClassStr)
        case "H5T_TIME":
            errorMessage = "HDF5XOP does not support data of class H5T_TIME."
            err = -1
            break
    endswitch

    return err
End

Function HDF5MakeHyperslabWave(path, numRows)
    String path         // Path to wave. e.g., "root:slab"
    Variable numRows
   
    Make /O /N=(numRows,4) $path
    Wave slab = $path
    slab = 1                                // Set all elements to 1.

    Variable row
    String dimLabel
    for(row=0; row<numRows; row+=1)
        sprintf dimLabel, "Dimension %d", row       // HR, 060206: Fixed setting of row dimension labels.
        SetDimLabel 0, row, $dimLabel, slab
    endfor
    SetDimLabel 1, 0, Start, slab
    SetDimLabel 1, 1, Stride, slab
    SetDimLabel 1, 2, Count, slab
    SetDimLabel 1, 3, Block, slab
End

Constant kHDF5DataInfoVersion = 1000        // 1000 means 1.000.
Structure HDF5DataInfo                      // Use with HDF5DatasetInfo and HDF5AttributeInfo functions
    // Input fields (inputs to HDF5 XOP)
    uint32 version                          // Must be set to kHDF5DataInfoVersion
    char structName[16]                 // Must be "HDF5DataInfo".

    // Output fields (outputs from HDF5 XOP)
    double datatype_class;                  // e.g., H5T_INTEGER, H5T_FLOAT.
    char datatype_class_str[32];            // String with class spelled out. e.g., "H5T_INTEGER", "H5T_FLOAT".
    double datatype_size;                   // Size in bytes of one element.
    double datatype_sign;                   // H5T_SGN_NONE (unsigned), H5T_SGN_2 (signed), H5T_SGN_ERROR (this type does not have a sign, i.e., it is not an integer type).
    double datatype_order;                  // H5T_ORDER_LE, H5T_ORDER_BE, H5T_ORDER_VAX
    char datatype_str[64];                  // Human-readable string, e.g., "16-bit unsigned integer"
    double dataspace_type;                  // H5S_NO_CLASS, H5S_SCALAR, H5S_SIMPLE
    double ndims;                           // Zero for H5S_SCALAR. Number of dimensions in the dataset for H5S_SIMPLE.
    double dims[H5S_MAX_RANK];          // Size of each dimension.
    double maxdims[H5S_MAX_RANK];       // Maximum size of each dimension.
EndStructure

Function InitHDF5DataInfo(di)               // Sets input fields.
    STRUCT HDF5DataInfo &di
   
    // HDF5XOP uses these fields to make sure the structure passed in to it is compatible.
    di.version = kHDF5DataInfoVersion
    di.structName = "HDF5DataInfo"
End

//  HDF5DatasetRank(locationID, name)
//  Returns rank or zero in event of error.
Function HDF5DatasetRank(locationID, name)
    Variable locationID
    String name

    STRUCT HDF5DataInfo di
    InitHDF5DataInfo(di)            // Set input fields.

    Variable err = HDF5DatasetInfo(locationID, name, 1, di)
    if (err != 0)
        return 0
    endif
    Variable rank = di.ndims
    return rank
End

//  HDF5AttributeRank(locationID, name)
//  Returns rank or zero in event of error.
Function HDF5AttributeRank(locationID, objectName, objectType, attributeName)
    Variable locationID
    String objectName
    Variable objectType
    String attributeName

    STRUCT HDF5DataInfo di
    InitHDF5DataInfo(di)            // Set input fields.

    Variable err = HDF5AttributeInfo(locationID, objectName, objectType, attributeName, 1, di)
    if (err != 0)
        return 0
    endif
    Variable rank = di.ndims
    return rank
End

Constant kHDF5DatatypeInfoVersion = 1000        // 1000 means 1.000.
Structure  HDF5DatatypeInfo                         // Use with HDF5TypeInfo functions
    // Input fields (inputs to HDF5 XOP)
    uint32 version                  // Structure version. Used for backward compatibility.
    char structName[32]         // Must be "HDF5DatatypeInfo". Used to prevent passing wrong structure to XFUNC.
   
    // Output fields (outputs from HDF5 XOP)
    double type_class               // e.g., H5T_INTEGER, H5T_FLOAT.
    char type_class_str[32]         // String with class spelled out. e.g., "H5T_INTEGER", "H5T_FLOAT".
    double size                     // Size in bytes of one element.
    double sign                     // H5T_SGN_NONE (unsigned), H5T_SGN_2 (signed), H5T_SGN_ERROR (this type does not have a sign, i.e., it is not an integer type).
    double order                    // H5T_ORDER_LE, H5T_ORDER_BE, H5T_ORDER_VAX, H5T_ORDER_ERROR (this type does not have an order).
    double cset                     // H5T_CSET_ASCII, H5T_CSET_UTF8, H5T_CSET_ERROR
    double strpad                   // H5T_str_t: H5T_STR_ERROR, H5T_STR_NULLTERM, H5T_STR_NULLPAD, H5T_STR_SPACEPAD
    double nmembers             // For enum or compound datatypes only, number of members.
    String names                    // For enum or compound datatypes only, semicolon-separated list of enum names.
    int32 values[100]               // For enum datatype only, list of enum values. For compound datatype, list of classes.
    String opaque_tag               // For opaque datatypes only, tag name.
EndStructure

Function InitHDF5DatatypeInfo(dti)          // Sets input fields.
    STRUCT HDF5DatatypeInfo &dti
   
    // HDF5XOP uses these fields to make sure the structure passed in to it is compatible.
    dti.version = kHDF5DatatypeInfoVersion
    dti.structName = "HDF5DatatypeInfo"
End

// ************* End of HDF5 Utility Routines ***************

// ************* Start of HDF5 Save Routines ***************

static Function StringsAreEqual(str1, str2) // Case sensitive
    String str1, str2
   
    Variable len1=strlen(str1), len2=strlen(str2)
    if (len1 != len2)
        return 0
    endif
   
    Variable i
    for(i=0; i<len1; i+=1)
        if (char2num(str1[i]) != char2num(str2[i]))
            return 0
        endif
    endfor
   
    return 1
End

static Function/S GetUnquotedLeafName(path)
    String path         // Path to data folder or wave
   
    String name
    name = ParseFilePath(0, path, ":", 1, 0)        // Just the name without path.
   
    // Remove single quotes if present
    if (CmpStr(name[0],"'") == 0)
        Variable len = strlen(name)
        name = name[1,len-2]   
    endif
   
    return name
End

static Function HaveObjectNameConflict(listOfObjectsInGroup, listOfObjectsToBeSaved, conflictingObjectName)
    String listOfObjectsInGroup         // Semicolon-separated list of all types of objects in selected group or list of datasets in selected group.
    String listOfObjectsToBeSaved       // Semicolon-separated list of names of objects about to be saved in the HDF5 file.
    String &conflictingObjectName
   
    conflictingObjectName = ""
   
    Variable i, j
    Variable numObjectsInList, numObjectsToBeSaved
   
    numObjectsInList = ItemsInList(listOfObjectsInGroup)
    numObjectsToBeSaved = ItemsInList(listOfObjectsToBeSaved)
    for(i=0; i<numObjectsToBeSaved; i+=1)
        String objectToBeSavedName = StringFromList(i, listOfObjectsToBeSaved)
        objectToBeSavedName = GetUnquotedLeafName(objectToBeSavedName)      // Just the name without path.
        if (CmpStr(objectToBeSavedName, "root") == 0)
            objectToBeSavedName = IgorInfo(1)       // Use name of current experiment instead of "root".
        endif
        for(j=0; j<numObjectsInList; j+=1)
            String groupObjectName = StringFromList(j, listOfObjectsInGroup)
            if (StringsAreEqual(groupObjectName,objectToBeSavedName))
                conflictingObjectName = groupObjectName
                return 1
            endif
        endfor
    endfor

    return 0
End

static Function SaveButtonProc(ctrlName) : ButtonControl
    String ctrlName
   
    String panelName = WinName(0, 64)
    String message
   
    String list = WS_SelectedObjectsList(panelName, "SelectorList")
    if (strlen(list) == 0)
        strswitch(panelName)
            case "HDF5SaveWavesPanel":
                DoAlert 0, "You must select one or more waves to save first."
                break
               
            case "HDF5SaveDataFolderPanel":
                DoAlert 0, "You must select a data folder to save first."
                break
        endswitch
        return -1
    endif
   
    String browserName = HDF5GetTopBrowserName()
    if (strlen(browserName) == 0)
        return -1                               // HDF5 Browser was killed.
    endif

    STRUCT HDF5BrowserData bd
    SetHDF5BrowserData(browserName, bd)
   
    // Get list of all types of objects in the selected group (including named datasets and links)
    HDF5ListGroup /TYPE=15 bd.fileID, bd.groupPath
    String listOfObjectsInSelectedGroup = S_HDF5ListGroup

    Variable haveConflict
    String conflictingObjectName
    haveConflict = HaveObjectNameConflict(listOfObjectsInSelectedGroup,list,conflictingObjectName)
    if (haveConflict)
        sprintf message, "The name '%s' is in use.\r\rOverwrite objects with conflicting names?", conflictingObjectName
        DoAlert 1, message
        if (V_flag != 1)
            return -1
        endif
    endif

    ControlInfo /W=$panelName IncludeIgorAttributes
    Variable igorAttributesMask = V_value ? -1 : 0
    Variable varMode = V_value ? 1 : 0
   
    String groupPath = SelectedGroupPath(bd)    // Currently selected group
    String newGroupPath = ""                            // Name of group we created, if any.
   
    Variable index = 0
    do
        String item = StringFromList(index, list)
        if (strlen(item) == 0)
            break                                   // No more waves
        endif
       
        strswitch(panelName)
            case "HDF5SaveWavesPanel":
                Wave w = $item
                String datasetPath = HDF5GetObjectFullPath(groupPath, NameOfWave(w))
                HDF5SaveData /IGOR=(igorAttributesMask) /O w, bd.fileID, datasetPath
                break
               
            case "HDF5SaveDataFolderPanel":
                String dfName = ParseFilePath(0, item, ":", 1, 0)       // Just the data folder name without path.
                if (CmpStr(item, "root") == 0)
                    dfName = IgorInfo(1)        // Use name of current experiment instead of "root".
                endif
                newGroupPath = HDF5GetObjectFullPath(groupPath, dfName)

                ControlInfo/W=$panelName SaveGroupsRecursively
                if (V_value)
                    HDF5SaveGroup /IGOR=(igorAttributesMask) /VAR=(varMode) /O /R /T=dfName $item, bd.fileID, groupPath
                else
                    HDF5SaveGroup /IGOR=(igorAttributesMask) /VAR=(varMode) /O /T=dfName $item, bd.fileID, groupPath
                endif
                break
        endswitch
       
        if (V_flag != 0)
            break                                   // Save error.
        endif
       
        index += 1
    while(1)
   
    strswitch(panelName)
        case "HDF5SaveWavesPanel":
            FillDatasetsList(bd)
            FillDatasetAttributesList(bd)
            SetButtonStates(bd)             // Needed to set Load Dataset button if we go from 0 datasets to >0 datasets.
            break
           
        case "HDF5SaveDataFolderPanel":
            if (strlen(newGroupPath) > 0)
                FillLists(bd)
            endif
            break
    endswitch
End

static Function DoneButtonProc(ctrlName) : ButtonControl
    String ctrlName

    String panelName = WinName(0, 64)
    DoWindow/K $panelName
End

static Function GetPrefSavePanelSettings(saveGroupsRecursively, includeIgorAttributes)
    Variable &saveGroupsRecursively
    Variable &includeIgorAttributes

    STRUCT HDF5BrowserPrefs prefs
   
    HDF5BrowserLoadPackagePrefs(prefs)

    saveGroupsRecursively = prefs.saveGroupsRecursively
    includeIgorAttributes = prefs.includeIgorAttributes
End

static Function SetPrefSavePanelSettings(panelName)
    String panelName
   
    STRUCT HDF5BrowserPrefs prefs
   
    HDF5BrowserLoadPackagePrefs(prefs)
   
    strswitch(panelName)
        case "HDF5SaveWavesPanel":
            ControlInfo/W=$panelName IncludeIgorAttributes
            prefs.includeIgorAttributes = V_value
            break
   
        case "HDF5SaveDataFolderPanel":
            ControlInfo/W=$panelName SaveGroupsRecursively
            prefs.saveGroupsRecursively = V_value
            ControlInfo/W=$panelName IncludeIgorAttributes
            prefs.includeIgorAttributes = V_value
            break
    endswitch

    HDF5BrowserSavePackagePrefs(prefs)
End

static Function SetSaveButtonState(panelName)
    String panelName
   
    String selection = WS_SelectedObjectsList(panelName, "SelectorList")
    Variable code = strlen(selection) > 0 ? 0:2
    Button Save, win=$panelName, disable=code  
End

Function HDF5SaveWavesPanelHook(infoStr)
    String infoStr
   
    String panelName = "HDF5SaveWavesPanel"

    String event= StringByKey("EVENT",infoStr)

    strswitch(event)
        case "activate":                // We do not get this on Windows when the panel is first created.
            SetSaveButtonState(panelName)
            break
           
        case "resize":
        case "moved":                   // This message was added in Igor Pro 5.04B07.
            SetPrefWindowCoords(panelName)
            break
    endswitch
   
    return 0
End

Function HDF5SaveDataFolderPanelHook(infoStr)
    String infoStr
   
    String panelName = "HDF5SaveDataFolderPanel"

    String event= StringByKey("EVENT",infoStr)

    strswitch(event)
        case "activate":                // We do not get this on Windows when the panel is first created.
            SetSaveButtonState(panelName)
            break
           
        case "resize":
        case "moved":                   // This message was added in Igor Pro 5.04B07.
            SetPrefWindowCoords(panelName)
            break
    endswitch
   
    return 0
End

static Function DisplaySaveWavesPanel()
    String panelName = "HDF5SaveWavesPanel"

    DoWindow/F $panelName
    if (V_flag == 0)
        Variable left, top, right, bottom
        GetPrefWindowCoords(panelName, left, top, right, bottom)    // See if prefs set.
        if (right-left<200 || bottom-top<200)
            left = 200
            top = 100
            right = 584
            bottom = 632
        endif
       
        Variable recursive, includeIgorAttributes
        GetPrefSavePanelSettings(recursive, includeIgorAttributes)

        Variable showWhat = WMWS_Waves
        NewPanel /W=(left,top,right,bottom) /N=$panelName /K=1 as "Save Waves as HDF5 Datasets"
        TitleBox ListTitle,pos={20,16},size={163,16},title="Select Wave(s) to Save as Datasets"
        TitleBox ListTitle,fSize=14,frame=0,fStyle=1
        ListBox SelectorList,pos={16,48},size={350,390},mode=4  // Multiple disjoint selection allowed.
        MakeListIntoWaveSelector(panelName, "SelectorList", content=showWhat)
        WS_SetNotificationProc(panelName, "SelectorList", "SelectorNotification", isExtendedProc=1)
        Button Save,pos={46,457},size={100,20},proc=HDF5Browser#SaveButtonProc,title="Save"
        Button Done,pos={226,457},size={100,20},proc=HDF5Browser#DoneButtonProc,title="Done"
        CheckBox IncludeIgorAttributes,pos={36,488},size={121,14},title="Include Igor Attributes"
        CheckBox IncludeIgorAttributes,proc=HDF5BrowserPrefCheckboxProc,help={"When checked, attributes are written so that wave properties can be recreated when loading back into Igor."}
        CheckBox IncludeIgorAttributes,value=includeIgorAttributes
        SetSaveButtonState(panelName)
        SetWindow kwTopWin,hook=HDF5SaveWavesPanelHook
    endif
End

static Function DisplaySaveDataFolderPanel()
    String panelName = "HDF5SaveDataFolderPanel"

    DoWindow/F $panelName
    if (V_flag == 0)
        Variable left, top, right, bottom
        GetPrefWindowCoords(panelName, left, top, right, bottom)    // See if prefs set.
        if (right-left<200 || bottom-top<200)
            left = 200
            top = 100
            right = 584
            bottom = 632
        endif
       
        Variable recursive, includeIgorAttributes
        GetPrefSavePanelSettings(recursive, includeIgorAttributes)

        Variable showWhat = WMWS_DataFolders
        NewPanel /W=(left,top,right,bottom) /N=$panelName /K=1 as "Save Data Folder as HDF5 Group"
        TitleBox ListTitle,pos={20,16},size={163,16},title="Select Data Folder to Save as Group"
        TitleBox ListTitle,fSize=14,frame=0,fStyle=1
        ListBox SelectorList,pos={16,48},size={350,390},mode=1      // Single selection only.
        MakeListIntoWaveSelector(panelName, "SelectorList", content=showWhat)
        WS_SetNotificationProc(panelName, "SelectorList", "SelectorNotification", isExtendedProc=1)
        Button Save,pos={46,457},size={100,20},proc=HDF5Browser#SaveButtonProc,title="Save"
        Button Done,pos={226,457},size={100,20},proc=HDF5Browser#DoneButtonProc,title="Done"
        CheckBox SaveGroupsRecursively,pos={44,485},size={66,14},title="Save Groups Recursive",value=recursive
        CheckBox SaveGroupsRecursively,proc=HDF5BrowserPrefCheckboxProc,help={"When checked, sub-data folders are recursively saved."}
        CheckBox IncludeIgorAttributes,pos={44,507},size={121,14},title="Include Igor Attributes"
        CheckBox IncludeIgorAttributes,proc=HDF5BrowserPrefCheckboxProc,help={"When checked, attributes are written so that wave properties can be recreated when loading back into Igor."}
        CheckBox IncludeIgorAttributes,value=includeIgorAttributes
        SetSaveButtonState(panelName)
        SetWindow kwTopWin,hook=HDF5SaveDataFolderPanelHook
    endif
End

Function SelectorNotification(SelectedItem, EventCode, panelName, controlName)
    String SelectedItem
    Variable EventCode
    String panelName
    String controlName
   
    // Printf "Panel=%s, Control=%s, Event code=%d, selection=\"%s\"\r", panelName, controlName, eventCode, selectedItem

    switch(eventCode)
        case WMWS_DoubleClick:
                break
   
        case WMWS_FolderOpened:             // Selection is emptied when folder is opened.
        case WMWS_FolderClosed:             // Selection is emptied when folder is opened.
        case WMWS_SelectionChanged:
        case WMWS_SelectionChangedShift:
            SetSaveButtonState(panelName)
            break
    endswitch
End

static Function SaveWavesButtonProc(ctrlName) : ButtonControl
    String ctrlName
   
    DoWindow/K HDF5SaveDataFolderPanel  // One save panel open at a time.
    DisplaySaveWavesPanel()
   
    return 0
End

static Function SaveDataFolderButtonProc(ctrlName) : ButtonControl
    String ctrlName
   
    DoWindow/K HDF5SaveWavesPanel   // One save panel open at a time.
    DisplaySaveDataFolderPanel()
   
    return 0
End

static Function CloseSavePanels()
    DoWindow/K HDF5SaveWavesPanel
    DoWindow/K HDF5SaveDataFolderPanel
End

static Function HDF5SaveWavesPanelIsVisible()
    DoWindow HDF5SaveWavesPanel
    return V_flag
End

static Function HDF5SaveDFPanelIsVisible()
    DoWindow HDF5SaveDataFolderPanel
    return V_flag
End

// ************* End of HDF5 Save Routines ***************

 

I want to implement it into my own code what are the things that I need to change.

 

That huge code-dump is WaveMetrics code that ships with Igor.

You have not explained to us what your aim is. Why do you need to change it at all? It's good code, I know the author well and he writes good code.

To improve your chances of getting an answer, here are some tips:

http://stackoverflow.com/help/how-to-ask

dear johnweeks,

I had posted my problems regarding not able to call the HDF5 Load/ Open Group/Load Data due to some simple errors which i had posted previously but due to lack of community support I had to start looking around for my self then I had hit a technical snag and IGOR threw me the Errors iin  the HDF5 Browser code so now i think that i shall change some features of the browser code for my own.

Here is the Link for my previous question:https://www.wavemetrics.com/forum/general/hdf5-issues-when-trying-open-and-load-it-through-program

ps No offense.

Please take no offense in this, but your above post comes over as somewhat aggressive. I would recommend you have a good look at the link John posted. In short:

- You are not explaining what you want to do exactly.

- You are not explaining what 'is not working' for you.

- Instead you just post dumps of code and many posts in a row without the patience to wait for answers.

How do you expect that anybody can help you this way? If you want help, please make everybody's life easier in providing the necessary (and not unnecessary) information to help in the first place. And please note that many users do not get paid and are not ready to answer any question 24/7. I have some experience in programming with HDF5 files and have read your last post. But I saw no way of helping you, short of recreating the type of data you are using and writing a test environment for testing every case what might be wrong without reading your mind. I hope there will be a way to solve your problems, which we are all interested in (and that's what the forum is for).

In reply to by chozo

well I had clearly stated that "I just want to load HDF5 files in IGOR through a computer program written in IGOR" . I never asked for an analysis code so why do I need to post a set of information pertaining to data and recreating testing environment. open and loading a specific type of file consists of every possible case if browser can do it, then HDF5LoadData call,HDF5OpenGroup,HDF5OpenFile shall satisfy the same purpose, isn't it?

ps: no offense.