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. Clicking on a listbox item inserts the item as the selection. 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 return key passes the notebook text on to the doSomething() function for further processing.

Execute GUIdemo() to create an example interface using the output from FunctionList and OperationList to generate a string list, or pass your own list to ChooseFromListGUI(strList).

 

#pragma IgorVersion=8.02
#pragma version=1.00

static constant kAutoComplete=1 // 0 for no autocomplete
static constant kArrowMode=1 // 1 allows right arrow to accept next character of autocomplete suggestion

function GUIdemo()
   
    string strList=FunctionList("*", ";", "KIND:1")+operationList("*", ";", "KIND:1")
    ChooseFromListGUI(strList)
end

function ChooseFromListGUI(strList)
    string strList
   
    dowindow /k ChooserPanel
   
    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,350,360)/N=ChooserPanel as "List Item Chooser GUI"
    PopupMenu popup0, win=ChooserPanel, pos={25,10},size={127.00,23.00},title="find text",fSize=14
    PopupMenu popup0, win=ChooserPanel, mode=1,popvalue="at start",value= #"\"at start;contiguous;anywhere\""
    PopupMenu popup0, win=ChooserPanel, proc=ChooserPopMenuProc
   
    NewNotebook /F=1 /N=nb0 /HOST=ChooserPanel /W=(25,40,225,62) /OPTS=3
    notebook ChooserPanel#nb0 fSize=16, showRuler=0
    SetWindow ChooserPanel#nb0, activeChildFrame=0
   
    ListBox listbox0, win=ChooserPanel, pos={25,80},size={200, 200}, listwave=displayList
    ListBox listbox0, win=ChooserPanel, mode=1, proc=ChooserListBoxProc, selRow=-1
   
    SetWindow ChooserPanel hook(hChooserHook)=ChooserHook
end

function ChooserPopMenuProc(s)
    STRUCT WMPopupaction &s
   
    if (s.eventcode==2)
        NVAR stubLen=root:Packages:ChooseFromListGUI:stubLen
        // store current selection
        GetSelection notebook, ChooserPanel#nb0, 1
        variable selStart=V_startPos, selEnd=V_endPos
        Notebook ChooserPanel#nb0 selection={startOfFile,(0,stubLen)}
        GetSelection notebook, ChooserPanel#nb0, 2
        UpdateListboxWave(s_selection)
        // restore selection
        Notebook ChooserPanel#nb0 selection={(0,selStart),(0,selEnd)}
    endif
    return 0
end

