#pragma TextEncoding = "UTF-8"
#pragma rtGlobals=3		// Use modern global access method and strict wave access.
#pragma IgorVersion = 6.36
#pragma ModuleName = PearlDataExplorer
#pragma version = 2.2
#include <HierarchicalListWidget>, version >= 1.14
#include "pearl-area-import"
#include "pearl-area-profiles"
#include "pearl-area-display"
#include "pearl-compat"
#include "pearl-pshell-import"

// copyright (c) 2013-25 Paul Scherrer Institut
//
// Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//   http:///www.apache.org/licenses/LICENSE-2.0

/// @file
/// @brief preview and import panel for PEARL data
/// @ingroup ArpesPackage
///
///
/// preview and import panel for PEARL data:
/// @arg area detector (HDF5) files from scienta analyser and prosilica cameras (if HDF5.xop is installed).
/// @arg igor text files from s-scans and otf-scans.
/// @arg pshell (HDF5) data files (if HDF5.xop is installed).

/// @namespace PearlDataExplorer
/// @brief preview and import panel for PEARL data
///
/// PearlDataExplorer is declared in @ref pearl-data-explorer.ipf.

static strconstant package_name = "pearl_explorer"
static strconstant package_path = "root:packages:pearl_explorer:"

static strconstant ks_filematch_adh5 = "*.h5"
static strconstant ks_filematch_pshell = "psh*.h5"
static strconstant ks_filematch_itx = "*.itx"

/// show the pearl data explorer window
///
/// create a pearl data explorer window or bring the existing one to the front.
/// if a new window is created, also initialize all package variables and load package preferences.
///
function pearl_data_explorer()
	DoWindow /HIDE=0 /F PearlDataExplorer
	if (v_flag == 0)
		init_package()
		load_prefs()
		execute /q/z "PearlDataExplorer()"
		MakeListIntoHierarchicalList("PearlDataExplorer", "lb_contents", "PearlDataExplorer#hlp_contents_open", selectionMode=WMHL_SelectionNonContinguous, pathSeparator="/")
		WMHL_AddColumns("PearlDataExplorer", "lb_contents", 1)
		WMHL_SetNotificationProc("PearlDataExplorer", "lb_contents", "PearlDataExplorer#hlp_contents_selection", WMHL_SetSelectNotificationProc)
		ListBox lb_contents win=PearlDataExplorer, widths={6,60,20}
		update_controls()
	endif
end

static function init_package()
	dfref save_df = GetDataFolderDFR()
	SetDataFolder root:
	newdatafolder /o/s packages
	newdatafolder /o/s $package_name
	if (exists("v_InitPanelDone") == 2)
	 	SetDataFolder save_df
		return 0
	endif
	
	make /o/n=0/t wtFiles
	make /o/n=0/i wSelectedFiles,wSelectedDatasets
	make /o/n=0/t wtDatasets
	make /o/n=0/t wtPositioners,wtDetectors
	make /o/n=0/i wSelectedPositioners,wSelectedDetectors
	
	make /o/n=(1,1) preview_image // preview 2D data
	make /o/n=0 preview_trace // preview 1D data
	make /o/n=0/t attr_names, attr_values, attr_filter, attr_filter_summary

	// persistent strings and variables. persistent = saved in preferences
	string /g s_filepath = "" // directory path to be listed
	string /g s_hdf_options = "" // recently used HDF5 load options
	string /g s_reduction_params = "" // recently used reduction parameters
	string /g s_preview_pvs = "" // semicolon-separated list of EPICS PVs to display in preview.
		// the list items can contain wildcards for StringMatch
	s_preview_pvs = "*OP:CURRENT*;*Stats*Total*;*KEITHLEY*READOUT;*CADC*"

	redimension /n=26 attr_filter_summary
	attr_filter_summary[0] = "MonoEnergy"
	attr_filter_summary[1] = "MonoGrating"
	attr_filter_summary[2] = "ExitSlit"
	attr_filter_summary[3] = "FrontendHSize"
	attr_filter_summary[4] = "FrontendVSize"
	attr_filter_summary[5] = "ManipulatorPhi"
	attr_filter_summary[6] = "ManipulatorTheta"
	attr_filter_summary[7] = "ManipulatorTilt"
	attr_filter_summary[8] = "ManipulatorX"
	attr_filter_summary[9] = "ManipulatorY"
	attr_filter_summary[10] = "ManipulatorZ"
	attr_filter_summary[11] = "PassEnergy"
	attr_filter_summary[12] = "LensMode"
	attr_filter_summary[13] = "ScientaDwellTime"
	attr_filter_summary[14] = "ScientaCenterEnergy"
	attr_filter_summary[15] = "ScientaChannelBegin"
	attr_filter_summary[16] = "ScientaChannelEnd"
	attr_filter_summary[17] = "ScientaSliceBegin"
	attr_filter_summary[18] = "ScientaSliceEnd"
	attr_filter_summary[19] = "ScientaNumChannels"
	attr_filter_summary[20] = "StepSize"
	attr_filter_summary[21] = "ScientaNumSlices"
	attr_filter_summary[22] = "ManipulatorTempA"
	attr_filter_summary[23] = "ManipulatorTempB"
	attr_filter_summary[24] = "RefCurrent"
	attr_filter_summary[25] = "SampleCurrent"
	
	// non-persistent strings and variables
	string /g s_short_filepath = "" // abbreviated directory path
	string /g s_selected_file = ""
	string /g s_selected_dataset = ""
	string /g s_preview_file = "" // file or folder name of the current preview
	string /g s_preview_source = "" // data source, e.g. EPICS channel name, of the current preview
	string /g s_profiles_graph = "" // window name of the current preview if the data is two-dimensional
	string /g s_preview_trace_graph = "" // window name of the current preview if the data is one-dimensional
	string /g s_preview_graph = "" // window name of the most recent preview graph
	string /g s_file_info = "" // description of selected file
	string /g s_result = "" // result of last operation
	
	variable/g v_InitPanelDone = 1
	
 	SetDataFolder save_df
end

/// save persistent package data to the preferences file.
///
/// this function is called when the user clicks the corresponding button.
/// the data saved in the file are: data file path, attributes filter
///
static function save_prefs()
	dfref save_df = GetDataFolderDFR()
	dfref df = $package_path
	if (DataFolderRefStatus(df) == 1)
		string fullPath = SpecialDirPath("Packages", 0, 0, 0)
		fullPath += package_name
		NewPath/O/C/Q tempPackagePrefsPath, fullPath
		fullPath += ":preferences.pxp"
		SetDataFolder df
		string objects = "attr_filter;attr_filter_summary;s_filepath;s_hdf_options;s_reduction_params;s_preview_pvs"
		SaveData /O /Q /J=objects fullPath
		KillPath/Z tempPackagePrefsPath
	endif
	SetDataFolder save_df
end

static function load_prefs()
	// loads persistent package data from the preferences file
	// the preferences file is an Igor packed experiment file in a special preferences folder
	dfref save_df = GetDataFolderDFR()

	variable result = -1
	setdatafolder root:
	NewDataFolder /O/S packages
	NewDataFolder /O/S $package_name
	dfref package_df = GetDataFolderDFR()
	string fullPath = SpecialDirPath("Packages", 0, 0, 0)
	fullPath += package_name

	GetFileFolderInfo /Q /Z fullPath
	if (V_Flag == 0) // Disk directory exists?
		fullPath += ":preferences.pxp"
		GetFileFolderInfo /Q /Z fullPath
		if (V_Flag == 0) // Preference file exist?
			LoadData /O /R /Q fullPath
			result = 0
		endif
	endif
	
	if (result == 0)
		svar /sdfr=package_df filepath = s_filepath
		NewPath /O/Z pearl_explorer_filepath, filepath
		update_filepath()
		update_filelist()
	endif
	
 	SetDataFolder save_df
	return result
end

/// check whether a file can be imported by this module.
///
/// the file type is determined by the extension of the file name.
///
/// @return file type
///	@arg 0	not a recognized file type
///	@arg 1	PShell file (HDF5, name starts with psh_)
///	@arg 2	area detector HDF5 file
///	@arg 3	Igor text (itx) file
///
static function pearl_file_type(filename)
	string filename
	
	if (StringMatch(filename, ks_filematch_pshell))
		return 1
	elseif (StringMatch(filename, ks_filematch_adh5))
		return 2
	elseif (StringMatch(filename, ks_filematch_itx))
		return 3
	else
		return 0
	endif
end

/// update the file path after path change
///
/// read the path info from pearl_explorer_filepath
/// and update the path control
///
static function update_filepath()
	PathInfo /S pearl_explorer_filepath
	svar filepath = $(package_path + "s_filepath")
	svar shortpath = $(package_path + "s_short_filepath")
	filepath = s_path
	shortpath = shorten_filepath(filepath, 40)
end

/// read a list of PEARL files from the file system
///
/// wtFiles and wSelectedFiles in the package data folder are updated.
/// only files for which pearl_file_type() returns non-zero are listed.
///
static function update_filelist()
	dfref save_df = GetDataFolderDFR()

	string all_files
	wave /t wtFiles = $(package_path + "wtFiles")
	wave wSelectedFiles = $(package_path + "wSelectedFiles")
	variable nn
	
	PathInfo pearl_explorer_filepath
	if (v_flag == 1)
		all_files = IndexedFile(pearl_explorer_filepath, -1, "????")
		nn = ItemsInList(all_files)
	else
		all_files = ""
		nn = 0
	endif
	
	make /n=(nn) /t /free wtAllFiles
	wtAllFiles = StringFromList(p, all_files)
	Extract /o /t wtAllFiles, wtFiles, pearl_file_type(wtAllFiles[p])
	Sort /A /R wtFiles, wtFiles
	
	redimension /n=(numpnts(wtFiles)) wSelectedFiles
	wSelectedFiles = 0
	
	setdatafolder save_df
end

// ====== metadata ======

