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.