function ChooserListBoxProc(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 ChooserPanel#nb0 selection={startOfFile,endOfFile}, textRGB=(0,0,0), text=s.listwave[s.row]
        NVAR stubLen=root:Packages:ChooseFromListGUI:stubLen
        stubLen=strlen(s.listwave[s.row])
    endif
    return 0
end

// update listbox wave based on string s
static function UpdateListboxWave(s)
    string s
   
    DFREF dfr = root:Packages:ChooseFromListGUI
    wave /SDFR=dfr/T  displayList,  fullList
   
    variable i
    string regEx="(?i)"
   
    ControlInfo /W=ChooserPanel popup0
    if(V_Value==1)
        regEx+="^"
    endif
       
    if(v_value==3)
        for(i=0;i<strlen(s);i+=1)
            regEx+="(.*?)"+s[i]
        endfor
    else
        regEx+=s       
    endif  
    grep /Z/E=regEx fullList as displayList
    ListBox listbox0, win=ChooserPanel, selRow=-1
end

// intercept and deal with keyboard events in notebook subwindow
function ChooserHook(s)
    STRUCT WMWinHookStruct &s
   
    DFREF dfr = root:Packages:ChooseFromListGUI
   
    if(s.eventcode==2)
        killDataFolder /Z dfr
        return 0
    endif
   
    GetWindow $s.winName activeSW
    if (CmpStr(S_value,"ChooserPanel#nb0") != 0)
        return 0
    endif
   
    NVAR stubLen=dfr:stubLen

    if(s.eventcode==5) // mouseup  
        GetSelection notebook, ChooserPanel#nb0, 1 // get current position in notebook
        V_endPos=min(stubLen,V_endPos)
        V_startPos=min(stubLen,V_startPos)
        Notebook ChooserPanel#nb0 selection={(0,V_startPos),(0,V_endPos)}
        return 1
    endif
   
    if(s.eventcode==10) // menu
        strswitch(s.menuItem)
            case "Paste":
                GetSelection notebook, ChooserPanel#nb0, 1 // get current position in notebook     
                string strScrap=getscrapText()
                strScrap=replacestring("\r", strScrap, "")
                strScrap=replacestring("\n", strScrap, "")
                strScrap=replacestring("\t", strScrap, "")
                Notebook ChooserPanel#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, ChooserPanel#nb0, 3 // get current position in notebook
                putscrapText s_selection
                Notebook ChooserPanel#nb0 selection={(0,V_startPos),(0,V_endPos)}, text=""
                stubLen-=strlen(s_selection)
                s.eventcode=11
                break
            case "Clear":
                GetSelection notebook, ChooserPanel#nb0, 3 // get current position in notebook
                Notebook ChooserPanel#nb0 selection={(0,V_startPos),(0,V_endPos)}, text="" // clear text
                stubLen-=strlen(s_selection)
                s.eventcode=11
                break
        endswitch
    endif
       
    if(s.eventcode!=11)
        return 0
    endif
   
    // deal with some non-printing characters
    switch(s.keycode)
        case 9: // tab: jump to end
            notebook ChooserPanel#nb0 selection={startOfFile,endofFile}, textRGB=(0,0,0)
            notebook ChooserPanel#nb0 selection={endOfFile,endofFile}
            GetSelection notebook, ChooserPanel#nb0, 1 // get current position in notebook
            stubLen=V_endPos
            break
        case 3:
        case 13: // enter or return: do something with entered text
            notebook ChooserPanel#nb0 selection={startOfFile,endofFile}
            GetSelection notebook, ChooserPanel#nb0, 3
            doSomething(s_selection)
            notebook ChooserPanel#nb0 text=""
            stubLen=0
            UpdateListboxWave("")  
            return 1
        case 28: // left arrow
            return 0
        case 29: // right arrow
            GetSelection notebook, ChooserPanel#nb0, 1
            if(V_endPos>=stubLen && kAutoComplete)
                if(s.eventMod&2) // shift key
                    Notebook ChooserPanel#nb0 selection={(0,V_startPos),(0,stubLen)}
                elseif(kArrowMode==1)
                    Notebook ChooserPanel#nb0 selection={(0,V_endPos+1),(0,V_endPos+1)}
                    GetSelection notebook, ChooserPanel#nb0, 1
                    stubLen=V_endPos
                    break
                else
                    Notebook ChooserPanel#nb0 selection={(0,stubLen),(0,stubLen)}
                endif
                return 1
            endif
            return 0
        case 8:
        case 127: // delete or forward delete
            GetSelection notebook, ChooserPanel#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 ChooserPanel#nb0 selection={(0,V_startPos),(0,V_endPos)}, text=""
            stubLen-=abs(V_endPos-V_startPos)
            break          
    endswitch
       
    // find and save current position  
    GetSelection notebook, ChooserPanel#nb0, 1
    variable selEnd=V_endPos
       
    if(strlen(s.keyText)==1) // a one-byte printing character
        // insert character into current selection
        Notebook ChooserPanel#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, ChooserPanel#nb0, 1
        selEnd=V_endPos
    endif  
   
    string strStub="", strInsert="", strEnding=""
       
    if(kAutoComplete)
        // do auto-completion based on stubLen characters
        wave /T  fullList=dfr:fullList
        // select and format stub
        Notebook ChooserPanel#nb0 selection={startOfFile,(0,stubLen)}, textRGB=(0,0,0) 
        // get stub text
        GetSelection notebook, ChooserPanel#nb0, 3
        strStub=s_selection
       
        if(s.keycode==30 || s.keycode==31) // up or down arrow
            Notebook ChooserPanel#nb0 selection={(0,stubLen),endOfFile}
            GetSelection notebook, ChooserPanel#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 ChooserPanel#nb0 selection={(0,stubLen),endOfFile}, textRGB=(40000,40000,40000), text=strInsert   
    else
        Notebook ChooserPanel#nb0 selection={startOfFile,endOfFile}, textRGB=(0,0,0)
        GetSelection notebook, ChooserPanel#nb0, 3
        strStub=s_selection
    endif
    Notebook ChooserPanel#nb0 selection={(0,selEnd),(0,selEnd)}
    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
   
    variable p1
   
    // extract last word
    p1=strsearch(s, " ", strlen(s)-1, 3)   
    p1+=1 // 0 if not found, otherwise character following last space
    string stub=s[p1,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 p1
   
    // extract last word
    p1=strsearch(stub, " ", strlen(stub)-1, 3) 
    p1+=1 // 0 if not found, otherwise character following last space
    stub=stub[p1,strlen(stub)-1]
    variable 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


static function doSomething(selection)
    string selection
   
    DFREF dfr = root:Packages:ChooseFromListGUI
    wave /SDFR=dfr/T fullList
    string msg
    findValue /TEXT=selection/TXOP=4/Z fullList
    sprintf msg "%s %s in the list", selection, selectString((v_value>-1) , "is not", "is")
    doalert 0, msg
    return 1
end

 

Forum

Support

Gallery

Igor Pro 8

Learn More

Igor XOP Toolkit

Learn More

Igor NIDAQ Tools MX

Learn More