/// load the internal structure of a file
///
/// this loads metadata for updating the panels.
///
/// for a pshell file, metadata includes:
/// - list of all datasets with types and dimensions
/// - general group
///
/// @return	0 if successful
///				-1 if no data was loaded because the file was not recognized, 
///				-2 if no data is found in file
///
static function get_file_info(filename)
	string filename
	
	dfref save_df = GetDataFolderDFR()
	dfref package_df = $package_path
	
	variable ft = pearl_file_type(filename)
	variable result = 0
	
	switch(ft)
		case 1:
		case 2:
			dfref file_df = get_pshell_info("pearl_explorer_filepath", filename)
			result = hl_contents_update(file_df)
			result = result >= 3 ? 0 : -2
			break
		default:
			hl_contents_clear()
			dfref file_df = package_df:file_info
			KillDataFolder /z file_df
			result = -1
	endswitch
	
	setdatafolder save_df
	return result
end

/// load attributes
static function attributes_notebook(filename)
	string filename
	
	dfref save_df = GetDataFolderDFR()
	dfref temp_df = NewFreeDataFolder()
	
	load_file(filename, options="mode:load_diags", dest_df=temp_df, quiet=1)
	svar /sdfr=temp_df /z s_loaded_datasets
	string scan
	dfref scan_df
	if (SVAR_Exists(s_loaded_datasets) && (strlen(s_loaded_datasets) >= 4))
		scan = StringFromList(0, psh5_extract_scan_paths(s_loaded_datasets), ";")
		scan_df = psh5_dataset_to_folder(temp_df, scan)
	else
		scan_df = temp_df
	endif
	
	dfref attr_df = ps_find_attr_folder(scan_df)
	if (DataFolderRefStatus(attr_df))
		extract_attributes(attr_df, dest_df=temp_df)
		wave /t /sdfr=temp_df /z attr_names
		wave /t /sdfr=temp_df /z attr_values
		if (WaveExists(attr_names) && WaveExists(attr_values))
			create_attributes_notebook(attr_names, attr_values, filename)
		endif
	endif
	
	setdatafolder save_df
end

/// extract summary from attribute waves
///
/// by default, all existing attributes are copied.
/// if a text wave attr_filter exists in the pear_explorer folder, only the attributes referenced therein are copied.
/// to set up a filter, duplicate the attr_names wave of a template dataset, and remove unwanted items.
///
/// @param attr_df					data folder which contains the original data, e.g. the attr, diags or snaps folder in pshell files.
/// @param dest_df					destination folder. the output is written to the attr_names and attr_values waves.
///										default = package folder.
/// @param attr_filter				(text wave) list of attributes allowed in the output.
///										default = use attr_filter of package folder.
/// @param include_datawaves		@arg 1 (default) include data waves (any numeric wave which has a PV=name note).
///										@arg 0 don't include attributes from data waves.
/// @param include_infowaves		@arg 1 (default) include attributes from info waves (IN, ID, IV, IU).
///										@arg 0 don't include attributes from info waves.
///
static function extract_attributes(attr_df, [dest_df, attr_filter, include_datawaves, include_infowaves])
	dfref attr_df
	dfref dest_df
	wave /t attr_filter
	variable include_datawaves
	variable include_infowaves
	
	dfref save_df = GetDataFolderDFR()
	dfref package_df = $package_path
	
	if (ParamIsDefault(dest_df))
		dest_df = GetDataFolderDFR()
	endif
	if (ParamIsDefault(attr_filter) || !WaveExists(attr_filter))
		wave /t /sdfr=package_df /z attr_filter
	endif
	if (ParamIsDefault(include_datawaves))
		include_datawaves = 1
	endif
	if (ParamIsDefault(include_infowaves))
		include_infowaves = 1
	endif
	
	setdatafolder dest_df
	wave /t /z attr_names, attr_values
	if (!WaveExists(attr_names) || !WaveExists(attr_values))
		make /n=(1) /o /t attr_names, attr_values
	endif
	attr_names = ""
	attr_values = ""
	
	setdatafolder attr_df
	wave /t /z IN
	wave /t /z ID
	wave /t /z IV
	wave /t /z IU

	// compile list of attributes
	variable nattr	// destination attributes
	variable iattr
	variable ninfo // info wave elements
	variable iinfo
	variable nw	// attribute waves
	variable iw
	string sw
	string ss

	if (WaveExists(IN) && include_infowaves)
		ninfo = numpnts(IN)
	else
		ninfo = 0
	endif
	if (include_datawaves)
		string waves = WaveList("*", ";", "")
		string exceptions = "ID;IN;IU;IV"
		waves = RemoveFromList(exceptions, waves)
		nw = ItemsInList(waves, ";")
	else
		nw = 0
	endif
	
	if (WaveExists(attr_filter) && (numpnts(attr_filter) >= 1))
		nattr = numpnts(attr_filter)
		redimension /n=(nattr)  attr_names
		attr_names = attr_filter
	else
		if(ninfo > 0)
			redimension /n=(ninfo) attr_names
			attr_names = SelectString(strlen(ID[p]) >= 0, IN[p], ID[p])	// use ID unless empty
		endif
		
		nattr = ninfo + nw
		iattr = ninfo
		redimension /n=(nattr) attr_names
		for (iw = 0; iw < nw; iw +=1 )
			sw = StringFromList(iw, waves, ";")
			ss = StringByKey("PV", note($sw), "=", "\r")
			FindValue /text=sw attr_names
			if ((v_value < 0) && (strlen(ss) >= 0))
				attr_names[iattr] = sw
				iattr += 1
			endif
		endfor
		nattr = iattr
	endif
	redimension /n=(nattr)  attr_names, attr_values
	sort attr_names, attr_names
	
	// look up attribute values
	for (iattr = 0; iattr < nattr; iattr += 1)
		sw = attr_names[iattr]
		// try info waves
		if (ninfo > 0)
			FindValue /text=sw ID
			if (v_value >= 0)
				attr_values[iattr] = IV[v_value]
			endif
			FindValue /text=sw IN
			if (v_value >= 0)
				attr_values[iattr] = IV[v_value]
			endif
		endif
		
		// override from attribute wave if existent
		if (nw > 0)
			switch (WaveType($sw, 1))
				case 1: // numeric
					wave /z w = $sw
					if (WaveExists(w) && (numpnts(w) >= 1))
						sprintf ss, "%g", w[0]
						attr_values[iattr] = ss
					endif
					break
				case 2: // text
					wave /t/z wt = $sw
					if (WaveExists(wt) && (numpnts(wt) >= 1))
						attr_values[iattr] = wt[0]
					endif
					break
			endswitch
		endif
	endfor
	
	setdatafolder save_df
end

function test_attributes_notebook()
	dfref df = GetDataFolderDFR()
	wave /t /sdfr=df attr_names
	wave /t /sdfr=df attr_values
	create_attributes_notebook(attr_names, attr_values, GetDataFolder(0))
end

static function create_attributes_notebook(attr_names, attr_values, title)
	wave /t attr_names
	wave /t attr_values
	string title
	
	dfref save_df = GetDataFolderDFR()
	setdatafolder $package_path
	wave /t/z attr_filter, attr_filter_summary

	string name = PearlCleanupName("nb_" + title[0,27])
	if (WinType(name) == 5)
		Notebook $name selection={startOfFile, endOfFile}	
		Notebook $name text=""
	else
		NewNotebook /F=1 /K=1 /N=$name as title
		GetWindow $name wsize
		v_right = v_left + 260
		v_bottom = v_top + 360
		MoveWindow /W=$name v_left, v_top, v_right, v_bottom
		Notebook $name tabs={2*72}
	endif

	// summary
	if (WaveExists(attr_filter_summary) && (numpnts(attr_filter_summary) >= 1))
		notebook $name fStyle=1, text="Summary\r\r"
		notebook $name fStyle=0
		notebook_add_attributes(name, attr_filter_summary, attr_names, attr_values)
		notebook $name text="\r"
	endif

	// all attributes
	notebook $name fStyle=1, text="All Attributes\r\r"
	notebook $name fStyle=0
	notebook_add_attributes(name, $"", attr_names, attr_values)
	notebook $name selection={startOfFile,startOfFile}, findText={"",1}
	
	setdatafolder save_df
end

static function notebook_add_attributes(notebook_name, attr_filter, attr_names, attr_values)
	string notebook_name
	wave /t /z attr_filter
	wave /t attr_names
	wave /t attr_values

	variable nw = numpnts(attr_names)
	variable iw
	string sw
	string ss

	variable do_filter = WaveExists(attr_filter)
	
	for (iw = 0; iw < nw; iw += 1)
		if (do_filter)
			sw = attr_names[iw]
			FindValue /text=sw attr_filter
		else
			v_value = 0
		endif
		if (v_value >= 0)
			sprintf ss, "%s\t%s\r", attr_names[iw], attr_values[iw]
			notebook $notebook_name text=ss
		endif
	endfor
end

// ====== preview ======

static function preview_file(filename)
	string filename
	
	dfref save_df = GetDataFolderDFR()
	dfref preview_df = $package_path
	
	killStrings /z authors, pgroup, proposal, proposer, sample
	
	variable ft = pearl_file_type(filename)
	switch(ft)
		case 1:
			wave /z image = preview_pshell_file(filename)
			break
		case 2:
			wave /z image = preview_hdf_file(filename)
			break
		case 3:
			wave /z image = preview_itx_file(filename)
			break
		default:
			wave /z image = $""
	endswitch

	if (WaveExists(image))
		show_preview_graph(image)
	endif

	setdatafolder save_df
	return 0
end

/// load the preview of a PShell HDF5 file.
///
/// the preview is an arbitrary detector image extracted from the file, see adh5_load_preview().
/// the preview is loaded to the preview_image wave in the pear_explorer data folder.
///
/// the s_file_info string is updated with information about the scan dimensions.
///
/// @param	filename		name of a file in the directory specified by the pearl_explorer_filepath path object.
///
/// @return	wave reference of the preview image
///
static function /wave preview_pshell_file(filename)
	string filename

	dfref save_df = GetDataFolderDFR()

	setdatafolder $package_path
	dfref preview_df = GetDataFolderDFR()
	svar s_preview_file
	svar s_preview_source
	svar /z s_file_info
	if (! svar_exists(s_file_info))
		string /g s_file_info
	endif
	
	dfref temp_df = NewFreeDataFolder()
	dfref file_df = psh5_preview("pearl_explorer_filepath", filename, dest_df=temp_df)
	svar /z /sdfr=temp_df dataname=s_preview_wave	
	
	s_preview_file = filename
	s_preview_source = ""

	wave /z /sdfr=temp_df data = $dataname
	if (waveexists(data))
		duplicate /o data, preview_df:preview_image
	else
		print "no data found in file " + filename
	endif
	
	s_file_info = ""
	
	setdatafolder save_df
	wave /z /sdfr=preview_df preview_image
	return preview_image
