#pragma TextEncoding="UTF-8" #pragma rtGlobals=3 #pragma IgorVersion=7 // feedback? ideas? tony.withers@uwo.ca // This file should be saved in the Igor Procedures folder (look in the // Help menu for "Show Igor Pro User Files"). // If you want Igor to automatically reload updated files, choose // Misc->Miscellaneous Settings, click Text Editing in the left pane, // click the External Editor tab, and select Reload Automatically, As // Soon as Modification is Detected. // If you're an end-user of packages that can be updated this way, that's // all you need to do. // ------------------------------------------------------------------------ // If you're developing a package and wish to use this updater: // 1. Copy and paste this block of pragma statements and constants to the procedure file you // want to keep updated. Edit values to match your package - the values here are for this file. #pragma version=2.10 // Edit version number - must be same as release version on IgorExchange #pragma moduleName=updater // Edit regular module name static constant kCHECK_FOR_UPDATES=1 // set to 1 to check for updates on init static strconstant ksSHORTNAME="Updater" // Must correspond to shortname of IgorExchange release // (ksSHORTNAME isn't needed if moduleName matches project shortname) // 2. Insert this code in your procedure file (without //): //static function CheckForUpdates() // if(kCHECK_FOR_UPDATES) // #if (exists("Updater#UpdateCheck")==6) // return Updater#UpdateCheck("fileloc:"+FunctionPath("")) // #endif // endif // return 0 //end // 3. Add this to your package initialization function: // if (CheckForUpdates()) // return 0 // this file has been updated; quit to allow user to reload // endif // --------------------------------------------------------------------------- // -- Details // If the project release is a zip archive, Updater downloads the archive // to a temporary location on the desktop, and moves all of the contents // to a location in the User Procedures folder based on the location of // the ipf file to be updated. For this to work, the zip archive had // better have a straightforward structure. Caveat emptor! // -- Wishlist // Would like to know current status of Misc settings for reloading // updated files. // Provide a way to rollback to earlier releases, or give a list of // releases for the user to choose from. // On a computer running some kind of communication blocking software the // code could stall at the FetchURL lines. Maybe switching to URLRequest // would help? I don't have an easy way to test. // ----------------------------------------------------------------------------- // -- code for use in independent modules: //static function CheckForUpdates() // // if (kCHECK_FOR_UPDATES==0) // return 0 // endif // // string keyStr="" // keyStr=ReplaceStringByKey("fileloc", keyStr, FunctionPath("")) // //// find out whether Updater is installed // #if (exists("Updater#UpdateCheck")==6) // this will be 0 if running as an independent module // return Updater#UpdateCheck(keyStr) // #endif // // if(stringmatch(GetIndependentModuleName(), "ProcGlobal")) // return 0 // endif // //// Running as independent module, so will have to use execute. //// Find out whether procedures are compiled: // // variable /G V_updated=0 // variable isUpdated=0 // string cmd, infoStr=FunctionInfo("ProcGlobal#updater#UpdateCheck", "Updater.ipf") // // if (stringmatch(StringByKey("MODULE", infoStr), "updater")) // updater is available AND compiled // sprintf cmd, "V_updated=ProcGlobal#Updater#UpdateCheck(\"%s\")", keyStr // execute cmd // isUpdated=V_updated // endif // // killvariables /Z V_updated // // return isUpdated //end // ------------------------------------------------------------------------------------------------------------ static constant kFREQUENCY=604800 // do not check more frequently than this. weekly=604800. // you can override this setting by adding a copy of the above line to your procedure file and editing the value // you can also override the default location for updates by adding something like this to your procedure: // static strconstant ksLOCATION="https://www.wavemetrics.com/project-releases/1234" // ------------------------------------------------------------------------------------------------------------ menu "Help" "Updater: Check for new releases", /Q, updater#UpdateAll() end // We'll keep a very small binary file in the updater preferences folder to // store the last update for each project that works with updater static structure LastUpdateStructure uint32 version // Preferences structure version number. 100 means 1.00. uint32 lastUpdate // 32 bits should be good for another 20 years or so... uint32 reserved[8] // Reserved for future use endStructure // Wrapper function for update() // This function is called from the file to be checked static function UpdateCheck(keyList) string keyList variable updated=0 NVAR /Z v_updater=v_updater // if this exists we want to check all open procedures for possible updates if (!NVAR_Exists(v_updater)) // avoid repeatedly checking this file // first find out whether this file needs updating string keyListThisFile="" keyListThisFile=ReplaceStringByKey("fileloc", keyListThisFile, FunctionPath("")) updated+=update(keyListThisFile) endif // now check the calling procedure file updated+=update(keyList) return updated end // Looks for procedure windows with defined modules that contain the // static CheckForUpdates function, and executes that function wherever // it is found. Only works for procedure files in which a regular module // name is defined. static function UpdateAll() // Use a global variable to let UpdateCheck() function in this // procedure know that we want to ignore the frequency setting and // check now for updates to all files that use updater and have // kCheckForUpdates set. variable /g v_updater=0 string procList="", IMlist="", moduleList="", moduleStr="", IMstr="", cmd="" variable i,j, imax, jmax execute "SetIgorOption IndependentModuleDev=1" // would be good to record current status before setting this procList=winlist("*",";","WIN:128") execute "SetIgorOption IndependentModuleDev=0" IMlist=IndependentModuleList(";") // loop through procedure windows imax=itemsinlist(procList) for(i=0; iView all releases", 0, 2) selStart=strsearch(webPageText, "href=", selEnd, 3) if (selEnd==-1 || selStart==-1) return 0 endif url="https://www.wavemetrics.com"+webPageText[selStart+6,selEnd-2] endif // version number of local file Grep /Q/E="(?i)^#pragma[\s]*version[\s]*=" /LIST/Z procPathStr s_value=lowerStr(trimString(s_value, 1)) sscanf s_value, "#pragma version = %f", localVersion if (V_flag!=1 || localVersion<=0) printf "Could not find version pragma in %s\r", ipfName return 0 endif if(chatty) sprintf cmd, "Check for updates to %s?", shortName doalert 1, cmd if (v_flag==2) return 0 endif endif printf "%s: Checking for updates... ", shortName s.lastUpdate=datetime // will be rounded to 32 bit integer SavePackagePreferences "updater", prefsFile, 1, s webPageText = FetchURL(url); err = GetRTError(1) if (err != 0) sprintf cmd, "Could not load %s", url print cmd if(chatty) doalert 0, cmd endif return 0 endif // Look for new releases. If the web page location is not IgorExchange, // ParseReleases will have to be changed to take into account the format // of the web page. wave /T w_releases=ParseReleases(webPageText) if (dimsize(w_releases,0)==0) sprintf cmd, "Could not find packages at %s.\r", url cmd+="\tPlease check for new versions at https://www.wavemetrics.com/projects" if (chatty) doalert 0, cmd endif print cmd return 0 endif variable i url="" for(i=0;i=releaseVersion) sprintf cmd, "Current version (%g) is the most recent release\r", localVersion if (chatty) doalert 0, cmd endif print cmd return 0 endif if(releaseVersion>localVersion) if(currentIgorVersion= %g\r", projectName, releaseVersion, releaseIgorVersion elseif(FindListItem(system, w_releases[i][5])==-1) printf "%s %g not available for %s\r", projectName, releaseVersion, system else url=w_releases[i][4] break endif endif endfor if (strlen(url)==0) return 0 endif fileType=parseFilePath(4, url,":", 0, 0) isZip=stringMatch(fileType, "zip") if(cmpstr(fileType, "ipf") && cmpstr(fileType, "zip")) printf "New version of %s released\r", projectName printf "Updater couldn't find a useable file at %s\r", url return 0 endif if (chatty) sprintf cmd, "%s: Found version %g. Download now?", projectName, releaseVersion doalert 1, cmd if (v_flag==2) // print file location for manual download printf "New version of %s available at %s\r", projectName, url return 0 endif endif if(testing) // for testing, don't hit server pointlessly printf "testing: substituting local file for %s\r", url url="file://C:/Users/tony/Desktop/archive.zip" url="file:///Users/tony/Desktop/archive.zip" isZip=1 endif // download the package file webPageText=FetchURL(url); err = GetRTError(1) if (err != 0) sprintf cmd, "Updater couldn't load %s", url print cmd if(chatty) doalert 0, cmd endif return 0 endif printf "Downloaded %s version %g from %s\r", projectName, releaseVersion, url if(archiveOldFile) // this will archive just the ipf file string oldFileprocPathStr sprintf oldFileprocPathStr, "%s%s%g.%s", SpecialDirPath("Desktop",0,0,0), parseFilePath(3, procPathStr, ":", 0, 0), localVersion, parseFilePath(4, procPathStr, ":", 0, 0) variable flagI=0 getFileFolderInfo /Q/Z oldFileprocPathStr if(v_flag==0) // file already exists in archive location flagI=2 // check before overwriting endif movefile /O/S="Archive current file"/I=(flagI)/Z procPathStr as oldFileprocPathStr if(v_flag==0) printf "Saved copy of %s version %g to %s\r", projectName, localVersion, s_path endif endif // make sure we have somewhere to write our download if(isZip) downloadPathStr=SpecialDirPath("Desktop",0,0,0)+"ACW_PackageInstallerTemp:" // create the temporary folder newpath /C/O/Q downloadPathTemp, downloadPathStr killpath /z downloadPathTemp // write download to zip file open /Z refNum as downloadPathStr+parseFilePath(0, url,"/", 1, 0) if(refNum==0) return 0 endif FBinWrite refNum, webPageText close refNum archivePathStr=S_fileName // path to zip file archiveName=ParseFilePath(0, S_fileName, ":", 1, 0) printf "Saved temporary file %s\r", archiveName // create a temporary directory for the uncompressed files string unzipPathStr="" i=0 do sprintf unzipPathStr,"%sInstallerTemp%d:", downloadPathStr, i GetFileFolderInfo /Q/Z unzipPathStr i+=1 while(V_Flag==0) newpath /C/O/Q unzipPath, unzipPathStr // inflate archive variable success = unzipArchive(archivePathStr, unzipPathStr) if (success==0) printf "unzipArchive failed to expand %s\r", S_fileName return 0 endif printf "Inflated %s to %s\r", archiveName, unzipPathStr string fileList, folderList fileList=IndexedFile(unzipPath, -1, "????") folderList=IndexedDir(unzipPath, -1, 0) killpath /Z unzipPath // ignore pesky __MACOSX folder i=0 do if(stringmatch(StringFromList(i, folderList), "__MACOSX")) folderList=removeListItem(i, folderList) else i+=1 endif while (iselStart+150) return w_releases endif projectName=WebPageText[selStart, selEnd-1] do // find start and end of project release fields selStart= strsearch(WebPageText, "
", selStart, 2) if(selStart==-1) break endif blockStart=selStart selEnd=strsearch(WebPageText, "
", selStart, 2) if(selEnd==-1) break endif blockEnd=selEnd // locate version info selStart=strsearch(WebPageText, "\"field-paragraph-version\">", blockStart, 2) selStart+=26 selEnd=strsearch(WebPageText, "<", selStart, 2) if(selStartblockEnd) break endif requiredVersion=WebPageText[selStart, selEnd-1] selStart=strsearch(WebPageText, "\"field-paragraph-version-major\">", blockStart, 2) selStart+=32 selEnd=strsearch(WebPageText, "<", selStart, 2) if(selStartblockEnd) break endif releaseMajor=WebPageText[selStart, selEnd-1] selStart=strsearch(WebPageText, "\"field-paragraph-version-patch\">", blockStart, 2) selStart+=32 selEnd=strsearch(WebPageText, "<", selStart, 2) if(selStartblockEnd) break endif releaseMinor=WebPageText[selStart, selEnd-1] // find download link selStart=strsearch(WebPageText, "\"field-paragraph-file\"", blockStart, 2) selStart=strsearch(WebPageText, "blockEnd) break endif url=WebPageText[selStart, selEnd-1] platform="" selFound=strsearch(WebPageText, "Windows", blockStart, 2) if(selFound>blockStart && selFoundMac-", blockStart, 2) if(selFound>blockStart && selFoundmaxLevels || (folderCount-folderIndex)>folderLimit) break endif while(1) killpath /Z tempPath return "" end // Search recursively for fileName within folder described by folderPathStr. // Don't expect this to be quick. // Returns path to file. Wildcards okay. static function /T FindFile(folderPathStr, fileName) String folderPathStr, fileName variable maxLevels=10 // quit after recursion through this many sublevels variable folderLimit=5000 // quit after looking in this many folders. Variable folderIndex, fileIndex, subfolderIndex, folderCount=1, sublevels=0 string folderList, indexFileName if (strlen(folderPathStr)==0) // it would be nice to default to the current save location, but I couldn't find a way to do that folderPathStr=SpecialDirPath("Documents", 0, 0, 0) endif make /free/T/n=0 w_folders, w_subfolders w_folders={folderPathStr} do for(folderIndex=0;folderIndexmaxLevels || (folderCount-folderIndex)>folderLimit) break endif while(1) killpath /Z tempPathFF return "" end // search recursively in folderPathStr for files matching fileName. // returns list of complete paths to files static function /T RecursiveFileList(folderPathStr, fileName) String folderPathStr, fileName variable maxLevels=5 // quit after recursion through this many sublevels variable folderLimit=500 // quit after looking in this many folders Variable folderIndex, fileIndex, subfolderIndex, folderCount=1, sublevels=0 string folderList, indexFileName string fileList="" if (strlen(folderPathStr)==0) return "" endif make /free/T/n=0 w_folders, w_subfolders w_folders={folderPathStr} do for(folderIndex=0;folderIndexmaxLevels) || (folderLimit && (folderCount-folderIndex)>folderLimit))) break endif while(1) killpath /Z tempPathFF return fileList end // ------------------------- Project Installer --------------------- // install a package hosted on IgorExchange.com // packageList is a list of project short names. // The package URL can be used in place of the short name. // Looks for install.itx within package folder and executes if found. // install.itx should create shortcuts for help files and XOPs as needed. // Use installWindows.itx or installMacintosh.itx for OS specific installations. // install("foo") installs IgorExchange package with shortname foo in a // location within the Igor User Files folder chosen by the user. // install("foo;bar;", location=SpecialDirPath("Igor Pro User // Files",0,0,0)+"User Procedures:") installs packages foo and bar in the // User Procedures folder. // if location is supplied for zip archive(s), subfolders may or may not // not be created, depending on the structure of the archive. // uses parseReleases, unzipArchive, findFile, findAlias, extractFilename // functions from Updater code. function install(packageList, [location]) string packageList, location // where to save downloaded files - a temporary location // safe to edit this, but stuff in this location will be overwritten or deleted! string downloadPathStr=SpecialDirPath("Desktop",0,0,0)+"ACW_PackageInstallerTemp:" variable testing=0, autoloadOption=0 // autoloadOption=1 will prompt user to create an alias in Igor Procedures folder. string url, webPageText, projectName, cmd string IncludeProcStr="", packagePathStr="", packageName="" variable selStart, selEnd, releaseIgorVersion, releaseVersion, refNum, err variable i, ListIndex=0 variable currentIgorVersion=str2num(StringByKey("IGORFILEVERSION", igorinfo(3))) string system=selectString(stringmatch(StringByKey("OS", igorinfo(3))[0,2],"Mac"), "Windows", "Macintosh") variable isWin=stringmatch(system[0,2],"Win") variable nameIsURL=0 for(ListIndex=0;ListIndexView all releases", 0, 2) selStart=strsearch(webPageText, "href=", selEnd, 3) if (selEnd==-1 || selStart==-1) printf "Installer could not find releases at %s\r", url continue endif url="https://www.wavemetrics.com"+webPageText[selStart+6,selEnd-2] // try to download 'all releases' page webPageText = FetchURL(url); err = GetRTError(1) if (err != 0) printf "Could not load %s", url continue endif printf "Looking for releases at %s\r", url // parse page to populate w_releases wave /T w_releases=ParseReleases(webPageText) if (dimsize(w_releases,0)==0) sprintf cmd, "Could not find packages at %s.\r", url continue endif // find most recent OS- and version-compatible release url="" for(i=0;i= %g\r", projectName, releaseVersion, releaseIgorVersion elseif(FindListItem(system, w_releases[i][5])==-1) printf "%s %g not available for %s\r", projectName, releaseVersion, system else url=w_releases[i][4] break endif endfor if (strlen(url)==0) continue endif if(testing) // for testing, don't hit server pointlessly printf "testing: substituting local file for %s\r", url url="file://C:/Users/tony/Desktop/archive.zip" url="file:///Users/tony/Desktop/archive.zip" endif // download package webPageText=FetchURL(url); err = GetRTError(1) if (err != 0) printf "Could not load %s", url continue endif printf "Downloaded %s\r", url // create the temporary folder newpath /C/O/Q downloadPathTemp, downloadPathStr killpath /z downloadPathTemp // save file to disk open /Z refNum as downloadPathStr+parseFilePath(0, url,"/", 1, 0) if(refNum==0) printf "Could not save to %s", downloadPathStr+parseFilePath(0, url,"/", 1, 0) continue endif FBinWrite refNum, webPageText close refNum // S_fileName created by open printf "Saved temporary file %s\r", S_fileName // consider only two possibilities: an uncompressed ipf file or a zip archive if (stringmatch(parseFilePath(4, S_fileName,":", 0, 0), "zip")) if(paramisdefault(location)) // unzipPackage will prompt user for install path // package folder will be created if necessary packagePathStr=unzipPackage(packageName, S_fileName) else getfileFolderInfo /Q/Z location if(v_flag!=0 || V_isFolder==0) printf "Install path not found: %s\r", location continue endif packagePathStr=unzipPackage(packageName, S_fileName, packagePathStr=location) endif if (strlen(packagePathStr)==0) continue endif string ipfList="", ipfStr="" newpath /C/O/Q packagePathTemp, packagePathStr i=0 do ipfStr=IndexedFile(packagePathTemp, i, ".ipf") if(!strlen(ipfStr)) break endif ipfStr=removeending(ipfStr, ".ipf") ipfList=AddListItem(ipfStr, ipfList) i+=1 while(1) killpath /z packagePathTemp if(i==1) // only one procedure file at top level of package folder IncludeProcStr=StringFromList(0, ipfList) elseif(i>1 && (WhichListItem(packageName, ipfList)!=-1) ) // package folder includes a procedure with project name IncludeProcStr=packageName endif // if there's an XOP in the package, don't try to open any procedure file if(strlen(FindFile(packagePathStr,"*.xop"))) IncludeProcStr="" endif // Look for and run itx-format installation script. Script will run // from within the package folder saved in location of user's // choice within Igor Pro User Files folder. string msg string pathToScriptStr="" getFileFolderInfo /Q/Z packagePathStr+"install.itx" if (V_Flag==0) // s_path has been reset by getFileFolderInfo pathToScriptStr=s_path else getFileFolderInfo /Q/Z packagePathStr+"install"+system+".itx" if (V_Flag==0) // s_path has been reset by getFileFolderInfo pathToScriptStr=s_path endif endif if(strLen(pathToScriptStr)) // script could do naughty things, so ask before allowing it to run sprintf msg, "Run installation script?\r%s was downloaded from %s", ParseFilePath(0, pathToScriptStr, ":", 1, 0), url doAlert 1, msg if (v_flag==1) sprintf cmd, "Loadwave /T \"%s\"", s_path execute cmd // will be printed to history endif endif elseif(stringmatch(parseFilePath(4, S_fileName,":", 0, 0), "ipf")) if(paramisdefault(location)) packagePathStr=ChooseInstallLocation(packageName) if (strlen(packagePathStr)==0) continue endif else getfileFolderInfo /Q/Z location if(v_flag==0) printf "Install path not found: %s\r", location continue endif packagePathStr=location endif getFileFolderInfo /Q/Z packagePathStr+packageName+".ipf" if (v_flag==0) // already exists doalert 1, "overwrite "+packageName+".ipf?" if (v_flag==2) continue endif endif // S_filename still set by open movefile /O/Z S_fileName as packagePathStr+packageName+".ipf" if(V_flag!=0) continue endif IncludeProcStr=packageName printf "Saved %s in %s\r", packageName+".ipf", packagePathStr endif if (strlen(IncludeProcStr)) // found a 'master' procedure file if(autoloadOption==1) // find out whether an alias already exists string IgorProcPathStr=SpecialDirPath("Igor Pro User Files",0,0,0)+"Igor Procedures:" string targetPathStr=packagePathStr+IncludeProcStr+".ipf" autoloadOption=(strlen(FindAlias(IgorProcPathStr, targetPathStr))==0) // make sure target isn't in Igor Procedures folder if(autoloadOption && stringmatch(targetPathStr, IgorProcPathStr+"*")==0) string aliasName=ParseFilePath(3, targetPathStr, ":", 0, 0)+selectstring(isWin, " Alias", " Shortcut") sprintf cmd "Create %s alias in Igor Procedures to autoload %s?", ParseFilePath(0, targetPathStr, ":", 1, 0), packageName doAlert 1, cmd if (v_flag==1) createAliasShortcut targetPathStr as IgorProcPathStr+aliasName printf "Created %s in %s\r", aliasName, IgorProcPathStr endif endif endif // find out if procedure is already loaded wave /T w_loadedProcs=ListToTextWave(winlist("*",";","WIN:128,INDEPENDENTMODULE:1"),";") w_loadedProcs=extractFilename(w_loadedProcs) FindValue /TEXT=IncludeProcStr/TXOP=4 /Z w_loadedProcs if(V_Value==-1) sprintf cmd "Do you want to open %s now?", IncludeProcStr doAlert 1, cmd if(v_flag==1) // load the procedure sprintf cmd, "INSERTINCLUDE \"%s\"", IncludeProcStr Execute/P/Q/Z cmd Execute/P/Q/Z "COMPILEPROCEDURES " printf "Included %s in current experiment\r", IncludeProcStr endif endif endif endfor deleteFolder /M="OK to delete temporary installation files?"/Z downloadPathStr return 1 end // Inflates zip archive to a temporary folder, moves inflated archive to // destination in Igor Pro User Files. static function /S unzipPackage(shortName, archivePathStr, [packagePathStr]) string shortName, archivePathStr, packagePathStr GetFileFolderInfo /Q/Z archivePathStr if(V_Flag || V_isFile==0) return "" endif variable CreatePackageFolder=0, success=0 if(paramIsDefault(packagePathStr)) CreatePackageFolder=1 packagePathStr=ChooseInstallLocation(shortName) // find out whether user has chosen an existing subfolder with the name of the project if(stringmatch(ParseFilePath(0, packagePathStr, ":", 1, 0), shortName)==1) CreatePackageFolder=0 endif endif if (strlen(packagePathStr)==0) return "" endif string tmpPathStr=parseFilePath(1, archivePathStr, ":", 1, 0) string tmpDirStr="" newpath /C/O/Q unzipPath, tmpPathStr // make sure folder exists on disk variable i=0 do sprintf tmpDirStr,"InstallerTemp%d", i GetFileFolderInfo /Q/Z tmpPathStr+tmpDirStr i+=1 while(V_Flag==0) string unzipPathStr=tmpPathStr+tmpDirStr+":" // create a temporary directory for the uncompressed files newpath /C/O/Q unzipPath, unzipPathStr // inflate archive success = unzipArchive(archivePathStr, unzipPathStr) if (success==0) printf "unzipArchive failed to expand %s\r", archivePathStr killpath /Z unzipPath return "" endif printf "Inflated %s to %s\r", ParseFilePath(0, archivePathStr, ":", 1, 0), unzipPathStr string fileList=IndexedFile(unzipPath, -1, "????") string folderList=IndexedDir(unzipPath, -1, 0) killpath /Z unzipPath // ignore pesky __MACOSX folder i=0 do if(stringmatch(StringFromList(i, folderList), "__MACOSX")) folderList=removeListItem(i, folderList) else i+=1 endif while (i", 0, 0) if (pStart==-1) break // no more projects endif pEnd=0 // loop through projects on listPage for (projectNum=0;projectNum<50; projectNum+=1) pStart=strsearch(listPageText, "
", pEnd, 0) pEnd=strsearch(listPageText, "
", pStart, 0) if (pEnd==-1 || pStart==-1) break // no more projects on this listPage endif selStart=strsearch(listPageText, "", pEnd, 3) if(selStart", selStart, 0) selStart+=2 selEnd=strsearch(listPageText, "", selStart, 0) strAuthor=listPageText[selStart,selEnd-1] selStart=strsearch(listPageText, "

", selStart, 0) strProjectURL=baseURL+listPageText[selStart,selEnd-1] selStart=selEnd+6 selEnd=strsearch(listPageText, "

", selStart, 0) strName=listPageText[selStart,selEnd-1] if (strLen(strName)==0) continue endif // Don't try to get project details at this point: too many pages to download // projectPageText=FetchURL(strProjectURL); err = GetRTError(1) // if (err != 0) // continue // endif // selStart=strsearch(projectPageText, "-1) print "install(\""+matchList[v_value][%projectURL]+"\")" install(matchList[v_value][%projectURL]) endif return 0 end static function InstallerListBoxProc(s) STRUCT WMListboxAction &s if(s.eventCode==4 || s.eventCode==5) // a list item is selected if(s.row>=numpnts(s.listwave)) return 0 endif // insert text into notebook subwindow Notebook InstallerPanel#nb0 selection={startOfFile,endOfFile}, textRGB=(0,0,0), text=s.listwave[s.row] NVAR stubLen=root:Packages:Installer:stubLen stubLen=strlen(s.listwave[s.row]) Button button0, win=InstallerPanel, disable=0 endif return 0 end // update listbox wave based on string s static function UpdateListboxWave(s) string s DFREF dfr = root:Packages:Installer wave /SDFR=dfr/T fullList, matchList //, authorList/ variable i string regEx="(?i)" ControlInfo /W=InstallerPanel popup0 if(V_Value==1) regEx+="^" endif if(v_value==3) for(i=0;i=stubLen) if(s.eventMod&2) // shift key Notebook InstallerPanel#nb0 selection={(0,V_startPos),(0,stubLen)} else Notebook InstallerPanel#nb0 selection={(0,stubLen),(0,stubLen)} endif return 1 endif return 0 case 8: case 127: // delete or forward delete GetSelection notebook, InstallerPanel#nb0, 1 if(V_startPos==V_endPos) V_startPos += (s.keycode==8) ? -1 : 1 endif V_startPos=min(stubLen,V_startPos); V_endPos=min(stubLen,V_endPos) V_startPos=max(0, V_startPos); V_endPos=max(0, V_endPos) Notebook InstallerPanel#nb0 selection={(0,V_startPos),(0,V_endPos)}, text="" stubLen-=abs(V_endPos-V_startPos) break endswitch // find and save current position GetSelection notebook, InstallerPanel#nb0, 1 variable selEnd=V_endPos if(strlen(s.keyText)==1) // a one-byte printing character // insert character into current selection Notebook InstallerPanel#nb0 text=s.keyText, textRGB=(0,0,0) stubLen+=1-abs(V_endPos-V_startPos) // find out where we want to leave cursor GetSelection notebook, InstallerPanel#nb0, 1 selEnd=V_endPos endif string strStub="", strInsert="", strEnding="" // do auto-completion based on stubLen characters wave /T fullList=dfr:fullList // select and format stub Notebook InstallerPanel#nb0 selection={startOfFile,(0,stubLen)}, textRGB=(0,0,0) // get stub text GetSelection notebook, InstallerPanel#nb0, 3 strStub=s_selection if(s.keycode==30 || s.keycode==31) // up or down arrow Notebook InstallerPanel#nb0 selection={(0,stubLen),endOfFile} GetSelection notebook, InstallerPanel#nb0, 3 strEnding=s_selection strInsert=arrowKey(strStub, strEnding, 1-2*(s.keycode==30), fullList) else strInsert=completeStr(strStub, fullList) endif // insert completion text in grey Notebook InstallerPanel#nb0 selection={(0,stubLen),endOfFile}, textRGB=(40000,40000,40000), text=strInsert Notebook InstallerPanel#nb0 selection={(0,selEnd),(0,selEnd)}, findText={"",1} UpdateListboxWave(strStub) return 1 // tell Igor we've handled all keyboard events end // returns completion text for first match of string s in text wave w static function /T completeStr(s, w) string s wave /T w if (strlen(s)==0) return "" endif string stub=s[0,strlen(s)-1] variable stubLen=strlen(stub) if (stubLen==0) return "" endif make /free/T/N=0 w_out grep /Z/E="(?i)^"+stub w as w_out if(numpnts(w_out)==0) return "" endif return (w_out[0])[stubLen,inf] end // find next or previous matching entry in wList and return completion text static function /T arrowKey(stub, ending, increment, wList) string stub, ending variable increment wave /T wList if (strlen(stub)==0) return "" endif variable stubLen=strlen(stub) if (stubLen==0) return "" endif make /free/T/N=0 w_out grep /Z/E="(?i)^"+stub/DCOL={0} wList as w_out if(numpnts(w_out)==0) return "" endif findValue /RMD=[][0,0]/TEXT=stub+ending /TXOP=4/Z w_out if(v_value>-1) v_value+=increment v_value = V_value<0 ? DimSize(w_out,0)-1 : v_value v_value = V_value>=DimSize(w_out,0) ? 0 : v_value else return (w_out[0])[stubLen,inf] endif return (w_out[v_value])[stubLen,inf] end // ----------------------------------------------------------------