Interface to choose a list item by typing, with autocompletion
A user-interface to select an item from a list, using keyboard entry to refine the selection options.
I used a notebook subwindow to create a control-like interface. Typing in the subwindow refines the list of possible matches within the provided list of values. The matches are displayed in a listbox. Autocompletion suggestions are shown as grey text. The tab key accepts the current autocomplete suggestion, and the up and down arrows step through the matches. The "Do It" button passes the notebook text on to the doSomething() function for further processing.
Execute GUIdemo() to create an example interface using the output from FunctionList to generate a string list, or pass your own list to ChooseFromListGUI(strList).
#pragma IgorVersion=8
#pragma version=1.31
#pragma ModuleName=ListFilter
#include <Resize Controls>
// https: www.wavemetrics.com/user/tony
// ---------------- edit this section ---------------------
// edit this function to do something useful with selection
static function doSomething(string selection)
DoAlert 0, "You selected " + selection
end
// edit this function to choose what to do with a double click
static function onDoubleClick(string selection)
doSomething(selection)
end
// execute ChooseFromListGUI(strList) to create the GUI
function GUIdemo()
string strList=FunctionList("*", ";", "KIND:1")
ChooseFromListGUI(strList, title="List Selector Demo")
end
// ----------------------------------------------------------
function ChooseFromListGUI(string strList, [string title])
title = SelectString(ParamIsDefault(title), title, "")
// killing any old window also clears package data folder
KillWindow /Z FilterPanel
// create package data folder
NewDataFolder /O root:Packages
NewDataFolder /O root:Packages:ChooseFromListGUI
DFREF dfr = root:Packages:ChooseFromListGUI
variable /G dfr:stubLen=0
// create a sorted text wave
wave w = ListToTextWave(strList, ";")
Sort w, w
Duplicate /O w dfr:displayList /WAVE=displayList, dfr:fullList
// make a control panel GUI
NewPanel/K=1/W=(100,50,310,240)/N=FilterPanel as title
ModifyPanel/W=FilterPanel, noEdit=1
// insert a notebook subwindow to be used for filtering lists
DefineGuide/W=FilterPanel nbR={FR,-28}
NewNotebook/F=1/N=nb0/HOST=FilterPanel/W=(10,10,190,35)/FG=($"",$"",nbR,$"") /OPTS=3
Notebook FilterPanel#nb0 fSize=12, showRuler=0
Notebook FilterPanel#nb0 spacing={4,0,5}
Notebook FilterPanel#nb0 margins={0,0,1000}
SetWindow FilterPanel#nb0, activeChildFrame=0
ClearText(1) // sets notebook to its default appearance
// make a button for clearing text in notebook subwindow
Button buttonClear, win=FilterPanel,pos={185,14},size={15,15},title=""
Button buttonClear, Picture=ListFilter#ClearTextPicture,Proc=ListFilter#ButtonProc, disable=1
ListBox listbox0, win=FilterPanel, pos={10,40}, size={190,120}, fsize=12, listwave=displayList
ListBox listbox0, win=FilterPanel, mode=1, Proc=ListFilter#ListBoxProc, selRow=-1
Button buttonDoIt, win=FilterPanel,pos={150,165},size={50,20},title="Do It"
Button buttonDoIt, win=FilterPanel,Proc=ListFilter#ButtonProc, disable=2
DoUpdate/W=FilterPanel
// resizing userdata for controls
Button buttonClear, win=FilterPanel,userdata(ResizeControlsInfo) = A"!!,GI!!#;m!!#<(!!#<(z!!#o2B4uAezzzzzzzzzzzzzz!!#o2B4uAezz"
Button buttonClear, win=FilterPanel,userdata(ResizeControlsInfo) += A"zzzzzzzzzzzz!!#u:Du]k<zzzzzzzzzzz"
Button buttonClear, win=FilterPanel,userdata(ResizeControlsInfo) += A"zzz!!#u:Du]k<zzzzzzzzzzzzzz!!!"
ListBox listbox0, win=FilterPanel,userdata(ResizeControlsInfo) = A"!!,A.!!#>.!!#AM!!#@Tz!!#](Aon\"Qzzzzzzzzzzzzzz!!#o2B4uAezz"
ListBox listbox0, win=FilterPanel,userdata(ResizeControlsInfo) += A"zzzzzzzzzzzz!!#u:Du]k<zzzzzzzzzzz"
ListBox listbox0, win=FilterPanel,userdata(ResizeControlsInfo) += A"zzz!!#?(FEDG<zzzzzzzzzzzzzz!!!"
Button buttonDoIt, win=FilterPanel,userdata(ResizeControlsInfo) = A"!!,G&!!#A4!!#>V!!#<Xz!!#o2B4uAezzzzzzzzzzzzzz!!#o2B4uAezz"
Button buttonDoIt, win=FilterPanel,userdata(ResizeControlsInfo) += A"zzzzzzzzzzzz!!#?(FEDG<zzzzzzzzzzz"
Button buttonDoIt, win=FilterPanel,userdata(ResizeControlsInfo) += A"zzz!!#?(FEDG<zzzzzzzzzzzzzz!!!"
// resizing userdata for panel
SetWindow FilterPanel,userdata(ResizeControlsInfo) = A"!!*'\"z!!#Aa!!#AMzzzzzzzzzzzzzzzzzzzzz"
SetWindow FilterPanel,userdata(ResizeControlsInfo) += A"zzzzzzzzzzzzzzzzzzzzzzzzz"
SetWindow FilterPanel,userdata(ResizeControlsInfo) += A"zzzzzzzzzzzzzzzzzzz!!!"
SetWindow FilterPanel,userdata(ResizeControlsGuides) = "nbR;"
SetWindow FilterPanel,userdata(ResizeControlsInfonbR) = "NAME:nbR;WIN:FilterPanel;TYPE:User;HORIZONTAL:0;POSITION:182.00;GUIDE1:FR;GUIDE2:;RELPOSITION:-28;"
// resizing panel hook
SetWindow FilterPanel hook(ResizeControls)=ResizeControls#ResizeControlsHook
// filter hook
SetWindow FilterPanel hook(hFilterHook)=ListFilter#FilterHook
end
static function ClearText(variable doIt)
if (doIt)
Notebook FilterPanel#nb0 selection={startOfFile,endofFile}, textRGB=(50000,50000,50000), text="Filter"
Notebook FilterPanel#nb0 selection={startOfFile,startOfFile}
endif
end
static function ButtonProc(STRUCT WMButtonAction &s)
if (s.eventCode != 2)
return 0
endif
strswitch(s.ctrlName)
case "buttonClear" :
ClearText(1)
NVAR stubLen = root:Packages:ChooseFromListGUI:stubLen
stubLen = 0
Button buttonClear, win=FilterPanel, disable=3
UpdateListboxWave("")
break
case "buttonDoIt" :
ControlInfo/W=FilterPanel listbox0
if (V_Value >- 1)
wave/T matchList = $(S_DataFolder+S_Value)
doSomething(matchList[V_Value])
else
Button buttonDoIt, win=FilterPanel, disable=2
endif
break
endswitch
return 0
end
static function ListBoxProc(STRUCT WMListboxAction &s)
switch (s.eventCode)
case 2: // mouseup - this captures deselection by shift-click
ControlInfo/W=$(s.win) $(s.ctrlName)
s.row = V_value
case 4:
case 5:
// a list item is selected
Button buttonDoIt, win=FilterPanel, disable=2*(s.row>=DimSize(s.listwave,0) || s.row<0)
break
case 3: // double click
if (s.row<DimSize(s.listwave,0) && s.row>=0)
string selection = s.listwave[s.row]
onDoubleClick(selection)
endif
break
endswitch
return 0
end
// update listbox wave based on string str
static function UpdateListboxWave(string str)
DFREF dfr = root:Packages:ChooseFromListGUI
wave/SDFR=dfr/T displayList, fullList
string regEx="(?i)"+str
Grep/Z/E=regEx fullList as displayList
ListBox listbox0, win=FilterPanel, selRow=-1
Button buttonDoIt, win=FilterPanel, disable=2
end
// intercept and deal with keyboard events in notebook subwindow
static function FilterHook(STRUCT WMWinHookStruct &s)
if (s.eventcode == 2) // window is being killed
KillDataFolder/Z root:Packages:ChooseFromListGUI
return 1
endif
GetWindow/Z FilterPanel#nb0 active
if (V_Value == 0) // this check is not reliable
return 0
endif
if (s.eventCode==22 && cmpstr(s.winName, "FilterPanel#nb0")==0)
return 1 // don't allow scrolling in notebook subwindow
endif
DFREF dfr = root:Packages:ChooseFromListGUI
NVAR stubLen = dfr:stubLen
if (s.eventcode==3 && stubLen==0) // mousedown
return 1
endif
if (s.eventcode == 5) // mouseup
GetSelection Notebook, FilterPanel#nb0, 1 // get current position in notebook
V_endPos = min(stubLen,V_endPos)
V_startPos = min(stubLen,V_startPos)
Notebook FilterPanel#nb0 selection={(0,V_startPos),(0,V_endPos)}
return 1
endif
if (s.eventcode == 10) // menu
strswitch(s.menuItem)
case "Paste":
GetSelection Notebook, FilterPanel#nb0, 1 // get current position in notebook
string strScrap = GetScrapText()
strScrap = ReplaceString("\r", strScrap, "")
strScrap = ReplaceString("\n", strScrap, "")
strScrap = ReplaceString("\t", strScrap, "")
Notebook FilterPanel#nb0 selection={(0,V_startPos),(0,V_endPos)}, text=strScrap
stubLen += strlen(strScrap)-abs(V_endPos-V_startPos)
s.eventcode = 11
// pretend this was a keyboard event to allow execution to continue
break
case "Cut":
GetSelection Notebook, FilterPanel#nb0, 3 // get current position in notebook
PutScrapText s_selection
Notebook FilterPanel#nb0 selection={(0,V_startPos),(0,V_endPos)}, text=""
stubLen -= strlen(s_selection)
s.eventcode = 11
break
case "Clear":
GetSelection Notebook, FilterPanel#nb0, 3 // get current position in notebook
Notebook FilterPanel#nb0 selection={(0,V_startPos),(0,V_endPos)}, text="" // clear text
stubLen -= strlen(s_selection)
s.eventcode = 11
break
endswitch
Button buttonClear, win=FilterPanel, disable=3*(stublen==0)
ClearText((stubLen == 0))
endif
if (s.eventcode != 11)
return 0
endif
if (stubLen == 0) // Remove "Filter" text before starting to deal with keyboard activity
Notebook FilterPanel#nb0 selection={startOfFile,endofFile}, text=""
endif
// deal with some non-printing characters
switch(s.keycode)
case 9: // tab: jump to end
case 3:
case 13: // enter or return: jump to end
Notebook FilterPanel#nb0 selection={startOfFile,endofFile}, textRGB=(0,0,0)
Notebook FilterPanel#nb0 selection={endOfFile,endofFile}
GetSelection Notebook, FilterPanel#nb0, 1 // get current position in notebook
stubLen = V_endPos
break
case 28: // left arrow
ClearText((stubLen == 0)); return 0
case 29: // right arrow
GetSelection Notebook, FilterPanel#nb0, 1
if(V_endPos >= stubLen)
if (s.eventMod & 2) // shift key
Notebook FilterPanel#nb0 selection={(0,V_startPos),(0,stubLen)}
else
Notebook FilterPanel#nb0 selection={(0,stubLen),(0,stubLen)}
endif
ClearText((stubLen==0)); return 1
endif
ClearText((stubLen==0)); return 0
case 8:
case 127: // delete or forward delete
GetSelection Notebook, FilterPanel#nb0, 1
if (V_startPos == V_endPos)
V_startPos -= (s.keycode==8)
V_endPos += (s.keycode==127)
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 FilterPanel#nb0 selection={(0,V_startPos),(0,V_endPos)}, text=""
stubLen -= abs(V_endPos-V_startPos)
break
endswitch
// find and save current position
GetSelection Notebook, FilterPanel#nb0, 1
variable selEnd = V_endPos
if (strlen(s.keyText) == 1) // a one-byte printing character
// insert character into current selection
Notebook FilterPanel#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, FilterPanel#nb0, 1
selEnd = V_endPos
endif
string strStub = "", strInsert = "", strEnding = ""
// select and format stub
Notebook FilterPanel#nb0 selection={startOfFile,(0,stubLen)}, textRGB=(0,0,0)
// get stub text
GetSelection Notebook, FilterPanel#nb0, 3
strStub = s_selection
// get matches based on stub text
UpdateListboxWave(strStub)
// do auto-completion based on stubLen characters
wave /T matchList = dfr:DisplayList
if (s.keycode==30 || s.keycode==31) // up or down arrow
Notebook FilterPanel#nb0 selection={(0,stubLen),endOfFile}
GetSelection Notebook, FilterPanel#nb0, 3
strEnding = s_selection
strInsert = arrowKey(strStub, strEnding, 1-2*(s.keycode==30), matchList)
else
strInsert = completeStr(strStub, matchList)
endif
// insert completion text in grey
Notebook FilterPanel#nb0 selection={(0,stubLen),endOfFile}, textRGB=(50000,50000,50000), text=strInsert
Notebook FilterPanel#nb0 selection={startOfFile,startOfFile}, findText={"",1}
Notebook FilterPanel#nb0 selection={(0,selEnd),(0,selEnd)}, findText={"",1}
Button buttonClear, win=FilterPanel, disable=3*(stublen==0)
ClearText((stubLen == 0))
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(string stub, wave /T w)
int stubLen = strlen(stub)
if (stubLen == 0)
return ""
endif
Make/free/T/N=1 w_out
Grep/Z/E="(?i)^"+stub w as w_out
if (DimSize(w_out,0) == 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(string stub, string ending, variable increment, wave /T wList)
int stubLen = strlen(stub)
if (stubLen == 0)
return ""
endif
Make/free/T/N=0 w_out
Grep/Z/E="(?i)^"+stub wList as w_out
if (numpnts(w_out) == 0)
return ""
endif
FindValue/TEXT=stub+ending /TXOP=4/Z w_out
if (v_value >- 1)
v_value += increment
v_value = V_value<0 ? numpnts(w_out)-1 : v_value
v_value = V_value >= numpnts(w_out) ? 0 : v_value
else
return (w_out[0])[stubLen,Inf]
endif
return (w_out[v_value])[stubLen,Inf]
end
// PNG: width= 90, height= 30
static Picture ClearTextPicture
ASCII85Begin
M,6r;%14!\!!!!.8Ou6I!!!"&!!!!?#R18/!3BT8GQ7^D&TgHDFAm*iFE_/6AH5;7DfQssEc39jTBQ
=U"$&q@5u_NKm@(_/W^%DU?TFO*%Pm('G1+?)0-OWfgsSqYDhC]>ST`Z)0"D)K8@Ncp@>C,GnA#([A
Jb0q`hu`4_P;#bpi`?T]j@medQ0%eKjbh8pO.^'LcCD,L*6P)3#odh%+r"J\$n:)LVlTrTIOm/oL'r
#E&ce=k6Fiu8aXm1/:;hm?p#L^qI6J;D8?ZBMB_D14&ANkg9GMLR*Xs"/?@4VWUdJ,1MBB0[bECn33
KZ1__A<"/u9(o<Sf@<$^stNom5GmA@5SIJ$^\D=(p8G;&!HNh)6lgYLfW6>#jE3aT_'W?L>Xr73'A#
m<7:2<I)2%%Jk$'i-7@>Ml+rPk4?-&B7ph6*MjH9&DV+Uo=D(4f6)f(Z9"SdCXSlj^V?0?8][X1#pG
[0-Dbk^rVg[Rc^PBH/8U_8QFCWdi&3#DT?k^_gU>S_]F^9g'7.>5F'hcYV%X?$[g4KPRF0=NW^$Z(L
G'1aoAKLpqS!ei0kB29iHZJgJ(_`LbUX%/C@J6!+"mVIs6V)A,gbdt$K*g_X+Um(2\?XI=m'*tR%i"
,kQIh51]UETI+HBA)DV:t/sl4<N*#^^=N.<B%00/$P>lNVlic"'Jc$p^ou^SLA\BS?`$Jla/$38;!#
Q+K;T6T_?\3*d?$+27Ri'PYY-u]-gEMR^<d.ElNUY$#A@tX-ULd\IU&bfX]^T)a;<u7HgR!i2]GBpt
SiZ1;JHl$jf3!k*jJlX$(ZroR:&!&8D[<-`g,)N96+6gSFVi$$Gr%h:1ioG%bZgmgbcp;2_&rF/[l"
Qr^V@O-"j&UsEk)HgI'9`W31Wh"3^O,KrI/W'chm_@T!!^1"Y*Hknod`FiW&N@PIluYQdKILa)RK=W
Ub4(Y)ao_5hG\K-+^73&AnBNQ+'D,6!KY/`F@6)`,V<qS#*-t?,F98]@h"8Y7Kj.%``Q=h4.L(m=Nd
,%6Vs`ptRkJNBdbpk]$\>hR4"[5SF8$^:q=W([+TB`,%?4h7'ET[Y6F!KJ3fH"9BpILuUI#GoI.rl(
_DAn[OiS_GkcL7QT`\p%Sos;F.W#_'g]3!!!!j78?7R6=>B
ASCII85End
end
Forum
Support
Gallery
Igor Pro 9
Learn More
Igor XOP Toolkit
Learn More
Igor NIDAQ Tools MX
Learn More
Version 1.10 cleans up the interface.
Panel is resizable.
September 16, 2020 at 11:48 am - Permalink
Tony- I haven't tried your demo code yet; I'm sure it's terrific!
Are you aware of the ListBox keyword "keySelectCol"? A listbox control will move the selection to a row that matches what you have typed, as long as you type quickly enough. By default it looks at column zero, this keyword will change the column used for the purpose.
September 16, 2020 at 03:16 pm - Permalink
Yes, that functionality is not affected by adding a 'filtering' option.
The demo GUI is a bit like the command tab of the help browser, where you can either type-to-select or type in the filter area to refine the list.
September 17, 2020 at 12:34 am - Permalink