end

/// load the preview of a PEARL HDF5 file.
///
/// the preview is an arbitrary detector image extracted from the file, see adh5_load_preview().
/// the preview is loaded to the preview_image wave in the pear_explorer data folder.
///
/// the s_file_info string is updated with information about the scan dimensions.
///
/// @param	filename		name of a file in the directory specified by the pearl_explorer_filepath path object.
///
/// @return	wave reference of the preview image
///
static function /wave preview_hdf_file(filename)
	string filename

	dfref save_df = GetDataFolderDFR()
	setdatafolder $package_path
	svar s_preview_file
	svar s_preview_source
	adh5_load_preview("preview_image", "pearl_explorer_filepath", filename)
	s_preview_file = filename
	s_preview_source = ""
	wave /z preview_image
	
	svar /z s_file_info
	if (! svar_exists(s_file_info))
		string /g s_file_info
	endif
	if (strlen(s_preview_file) > 0)
		s_file_info = adh5_load_info("pearl_explorer_filepath", filename)
	else
		s_file_info = ""
	endif
	
	setdatafolder save_df
	return preview_image
end

/// load the preview of a general ITX file.
///
/// the function is designed for PEARL OTF and EPICS scan data converted from MDA files.
/// the function picks the first wave whose PV note matches one from the global string s_preview_pvs
/// (see @ref preview_datafolder and @ref init_package).
///
/// the preview is loaded to the preview_image wave in the pearl_explorer data folder.
/// the s_file_info string is updated with information about the scan dimensions.
///
/// @note: the ITX files should load their waves into the current data folder (a "free" data folder).
/// some early versions of PEARL ITX data files created a data folder of their own.
/// both ways are allowed, while the first one is preferred.
/// on return, the current data folder must point to either the original free folder or the newly created one.
///
/// @param	filename		name of a file in the directory specified by the pearl_explorer_filepath path object.
///
/// @return	wave reference of the preview trace.
///			empty wave reference if the function failed.
///
static function /wave preview_itx_file(filename)
	string filename

	dfref save_df = GetDataFolderDFR()
	setdatafolder $package_path
	svar s_preview_file
	svar s_preview_source
	wave preview_image

	dfref data_df = newfreedatafolder()
	setdatafolder data_df
	LoadWave /t/p=pearl_explorer_filepath/q filename
	s_preview_file = s_filename
	s_preview_source = ""
	
	preview_datafolder()
	
	setdatafolder save_df
	return preview_image
end

/// extract a preview image from a wave of arbitrary dimension
static function extract_preview_image(data, preview)
	wave data
	wave preview

	variable z1, z2

	// extract image
	switch (WaveDims(data))
		case 1:
			redimension /n=(numpnts(data)) preview
			preview = data[p]
			break
		case 2:
			redimension /n=(dimsize(data, 0), dimsize(data, 1)) preview
			preview = data
			break
		case 3:
			redimension /n=(dimsize(data, 0), dimsize(data, 1)) preview
			z1 = floor(DimSize(data, 2) / 2)
			z2 = z1
			wave slab = ad_extract_slab(data, nan, nan, nan, nan, z1, z2, "", pscale=1)
			preview = slab
			break
		case 4:
			// not implemented
	endswitch
	
	switch (WaveDims(data))
		case 4:
		case 3:
		case 2:
			setscale /p y dimoffset(data, 1), dimdelta(data, 1), waveunits(data, 1), preview
		case 1:
			setscale /p x dimoffset(data, 0), dimdelta(data, 0), waveunits(data, 0), preview
			setscale d 0, 0, waveunits(data, -1), preview
	endswitch
end

/// preview data in the current data folder
///
/// used by preview_itx_file
///
static function preview_datafolder()
	dfref save_df = GetDataFolderDFR()

	setdatafolder $package_path
	svar s_preview_file
	svar s_preview_source
	svar s_preview_pvs
	wave preview_image

	setdatafolder save_df
	
	// select a wave to display
	// consider only double-precision waves, i.e. ignore text and other special waves
	// filter by matching PV name to s_preview_pvs
	string d_names = WaveList("*", ";", "DP:1")
	variable nw = ItemsInList(d_names, ";")
	variable npv = ItemsInList(s_preview_pvs, ";")
	variable iw, ipv
	string wname, wnote, pv_name, pv_match
	for (iw = 0; iw < nw; iw += 1)
		wname = StringFromList(iw, d_names, ";")
		wnote = note($wname)
		pv_name = StringByKey("PV", wnote, "=", "\r")
		// find matching data wave by PV name
		for (ipv = 0; ipv < npv; ipv += 1)
			pv_match = StringFromList(ipv, s_preview_pvs)
			if (StringMatch(pv_name, pv_match))
				wave data = $wname
				s_preview_source = pv_name
				extract_preview_image(data, preview_image)
				preview_setscale_x(data, preview_image)
				npv = 0
				nw = 0
			endif
		endfor
	endfor
	
	setdatafolder save_df
end

static function preview_setscale_x(data, preview)
	// sets the approximate x scale of OTF data.
	// requires an Axis1 tag with name of x wave in the wave note.
	// if any of these conditions is true, the function does not change the scaling:
	// 1) Axis1 tag or referenced wave is missing.
	// 2) preview wave is not set to point scaling.
	// 3) x wave is not monotonic (90% of the steps in the same direction).
	wave data
	wave preview
	
	if ((DimOffset(preview, 0) == 0) && (DimDelta(preview, 0) == 1))
		string xname = StringByKey("Axis1", note(data), "=", "\r")
		wave /z xwave = $xname
		if (WaveExists(xwave))
			// check for monotonicity
			variable monotonic = 0
			duplicate /free xwave, xdiff
			differentiate /p xwave /D=xdiff
			duplicate /free xdiff, xflag
			xflag = xdiff > 0
			monotonic = sum(xflag) > numpnts(xwave) * 0.9
			xflag = xdiff < 0
			monotonic = monotonic || (sum(xflag) > numpnts(xwave) * 0.9)
			if (monotonic)
				setscale /i x xwave[0], xwave[numpnts(xwave)-1], waveunits(xwave, -1), preview
			endif
		endif
	endif
end

/// displays the graph of a loaded dataset in its own window
static function display_dataset(file_df, dataset)
	dfref file_df		// top data folder of file
	string dataset	// dataset path inside data folder
	
	dfref save_df = GetDataFolderDFR()

	dfref data_df = psh5_dataset_to_folder(file_df, dataset)
	SetDataFolder data_df
	string data_name = StringFromList(ItemsInList(dataset, "/") - 1, dataset, "/")
	wave /z data=$data_name

	if (WaveExists(data))
		switch(WaveDims(data))
			case 1:
			case 2:
				show_preview_graph(data)
				break
			case 3:
				ad_display_slice(data)
				ad_brick_slicer(data)
				break
		endswitch
	endif

	setdatafolder save_df
	return 0
end

static function /s show_preview_graph(data, [xdata])
	// displays a preview of one- or two-dimensional data
	wave data // data to be displayed. must either one-dimensional or two-dimensional
	wave xdata // positions on x axis
	
	dfref save_df = GetDataFolderDFR()
	setdatafolder $package_path
	
	svar s_profiles_graph
	svar s_preview_file
	svar s_preview_source
	svar s_preview_trace_graph
	svar s_preview_graph
	
	if ((strlen(s_profiles_graph) > 0) && (WinType(s_profiles_graph) == 1))
		KillWindow $s_profiles_graph
	endif
	if ((strlen(s_preview_trace_graph) > 0) && (WinType(s_preview_trace_graph) == 1))
		KillWindow $s_preview_trace_graph
	endif

	string graphname
	if (wavedims(data) == 2)
		s_profiles_graph = ad_display_profiles(data)
		ModifyGraph /w=$s_profiles_graph /z wbRGB=(48640,56832,60160)
		graphname = s_profiles_graph
	elseif (wavedims(data) == 1)
		duplicate /o data, preview_trace
		if (!ParamIsDefault(xdata))
			duplicate /o xdata, preview_trace_x
		else
			duplicate /o data, preview_trace_x
			preview_trace_x = x
			setscale d 0, 0, WaveUnits(data, 0), preview_trace_x
		endif
		s_preview_trace_graph = display_preview_trace(preview_trace_x, preview_trace)
		ModifyGraph /w=$s_preview_trace_graph wbRGB=(48640,56832,60160)
		graphname = s_preview_trace_graph
	else
		return ""
	endif
	
	string title = "Preview " + s_preview_file
	if (strlen(s_preview_source) > 0)
		title = title + " (" + s_preview_source[0,31] + ")"
	endif
	dowindow /f/t $graphname, title
	s_preview_graph = graphname
	
	setdatafolder save_df
	return graphname
end

static function /s display_preview_trace(xtrace, ytrace)
	wave /z xtrace
	wave ytrace

	if (WaveExists(xtrace))	
		display /n=pearl_explorer_1d /k=1 ytrace vs xtrace as "Preview"
	else
		display /n=pearl_explorer_1d /k=1 ytrace as "Preview"
	endif
	
	string graphname = s_name
	ModifyGraph /w=$graphname rgb[0]=(0,0,0)
	ModifyGraph /w=$graphname grid=2
	ModifyGraph /w=$graphname mirror=1
	ModifyGraph /w=$graphname minor=1
	ModifyGraph /w=$graphname axThick=0.5
	ModifyGraph /w=$graphname gridRGB=(52224,52224,52224)
	ModifyGraph /w=$graphname gridHair=0
	ModifyGraph /w=$graphname tick=0
	ModifyGraph /w=$graphname btLen=4
	
	// axis labels	
	string labels = note(ytrace)
	string lab
	lab = StringByKey("AxisLabelX", labels, "=", "\r")
	if (!strlen(lab))
		lab = "X"
	endif
	Label /w=$graphname bottom lab + " (\\U)"
	lab = StringByKey("Dataset", labels, "=", "\r")
	if (!strlen(lab))
		lab = "value"
	endif
	Label /w=$graphname left lab + " (\\U)"

	return s_name
