#pragma rtGlobals=3 // Use modern global access method and strict wave access. #pragma version = 1.31 #pragma IgorVersion = 6.2 #pragma ModuleName = PearlElog // author: matthias.muntwiler@psi.ch // Copyright (c) 2013-16 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 interface for writing ELOG entries with Igor graphs as attachment. /// @ingroup ArpesPackage /// /// /// the functions in this module support the following work flows: /// 1. send a preview of a selected measurement to ELOG. /// 2. send any Igor graph to ELOG. (CLI and GUI) /// 3. direct access to all ELOG parameters. (CLI only) /// /// the configuration of the ELOG server and logbooks (except user name and password) /// as well as the most recently used attributes are persisted in the preference file. /// /// elog command line ///@verbatim /// elog -h [-p port] [-d subdir] /// Location where elogd is running /// -l logbook Name of logbook /// -s Use SSL for communication /// [-v] For verbose output /// [-w password] Write password defined on server /// [-u username password] User name and password /// [-f ] Up to 50 attachments /// -a = Up to 50 attributes /// [-r ] Reply to existing message /// [-q] Quote original text on reply /// [-e ] Edit existing message /// [-x] Suppress email notification /// [-n 0|1|2] Encoding: 0:ELcode,1:plain,2:HTML /// -m ] | ///@endverbatim /// /// user name and password are used if configured in the package data folder. /// there is currently no separate user interface. /// /// @attention some functions in this module refer specifically to the ELOG configuration at PEARL. /// /// @todo ask for logbook, username and password before opening the panel. /// /// @author matthias muntwiler, matthias.muntwiler@psi.ch /// /// @copyright 2013-16 Paul Scherrer Institut @n /// Licensed under the Apache License, Version 2.0 (the "License"); @n /// you may not use this file except in compliance with the License. @n /// You may obtain a copy of the License at /// http://www.apache.org/licenses/LICENSE-2.0 /// @namespace PearlElog /// @brief interface for writing ELOG entries with Igor graphs as attachment. /// /// PearlElog is declared in @ref pearl-elog.ipf. static strconstant package_name = "pearl_elog" static strconstant package_path = "root:packages:pearl_elog:" /// main function to initialize ELOG and to open an ELOG panel. /// /// this function takes care of all necessary initialization, configuration, and preferences. /// if a panel exists, it will be moved to the front. /// /// @param logbook name of the logbook /// if empty, the user is prompted to select or create a logbook by elog_prompt_logbook(). /// /// function pearl_elog(logbook) string logbook if (init_package() == 0) load_prefs() string templates = list_logbooks(templates=1) if (ItemsInList(templates) < 1) init_pearl_templates() endif endif if (strlen(logbook) == 0) logbook = elog_prompt_logbook() endif string win_name = logbook + "ElogPanel" if (strlen(logbook) > 0) if (strlen(WinList(win_name, ";", "")) > 0) DoWindow /F $win_name else PearlElogPanel(logbook) endif endif end /// save preferences and recent values before Igor opens a new experiment. static function IgorBeforeNewHook(igorApplicationNameStr) string igorApplicationNameStr save_prefs() return 0 end /// save preferences and recent values before Igor quits. static function IgorQuitHook(igorApplicationNameStr) string igorApplicationNameStr save_prefs() return 0 end /// initialize the package and reload preferences after an experiment is loaded. static function AfterFileOpenHook(refNum,file,pathName,type,creator,kind) Variable refNum,kind String file,pathName,type,creator if( (kind >= 1) && (kind <= 2)) init_package(clean=1) load_prefs() endif return 0 end static constant kdfRoot = 0 static constant kdfVolatile = 1 static constant kdfPersistent = 2 static constant kdfTemplates = 3 /// get the package, logbook, or template datafolder. /// /// @param name name of logbook or template, or empty string for respective parent folder. /// /// @param category parameter category: /// @arg kdfRoot package root /// @arg kdfVolatile volatile /// @arg kdfPersistent persistent /// @arg kdfTemplates template /// /// @returns data folder reference /// static function /df get_elog_df(name, category) string name variable category dfref df_package = $package_path dfref df_persistent = df_package:persistent dfref df_volatile = df_package:volatile switch(category) case kdfRoot: dfref df_parent = df_package break case kdfPersistent: dfref df_parent = df_persistent break case kdfTemplates: dfref df_parent = df_persistent:templates break case kdfVolatile: dfref df_parent = df_volatile break default: Abort "get_elog_df: undefined data folder category." endswitch if ((strlen(name) > 0) && (category >= 1)) if (category == kdfTemplates) dfref df_logbooks = df_parent else dfref df_logbooks = df_parent:logbooks endif dfref df_logbook = df_logbooks:$name return df_logbook else return df_parent endif end /// initialize the package data folder. /// /// the data folder is initialized with a default, local configuration without any logbooks. /// the server configuration should be set in the preferences. /// /// @param clean decides what to do if the package configuration exists. /// @arg 0 (default) keep existing configuration. /// @arg 1 overwrite existing configuration. /// static function init_package([clean]) variable clean if (ParamIsDefault(clean)) clean = 0 endif dfref savedf = getdatafolderdfr() dfref df_root = get_elog_df("", kdfRoot) if ((clean == 0) && (DataFolderRefStatus(df_root) == 1)) return 1 endif setdatafolder root: newdatafolder /o/s packages newdatafolder /o/s $package_name dfref df_package_root = getdatafolderdfr() newdatafolder /o/s volatile dfref df_volatile = getdatafolderdfr() newdatafolder /o logbooks setdatafolder df_package_root newdatafolder /o/s persistent dfref df_persistent = getdatafolderdfr() newdatafolder /o logbooks newdatafolder /o templates // common configuration setdatafolder df_persistent string /g elog_path = "c:\\program files (x86)\\ELOG\\elog.exe" string /g hostname = "localhost" variable /g port = 0 // 0 = unspecified (default) variable /g ssl = 0 // 0 = plain text (incl. passwords), 1 = secure connection string /g subdir = "" variable /g loglevel = 3 setdatafolder savedf return 0 end /// setup PEARL template logbooks. /// /// template logbooks for PEARL. /// /// @remark this function is specific to the setup at PEARL. /// static function init_pearl_templates() dfref savedf = getdatafolderdfr() dfref df_root = get_elog_df("", kdfRoot) dfref df_persistent = get_elog_df("", kdfPersistent) dfref df_templates = get_elog_df("", kdfTemplates) // Experiments template setdatafolder df_templates newdatafolder /o/s Experiments // attributes (persistent) // available attributes string /g attributes = "author;project;sample;source;task;technique;valid;file" // controls corresponding to attributes // prefix determines the control type: sv_ = setvariable (string), pm_ = popup menu, cb = check box string /g controls = "sv_author;sv_project;sv_sample;pm_source;pm_task;pm_technique;cb_valid;sv_file" // attributes with fixed options, value item declares the options string string /g options = "source=sources;task=tasks;technique=techniques" // attributes which must be defined string /g required_attributes = "author;project;sample;source;task;technique;valid" // option lists string /g sources = "Manual Entry;PShell;Scienta Data;SScan Data;Prosilica Data;OTF Data;Beamline Status;LEED Data;QMS Data;Matrix Data;Igor Pro;Other" string /g tasks = "Measurement;Sample Preparation;Sample Storage;Optimization;Analysis;Development;Maintenance;Test;Comment;Other" string /g techniques = "XPS;UPS;XPD;XAS;XMCD;PhD;ARUPS;LEED;AES;STM;STS;QMS;MBE;Test;Other" // Calculations template setdatafolder df_templates newdatafolder /o/s Calculations // attributes (persistent) // available attributes string /g attributes = "author;project;sample;program;revision;machine;job;source path;result path;valid" // controls corresponding to attributes // prefix determines the control type: sv_ = setvariable (string), pm_ = popup menu, cb = check box string /g controls = "sv_author;sv_project;sv_sample;pm_program;sv_revision;pm_machine;sv_job;sv_sourcepath;sv_resultpath;cb_valid" // attributes with fixed options, value item declares the options string string /g options = "program=programs;machine=machines" // attributes which must be defined string /g required_attributes = "author;project;sample" // option lists string /g programs = "DMSUP;EDAC;MSC;MUFPOT;SSC" string /g machines = "llcx;Merlin;PC" setdatafolder savedf return 0 end /// initialize volatile variables. /// /// create and initialize all volatile variables for the configured notebooks. /// values of existing variables are not changed. /// /// this function must be called after new logbooks have been configured, /// specifically by elog_create_logbook() and load_prefs(). /// static function init_volatile_vars() dfref savedf = GetDataFolderDFR() dfref df_volatile_root = get_elog_df("", kdfVolatile) dfref df_volatile_parent = df_volatile_root:logbooks string logbooks = list_logbooks() string logbook variable nlb = ItemsInList(logbooks) variable ilb for (ilb = 0; ilb < nlb; ilb += 1) logbook = StringFromList(ilb, logbooks) SetDataFolder df_volatile_parent if (DataFolderExists(logbook)) SetDataFolder $logbook else NewDataFolder /o/s $logbook endif if (exists("username") != 2) string /g username = "" endif if (exists("password") != 2) string /g password = "" endif if (exists("msg_id") != 2) variable /g msg_id = 0 endif endfor SetDataFolder savedf return 0 end /// @var persistent:loglevel /// @brief filter history messages by log level. /// /// messages are printed to the history only if their level exceeds this setting. /// this is a global variable in the "persistent" folder of the package configuration. /// /// @arg 0 none. do not print any messages. no messages have this level. /// @arg 1 critical. severe error with possible data corruption. /// @arg 2 error. a function did not complete successfully. /// @arg 3 warning. everything worked fine, but some attention of the user is required. /// @arg 4 info. status message which may be useful to the normal user. /// @arg 5 debug. status message which may be useful to the developer. /// /// create a new logbook. /// /// create a new empty logbook or duplicate from a template. /// /// @param name name of the new logbook. /// /// @param template name of the template. /// if empty string, a new empty logbook is created. /// function elog_create_logbook(name, [template]) string name string template if (ParamIsDefault(template)) template = "" endif dfref savedf = getdatafolderdfr() dfref df_root = get_elog_df("", kdfRoot) dfref df_persistent_root = get_elog_df("", kdfPersistent) dfref df_persistent_parent = df_persistent_root:logbooks dfref df_volatile_root = get_elog_df("", kdfVolatile) dfref df_volatile_parent = df_volatile_root:logbooks if (strlen(template) > 0) dfref df_template = get_elog_df(template, kdfTemplates) DuplicateDataFolder df_template, df_persistent_parent:$name else NewDataFolder /o/s df_persistent_parent:$name // ELOG logbook name string /g logbook = name // attributes (persistent) // available attributes string /g attributes = "" // controls corresponding to attributes // prefix determines the control type: sv_ = setvariable (string), pm_ = popup menu, cb = check box string /g controls = "" // attributes with fixed options, value item declares the options string string /g options = "" // attributes which must be defined string /g required_attributes = "" endif // usage data (persistent) setdatafolder get_elog_df(name, kdfPersistent) string /g recent = "" string /g recent_message = "" init_volatile_vars() setdatafolder savedf return 0 end /// set global module configuration parameters /// function elog_config([elog_path, hostname, port, subdir]) string elog_path string hostname variable port string subdir dfref df = get_elog_df("", kdfPersistent) if (!ParamIsDefault(elog_path)) svar /sdfr=df g_elog_path = elog_path g_elog_path = elog_path endif if (!ParamIsDefault(hostname)) svar /sdfr=df g_hostname = hostname g_hostname = hostname endif if (!ParamIsDefault(port)) nvar /sdfr=df g_port = port g_port = port endif if (!ParamIsDefault(subdir)) svar /sdfr=df g_subdir = subdir g_subdir = subdir endif end /// set username and password for login to a logbook /// /// the username and password are stored (in plain text) in global strings under the selected logbook folder. /// this is necessary for sending data to the ELOG server. /// /// call elog_logout() to clear the password variables and to avoid unintended use of your credentials. /// /// @warning /// igor does not have a built-in mechanism to protect passwords. /// user names and passwords are stored in plain text in the data folder tree. /// as such they are saved to experiment files and preferences. /// /// @param logbook name of the target logbook. /// function elog_login(logbook, username, password) string logbook string username string password dfref df = get_elog_df(logbook, kdfVolatile) svar /sdfr=df g_username=username svar /sdfr=df g_password=password g_username = username g_password = password end /// clear username and password of a logbook or all logbooks. /// /// the username and password are stored (in plain text) in global strings under the selected logbook folder. /// this function resets the username and password strings. /// /// @param logbook name of the target logbook. /// if empty, the passwords of all logbooks are cleared. /// function elog_logout(logbook) string logbook dfref df = get_elog_df(logbook, kdfVolatile) if (strlen(logbook) > 0) svar /z /sdfr=df g_username=username svar /z /sdfr=df g_password=password if (svar_exists(g_username)) g_username = "" endif if (svar_exists(g_password)) g_password = "" endif else dfref df2 = df:logbooks variable nlb = CountObjectsDFR(df2, 4) variable ilb string slb for (ilb = 0; ilb < nlb; ilb += 1) slb = GetIndexedObjNameDFR(df2, 4, ilb) if (strlen(slb) > 0) elog_logout(slb) endif endfor endif end /// save persistent package data to the preferences file. /// /// saves everything under the persistent folder of the package. /// static function save_prefs() dfref saveDF = GetDataFolderDFR() dfref df = get_elog_df("", kdfPersistent) 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 SaveData /O /Q /R fullPath KillPath/Z tempPackagePrefsPath endif SetDataFolder saveDF end /// load persistent package data from the preferences file. /// /// the preferences file is an Igor packed experiment file in a special preferences folder static function load_prefs() dfref saveDF = GetDataFolderDFR() variable result = -1 init_package() setdatafolder get_elog_df("", kdfPersistent) 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 init_volatile_vars() result = 0 endif endif SetDataFolder saveDF return result end /// get a list of configured logbooks or templates. /// /// this is list of data folder names under persistent:logbooks (or persistent:templates). /// the function does not check whether the folders contain valid data. /// /// @param templates select whether logbooks (0, default) or templates (1) are returned. /// /// @return semicolon-separated list of logbooks /// static function /s list_logbooks([templates]) variable templates if (ParamIsDefault(templates)) templates = 0 endif dfref df_persistent = get_elog_df("", kdfPersistent) if (templates) dfref df_logbooks = df_persistent:templates else dfref df_logbooks = df_persistent:logbooks endif string logbooks = "" variable nlb = CountObjectsDFR(df_logbooks, 4) variable ilb string slb for (ilb = 0; ilb < nlb; ilb += 1) slb = GetIndexedObjNameDFR(df_logbooks, 4, ilb) if (strlen(slb) > 0) logbooks = AddListItem(slb, logbooks) endif endfor return SortList(logbooks, ";", 16) end /// validate attributes /// /// @returns 0 if all required attributes are present and enumerated items are correct. /// non-zero if a violation is detected. /// /// @todo /// function currently not implemented, always returns 0 function elog_validate_attributes(logbook, attributes) string logbook // name of the logbook (as in igor folder name) string attributes // key=value list of attributes, semicolon separated variable result = 0 return result end /// create a new entry in ELOG /// /// this is the main function to create a new entry in a logbook. /// /// @param logbook name of the target logbook. /// /// @param attributes key=value list of attributes, semicolon separated. /// /// @param message free text part of the entry. /// /// @param encoding encoding of message, 0:ELcode, 1:plain (default), 2:HTML. /// /// @param graphs names of graph windows to be added as attachments, semicolon separated. /// /// @param replyto existing message ID (> 1) to follow up on. /// 0 or default: start new thread. /// /// @return ID number of the new entry (> 0), or error code (< 0). /// @arg -1: failed to save temporary message file. /// @arg -2: invalid/missing command line. /// @arg -3: invalid/missing attributes. /// @arg -4: elog returned error /// function elog_create_entry(logbook, attributes, message, [encoding, graphs, replyto]) string logbook string attributes string message variable encoding string graphs variable replyto if (ParamIsDefault(encoding)) encoding = 1 endif if (ParamIsDefault(graphs)) graphs = "" endif if (ParamIsDefault(replyto)) replyto = 0 endif dfref savedf = getdatafolderdfr() dfref df_general = get_elog_df("", kdfPersistent) dfref df_volatile = get_elog_df(logbook, kdfVolatile) variable result = 0 nvar /sdfr=df_volatile msg_id nvar /sdfr=df_general loglevel if (elog_validate_attributes(logbook,attributes) != 0) if (loglevel >= 2) print "ELOG: failed to validate attributes." endif result = -3 endif string cmd = prepare_command_line(logbook) if (strlen(cmd) == 0) if (loglevel >= 2) print "ELOG: failed to prepare command line." endif result = -2 endif if (replyto >= 1) cmd += " -r " + num2str(replyto) endif cmd += " -n " + num2str(encoding) variable nattr = ItemsInList(attributes, ";") variable iattr string sattr for (iattr = 0; (iattr < nattr) && (result == 0); iattr += 1) sattr = StringFromList(iattr, attributes, ";") if (strlen(StringFromList(1, sattr, "=")) > 0) sattr = ReplaceString("%", sattr, "") cmd += " -a \"" + sattr + "\"" endif endfor if (result == 0) string cmd_graphs = prepare_graph_attachments(graphs) cmd += " " + cmd_graphs endif if ((result == 0) && (strlen(message) > 0)) string messagefile = create_message_file(message) if (strlen(messagefile) > 0) cmd += " -m \"" + messagefile + "\"" cmd += " > \"" + get_log_path() + "\"" if (loglevel >= 5) print cmd endif string cmd_file_path = create_cmd_file(cmd) ExecuteScriptText cmd_file_path variable id = parse_result() if (id > 0) msg_id = id if (loglevel >= 4) print "ELOG: sent message " + num2str(id) endif else if (loglevel >= 2) print "ELOG: sending message failed." endif result = -4 endif cleanup_temp_files() else if (loglevel >= 2) print "ELOG: failed to create temporary message file." endif result = -1 endif endif setdatafolder savedf return result end /// add one or more graphs to an existing ELOG entry /// /// @param logbook name of the target logbook /// @param id identification number of the existing entry /// @param graphs names of graph windows to be added as attachments, semicolon separated /// /// @warning this will delete all existing attachments of the entry! function elog_add_attachment(logbook, id, graphs) string logbook variable id // existing entry ID string graphs // names of graph windows to be added as attachments, semicolon separated dfref savedf = getdatafolderdfr() dfref df_general = get_elog_df("", kdfPersistent) dfref df_volatile = get_elog_df(logbook, kdfVolatile) variable result = 0 nvar /sdfr=df_volatile msg_id nvar /sdfr=df_general loglevel string cmd = prepare_command_line(logbook) if (strlen(cmd) == 0) result = -2 // error: invalid/missing command line endif cmd += " -e " + num2str(id) if (result == 0) string cmd_graphs = prepare_graph_attachments(graphs) if (strlen(cmd_graphs) == 0) result = -3 // error: invalid/missing graphs endif endif if (result == 0) cmd += " " + cmd_graphs cmd += " > \"" + get_log_path() + "\"" string cmd_file_path = create_cmd_file(cmd) ExecuteScriptText cmd_file_path id = parse_result() if (id > 0) msg_id = id if (loglevel >= 4) print "ELOG: attached graphs to message " + num2str(id) endif else if (loglevel >= 2) print "ELOG: failed to attach graphs." endif result = -4 // error: elog returned error endif cleanup_temp_files() endif setdatafolder savedf return result end /// format the ELOG command and essential address arguments. /// /// the following arguments are included (from global variables) if applicable: /// host name, port, SSL, sub-dir, username, password /// the result string does not include leading or trailing space /// /// @param logbook name of the target logbook static function /s prepare_command_line(logbook) string logbook dfref df_general = get_elog_df("", kdfPersistent) dfref df_persistent = get_elog_df(logbook, kdfPersistent) dfref df_volatile = get_elog_df(logbook, kdfVolatile) svar /sdfr=df_general elog_path svar /sdfr=df_general hostname nvar /sdfr=df_general port nvar /sdfr=df_general ssl svar /sdfr=df_general subdir nvar /sdfr=df_general loglevel svar /sdfr=df_volatile username svar /sdfr=df_volatile password string cmd cmd = "\"" + elog_path + "\"" if (loglevel >= 5) cmd += " -v" endif cmd += " -h " + hostname if ((nvar_exists(port)) && (port > 0)) cmd += " -p " + num2str(port) endif if ((svar_exists(subdir)) && (strlen(subdir) > 0)) cmd += " -d " + subdir endif cmd += " -l \"" + logbook + "\"" if ((nvar_exists(ssl)) && (ssl != 0)) cmd += " -s" endif //cmd += " -w " + password if (svar_exists(username) && svar_exists(password) && (strlen(username) > 0) && (strlen(password) > 0)) cmd += " -u " + username + " " + password endif if (loglevel >= 5) print cmd endif return cmd end /// prepare screenshots of graph windows for attachments /// /// prepares the attachment files from Igor graph windows /// and returns the arguments to the elog command to attach the files. /// the result string does not include leading or trailing space /// /// @param graphs names of graph windows to be added as attachments, semicolon separated /// static function /s prepare_graph_attachments(graphs) string graphs // names of graph windows to be added as attachments, semicolon separated string cmd = "" variable ngraphs = ItemsInList(graphs, ";") variable igraph string sgraph string graph_path for (igraph = 0; igraph < ngraphs; igraph += 1) sgraph = StringFromList(igraph, graphs, ";") graph_path = create_graph_file(sgraph, igraph) if (strlen(graph_path) > 0) cmd += " -f \"" + graph_path + "\"" endif endfor return cmd end static function /s get_timestamp(sep) string sep Variable now = DateTime string dat = ReplaceString("-", Secs2Date(DateTime, -2), "") string tim = ReplaceString(":", Secs2Time(DateTime, 3), "") return dat + sep + tim end static function /s create_message_file(message) string message message = ReplaceString("%", message, "") string path = SpecialDirPath("Temporary", 0, 1, 0) variable len = strlen(path) if (numtype(len) == 0) path += "elog_temp_message.txt" variable f1 Open f1 as path fprintf f1, message Close f1 else path = "" endif return path end static function /s create_graph_file(graphname, fileindex) string graphname variable fileindex string path = SpecialDirPath("Temporary", 0, 1, 0) string ts = get_timestamp("_") variable len = strlen(path) if (numtype(len) == 0) path += "elog_" + ts + "_" + num2str(fileindex) + ".png" SavePICT /B=72 /E=-5 /M /O /W=(0,0,8,6) /WIN=$graphname /Z as path if (v_flag != 0) path = "" endif else path = "" endif return path end static function /s create_cmd_file(cmd) string cmd string path = SpecialDirPath("Temporary", 0, 1, 0) variable len = strlen(path) if (numtype(len) == 0) path += "elog_temp_cmd.bat" variable f1 Open f1 as path fprintf f1, cmd Close f1 else path = "" endif return path end static function /s get_log_path() string path = SpecialDirPath("Temporary", 0, 1, 0) variable len = strlen(path) if (numtype(len) == 0) path += "elog.log" else path = "" endif return path end static function /s cleanup_temp_files() string path = SpecialDirPath("Temporary", 0, 1, 0) string cmd sprintf cmd, "del \"%s\elog*.*\"", path //ExecuteScriptText cmd return path end static strconstant elog_success_msg = "Message successfully transmitted" static strconstant elog_parse_id = "ID=%u" /// parse the result file from an elog invokation. /// /// @returns the ID of the generated message, /// or a value <= 0 if an error occurred. /// static function parse_result() dfref df_general = get_elog_df("", kdfPersistent) nvar /sdfr=df_general loglevel string path = get_log_path() string line = "" string output = "" variable success = 0 variable id = -1 string part1 = "" string part2 = "" variable len = strlen(path) if (numtype(len) == 0) variable f1 Open /R/Z f1 as path if (v_flag == 0) do FReadLine f1, line if (strlen(line) > 0) part1 = StringFromList(0, line, ",") part2 = ReplaceString(" ", StringFromList(1, line, ","), "") success = cmpstr(part1, elog_success_msg) == 0 if (success) sscanf part2, elog_parse_id, id endif else break endif output += line while(!success) Close f1 endif endif if (loglevel >= 5) print output endif return id end /// prompt to open or create a logbook /// function /s elog_prompt_logbook() string logbooks = list_logbooks(templates=0) logbooks = AddListItem("(new)", logbooks) string templates = list_logbooks(templates=1) templates = AddListItem("(none)", templates) string logbook = StringFromList(0, logbooks) string template = StringFromList(0, logbooks) string name = "" string username = "" string password = "" prompt logbook, "logbook", popup logbooks prompt template, "template", popup templates prompt name, "new logbook name" doprompt "select logbook", logbook, template, name if (!v_flag) if (cmpstr(logbook, "(new)") == 0) elog_create_logbook(name, template=template) logbook = name endif else logbook = "" endif return logbook end /// prompt the user for login to a logbook /// function elog_prompt_login(logbook) string logbook string logbooks = list_logbooks(templates=0) string username = "" string password = "" prompt logbook, "logbook", popup logbooks prompt username, "user name" prompt password, "password (blank to log out)" doprompt "log in to logbook", logbook, username, password if (!v_flag) elog_login(logbook, username, password) endif return v_flag end /// open a new panel for submitting data to ELOG. /// /// this function creates only the panel but not the necessary data folders. /// call init_package() and load_prefs() once before creating panels. /// /// @param logbook name of the target logbook /// function /s PearlElogPanel(logbook) string logbook dfref savedf = getdatafolderdfr() dfref df_general = get_elog_df("", kdfPersistent) dfref df_persistent = get_elog_df(logbook, kdfPersistent) dfref df_volatile = get_elog_df(logbook, kdfVolatile) string win_name = logbook + "ElogPanel" string win_title = "ELOG " + logbook NewPanel /K=1 /N=$win_name /W=(600,200,926,494) as win_title win_name = s_name ModifyPanel /w=$win_name cbRGB=(52224,52224,65280) svar /sdfr=df_persistent attributes svar /sdfr=df_persistent controls svar /sdfr=df_persistent options variable iattr variable nattr = ItemsInList(attributes, ";") string s_attr string s_control string s_option string persistent_path = GetDataFolder(1, df_persistent) string volatile_path = GetDataFolder(1, df_volatile) string options_path string variable_path variable ypos = 2 Button b_login,win=$win_name, pos={264,ypos},size={46,20},proc=PearlElog#bp_login,title="Login" Button b_login,win=$win_name, help={"Enter user name and password."} Button b_login,win=$win_name, fcolor=(56576,60928,47872) Button b_logout,win=$win_name, pos={264,ypos},size={46,20},proc=PearlElog#bp_logout,title="Logout" Button b_logout,win=$win_name, help={"Clear user name and password."} Button b_logout,win=$win_name, fcolor=(56576,60928,47872), disable=3 for (iattr = 0; iattr < nattr; iattr += 1) s_attr = StringFromList(iattr, attributes, ";") s_control = StringFromList(iattr, controls, ";") strswitch(s_control[0,1]) case "sv": SetVariable $s_control, win=$win_name, pos={0,ypos}, size={260,16}, bodyWidth=200 SetVariable $s_control, win=$win_name, title=s_attr, value= _STR:"" SetVariable $s_control, win=$win_name, userdata(attribute)=s_attr ypos += 18 break case "pm": options_path = persistent_path + StringByKey(s_attr, options, "=", ";") PopupMenu $s_control, win=$win_name, pos={0,ypos}, size={260,21}, bodyWidth=200 PopupMenu $s_control, win=$win_name, title=s_attr PopupMenu $s_control, win=$win_name, mode=1, popvalue="Test", value= #options_path PopupMenu $s_control, win=$win_name, userdata(attribute)=s_attr ypos += 23 break case "cb": CheckBox $s_control, win=$win_name, pos={60,ypos}, size={260,14} CheckBox $s_control, win=$win_name, title=s_attr, value= 1 CheckBox $s_control, win=$win_name, userdata(attribute)=s_attr ypos += 17 break endswitch endfor PopupMenu pm_attach,win=$win_name, pos={0,ypos},size={260,21},bodyWidth=200,title="Attachment" PopupMenu pm_attach,win=$win_name, mode=1,popvalue="(none)",value=PearlElog#pm_list_attach_items() PopupMenu pm_attach,win=$win_name, help={"Choose any visible Igor graph for attachment."} Button b_select_attach_top,win=$win_name, pos={264,ypos},size={60,20},proc=PearlElog#bp_select_attach_top,title="Top Graph" Button b_select_attach_top,win=$win_name, help={"Select top graph window."} Button b_select_attach_top,win=$win_name, fcolor=(56576,60928,47872) ypos += 246-160 Button b_submit,win=$win_name, pos={60,ypos},size={46,20},proc=PearlElog#bp_submit,title="Submit" Button b_submit,win=$win_name, help={"Submit form data to ELOG (new entry)."} Button b_submit,win=$win_name, fcolor=(56576,60928,47872) Button b_clear,win=$win_name, pos={110,ypos},size={46,20},proc=PearlElog#bp_clear,title="Clear" Button b_clear,win=$win_name, help={"Clear the form fields"} Button b_clear,win=$win_name, fcolor=(56576,60928,47872) ypos += 272-246 variable_path = volatile_path + "msg_id" SetVariable sv_id,win=$win_name, pos={46,ypos},size={109,16},bodyWidth=94 SetVariable sv_id,win=$win_name, title="ID",value=$variable_path SetVariable sv_id,win=$win_name, help={"ID of last submitted message, or message to attach or reply to."} ypos += 270-272 Button b_attach,win=$win_name, pos={160,ypos},size={48,20},proc=PearlElog#bp_attach,title="Attach" Button b_attach,win=$win_name, help={"Attach the selected graph to an existing ELOG entry (correct ID required)."} Button b_attach,win=$win_name, fcolor=(56576,60928,47872) Button b_reply,win=$win_name, pos={210,ypos},size={48,20},proc=PearlElog#bp_submit,title="Reply" Button b_reply,win=$win_name, help={"Submit form data to ELOG as a reply to an existing message (correct ID required)."} Button b_reply,win=$win_name, fcolor=(56576,60928,47872) ypos += 184-270 TitleBox t_message,win=$win_name, pos={0,ypos},size={58,16},fixedSize=1,frame=0,anchor=RT,title="Message" SetWindow $win_name, hook(elogPanelHook)=PearlElog#elog_panel_hook SetWindow $win_name, userdata(logbook)=logbook DefineGuide UGH0={FT,ypos},UGV0={FL,60},UGH1={FB,-52},UGV1={FR,-2} NewNotebook /F=0 /N=Message /OPTS=3 /W=(115,404,345,341)/FG=(UGV0,UGH0,UGV1,UGH1) /HOST=# Notebook kwTopWin, defaultTab=20, statusWidth=0, autoSave=0 Notebook kwTopWin font="Arial", fSize=10, fStyle=0, textRGB=(0,0,0) RenameWindow #,Message SetActiveSubwindow ## // restore recently used attributes and message svar /z /sdfr=df_persistent recent if (svar_exists(recent) && (strlen(recent) > 0)) set_panel_attributes(win_name, recent) endif svar /z /sdfr=df_persistent recent_message if (svar_exists(recent_message) && (strlen(recent_message) > 0)) set_panel_message(win_name, recent_message) endif setdatafolder savedf return win_name end static function elog_panel_hook(s) STRUCT WMWinHookStruct &s Variable hookResult = 0 switch(s.eventCode) case 6: // resize // move bottom-aligned controls when the window is resized variable b_top = s.winRect.bottom + 4 Button b_submit,pos={60,b_top} Button b_clear,pos={110,b_top} b_top += 24 Button b_attach,pos={160,b_top} Button b_reply,pos={210,b_top} b_top += 2 SetVariable sv_id,pos={46,b_top} break endswitch return hookResult // 0 if nothing done, else 1 end /// get a list of graph windows for a popup menu. static function /s pm_list_attach_items() // get graph names string names = WinList("*", ";", "WIN:1;VISIBLE:1") names = SortList(names, ";", 16) // get corresponding graph titles variable nnames = ItemsInList(names, ";") variable iname string name string item string items = "" // format each entry like "name: title" for (iname = 0; iname < nnames; iname += 1) name = StringFromList(iname, names, ";") getwindow /z $name, wtitle if (v_flag == 0) item = ReplaceString(";", s_value, ", ") // title item = item + " (" + name + ")" items = AddListItem(item, items, ";", inf) endif endfor items = AddListItem("(none)", items, ";") return items end /// button procedure for the Submit and Reply buttons static function bp_submit(ba) : ButtonControl STRUCT WMButtonAction &ba switch( ba.eventCode ) case 2: // mouse up string logbook = GetUserData(ba.win, "", "logbook") string attributes string message string graphs attributes = get_panel_attributes(ba.win) message = get_panel_message(ba.win) graphs = get_panel_graphs(ba.win) variable id if (cmpstr(ba.ctrlName, "b_reply") == 0) // Reply button ControlInfo /w=$ba.win sv_id id = v_value else // Submit button id = 0 endif if ((elog_validate_attributes(logbook, attributes) == 0) && (strlen(message) > 0)) variable result result = elog_create_entry(logbook, attributes, message, graphs=graphs, replyto=id) if (result == 0) dfref df = get_elog_df(logbook, kdfPersistent) svar /sdfr=df recent recent = attributes svar /sdfr=df recent_message recent_message = message else abort "Submission failed. Error code " + num2str(result) + "." endif else abort "Submission failed due to missing/invalid attribute." endif break case -1: // control being killed break endswitch return 0 end /// select top graph window for attachment static function bp_select_attach_top(ba) : ButtonControl STRUCT WMButtonAction &ba switch( ba.eventCode ) case 2: // mouse up string graphs = WinName(0, 1, 1) set_panel_graphs(ba.win, graphs) break case -1: // control being killed break endswitch return 0 end static function bp_attach(ba) : ButtonControl STRUCT WMButtonAction &ba switch( ba.eventCode ) case 2: // mouse up string logbook = GetUserData(ba.win, "", "logbook") string graphs graphs = get_panel_graphs(ba.win) variable id ControlInfo /w=$ba.win sv_id id = v_value // TODO : is there a way around this restriction? DoAlert /T="ELOG" 1, "This operation will replace all existing attachments. Do you want to continue?" if ((id > 0) && (v_flag == 1)) variable result result = elog_add_attachment(logbook, id, graphs) if (result != 0) abort "Submission failed. Error code " + num2str(result) + "." endif else abort "Submission failed due to missing/invalid attribute." endif break case -1: // control being killed break endswitch return 0 end static function bp_clear(ba) : ButtonControl STRUCT WMButtonAction &ba switch( ba.eventCode ) case 2: // mouse up set_panel_attributes(ba.win, "", clear=1) set_panel_message(ba.win, "") set_panel_graphs(ba.win, "") break case -1: // control being killed break endswitch return 0 end static function bp_login(ba) : ButtonControl STRUCT WMButtonAction &ba switch( ba.eventCode ) case 2: // mouse up string logbook = GetUserData(ba.win, "", "logbook") if (elog_prompt_login(logbook) == 0) Button b_login, win=$ba.win, disable=3 Button b_logout, win=$ba.win, disable=0 endif break case -1: // control being killed break endswitch return 0 end static function bp_logout(ba) : ButtonControl STRUCT WMButtonAction &ba switch( ba.eventCode ) case 2: // mouse up string logbook = GetUserData(ba.win, "", "logbook") elog_logout(logbook) Button b_login, win=$ba.win, disable=0 Button b_logout, win=$ba.win, disable=3 break case -1: // control being killed break endswitch return 0 end static function /s get_default_panel_name() string windowname windowname = StringFromList(0, WinList("*ElogPanel*", ";", "WIN:64"), ";") return windowname end /// get a list of attributes from the fields of the ELOG panel. /// /// @param windowname window name of the ELOG panel /// if empty, use default name "PearlElogPanel" /// /// @return list of attributes to in the format "key1=value1;key2=value2". /// static function /s get_panel_attributes(windowname) string windowname if (strlen(windowname) == 0) windowname = get_default_panel_name() endif if (strlen(windowname) == 0) return "" endif string controls = ControlNameList(windowname, ";") string attributes = "" string control string attribute variable ico variable nco = ItemsInList(controls, ";") for (ico = 0; ico < nco; ico += 1) control = StringFromList(ico, controls, ";") attribute = GetUserData(windowname, control, "attribute") if (strlen(attribute) > 0) ControlInfo /w=$windowname $control switch(v_flag) case 2: // checkbox attributes = ReplaceNumberByKey(attribute, attributes, v_value, "=", ";") break case 3: // popupmenu case 5: // setvariable attributes = ReplaceStringByKey(attribute, attributes, s_value, "=", ";") break endswitch endif endfor return attributes end /// set the fields of the ELOG panel /// /// @param windowname window name of the ELOG panel /// if empty, use default name "PearlElogPanel" /// /// @param attributes list of attributes to set (format "key1=value1;key2=value2") /// /// @param clear what to do if a key is missing in attributes? /// @arg 0 (default) leave the field unchanged /// @arg 1 clear the field /// static function /s set_panel_attributes(windowname, attributes, [clear]) string windowname string attributes variable clear if (strlen(windowname) == 0) windowname = get_default_panel_name() endif if (strlen(windowname) == 0) return "" endif if (ParamIsDefault(clear)) clear = 0 endif string path string controls = ControlNameList(windowname, ";") string control string attribute string value variable numval variable ico variable nco = ItemsInList(controls, ";") for (ico = 0; ico < nco; ico += 1) control = StringFromList(ico, controls, ";") attribute = GetUserData(windowname, control, "attribute") if (strlen(attribute)) value = StringByKey(attribute, attributes, "=", ";") if (strlen(value) || clear) ControlInfo /w=$windowname $control switch(v_flag) case 2: // checkbox numval = NumberByKey(attribute, attributes, "=", ";") if ((numtype(numval) != 0) && clear) numval = 0 endif if (numtype(numval) == 0) CheckBox $control, value=numval, win=$windowname endif break case 3: // popupmenu PopupMenu $control, popvalue=value, win=$windowname break case 5: // setvariable SetVariable /z $control, value= _STR:value, win=$windowname break endswitch endif endif endfor return attributes end /// get the message field of the ELOG panel /// /// @param windowname window name of the ELOG panel /// if empty, use default name "PearlElogPanel" /// /// @return message text /// static function /s get_panel_message(windowname) string windowname if (strlen(windowname) == 0) windowname = get_default_panel_name() endif if (strlen(windowname) == 0) return "" endif string nb = windowname + "#Message" notebook $nb selection={startOfFile, endOfFile} getselection notebook, $nb, 2 return s_selection end /// set the message field of the ELOG panel /// /// @param windowname window name of the ELOG panel /// if empty, use default name "PearlElogPanel" /// /// @param message message text that can be passed to the @c Notebook operation. /// /// @return original message (unchanged) /// static function /s set_panel_message(windowname, message) string windowname string message if (strlen(windowname) == 0) windowname = get_default_panel_name() endif string nb = windowname + "#Message" notebook $nb selection={startOfFile, endOfFile},text=message return message end /// get the names of the graphs selected for attachment /// /// @returns a semicolon-separated list, /// or the empty string if the selection is not valid. /// /// in the current version, the function returns at most one graph. /// in future versions, the function may return more than one graph. /// static function /s get_panel_graphs(windowname) string windowname // panel window name if (strlen(windowname) == 0) windowname = get_default_panel_name() endif if (strlen(windowname) == 0) return "" endif ControlInfo /W=$windowname pm_attach // menu item has the form: "title (name)" string graphname = ReplaceString(")", StringFromList(ItemsInList(s_value, "(") - 1, s_value, "("), "") string graphs = "" string windows = WinList(graphname, ";", "WIN:1") if (ItemsInList(windows) == 1) graphs = graphname else graphs = "" endif return graphs end static function /s set_panel_graphs(windowname, graphs) string windowname // panel window name. looks for default panel if empty. string graphs // name of graph window to select for attachment. if (strlen(windowname) == 0) windowname = get_default_panel_name() endif if (strlen(windowname) == 0) return "" endif // the panel supports only one graph string graph = StringFromList(0, graphs, ";") string items = pm_list_attach_items() string item string itemname variable nitems = ItemsInList(items, ";") variable iitem for (iitem = 0; iitem < nitems; iitem += 1) item = StringFromList(iitem, items, ";") itemname = ReplaceString(")", StringFromList(ItemsInList(item, "(") - 1, item, "("), "") if (cmpstr(graph, itemname) == 0) iitem += 1 PopupMenu pm_attach mode=iitem, win=$windowname break endif endfor return graph end