end

// ====== file loading ======

/// load the selected files
///
/// load the files that are selected in the data explorer panel.
/// the files are loaded using the load_file() function.
///
/// @note this function may change the current data folder!
///
static function load_selected_files([options])
	string options
	
	dfref save_df = GetDataFolderDFR()
	setdatafolder $package_path
	
	wave wSelectedFiles
	wave/t wtFiles
	variable nn = numpnts(wSelectedFiles)
	variable ii
	for (ii = 0; ii < nn; ii += 1)
		if (wSelectedFiles[ii])
			setdatafolder save_df
			if (ParamIsDefault(options))
				load_file(wtFiles[ii])
			else
				load_file(wtFiles[ii], options=options)
			endif
		endif
	endfor
end

/// load one file
///
/// this can be a PShell, HDF5, or ITX file.
///
/// @note this function may change the current data folder!
///
/// @param	options		`key:value;` list of load options.
///								the recognized keys are: `mode`, `reduction_func` and `reduction_params`.
///								see main text for a description of possible values.
///								by default (options not specified), options are read from `s_hdf_options.
///								if the option string is empty, the user is prompted for options.
///
/// @param	dest_df		destination data folder. default: a new folder derived from file name under root.
///
/// @param	quiet			@arg 0 (default) print mode and parameters to history.
///								@arg 1 do not print to history.
///
static function load_file(filename, [options, dest_df, quiet])
	string filename
	string options
	dfref dest_df
	variable quiet

	if (ParamIsDefault(options))
		options = ""
	endif
	
	variable ft = pearl_file_type(filename)
	switch(ft)
		case 1:
			load_pshell_file(filename, options=options, dest_df=dest_df, quiet=quiet)
			break
		case 2:
			load_hdf_file(filename, options=options, dest_df=dest_df, quiet=quiet)
			break
		case 3:
			load_itx_file(filename, dest_df=dest_df, quiet=quiet)
			break
		default:
			break
	endswitch
end

static function prompt_hdf_options(options)
	string &options

	string mode = StringByKey("mode", options, ":", ";")
	string reduction_func = StringByKey("reduction_func", options, ":", ";")
	
	string modes = "load_scan;load_region;load_dataset;load_diags;load_complete;"
	string reduction_funcs = adh5_list_reduction_funcs()
	reduction_funcs = RemoveFromList("adh5_default_reduction", reduction_funcs, ";")
	
	if (strlen(mode) == 0)
		mode = StringFromList(0, modes, ";")
	endif
	if (strlen(reduction_func) == 0)
		reduction_func = StringFromList(0, reduction_funcs, ";")
	endif
	
	prompt mode, "Mode", popup, modes
	prompt reduction_func, "Reduction Function", popup, reduction_funcs
	doprompt "HDF5 Loading Options", mode, reduction_func

	if (v_flag == 0)
		options = ReplaceStringByKey("mode", options, mode, ":", ";")
		options = ReplaceStringByKey("reduction_func", options, reduction_func, ":", ";")
	endif
	return v_flag // 0 = OK, 1 = cancel
end

/// prototype for prompting for processing function parameters.
///
/// the function should prompt the user for function parameters,
/// and update the param argument if the user clicked OK.
/// returns 0 if the user clicked OK, 1 if the user cancelled.
///	
/// prompt functions must have the same name as the corresponding reduction function
/// with the prefix "prompt_".
/// be aware of the limited length of function names in Igor.
///
/// this function is a prototype. it does nothing but returns OK.
///
function prompt_default_process(param)
	string &param
	
	return 0
end

function prompt_func_params(func_name, func_param)
	string func_name
	string &func_param
	
	string prompt_name = "prompt_" + func_name
	if (exists(prompt_name) == 6)
		funcref prompt_default_process prompt_func = $prompt_name
		return prompt_func(func_param)
	else
		// ignore missing prompt function
		return 0
	endif
end

/// load a pshell file
///
/// if options is not specified, the complete file is loaded.
/// if options is an empty string, the package default options are used.
///
/// data selection is extracted from the datasets list box.
///
/// the file can be loaded in one of the following modes (`mode` key of the options argument):
///
/// @arg  `load_complete` load all datasets regardless of selection.
/// @arg  `load_scan` load default datasets of selected scans.
/// @arg  `load_region` load default datasets of selected regions.
/// @arg  `load_dataset` load selected datasets.
/// @arg  `load_diags` load diagnostic datasets of selected scans.
///
/// 3-dimensional datasets can be loaded with dimension reduction.
/// in this case, the name of the reduction function must be given under the `reduction_func` key.
/// the reduction parameters are prompted for if a prompt function for the reduction function is found.
/// the default reduction parameters are the most recent parameters `s_reduction_params` stored in the package data folder.
///
/// @arg `reduction_func:...` name of the reduction function.
///
/// if a reduction function is specified, default reduction parameters are read from `s_reduction_params` in the package data folder,
/// and the user is prompted to review/update the parameters.
///
/// @param	options		`key:value;` list of load options.
///								the recognized keys are: `mode`, `reduction_func` and `reduction_params`.
///								see main text for a description of possible values.
///								by default (options not specified), options are read from `s_hdf_options.
///								if the option string is empty, the user is prompted for options.
///
/// @param	dest_df		destination data folder. default: a new folder derived from file name under root.
///
/// @param	quiet			@arg 0 (default) print mode and parameters to history.
///								@arg 1 do not print to history.
///
/// @return	data folder reference of the loaded data. this is the folder which contains the scan sub-folders.
///
static function /df load_pshell_file(filename, [options, dest_df, quiet])
	string filename
	string options
	dfref dest_df
	variable quiet

	dfref save_df = GetDataFolderDFR()
	
	svar pref_options = $(package_path + "s_hdf_options")
	svar pref_params = $(package_path + "s_reduction_params")
	string path = "pearl_explorer_filepath"

	if (ParamIsDefault(options))
		options = pref_options
	endif

	if (strlen(options) == 0)
		if (prompt_hdf_options(options) == 0)
			pref_options = options
		else
			return $""
		endif
	endif

	string reduction_func = StringByKey("reduction_func", options, ":", ";")
	string reduction_params = pref_params
	variable max_rank = 2
	
	if (exists(reduction_func) == 6)
		max_rank = 3
		if (prompt_func_params(reduction_func, reduction_params) == 0)
			pref_params = reduction_params
		else
			return $""
		endif
	endif
	
	string mode = StringByKey("mode", options, ":", ";")
	string selected_datasets = WMHL_SelectedObjectsList("PearlDataExplorer", "lb_contents")
	string selected_scans = psh5_extract_scan_paths(selected_datasets)
	string selected_regions = psh5_extract_region_paths(selected_datasets)
	variable dsc
	
	if (!quiet)
		print mode, filename
		if (strlen(reduction_func))
			print reduction_func, reduction_params
		endif
	endif
	
	strswitch(mode)
		case "load_complete":
			dsc = kDSCAll
			dfref file_df = psh5_load(path, filename, "", "", "*", classes=dsc, reduction_func=reduction_func, reduction_params=reduction_params, dest_df=dest_df)
			break
		case "load_diags":
			if (ItemsInList(selected_scans, ";") == 0)
				if (!quiet)
					print "no scan selected - defaulting to scan 1."
				endif
				selected_scans = "/scan1;"
			endif
			dsc = kDSCAttrs | kDSCDiags | kDSCSnaps | kDSCMeta | kDSCMonitors
			dfref file_df = psh5_load(path, filename, selected_scans, "", "", classes=dsc, dest_df=dest_df)
			break
		case "load_scan":
			if (ItemsInList(selected_scans, ";") == 0)
				if (!quiet)
					print "no scan selected - defaulting to scan 1."
				endif
				selected_scans = "/scan1;"
			endif
			dsc = kDSCPositioners | kDSCDetectors | kDSCScientaScaling | kDSCEssentialDiags
			dfref file_df = psh5_load(path, filename, selected_scans, "", "", classes=dsc, max_rank=max_rank, reduction_func=reduction_func, reduction_params=reduction_params, dest_df=dest_df)
			break
		case "load_region":
			if (ItemsInList(selected_regions, ";") == 0)
				if (!quiet)
					print "no region selected - defaulting to scan 1/region 1."
				endif
				selected_regions = "/scan1/region1;"
			endif
			dsc = kDSCPositioners | kDSCDetectors | kDSCScientaScaling | kDSCEssentialDiags
			dfref file_df = psh5_load(path, filename, "", selected_regions, "", classes=dsc, max_rank=max_rank, reduction_func=reduction_func, reduction_params=reduction_params, dest_df=dest_df)
			break
		case "load_dataset":
			if (ItemsInList(selected_datasets, ";") > 0)
				dsc = kDSCAll
				dfref file_df = psh5_load(path, filename, "", "", selected_datasets, classes=dsc, reduction_func=reduction_func, reduction_params=reduction_params, dest_df=dest_df)
			else
				if (!quiet)
					DoAlert /T="PShell Import" 0, "Please select the datasets to load."
				endif
			endif
			break
	endswitch

	if (DataFolderRefStatus(file_df))
		setdatafolder file_df
		string /g pearl_explorer_import = "load_pshell_file"
		if (!quiet)
			print "data loaded to", GetDataFolder(1)
		endif
	else
		setdatafolder save_df
	endif
	
	return file_df
end

static function /df load_hdf_file(filename, [options, dest_df, quiet])
	string filename
	string options
	dfref dest_df
	variable quiet

	dfref save_df = GetDataFolderDFR()
	string nickname = ad_suggest_foldername(filename)
	string loaded_filename = ""
	
	if (ParamIsDefault(dest_df) || !DataFolderRefStatus(dest_df))
		//
	else
		DoAlert /T="load_hdf_file" 0, "optional argument dest_df not supported."
		return $""
	endif
	
	if (ParamIsDefault(options))
		loaded_filename = adh5_load_complete(nickname, "pearl_explorer_filepath", filename)
	else
		if (strlen(options) == 0)
			svar pref_options = $(package_path + "s_hdf_options")
			options = pref_options
			if (prompt_hdf_options(options) == 0)
				// OK
				pref_options = options
			else
				// cancel
				options = ""
			endif
		endif
		
		string mode = StringByKey("mode", options, ":", ";")
		
		strswitch(mode)
			case "load_reduced":
				string reduction_func = StringByKey("reduction_func", options, ":", ";")
				svar pref_params = $(package_path + "s_reduction_params")
				string reduction_params = pref_params
				if (prompt_func_params(reduction_func, reduction_params) == 0)
					pref_params = reduction_params
					print reduction_func, reduction_params
					loaded_filename = adh5_load_reduced(nickname, "pearl_explorer_filepath", filename, $reduction_func, reduction_params)
				endif
				break
		endswitch
	endif

	dfref data_df	
	if (strlen(loaded_filename) > 0)
		setdatafolder $("root:" + nickname)
		data_df = GetDataFolderDFR()
		string /g pearl_explorer_import = "load_hdf_file"
	else
		setdatafolder save_df
	endif
	
	return data_df
end

static function /df load_itx_file(filename, [options, dest_df, quiet])
	string filename
	string options
	dfref dest_df
	variable quiet

	dfref save_df = GetDataFolderDFR()
	string nickname = itx_suggest_foldername(filename)

	if (ParamIsDefault(options))
		options = ""
	endif
	
	variable own_data_df = 0
	if (ParamIsDefault(dest_df) || !DataFolderRefStatus(dest_df))
		setdatafolder root:
		newdatafolder /s/o $("root:" + nickname)
		own_data_df = 1
	else
		setdatafolder dest_df
	endif
	dfref data_df = GetDataFolderDFR()
	
	// note: some versions of PEARL data files save data to a new data folder,
	// and leave the newly created folder as the current folder.
	// the free data folder is used by those files which don't create their own data folder.
	// this is the new recommended behaviour

	LoadWave /t/p=pearl_explorer_filepath/q filename
	svar waves = s_wavenames
	dfref act_df = GetDataFolderDFR()
	if (v_flag > 0)
		string /g pearl_explorer_import = "load_itx_file"
	endif
	
	if (!DataFolderRefsEqual(act_df, data_df) && own_data_df)
		// the file created its own data folder.
		// let's kill the pre-allocated folder
		setdatafolder data_df
		if (ItemsInList(WaveList("*", ";", ""), ";") == 0)
			killdatafolder /z data_df
		endif
	endif
	
	setdatafolder save_df
	return act_df
end

/// suggest the name of a data folder based on an igor-text file name
///
/// if the file name follows the naming convention source-date-index.extension,
/// the function tries to generate the nick name as source_date_index.
/// otherwise it's just a cleaned up version of the file name.
///
/// igor text is used by the on-the-fly scan tool.
///
/// @param	filename		file name, including extension. 
///								can also include a folder path (which is ignored).
///								the extension is currently ignored, 
///								but may be used in a later version.
/// @param	ignoredate	if non-zero, the nick name will not include the date part.
/// 							defaults to zero
/// @param	sourcename	nick name of the data source.
///								the function tries to detect the source from the file name.
///								this option can be used to override auto-detection.
///								allowed values: sscan, otf
/// @param	unique			if non-zero, the resulting name is made a unique data folder name in the current data folder.
///								defaults to zero
///
function /s itx_suggest_foldername(filename, [ignoredate,sourcename,unique])
	string filename
	variable ignoredate
	string sourcename
	variable unique
	
	if (ParamIsDefault(ignoredate))
		ignoredate = 0
	endif
	if (ParamIsDefault(unique))
		unique = 0
	endif
	
	string basename = ParseFilePath(3, filename, ":", 0, 0)
	string extension = ParseFilePath(4, filename, ":", 0, 0)
	string nickname
	
	string autosource
	if (strsearch(basename, "X03DA_PC", 0, 2) >= 0)
		autosource = "sscan"
		basename = ReplaceString("_", basename, "-")
		ignoredate = 1
	elseif (strsearch(basename, "otf", 0, 2) >= 0)
		autosource = "otf"
	endif
	if (ParamIsDefault(sourcename))
		sourcename = autosource
	endif
	
	variable nparts = ItemsInList(basename, "-")
	if (nparts >= 3)
		string datepart = StringFromList(nparts - 2, basename, "-")
		string indexpart = StringFromList(nparts - 1, basename, "-")
		if (ignoredate)
			sprintf nickname, "%s_%s", sourcename, indexpart
		else
			sprintf nickname, "%s_%s_%s", sourcename, datepart, indexpart
		endif
	else
		nickname = PearlCleanupName(basename)
	endif
	
	if (unique && CheckName(nickname, 11))
		nickname = UniqueName(nickname + "_", 11, 0)
	endif
	
	return nickname
end

// ====== panel ======

/// macro to create the data explorer panel
///
/// note: after editing the panel using the graphical editor, 
/// remove all automatically generated assignments from the lb_contents listbox.
/// only the pos, size and keySelectCol properties should remain.
/// 
Window PearlDataExplorer() : Panel
	PauseUpdate; Silent 1		// building window...
	NewPanel /K=1 /W=(510,45,1190,539) as "PEARL Data Explorer"
	ModifyPanel cbRGB=(48640,56832,60160)
	GroupBox g_data_reduction,pos={355.00,370.00},size={306.00,49.00},title="data reduction"
	GroupBox g_data_reduction,help={"data reduction of 3D ScientaImage"}
	GroupBox gb_filepath,pos={8.00,4.00},size={328.00,48.00},title="file system folder"
	TitleBox tb_filepath,pos={20.00,28.00},size={279.00,21.00},frame=0
	TitleBox tb_filepath,variable= root:packages:pearl_explorer:s_short_filepath,fixedSize=1
	Button b_browse_filepath,pos={303.00,24.00},size={20.00,20.00},proc=PearlDataExplorer#bp_browse_filepath,title="..."
	Button b_browse_filepath,fColor=(65280,48896,32768)
	Button b_browse_filepath,help={"select the file system folder that contains the data files"}
	GroupBox gb_prefs,pos={8.00,351.00},size={65.00,131.00},title="prefs"
	GroupBox gb_prefs,help={"explorer package preferences"}
	Button b_save_prefs,pos={21.00,394.00},size={38.00,17.00},proc=PearlDataExplorer#bp_save_prefs,title="save"
	Button b_save_prefs,help={"save preferences of the data explorer package (data file path, attributes filter)"}
	Button b_save_prefs,fColor=(65280,48896,32768)
	Button b_load_prefs,pos={21.00,374.00},size={38.00,17.00},proc=PearlDataExplorer#bp_load_prefs,title="load"
	Button b_load_prefs,help={"load preferences of the data explorer package"}
	Button b_load_prefs,fColor=(65280,48896,32768)
	GroupBox gb_filelist,pos={8.00,55.00},size={328.00,293.00},title="data files"
	ListBox lb_files,pos={20.00,83.00},size={303.00,222.00},proc=PearlDataExplorer#lbp_filelist
	ListBox lb_files,listWave=root:packages:pearl_explorer:wtFiles
	ListBox lb_files,selWave=root:packages:pearl_explorer:wSelectedFiles,mode= 4
	Button b_update_filelist,pos={246.00,315.00},size={76.00,22.00},proc=PearlDataExplorer#bp_update_filelist,title="update list"
	Button b_update_filelist,fColor=(65280,48896,32768)
	Button b_update_filelist,help={"reload the file list"}
	CheckBox cb_file_preview,pos={94.00,375.00},size={59.00,14.00},title="preview"
	CheckBox cb_file_preview,help={"enable/disable automatic preview window when selecting a data file"}
	CheckBox cb_file_preview,value= 0
	CheckBox cb_file_elog,pos={94.00,395.00},size={82.00,14.00},title="ELOG"
	CheckBox cb_file_elog,help={"enable/disable sending metadata to ELOG panel when selecting a data file (does not submit to ELOG)"}
	CheckBox cb_file_elog,value= 0
	Button b_attr_notebook,pos={94.00,415.00},size={64.00,22.00},disable=2,proc=PearlDataExplorer#bp_attr_notebook,title="notebook"
	Button b_attr_notebook,help={"show a summary of attributes in a notebook window"}
	Button b_attr_notebook,fColor=(65280,48896,32768)
	Button b_file_prev,pos={20.00,314.00},size={22.00,22.00},proc=PearlDataExplorer#bp_file_prev,title="\\W646"
	Button b_file_prev,fColor=(65280,48896,32768)
	Button b_file_prev,help={"select previous file from list"}
	Button b_file_next,pos={44.00,314.00},size={22.00,22.00},proc=PearlDataExplorer#bp_file_next,title="\\W649"
	Button b_file_next,fColor=(65280,48896,32768)
	Button b_file_next,help={"select next file from list"}
	Button b_goto_dataset,pos={355.00,315.00},size={64.00,22.00},disable=2,proc=PearlDataExplorer#bp_goto_dataset,title="goto DF"
	Button b_goto_dataset,help={"change the current data folder to find the selected dataset (if loaded)"}
	Button b_goto_dataset,fColor=(65280,48896,32768)
	Button b_display_dataset,pos={423.00,315.00},size={64.00,22.00},disable=2,proc=PearlDataExplorer#bp_display_dataset,title="display"
	Button b_display_dataset,help={"display the selected dataset"}
	Button b_display_dataset,fColor=(65280,48896,32768)
	Button b_load_complete,pos={355.00,451.00},size={92.00,22.00},disable=2,proc=PearlDataExplorer#bp_load_options,title="all data"
	Button b_load_complete,help={"load all datasets of the selected file."}
	Button b_load_complete,userdata=  "mode:load_complete;"
	Button b_load_complete,fColor=(65280,48896,32768)
	TitleBox tb_selected_file,pos={360.00,28.00},size={309.00,22.00},frame=0
	TitleBox tb_selected_file,variable= root:packages:pearl_explorer:s_selected_file,fixedSize=1
	GroupBox gb_contents,pos={346.00,55.00},size={327.00,294.00},title="datasets"
	ListBox lb_contents,pos={355.00,84.00},size={305.00,222.00}
	ListBox lb_contents,keySelectCol= 1
	GroupBox gb_selected_file,pos={346.00,4.00},size={328.00,48.00},title="selected file"
	Button b_load_region,pos={355.00,426.00},size={92.00,22.00},disable=2,proc=PearlDataExplorer#bp_load_options,title="region"
	Button b_load_region,help={"load significant datasets and metadata from the selected region"}
	Button b_load_region,userdata=  "mode:load_region;",fColor=(65280,48896,32768)
	PopupMenu popup_reduction,pos={366.00,391.00},size={200.00,17.00},bodyWidth=200,proc=PearlDataExplorer#pmp_reduction_func
	PopupMenu popup_reduction,help={"data reduction function for 3d ScientaImage. note: the list may contain unsuitable functions. check the code or documentation!"}
	PopupMenu popup_reduction,mode=1,popvalue="None",value= #"PearlDataExplorer#pm_reduction_values()"
	GroupBox group_import,pos={346.00,351.00},size={326.00,131.00},title="import"
	Button b_load_scan,pos={450.00,426.00},size={94.00,22.00},disable=2,proc=PearlDataExplorer#bp_load_options,title="scan"
	Button b_load_scan,help={"load significant datasets and metadata from the selected scan"},userdata=  "mode:load_scan;"
	Button b_load_scan,fColor=(65280,48896,32768)
	Button b_load_diags,pos={450.00,451.00},size={94.00,22.00},disable=2,proc=PearlDataExplorer#bp_load_options,title="diagnostics"
	Button b_load_diags,help={"load diagnostic datasets of the selected scans"}
	Button b_load_diags,userdata=  "mode:load_diags;",fColor=(65280,48896,32768)
	Button b_load_dataset,pos={547.00,426.00},size={101.00,22.00},disable=2,proc=PearlDataExplorer#bp_load_options,title="dataset"
	Button b_load_dataset,help={"load the selected dataset(s) and significant metadata"}
	Button b_load_dataset,userdata=  "mode:load_dataset;",fColor=(65280,48896,32768)
	Button b_reduction_params,pos={571.00,390.00},size={71.00,19.00},disable=2,proc=PearlDataExplorer#bp_reduction_params,title="set params"
	Button b_reduction_params,help={"set data reduction parameters"}
	Button b_reduction_params,fColor=(65280,48896,32768)
	GroupBox g_fileinfo,pos={85.00,351.00},size={251.00,131.00},title="file info"
	ToolsGrid grid=(0,28.35,5)
EndMacro

/// update controls state
///
static function update_controls()
	dfref package_df = $package_path
	svar /z /sdfr=package_df hl_contents_datasets
	wave /z /sdfr=package_df wSelectedFiles

	variable file_selected = 0
	if (WaveExists(wSelectedFiles))
		file_selected = sum(wSelectedFiles)
	endif
		
	string selected_datasets = WMHL_SelectedObjectsList("PearlDataExplorer", "lb_contents")
	variable scan_selected = strsearch(selected_datasets, "scan", 0, 2) == 0
	variable region_selected = strsearch(selected_datasets, "region", 0, 2) >= 0

	variable dataset_selected = 0
	variable nds = ItemsInList(selected_datasets, ";")
	variable ids
	string ds
	if (svar_exists(hl_contents_datasets))
		for (ids = 0; ids < nds; ids += 1)
			ds = "/" + StringFromList(ids, selected_datasets, ";")
			if (NumType(NumberByKey(ds, hl_contents_datasets, ":", ";")) == 0)
				dataset_selected = 1
				break
			endif
		endfor	
	else
		nds = 0
	endif
	
	variable dis
	dis = file_selected ? 0 : 2
	Button b_load_complete win=PearlDataExplorer,disable=dis
	Button b_load_diags win=PearlDataExplorer,disable=dis
	dis = file_selected && scan_selected ? 0 : 2
	Button b_attr_notebook win=PearlDataExplorer,disable=dis
	
	dis = scan_selected ? 0 : 2
	Button b_load_scan win=PearlDataExplorer,disable=dis
	dis = region_selected ? 0 : 2
	Button b_load_region win=PearlDataExplorer,disable=dis
	dis = dataset_selected ? 0 : 2
	Button b_load_dataset win=PearlDataExplorer,disable=dis
	Button b_display_dataset win=PearlDataExplorer,disable=dis
	dis = file_selected && (nds > 0) ? 0 : 2
	Button b_goto_dataset win=PearlDataExplorer,disable=dis

	ControlInfo /W=PearlDataExplorer popup_reduction
	if ((cmpstr(S_Value, "None") != 0) && (exists(S_Value) == 6))
		GroupBox g_data_reduction win=PearlDataExplorer,labelBack=(65535,49151,49151)
		Button b_reduction_params win=PearlDataExplorer,disable=0
	else
		GroupBox g_data_reduction win=PearlDataExplorer,labelBack=0
		Button b_reduction_params win=PearlDataExplorer,disable=2
	endif

	return 0
end

static function bp_load_prefs(ba) : ButtonControl
	STRUCT WMButtonAction &ba

	switch( ba.eventCode )
		case 2: // mouse up
			load_prefs()
			update_controls()
			break
		case -1: // control being killed
			break
	endswitch

	return 0
End

static function bp_save_prefs(ba) : ButtonControl
	STRUCT WMButtonAction &ba

	switch( ba.eventCode )
		case 2: // mouse up
			save_prefs()
			break
		case -1: // control being killed
			break
	endswitch

	return 0
End

/// shorten a file path for display
///
/// @note the result is not a valid path any more!
///
static function /s shorten_filepath(long_path, max_len)
	string long_path
	variable max_len
	
	string path = long_path
	variable ellipsis = 0
	do
		if (strlen(path) > max_len)
			path = RemoveListItem(1, path, ":")
			ellipsis += 1
		else
			break
		endif
	while (1)
	
	if (ellipsis >= 1)
		path = AddListItem("…", path, ":", 1)
	endif
	
	return path
end

static function bp_browse_filepath(ba) : ButtonControl
	STRUCT WMButtonAction &ba

	dfref save_df = GetDataFolderDFR()

	switch( ba.eventCode )
		case 2: // mouse up
			PathInfo /S pearl_explorer_filepath
			NewPath /M="select data file folder" /O/Z pearl_explorer_filepath
			if (v_flag == 0)
				update_filepath()
				update_filelist()
				update_controls()
			endif
			break
		case -1: // control being killed
			break
	endswitch

	setdatafolder save_df
	return 0
End

static function bp_update_filelist(ba) : ButtonControl
	STRUCT WMButtonAction &ba

	switch( ba.eventCode )
		case 2: // mouse up
			update_filelist()
			update_controls()
			break
		case -1: // control being killed
			break
	endswitch

	return 0
End

/// items for data reduction popup
static function /s pm_reduction_values()
	string reduction_funcs = adh5_list_reduction_funcs()
	reduction_funcs = RemoveFromList("adh5_default_reduction", reduction_funcs, ";")
	reduction_funcs = AddListItem("None", reduction_funcs, ";", 0)
	return reduction_funcs
end

static function pmp_reduction_func(pa) : PopupMenuControl
	STRUCT WMPopupAction &pa

	switch( pa.eventCode )
		case 2: // mouse up
			Variable popNum = pa.popNum
			String popStr = pa.popStr
			update_controls()
			break
		case -1: // control being killed
			break
	endswitch

	return 0
End

static function bp_reduction_params(ba) : ButtonControl
	STRUCT WMButtonAction &ba

	switch( ba.eventCode )
		case 2: // mouse up
			ControlInfo /W=PearlDataExplorer popup_reduction
			if ((cmpstr(S_Value, "None") != 0) && (exists(S_Value) == 6))
				svar pref_params = $(package_path + "s_reduction_params")
				string reduction_func = S_Value
				string reduction_params = pref_params
				if (prompt_func_params(reduction_func, reduction_params) == 0)
					pref_params = reduction_params
				endif
			endif
			break
		case -1: // control being killed
			break
	endswitch

	return 0
End

static function bp_load_options(ba) : ButtonControl
	STRUCT WMButtonAction &ba

	switch( ba.eventCode )
		case 2: // mouse up
			// options must be in the button's unnamed user data in the form: "mode:load_complete".
			// see load_pshell_file for recognized values.
			string options=ba.userData
			
			// data reduction popup
			ControlInfo /W=PearlDataExplorer popup_reduction
			if ((cmpstr(S_Value, "None") != 0) && (exists(S_Value) == 6))
				options = ReplaceStringByKey("reduction_func", options, S_Value, ":", ";")
			endif
			
			load_selected_files(options=options)
			break
		case -1: // control being killed
			break
	endswitch

	return 0
End

/// actions after a file has been selected
///
/// - load metadata
/// - load preview if requested
/// - send to elog panel if requested
///
/// @param file		name of selected file
///
/// @param do_preview		enable/disable loading of preview data
///								non-zero: load preview,
///								zero: don't load preview
///
/// @param do_elog			enable/disable sending metadata to elog panel
///								non-zero: send,
///								zero: don't send
///
static function selected_file(file, do_preview, do_elog)
	string file
	variable do_preview
	variable do_elog
	
	dfref save_df = GetDataFolderDFR()
	setdatafolder $package_path
	svar s_selected_file
	s_selected_file = file
	variable fi = get_file_info(file)
	if (fi == 0 && do_preview != 0)
		preview_file(file)
	endif
	if (fi == 0 && do_elog != 0)
		send_to_elog()
	endif
	update_controls()
	
	setdatafolder save_df
	return 0
end

static function bp_file_next(ba) : ButtonControl
	STRUCT WMButtonAction &ba

	dfref save_df = GetDataFolderDFR()
	
	switch( ba.eventCode )
		case 2: // mouse up
			setdatafolder $package_path
			wave /t wtFiles
			wave wSelectedFiles
			FindValue /i=1 wSelectedFiles
			v_value += 1
			if (v_value >= numpnts(wtFiles))
				v_value = min(numpnts(wtFiles) - 1, 0)
			endif
			wSelectedFiles = p == v_value
			if (v_value >= 0)
				variable ifile = v_value
				ControlInfo /W=PearlDataExplorer cb_file_preview
				variable do_preview = v_value
				ControlInfo /W=PearlDataExplorer cb_file_elog
				variable do_elog = v_value
				selected_file(wtFiles[ifile], do_preview, do_elog)
			endif
			update_controls()
			break
		case -1: // control being killed
			break
	endswitch

	setdatafolder save_df
	return 0
End

static function bp_file_prev(ba) : ButtonControl
	STRUCT WMButtonAction &ba

	dfref save_df = GetDataFolderDFR()
	
	switch( ba.eventCode )
		case 2: // mouse up
			setdatafolder $package_path
			wave /t wtFiles
			wave wSelectedFiles
			FindValue /i=1 wSelectedFiles
			v_value -= 1
			if (v_value < 0)
				v_value = numpnts(wtFiles) - 1
			endif
			wSelectedFiles = p == v_value
			if (v_value >= 0)
				variable ifile = v_value
				ControlInfo /W=PearlDataExplorer cb_file_preview
				variable do_preview = v_value
				ControlInfo /W=PearlDataExplorer cb_file_elog
				variable do_elog = v_value
				selected_file(wtFiles[ifile], do_preview, do_elog)
			endif
			update_controls()
			break
		case -1: // control being killed
			break
	endswitch

	setdatafolder save_df
	return 0
End

static function lbp_filelist(lba) : ListBoxControl
	STRUCT WMListboxAction &lba

	dfref save_df = GetDataFolderDFR()
	
	Variable row = lba.row
	Variable col = lba.col
	WAVE/T/Z listWave = lba.listWave
	WAVE/Z selWave = lba.selWave

	switch( lba.eventCode )
		case -1: // control being killed
			break
		case 1: // mouse down
			setdatafolder $package_path
			wave wSelectedFiles
			if (selWave[row])
				if (sum(wSelectedFiles) == 1)
					ControlInfo /W=PearlDataExplorer cb_file_preview
					variable do_preview = v_value
					ControlInfo /W=PearlDataExplorer cb_file_elog
					variable do_elog = v_value
					selected_file(listWave[row], do_preview, do_elog)
				else
					selected_file(listWave[row], 0, 0)
				endif
			endif
			update_controls()
			break
		case 3: // double click
			break
		case 4: // cell selection
		case 5: // cell selection plus shift key
			break
		case 6: // begin edit
			break
		case 7: // finish edit
			break
		case 13: // checkbox clicked (Igor 6.2 or later)
			break
	endswitch

	setdatafolder save_df
	return 0
End

static function bp_attr_notebook(ba) : ButtonControl
	STRUCT WMButtonAction &ba

	dfref save_df = GetDataFolderDFR()

	switch( ba.eventCode )
		case 2: // mouse up
			setdatafolder $package_path
			wave wSelectedFiles
			wave/t wtFiles
			variable nn = numpnts(wSelectedFiles)
			variable ii
			for (ii = 0; ii < nn; ii += 1)
				if (wSelectedFiles[ii])
					attributes_notebook(wtFiles[ii])
					break
				endif
			endfor
			break
		case -1: // control being killed
			break
	endswitch

	setdatafolder save_df
	return 0
End


static function hlp_setup()
	dfref save_df = GetDataFolderDFR()
	setdatafolder $package_path

	MakeListIntoHierarchicalList("PearlDataExplorer", "lb_contents", "hlp_contents_open", selectionMode=WMHL_SelectionSingle, pathSeparator="/")
	
	setdatafolder save_df
	return 0
end

static function hl_contents_clear()
	do
		if (cmpstr(WMHL_GetItemForRowNumber("PearlDataExplorer", "lb_contents", 0), "<uninitialized>") != 0)
			WMHL_DeleteRowAndChildren("PearlDataExplorer", "lb_contents", 0)
		else
			break
		endif
	while (1)
end

/// populate the contents list box with the internal directory of a HDF5 file
///
/// @return the number of top-level objects
///
static function hl_contents_update(file_df)
	dfref file_df

	dfref save_df = GetDataFolderDFR()
	setdatafolder $package_path
	hl_contents_clear()

	variable nds
	variable ids
	string ds
	string extra
	string /g hl_contents_datasets = ""

	if (DataFolderRefStatus(file_df))
		svar /sdfr=file_df datasets = s_datasets
		svar /sdfr=file_df datatypes = s_datasets_datatypes
		svar /sdfr=file_df ranks = s_datasets_ranks
		svar /sdfr=file_df dimensions = s_datasets_dimensions

		nds = ItemsInList(datasets, ";")
		for (ids = 0; ids < nds; ids += 1)
			ds = StringFromList(ids, datasets, ";")
			extra = StringFromList(ids, dimensions, ";")
			hl_contents_datasets = ReplaceStringByKey(ds, hl_contents_datasets, extra, ":", ";")
		endfor
	endif

	variable nobj = hl_add_objects("", hl_contents_datasets)
	hl_expand_scans()
	hl_default_selection()
	setdatafolder save_df
	
	return nobj
end

static function /df get_pshell_info(path_name, file_name, [dest_df])
	string path_name
	string file_name
	dfref dest_df

	dfref save_df = GetDataFolderDFR()

	if (!ParamIsDefault(dest_df))
		setdatafolder dest_df
	else
		setdatafolder $package_path
		NewDataFolder /o /s file_info
	endif
	
	dfref file_df = psh5_open_file(path_name, file_name, dest_df=GetDataFolderDFR())
	if (DataFolderRefStatus(file_df))
		psh5_load_general_group(file_df)
		psh5_close_file(file_df)
	endif
		
	setdatafolder save_df
	return file_df
end

/// populate the contents list box with the given hierarchical paths
///
/// @return the number of top-level objects
///
static function hl_add_objects(parent_path, objects)
	string parent_path // e.g. "/a/b"
	string objects // all objects that might appear in the list. e.g. "/a/b/c:col0|col1;/a/b/d:col0|col1;/d/e/f:col0|col1;"

	if (cmpstr(parent_path[0], "/") != 0)
		parent_path = "/" + parent_path
	endif
	
	variable nobj = ItemsInList(objects, ";")
	variable iobj
	string obj
	string extra
	variable nel
	
	string child_path = ""
	string child_name = ""
	string child_names = ""   // e.g., "child1:1;child3:2;"
	string extra_data = ""    // e.g., "child1:col0|col1;child2:col0|col1;"
	
	// filter children of parent
	for (iobj = 0; iobj < nobj; iobj += 1)
		obj = StringFromList(iobj, objects, ";")
		
		if (cmpstr(obj[0, strlen(parent_path)-1], parent_path) == 0)
			child_path = StringFromList(0, obj, ":")
			child_path = child_path[strlen(parent_path), strlen(child_path)-1]
			if (cmpstr(child_path[0], "/") == 0)
				child_path = child_path[1, strlen(child_path)-1]
			endif
			child_name = StringFromList(0, child_path, "/")
			nel = ItemsInList(child_path, "/")
			child_names = ReplaceNumberByKey(child_name, child_names, nel)
			if (nel == 1)
				extra = RemoveListItem(0, obj, ":")
				extra_data = ReplaceStringByKey(child_name, extra_data, extra)
			endif
		endif
	endfor

	// add rows
	variable row
	variable children
	nobj = ItemsInList(child_names)
	for (iobj = 0; iobj < nobj; iobj += 1)
		obj = StringFromList(iobj, child_names)
		child_name = StringFromList(0, obj, ":")
		nel = NumberByKey(child_name, child_names)
		WMHL_AddObject("PearlDataExplorer", "lb_contents", parent_path[1, strlen(parent_path)], child_name, nel > 1)
		if (nel == 1)
			extra = StringByKey(child_name, extra_data)
			row = WMHL_GetRowNumberForItem("PearlDataExplorer", "lb_contents", parent_path[1, strlen(parent_path)] + "/" + child_name)
			if (row >= 0)
				WMHL_ExtraColumnData("PearlDataExplorer", "lb_contents", 0, row, StringFromList(0, extra, "|"), 0)
			endif
		endif
	endfor
	
	return nobj
end

static function hl_expand_scans()
	dfref save_df = GetDataFolderDFR()
	setdatafolder $package_path

	svar hl_contents_datasets
	variable nds = ItemsInList(hl_contents_datasets, ";")
	variable ids
	string sds
	string scan
	string scans = ""
	for (ids = 0; ids < nds; ids += 1)
		sds = StringFromList(ids, hl_contents_datasets, ";")
		if (cmpstr(sds[0,4], "/scan", 0) == 0)
			scan = StringFromList(1, sds, "/")
			scans = ReplaceNumberByKey(scan, scans, 1)
		endif
	endfor

	variable nscans = ItemsInList(scans)
	variable iscan
	for (iscan = 0; iscan < nscans; iscan += 1)
		scan = StringFromList(iscan, scans)
		scan = StringFromList(0, scan, ":")
		WMHL_OpenAContainer("PearlDataExplorer", "lb_contents", scan)
	endfor

	setdatafolder save_df
end

static function hl_default_selection()
	variable row
	row = WMHL_GetRowNumberForItem("PearlDataExplorer", "lb_contents", "scan 1")
	if (row < 0)
		row = WMHL_GetRowNumberForItem("PearlDataExplorer", "lb_contents", "scan1")
	endif
	if (row >= 0)
		WMHL_SelectARow("PearlDataExplorer", "lb_contents", row, 1)
	endif
end

static function hlp_contents_open(HostWindow, ListControlName, ContainerPath)
	String HostWindow, ListControlName, ContainerPath

	dfref save_df = GetDataFolderDFR()
	setdatafolder $package_path
	svar hl_contents_datasets
	hl_add_objects(ContainerPath, hl_contents_datasets)
	setdatafolder save_df
end

static function hlp_contents_selection(HostWindow, ListControlName, SelectedItem, EventCode)
	String HostWindow, ListControlName
	String SelectedItem
	Variable EventCode

	dfref save_df = GetDataFolderDFR()
	setdatafolder $package_path

	switch (eventCode)
		case 3: // double click
			// todo: load dataset?
			break
		case 4: // cell selection
		case 5: // cell selection plus shift key
			update_controls()
			break
	endswitch

	setdatafolder save_df
	return 0
end

/// open data folder corresponding to a file and data path
///
/// the function tries to find where a given dataset has been loaded
/// and selects the corresponding data folder.
/// the data folder must exist (after previous import from the file),
/// else an error code is returned and the folder selection will be the closest accessible parent folder of the target.
///
/// @param filename			file name (without path).
///								h5 and otf.itx files are supported.
/// @param datapath			dataset or group path inside the hdf5 file.
/// @return					0 if successful, 
///								-1 if the file type is unknown
///								-2 if the data path can't be found in the tree.
///
static function goto_dataset_folder(filename, datapath)
	string filename
	string datapath
	
	dfref save_df = GetDataFolderDFR()
	setdatafolder $package_path

	variable ft = pearl_file_type(filename)
	string parent_folder
	string folder
	string path
	
	switch(ft)
		case 1:
		case 2:
			parent_folder = ad_suggest_foldername(filename)
			path = "root:" + parent_folder
			if (DataFolderExists(path))
				setdatafolder $path
			else
				return -2
			endif

			variable nparts = ItemsInList(datapath, "/")
			variable ipart
			for (ipart = 0; ipart < nparts; ipart += 1)
				folder = StringFromList(ipart, datapath, "/")
				path = ":" + ps_fix_folder_name(folder)
				if (DataFolderExists(path))
					setdatafolder $path
				endif
			endfor
			break

		case 3:
			parent_folder = "root:" + itx_suggest_foldername(filename)
			if (DataFolderExists(parent_folder))
				setdatafolder $parent_folder
			else
				return -2
			endif
			break

		default:
			// unsupported file type
			return -1
	endswitch

	return 0
end

/// "goto DF" button
///
/// the button selects the data folder of the selected file and dataset.
/// an error message is shown if the data folder doesn't exist.
///
static function bp_goto_dataset(ba) : ButtonControl
	STRUCT WMButtonAction &ba

	switch( ba.eventCode )
		case 2: // mouse up
			dfref save_df = GetDataFolderDFR()
			setdatafolder $package_path
			svar s_selected_file
			svar hl_contents_datasets
			string datapath = StringFromList(0, WMHL_SelectedObjectsList("PearlDataExplorer", "lb_contents"))
			if (strsearch(hl_contents_datasets, datapath, 0) != 0)
				datapath = datapath + "/"
			endif
			variable result = goto_dataset_folder(s_selected_file, datapath)
			if (result != 0)
				setdatafolder save_df
				string msg
				msg = "Can't find data folder. Has the file been loaded?"
				DoAlert /T="Goto DF" 0, msg
			endif
			
			break
		case -1: // control being killed
			break
	endswitch

	return 0
End

/// "display dataset" button
///
///
static function bp_display_dataset(ba) : ButtonControl
	STRUCT WMButtonAction &ba

	switch( ba.eventCode )
		case 2: // mouse up
			dfref save_df = GetDataFolderDFR()
			setdatafolder $package_path
			svar s_selected_file
			svar hl_contents_datasets
			string datapath = StringFromList(0, WMHL_SelectedObjectsList("PearlDataExplorer", "lb_contents"))
			if (strsearch(hl_contents_datasets, datapath, 0) < 0)
				// path leads to folder
				return 0
			endif
			goto_dataset_folder(s_selected_file, "")
			display_dataset(GetDataFolderDFR(), datapath)
			setdatafolder save_df
			break
		case -1: // control being killed
			break
	endswitch

	return 0
End


/// *******
static function /s get_default_elog_module()
	string modules = "PearlSciLog;PearlElog"
	
	variable imod
	variable nmod = ItemsInList(modules, ";")
	string smod
	for (imod = 0; imod < nmod; imod += 1)
		smod = StringFromList(imod, modules, ";")
		if (exists(smod + "#set_panel_attributes") == 6)
			return smod
		endif
	endfor
	return ""
end


/// send file metadata to the ELOG panel
///
/// metadate is looked up in the following locations:
/// 1. data folder if it exists
/// 2. file info folder inside package folder
/// 3. package folder if it contains preview data from the selected file (???)
///
/// the data is sent to the first ElogPanel in the window list.
/// call open_pearl_elog() to ensure a new default panel.
///
static function send_to_elog()
	dfref save_df = GetDataFolderDFR()
	
	dfref preview_df = $package_path
	svar /z /sdfr=preview_df s_selected_file
	svar /z /sdfr=preview_df s_preview_file
	svar /z /sdfr=preview_df s_preview_graph

	if (!SVAR_Exists(s_selected_file) || (strlen(s_selected_file) < 1))
		return 0
	endif
	
	// check data folder
	variable result = -1
	result = goto_dataset_folder(s_selected_file, "")
	if (result == 0)
		dfref data_df = GetDataFolderDFR()
		svar /sdfr=data_df /z authors
		if (!svar_Exists(authors))
			result = -1
		endif
	endif

	// file info folder
	dfref infoDF = preview_df:file_info
	if ((result != 0) && (DataFolderRefStatus(infoDF)))
		svar /z /sdfr=infoDF s_filepath
		if (SVAR_Exists(s_filepath) && (strsearch(s_filepath, s_selected_file, inf, 1) >= 0))
			dfref data_df = infoDF
			result = 0
		endif
	endif

	// check preview (package) folder
	if ((result != 0) && (SVAR_Exists(s_preview_file) && (cmpstr(s_preview_file, s_selected_file) == 0)))
		dfref data_df = preview_df
		result = 0
	endif

	string graphname	
	if (SVAR_Exists(s_preview_graph) && (WinType(s_preview_graph) == 1))
		graphname = s_preview_graph
	else
		graphname = ""
	endif
	
	string module = get_default_elog_module()
	funcref PearlDataExplorer_proto_get_panel_name f_get_panel_name = $(module + "#get_default_panel_name")
	string panel = f_get_panel_name()
	
	if (result == 0)
		set_elog_attributes(module, panel, data_df, filename=s_selected_file, graphname=graphname)
	endif
	
	setdatafolder save_df
end

function /s PearlDataExplorer_proto_get_panel_name()
	return ""
end

function /s PearlDataExplorer_proto_set_panel_attributes(windowname, attributes, [clear])
	string windowname
	string attributes
	variable clear
	return ""
end

function /s PearlDataExplorer_proto_set_panel_graphs(windowname, graphs)
	string windowname
	string graphs
	return ""
end

/// send general metadata to ELOG panel - if available
///
/// the function works with any electronic logbook that has the same interfaces as pearl-elog.ipf.
/// the set_panel_attributes and set_panel_graphs functions are required.
///
/// the following metatdata are sent.
/// they must be present as strings in the specified data folder:
///
/// | ELOG parameter | global string | function argument |
/// | --- | --- | --- |
/// | file | s_filepath | filename |
/// | graph attachment | | graphname |
/// | author | authors | |
/// | p-group | pgroup | |
/// | project | proposal | |
/// | sample | sample | |
///
/// @param elog_module	Igor module name of the electronic logbook, PearlElog or PearlSciLog.
///
/// @param panel_name	Window name of the logbook panel
///
/// @param file_df		data folder that contains the metadata.
///
/// @param filename		override file path read from s_filepath global string variable.
///							if neither is declared, the file name is reset to empty field.
///
/// @param graphname	select this graph window for attaching.
///							default: do not change the selection.
///
static function set_elog_attributes(elog_module, panel_name, file_df, [filename, graphname])
	string elog_module
	string panel_name
	dfref file_df
	string filename
	string graphname

	if (ParamIsDefault(filename))
		svar /sdfr=file_df /z loaded_file=s_filepath
		if (svar_Exists(loaded_file))
			filename = loaded_file
		else
			filename = ""
		endif
	endif
	
	if (ParamIsDefault(graphname))
		graphname = ""
	endif

	string cmd
	string attrib = ""
	
	if (exists(elog_module + "#set_panel_attributes") == 6)
		funcref PearlDataExplorer_proto_set_panel_attributes f_set_attributes = $(elog_module + "#set_panel_attributes")
		funcref PearlDataExplorer_proto_set_panel_graphs f_set_graphs = $(elog_module + "#set_panel_graphs")
		
		attrib = ReplaceStringByKey("file", attrib, ParseFilePath(0, filename, ":", 1, 0), ":", ";")
		svar /sdfr=file_df /z authors
		if (svar_Exists(authors))
			if (strlen(authors)>=1)
				attrib = ReplaceStringByKey("author", attrib, authors, ":", ";")
			endif
		endif
		svar /sdfr=file_df /z pgroup
		if (svar_Exists(pgroup))
			if (strlen(pgroup)>=1)
				attrib = ReplaceStringByKey("p-group", attrib, pgroup, ":", ";")
			endif
		endif
		svar /sdfr=file_df /z proposal
		if (svar_Exists(proposal))
			if (strlen(proposal)>=1)
				attrib = ReplaceStringByKey("project", attrib, proposal, ":", ";")
			endif
		endif
		svar /sdfr=file_df /z proposer
		svar /sdfr=file_df /z sample
		if (svar_Exists(sample))
			if (strlen(sample)>=1)
				attrib = ReplaceStringByKey("sample", attrib, sample, ":", ";")
			endif
		endif
		
		if (strlen(attrib)>=3)
			f_set_attributes(panel_name, attrib)
		endif

		if ((strlen(graphname) > 0) && (WinType(graphname) == 1))
			f_set_graphs(panel_name, graphname)
		endif
	endif
end	
