Autocompleting Combobox and Strings

Share your advanced PureBasic knowledge/code with the community.
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

Autocompleting Combobox and Strings

Post by Xombie »

Here is an autocompletion routine for both the string gadget and combobox gadget. There are some slight bloats in variables since my own internal version has a few more things to it that are specific to my project. Feel free to remove what you like from this in order to lighten it up. The post text stuff could probably be safely removed, for example.

Anyway, string gadgets use string arrays for their completion. So do something like this...

Code: Select all

Dim Array01.s(10)
Array01(0) = "Jack"
Array01(1) = "and"
Array01(2) = "Jill"
Array01(3) = "went"
Array01(4) = "up"
Array01(5) = "the"
Array01(6) = "hill"
Array01(7) = "to"
Array01(8) = "fetch"
Array01(9) = "a"
Array01(10) = "pail"
And then set the autocompleting for the string gadget by simply doing...

Code: Select all

ac_SetAutocomplete(#StringTest, @Array01())
...that. You can have different string arrays tied to different string gadgets or the same string array tied to different string gadgets. Whatever you like. Setting autocomplete for a combobox is done with the same procedure by calling...

Code: Select all

ac_SetAutocomplete(#ComboTest, 0)
...that. It will use the combobox's internal strings to autocomplete.

You can remove autocompletion during the program by calling

Code: Select all

ac_RemoveAutocomplete(#comboTest)
Or remove all autocompletion (for example, at the end of your program) by calling

Code: Select all

ac_DestroyAutocomplete()
Let me know if y'all want some other functionality built in to handle some other behavior. Or modify yourself :)

Here's my test form - saved as "Main.pb"

Code: Select all

; By Xombie - 12/12/2005
;
Enumeration ; Window List
   #WindowMain
EndEnumeration
Enumeration ; Menu List
   #MenuMain
EndEnumeration
Enumeration ; Control List
   #ButtonTest
   #StringTest 
   #StringTest2
   #ComboTest
EndEnumeration
;- Global Variables
Dim Array01.s(10)
Dim array02.s(20)
;- Includes
XIncludeFile "Autocomplete.pb"
;- Main Program
DoQuit.b
;
If OpenWindow(#WindowMain, 100, 300, 300, 200, #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget, "Test")
   ;
   If CreateGadgetList(WindowID())
      AdvancedGadgetEvents(#True) 
      StringGadget(#StringTest, 0, 0, 100, 20, "")
      StringGadget(#StringTest2, 0, 21, 100, 20, "")
      ComboBoxGadget(#ComboTest, GadgetX(#StringTest2), GadgetY(#StringTest2) + GadgetHeight(#StringTest2) + 1, 100, 200, #PB_ComboBox_Editable)
      ButtonGadget(#ButtonTest, GadgetX(#ComboTest), GadgetY(#ComboTest) + GadgetHeight(#ComboTest) + 1, 100, 20, "Test")
   EndIf
   ;
   ;{ Create autocomplete lists
   ;
   ;/ Create items for the combobox.
   AddGadgetItem(#ComboTest, -1, "One")
   AddGadgetItem(#ComboTest, -1, "Two")
   AddGadgetItem(#ComboTest, -1, "Three")
   AddGadgetItem(#ComboTest, -1, "Four")
   AddGadgetItem(#ComboTest, -1, "Five")
   AddGadgetItem(#ComboTest, -1, "Six")
   AddGadgetItem(#ComboTest, -1, "Seven")
   AddGadgetItem(#ComboTest, -1, "Eight")
   AddGadgetItem(#ComboTest, -1, "Nine")
   AddGadgetItem(#ComboTest, -1, "Ten")
   AddGadgetItem(#ComboTest, -1, "The")
   AddGadgetItem(#ComboTest, -1, "quick")
   AddGadgetItem(#ComboTest, -1, "brown")
   AddGadgetItem(#ComboTest, -1, "fox")
   AddGadgetItem(#ComboTest, -1, "jumped")
   AddGadgetItem(#ComboTest, -1, "over")
   AddGadgetItem(#ComboTest, -1, "the")
   AddGadgetItem(#ComboTest, -1, "two")
   AddGadgetItem(#ComboTest, -1, "lazy")
   AddGadgetItem(#ComboTest, -1, "dogs")
   ;/ Create items for the first string
   Array01(0) = "Jack"
   Array01(1) = "and"
   Array01(2) = "Jill"
   Array01(3) = "went"
   Array01(4) = "up"
   Array01(5) = "the"
   Array01(6) = "hill"
   Array01(7) = "to"
   Array01(8) = "fetch"
   Array01(9) = "a"
   Array01(10) = "pail"
   ;/ Create items for the second string
   array02(0) = "Jack"
   array02(1) = "Sprat"
   array02(2) = "could"
   array02(3) = "eat"
   array02(4) = "no"
   array02(5) = "fat"
   array02(6) = "and"
   array02(7) = "his"
   array02(8) = "wife"
   array02(9) = "could"
   array02(10) = "eat"
   array02(11) = "no"
   array02(12) = "lean"
   array02(13) = "but"
   array02(14) = "between"
   array02(15) = "the"
   array02(16) = "both"
   array02(17) = "of"
   array02(18) = "them"
   array02(19) = "they"
   array02(20) = "licked"
   ;}
   ;
   ac_SetAutocomplete(#StringTest, @Array01())
   ;
   ac_SetAutocomplete(#StringTest2, @array02())
   ;
   ac_SetAutocomplete(#ComboTest, 0)
   ;
   Repeat
      ;
      EventID.l = WaitWindowEvent()
      ;
      If EventID = #PB_Event_CloseWindow  ; If the user has pressed on the close button
         ;
         DoQuit = #True
         ;
      ElseIf EventID = #PB_Event_Gadget
         ;
         If EventGadgetID() = #ButtonTest
            ;
            If EventType() = #PB_EventType_LeftClick 
               ;
               ;
            EndIf 
            ;
         EndIf
         ;
      EndIf
      ;
   Until DoQuit = #True

EndIf

ac_DestroyAutocomplete()
; Remove all autocompleting items.
End
And here's the autocomplete routines. I have this saved as "Autocomplete.pb" for my example. Yes, I know, big shock that it's not called "xComplete.pb", right? :D

Code: Select all

;
;- Coded by Xombie 12/19/2005
;
;- Enumeration
Enumeration ; Control Type Enumeration
   #ac_String
   #ac_Combo
EndEnumeration
;- Structures
Structure s_ACStringStructure
  FakeString.s[0] 
EndStructure
Structure s_AutoComplete
   ;
   Gadget.l
   Handle.l
   Parent.l
   CallBack.l
   ; The old callback handle.
   ArrayAddress.l
   ; Only valid for edit controls, this will be the address of the first element in an array of strings.
EndStructure
;- Global Variables
NewList _ac_Main.s_AutoComplete()
;- Helper Functions
Procedure.b ac_GetGadgetType(Handle.l)
   ; Return the gadget type based on the classname used in CreateWindowExW_()
   HoldString.s
   ; This will store the length of the wide character string, in characters.
   lCount.l
   ; Used to store the number of character copied.
   HoldString = Space(255)
   ; Allocate size for our string.
   GetClassName_(Handle, @HoldString, 255)
   ; Call our function to retrieve the classname.
   If HoldString = "Edit"
      ProcedureReturn #ac_String
   ElseIf HoldString = "ComboBox"
      ProcedureReturn #ac_Combo
   EndIf
   ;
EndProcedure
Procedure.b ac_GadgetExists(Gadget.l)
   ;
   ResetList(_ac_Main())
   While NextElement(_ac_Main())
      If _ac_Main()\Gadget = Gadget : ProcedureReturn #True : EndIf
   Wend
   ;
   ProcedureReturn #False
   ;
EndProcedure
Procedure.b ac_GetOldCallback(Handle.l)
   ;
   ResetList(_ac_Main())
   While NextElement(_ac_Main())
      If _ac_Main()\Handle = Handle : ProcedureReturn _ac_Main()\CallBack : EndIf
   Wend
   ;
   ProcedureReturn -1
   ;
EndProcedure
;- Callback Functions
Procedure.l ac_HandleEditEvents(HandleWindow.l, Message.l, wParam.l, lParam.l)
   ; Custom callback for edit controls.
   lResult.l
   ;
   CallBack.l
   ;
   Gadget.l
   ;
   ArrayAddress.l
   ;
   iLoop.l
   ;
   HoldTotal.l
   ;
   HoldLength.l
   ;
   HoldCombinedLength.l
   ;
   HoldStart.l
   ;
   HoldEnd.l
   ;
   HoldSelection.l
   ;
   HoldString.s
   ;
   PreText.s
   ;
   PostText.s
   ;
   CombinedText.s
   ;
   MatchText.s
   ;
   *Position.s_ACStringStructure
   ; This will be a pointer to the strings in the array.
   CallBack = ac_GetOldCallback(HandleWindow)
   ; Return the old callback procedure address.
   If CallBack = -1 : ProcedureReturn : EndIf
   ; This should never happen as this callback is only set for autocomplete items.
   ResetList(_ac_Main())
   While NextElement(_ac_Main())
      If _ac_Main()\Handle = HandleWindow : ArrayAddress = _ac_Main()\ArrayAddress : Gadget = _ac_Main()\Gadget : Break : EndIf
   Wend
   ;
   HoldTotal = PeekL(ArrayAddress - 8)
   ; Return the number of items in the array.
   If HoldTotal = 0 : ProcedureReturn : EndIf
   ; No need to complete if 0 items.
   *Position = ArrayAddress
   ; Point to the first element in the array.
   If Message = #WM_CHAR
      ;
      HoldString = GetGadgetText(Gadget)
      ; Store the current text.
      HoldLength = Len(HoldString)
      ;
      SendMessage_(HandleWindow, #EM_GETSEL, @HoldStart, @HoldEnd)
      ; Store the start and end selection values.
      PreText = Mid(HoldString, 1, HoldStart)
      ; The text before the selection.
      PostText = Mid(HoldString, HoldEnd + 1, HoldLength - HoldEnd)
      ; The text after the selection.
      CombinedText = LCase(PreText + PostText + Chr(wParam))
      ;
      HoldCombinedLength = Len(CombinedText)
      ;
      For iLoop = 0 To HoldTotal - 1
         ; Loop through all items in the array.
         MatchText = PeekS(*Position\FakeString[iLoop])
         ; Store the text at index iLoop.  This little trick of getting the items in a array by addresses is thanks (again) to freak ( http://forums.purebasic.com/english/viewtopic.php?t=15366 )
         If LCase(Left(MatchText, HoldCombinedLength)) = CombinedText
            ; Found a matching item in the combobox.
            SetGadgetText(Gadget, MatchText)
            ;
            SendMessage_(HandleWindow, #EM_SETSEL, HoldCombinedLength, -1)
            ;
            wParam = 0
            ;
            Break
            ; Exit the loop.
         EndIf
         ;
      Next iLoop
      ;
      If wParam <> 0 : lResult = CallWindowProc_(CallBack, HandleWindow, Message, wParam, lParam) : EndIf
      ;
   Else
      ;
      lResult = CallWindowProc_(CallBack, HandleWindow, Message, wParam, lParam)
      ;
   EndIf
   ;
   ProcedureReturn lResult
   ;
EndProcedure
Procedure.l ac_HandleComboEvents(HandleWindow.l, Message.l, wParam.l, lParam.l)
   ; Custom callback for combobox controls.
   lResult.l
   ;
   CallBack.l
   ;
   Gadget.l
   ;
   Parent.l
   ;
   iLoop.l
   ;
   HoldTotal.l
   ;
   HoldLength.l
   ;
   HoldCombinedLength.l
   ;
   HoldStart.l
   ;
   HoldEnd.l
   ;
   HoldSelection.l
   ;
   HoldString.s
   ;
   PreText.s
   ;
   PostText.s
   ;
   CombinedText.s
   ;
   MatchText.s
   ;
   CallBack = ac_GetOldCallback(HandleWindow)
   ; Return the old callback procedure address.
   If CallBack = -1 : ProcedureReturn : EndIf
   ; This should never happen as this callback is only set for autocomplete items.
   ResetList(_ac_Main())
   While NextElement(_ac_Main())
      If _ac_Main()\Handle = HandleWindow : Parent = _ac_Main()\Parent : Gadget = _ac_Main()\Gadget : Break : EndIf
   Wend
   ; Retrieve the handle to the combobox and the gadget id.
   If Message = #WM_CHAR
      ;
      HoldTotal = SendMessage_(Parent, #CB_GETCOUNT, 0, 0)
      ; Return the number of items in the combobox.
      If HoldTotal
         ; Only need to check for autocompletion if items exist in the combobox.
         HoldString = GetGadgetText(Gadget)
         ; Store the current combobox text.
         HoldLength = Len(HoldString)
         ;
         SendMessage_(Parent, #CB_GETEDITSEL, @HoldStart, @HoldEnd)
         ; Store the start and end selection values.
         PreText = Mid(HoldString, 1, HoldStart)
         ; The text before the selection.
         PostText = Mid(HoldString, HoldEnd + 1, HoldLength - HoldEnd)
         ; The text after the selection.
         CombinedText = LCase(PreText + PostText + Chr(wParam))
         ;
         HoldCombinedLength = Len(CombinedText)
         ;
         For iLoop = 0 To HoldTotal - 1
            ; Loop through all items in the combo box.
            MatchText = GetGadgetItemText(Gadget, iLoop, 0)
            ; Store the text at index iLoop.
            If LCase(Left(MatchText, HoldCombinedLength)) = CombinedText
               ; Found a matching item in the combobox.
               SetGadgetText(Gadget, MatchText)
               ;
               HoldSelection = HoldCombinedLength | -1 << 16
               ; Convert the start and end selection into an lParam.  The start position is the combined length of the pre 
               ; and post text.  The end is set to -1 to select the rest of the text.
               SendMessage_(Parent, #CB_SETEDITSEL, 0, HoldSelection)
               ;
               wParam = 0
               ;
               Break
               ; Exit the loop.
            EndIf
            ;
         Next iLoop
         ;
         If wParam <> 0 : lResult = CallWindowProc_(CallBack, HandleWindow, Message, wParam, lParam) : EndIf
         ;
      Else
         ;
         lResult = CallWindowProc_(CallBack, HandleWindow, Message, wParam, lParam)
         ;
      EndIf
      ;
   Else
      ;
      lResult = CallWindowProc_(CallBack, HandleWindow, Message, wParam, lParam)
      ;
   EndIf
   ;
   ProcedureReturn lResult
   ;
EndProcedure
;- Main Functions
Procedure ac_SetAutocomplete(Gadget.l, EditGadgetArrayAddress.l)
   ; EditGadgetArrayAddress is exactly that.  The address to the first element of a plain string array.  This will be used
   ; for the autocompletion list.  Feel free to modify this to handle linked lists or whatever.  It will be ignored for combobox.
   Type.b
   ;
   Handle.l
   ;
   If ac_GadgetExists(Gadget) : ProcedureReturn : EndIf
   ; Check if the gadget already exists in the autocomplete list.
   Handle = GadgetID(Gadget)
   ;
   Type = ac_GetGadgetType(Handle)
   ;
   If Type = #ac_String
      ; String type.
      If EditGadgetArrayAddress = 0 : ProcedureReturn : EndIf
      ; An edit control must be associated with an array address.
      AddElement(_ac_Main())
      ;
      _ac_Main()\Gadget = Gadget
      _ac_Main()\Handle = Handle
      ; The handle to the edit control.
      _ac_Main()\Parent = 0
      ; An edit control is not like a combobox.  Does not have a parent container.
      _ac_Main()\CallBack = SetWindowLong_(Handle, #GWL_WNDPROC, @ac_HandleEditEvents())
      ; The callback will be set for the edit control, not the combobox.
      _ac_Main()\ArrayAddress = EditGadgetArrayAddress
      ;
   ElseIf Type = #ac_Combo
      ; Combobox type
      AddElement(_ac_Main())
      ;
      _ac_Main()\Gadget = Gadget
      _ac_Main()\Handle = GetWindow_(Handle, #GW_CHILD)
      ; This is the handle to the edit control within the combobox.  I think I got this little tidbit from fr34k but don't quote me on that.
      _ac_Main()\Parent = Handle
      ; This will be the handle to the combobox control.
      _ac_Main()\CallBack = SetWindowLong_(_ac_Main()\Handle, #GWL_WNDPROC, @ac_HandleComboEvents())
      ; The callback will be set for the edit control, not the combobox.
      _ac_Main()\ArrayAddress = 0
      ;
   Else
      ; Not a combo or string type.  Currently, no other controls are supported.
      ProcedureReturn
      ;
   EndIf
   ;
EndProcedure
Procedure ac_RemoveAutocomplete(Gadget.l)
   ;
   Handle.l
   ;
   If ac_GadgetExists(Gadget) : ProcedureReturn : EndIf
   ; Check if the gadget already exists in the autocomplete list.
   Handle = GadgetID(Gadget)
   ;
   ResetList(_ac_Main())
   While NextElement(_ac_Main())
      If _ac_Main()\Gadget = Gadget : SetWindowLong_(Handle, #GWL_WNDPROC, _ac_Main()\CallBack) : Break : EndIf
   Wend
   ;
EndProcedure
Procedure ac_DestroyAutocomplete()
   ; Remove all autocomplete items.
   If CountList(_ac_Main()) = 0 : ProcedureReturn : EndIf
   ; No need to destroy if no items in the list.
   ResetList(_ac_Main())
   While NextElement(_ac_Main())
      SetWindowLong_(Handle, #GWL_WNDPROC, _ac_Main()\CallBack)
   Wend
   ;
EndProcedure
There it is. Let me know if you spot any bugs or need something added to it. Have fun ^_^ Now I'm off to make more code examples that refuse to work for gnozal! :D
rsts
Addict
Addict
Posts: 2736
Joined: Wed Aug 24, 2005 8:39 am
Location: Southwest OH - USA

Re: Autocompleting Combobox and Strings

Post by rsts »

Xombie wrote: There it is. Let me know if you spot any bugs or need something added to it. Have fun ^_^ Now I'm off to make more code examples that refuse to work for gnozal! :D
Excellent code plus a sense of humor. What a contributor. :lol:

Thanks for another nice one.

cheers
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

Xombie, you're a coding machine! :lol:

I'll tuck this one away - could be useful for a future app.

Thanks for sharing.
I may look like a mule, but I'm not a complete ass.
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

Post by Xombie »

Nah. Think of me like the military. I'll occasionally let out bits of my main project code for other people :) Also, things like this autocomplete take hardly any time. A lot of my code is reusable so I can just copy bits from other parts and put it together. So when I need something for my main project, I will put something together and think about whether it's alright to share.

Yay for Purebasic :)
rsts
Addict
Addict
Posts: 2736
Joined: Wed Aug 24, 2005 8:39 am
Location: Southwest OH - USA

Post by rsts »

Xombie wrote:
Yay for Purebasic :)
Yes, yay for Purebasic, and a bigger Yay for the the people who so generously share their time and code to the benefit of all of us.

I think you know who they are :wink:

cheers
User avatar
Progi1984
Addict
Addict
Posts: 806
Joined: Fri Feb 25, 2005 1:01 am
Location: France > Rennes
Contact:

Post by Progi1984 »

Your code does'nt run with PB4. Some help ?
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

Post by Xombie »

My apologies for not putting this into a zip file but I don't have access to my ftp server at the moment.

Save as 'Main.pb'

Code: Select all

; By Xombie - 12/12/2005 
; 
Enumeration ; Window List 
   #WindowMain 
EndEnumeration 
Enumeration ; Menu List 
   #MenuMain 
EndEnumeration 
Enumeration ; Control List 
   #ButtonTest 
   #StringTest 
   #StringTest2 
   #ComboTest 
EndEnumeration 
;- Global Variables 
Dim Array01.s(10) 
Dim array02.s(20) 
;- Includes 
XIncludeFile "Autocomplete.pb" 
;- Main Program 
DoQuit.b 
; 
If OpenWindow(#WindowMain, 100, 300, 300, 200, "Test", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget) 
   ; 
   If CreateGadgetList(WindowID(#WindowMain)) 
      StringGadget(#StringTest, 0, 0, 100, 20, "") 
      StringGadget(#StringTest2, 0, 21, 100, 20, "") 
      ComboBoxGadget(#ComboTest, GadgetX(#StringTest2), GadgetY(#StringTest2) + GadgetHeight(#StringTest2) + 1, 100, 200, #PB_ComboBox_Editable) 
      ButtonGadget(#ButtonTest, GadgetX(#ComboTest), GadgetY(#ComboTest) + GadgetHeight(#ComboTest) + 1, 100, 20, "Test") 
   EndIf 
   ; 
   ;{ Create autocomplete lists 
   ; 
   ;/ Create items for the combobox. 
   AddGadgetItem(#ComboTest, -1, "One") 
   AddGadgetItem(#ComboTest, -1, "Two") 
   AddGadgetItem(#ComboTest, -1, "Three") 
   AddGadgetItem(#ComboTest, -1, "Four") 
   AddGadgetItem(#ComboTest, -1, "Five") 
   AddGadgetItem(#ComboTest, -1, "Six") 
   AddGadgetItem(#ComboTest, -1, "Seven") 
   AddGadgetItem(#ComboTest, -1, "Eight") 
   AddGadgetItem(#ComboTest, -1, "Nine") 
   AddGadgetItem(#ComboTest, -1, "Ten") 
   AddGadgetItem(#ComboTest, -1, "The") 
   AddGadgetItem(#ComboTest, -1, "quick") 
   AddGadgetItem(#ComboTest, -1, "brown") 
   AddGadgetItem(#ComboTest, -1, "fox") 
   AddGadgetItem(#ComboTest, -1, "jumped") 
   AddGadgetItem(#ComboTest, -1, "over") 
   AddGadgetItem(#ComboTest, -1, "the") 
   AddGadgetItem(#ComboTest, -1, "two") 
   AddGadgetItem(#ComboTest, -1, "lazy") 
   AddGadgetItem(#ComboTest, -1, "dogs") 
   ;/ Create items for the first string 
   Array01(0) = "Jack" 
   Array01(1) = "and" 
   Array01(2) = "Jill" 
   Array01(3) = "went" 
   Array01(4) = "up" 
   Array01(5) = "the" 
   Array01(6) = "hill" 
   Array01(7) = "to" 
   Array01(8) = "fetch" 
   Array01(9) = "a" 
   Array01(10) = "pail" 
   ;/ Create items for the second string 
   array02(0) = "Jack" 
   array02(1) = "Sprat" 
   array02(2) = "could" 
   array02(3) = "eat" 
   array02(4) = "no" 
   array02(5) = "fat" 
   array02(6) = "and" 
   array02(7) = "his" 
   array02(8) = "wife" 
   array02(9) = "could" 
   array02(10) = "eat" 
   array02(11) = "no" 
   array02(12) = "lean" 
   array02(13) = "but" 
   array02(14) = "between" 
   array02(15) = "the" 
   array02(16) = "both" 
   array02(17) = "of" 
   array02(18) = "them" 
   array02(19) = "they" 
   array02(20) = "licked" 
   ;} 
   ; 
   ac_SetAutocomplete(#StringTest, @Array01()) 
   ; 
   ac_SetAutocomplete(#StringTest2, @array02()) 
   ; 
   ac_SetAutocomplete(#ComboTest, 0) 
   ; 
   Repeat 
      ; 
      EventID.l = WaitWindowEvent() 
      ; 
      If EventID = #PB_Event_CloseWindow  ; If the user has pressed on the close button 
         ; 
         DoQuit = #True 
         ; 
      ElseIf EventID = #PB_Event_Gadget 
         ; 
         If EventGadget() = #ButtonTest 
            ; 
            If EventType() = #PB_EventType_LeftClick 
               ; 
               ; 
            EndIf 
            ; 
         EndIf 
         ; 
      EndIf 
      ; 
   Until DoQuit = #True 

EndIf 

ac_DestroyAutocomplete() 
; Remove all autocompleting items. 
End
Save as 'Autocomplete.pb'

Code: Select all

; 
;- Coded by Xombie 12/19/2005, updated 7/13/2006
; 
;- Enumeration 
Enumeration ; Control Type Enumeration 
   #ac_String 
   #ac_Combo 
EndEnumeration 
;- Structures 
Structure s_ACStringStructure 
  FakeString.s[0] 
EndStructure 
Structure s_AutoComplete 
   ; 
   Gadget.l 
   Handle.l 
   Parent.l 
   CallBack.l 
   ; The old callback handle. 
   ArrayAddress.l 
   ; Only valid for edit controls, this will be the address of the first element in an array of strings. 
EndStructure 
;- Global Variables 
Global NewList _ac_Main.s_AutoComplete() 
;- Helper Functions 
Procedure.b ac_GetGadgetType(Handle.l) 
   ; Return the gadget type based on the classname used in CreateWindowExW_() 
   HoldString.s 
   ; This will store the length of the wide character string, in characters. 
   lCount.l 
   ; Used to store the number of character copied. 
   HoldString = Space(255) 
   ; Allocate size for our string. 
   GetClassName_(Handle, @HoldString, 255) 
   ; Call our function to retrieve the classname. 
   If HoldString = "Edit" 
      ProcedureReturn #ac_String 
   ElseIf HoldString = "ComboBox" 
      ProcedureReturn #ac_Combo 
   EndIf 
   ; 
EndProcedure 
Procedure.b ac_GadgetExists(Gadget.l) 
   ; 
   ResetList(_ac_Main()) 
   While NextElement(_ac_Main()) 
      If _ac_Main()\Gadget = Gadget : ProcedureReturn #True : EndIf 
   Wend 
   ; 
   ProcedureReturn #False 
   ; 
EndProcedure 
Procedure.l ac_GetOldCallback(Handle.l) 
   ; 
   ResetList(_ac_Main()) 
   While NextElement(_ac_Main()) 
      If _ac_Main()\Handle = Handle : ProcedureReturn _ac_Main()\CallBack : EndIf 
   Wend 
   ; 
   ProcedureReturn -1 
   ; 
EndProcedure 
;- Callback Functions 
Procedure.l ac_HandleEditEvents(HandleWindow.l, message.l, wParam.l, lParam.l) 
   ; Custom callback for edit controls. 
   lResult.l 
   ; 
   CallBack.l 
   ; 
   Gadget.l 
   ; 
   ArrayAddress.l 
   ; 
   iLoop.l 
   ; 
   HoldTotal.l 
   ; 
   HoldLength.l 
   ; 
   HoldCombinedLength.l 
   ; 
   HoldStart.l 
   ; 
   HoldEnd.l 
   ; 
   HoldSelection.l 
   ; 
   HoldString.s 
   ; 
   PreText.s 
   ; 
   PostText.s 
   ; 
   CombinedText.s 
   ; 
   MatchText.s 
   ; 
   *Position.s_ACStringStructure 
   ; This will be a pointer to the strings in the array. 
   CallBack = ac_GetOldCallback(HandleWindow) 
   ; Return the old callback procedure address. 
   If CallBack = -1 : ProcedureReturn : EndIf 
   ; This should never happen as this callback is only set for autocomplete items. 
   ResetList(_ac_Main()) 
   While NextElement(_ac_Main()) 
      If _ac_Main()\Handle = HandleWindow : ArrayAddress = _ac_Main()\ArrayAddress : Gadget = _ac_Main()\Gadget : Break : EndIf 
   Wend 
   ; 
   HoldTotal = PeekL(ArrayAddress - 8) 
   ; Return the number of items in the array. 
   If HoldTotal = 0 : ProcedureReturn : EndIf 
   ; No need to complete if 0 items. 
   *Position = ArrayAddress 
   ; Point to the first element in the array. 
   If message = #WM_CHAR 
      ; 
      HoldString = GetGadgetText(Gadget) 
      ; Store the current text. 
      HoldLength = Len(HoldString) 
      ; 
      SendMessage_(HandleWindow, #EM_GETSEL, @HoldStart, @HoldEnd) 
      ; Store the start and end selection values. 
      PreText = Mid(HoldString, 1, HoldStart) 
      ; The text before the selection. 
      PostText = Mid(HoldString, HoldEnd + 1, HoldLength - HoldEnd) 
      ; The text after the selection. 
      CombinedText = LCase(PreText + PostText + Chr(wParam)) 
      ; 
      HoldCombinedLength = Len(CombinedText) 
      ; 
      For iLoop = 0 To HoldTotal - 1 
         ; Loop through all items in the array. 
         MatchText = PeekS(@*Position\FakeString[iLoop]) 
         ; Store the text at index iLoop.  This little trick of getting the items in a array by addresses is thanks (again) to freak ( http://forums.purebasic.com/english/viewtopic.php?t=15366 ) 
         If LCase(Left(MatchText, HoldCombinedLength)) = CombinedText 
            ; Found a matching item in the combobox. 
            SetGadgetText(Gadget, MatchText) 
            ; 
            SendMessage_(HandleWindow, #EM_SETSEL, HoldCombinedLength, -1) 
            ; 
            wParam = 0 
            ; 
            Break 
            ; Exit the loop. 
         EndIf 
         ; 
      Next iLoop 
      ; 
      If wParam <> 0 : lResult = CallWindowProc_(CallBack, HandleWindow, message, wParam, lParam) : EndIf 
      ; 
   Else 
      ; 
      lResult = CallWindowProc_(CallBack, HandleWindow, message, wParam, lParam) 
      ; 
   EndIf 
   ; 
   ProcedureReturn lResult 
   ; 
EndProcedure 
Procedure.l ac_HandleComboEvents(HandleWindow.l, message.l, wParam.l, lParam.l) 
   ; Custom callback for combobox controls. 
   lResult.l 
   ; 
   CallBack.l 
   ; 
   Gadget.l 
   ; 
   Parent.l 
   ; 
   iLoop.l 
   ; 
   HoldTotal.l 
   ; 
   HoldLength.l 
   ; 
   HoldCombinedLength.l 
   ; 
   HoldStart.l 
   ; 
   HoldEnd.l 
   ; 
   HoldSelection.l 
   ; 
   HoldString.s 
   ; 
   PreText.s 
   ; 
   PostText.s 
   ; 
   CombinedText.s 
   ; 
   MatchText.s 
   ; 
   CallBack = ac_GetOldCallback(HandleWindow) 
   ; Return the old callback procedure address. 
   If CallBack = -1 : ProcedureReturn : EndIf 
   ; This should never happen as this callback is only set for autocomplete items. 
   ResetList(_ac_Main()) 
   While NextElement(_ac_Main()) 
      If _ac_Main()\Handle = HandleWindow : Parent = _ac_Main()\Parent : Gadget = _ac_Main()\Gadget : Break : EndIf 
   Wend 
   ; Retrieve the handle to the combobox and the gadget id. 
   If Message = #WM_CHAR 
      ; 
      HoldTotal = SendMessage_(Parent, #CB_GETCOUNT, 0, 0) 
      ; Return the number of items in the combobox. 
      If HoldTotal 
         ; Only need to check for autocompletion if items exist in the combobox. 
         HoldString = GetGadgetText(Gadget) 
         ; Store the current combobox text. 
         HoldLength = Len(HoldString) 
         ; 
         SendMessage_(Parent, #CB_GETEDITSEL, @HoldStart, @HoldEnd) 
         ; Store the start and end selection values. 
         PreText = Mid(HoldString, 1, HoldStart) 
         ; The text before the selection. 
         PostText = Mid(HoldString, HoldEnd + 1, HoldLength - HoldEnd) 
         ; The text after the selection. 
         CombinedText = LCase(PreText + PostText + Chr(wParam)) 
         ; 
         HoldCombinedLength = Len(CombinedText) 
         ; 
         For iLoop = 0 To HoldTotal - 1 
            ; Loop through all items in the combo box. 
            MatchText = GetGadgetItemText(Gadget, iLoop, 0) 
            ; Store the text at index iLoop. 
            If LCase(Left(MatchText, HoldCombinedLength)) = CombinedText 
               ; Found a matching item in the combobox. 
               SetGadgetText(Gadget, MatchText) 
               ; 
               HoldSelection = HoldCombinedLength | -1 << 16 
               ; Convert the start and end selection into an lParam.  The start position is the combined length of the pre 
               ; and post text.  The end is set to -1 to select the rest of the text. 
               SendMessage_(Parent, #CB_SETEDITSEL, 0, HoldSelection) 
               ; 
               wParam = 0 
               ; 
               Break 
               ; Exit the loop. 
            EndIf 
            ; 
         Next iLoop 
         ; 
         If wParam <> 0 : lResult = CallWindowProc_(CallBack, HandleWindow, Message, wParam, lParam) : EndIf 
         ; 
      Else 
         ; 
         lResult = CallWindowProc_(CallBack, HandleWindow, Message, wParam, lParam) 
         ; 
      EndIf 
      ; 
   Else 
      ; 
      lResult = CallWindowProc_(CallBack, HandleWindow, Message, wParam, lParam) 
      ; 
   EndIf 
   ; 
   ProcedureReturn lResult 
   ; 
EndProcedure 
;- Main Functions 
Procedure ac_SetAutocomplete(Gadget.l, EditGadgetArrayAddress.l) 
   ; EditGadgetArrayAddress is exactly that.  The address to the first element of a plain string array.  This will be used 
   ; for the autocompletion list.  Feel free to modify this to handle linked lists or whatever.  It will be ignored for combobox. 
   Type.b 
   ; 
   Handle.l 
   ; 
   If ac_GadgetExists(Gadget) : ProcedureReturn : EndIf 
   ; Check if the gadget already exists in the autocomplete list. 
   Handle = GadgetID(Gadget) 
   ; 
   Type = ac_GetGadgetType(Handle) 
   ; 
   If Type = #ac_String 
      ; String type. 
      If EditGadgetArrayAddress = 0 : ProcedureReturn : EndIf 
      ; An edit control must be associated with an array address. 
      AddElement(_ac_Main()) 
      ; 
      _ac_Main()\Gadget = Gadget 
      _ac_Main()\Handle = Handle 
      ; The handle to the edit control. 
      _ac_Main()\Parent = 0 
      ; An edit control is not like a combobox.  Does not have a parent container. 
      _ac_Main()\CallBack = SetWindowLong_(Handle, #GWL_WNDPROC, @ac_HandleEditEvents()) 
      ; The callback will be set for the edit control, not the combobox. 
      _ac_Main()\ArrayAddress = EditGadgetArrayAddress 
      ; 
   ElseIf Type = #ac_Combo 
      ; Combobox type 
      AddElement(_ac_Main()) 
      ; 
      _ac_Main()\Gadget = Gadget 
      _ac_Main()\Handle = GetWindow_(Handle, #GW_CHILD) 
      ; This is the handle to the edit control within the combobox.  I think I got this little tidbit from fr34k but don't quote me on that. 
      _ac_Main()\Parent = Handle 
      ; This will be the handle to the combobox control. 
      _ac_Main()\CallBack = SetWindowLong_(_ac_Main()\Handle, #GWL_WNDPROC, @ac_HandleComboEvents()) 
      ; The callback will be set for the edit control, not the combobox. 
      _ac_Main()\ArrayAddress = 0 
      ; 
   Else 
      ; Not a combo or string type.  Currently, no other controls are supported. 
      ProcedureReturn 
      ; 
   EndIf 
   ; 
EndProcedure 
Procedure ac_RemoveAutocomplete(Gadget.l) 
   ; 
   Handle.l 
   ; 
   If ac_GadgetExists(Gadget) : ProcedureReturn : EndIf 
   ; Check if the gadget already exists in the autocomplete list. 
   Handle = GadgetID(Gadget) 
   ; 
   ResetList(_ac_Main()) 
   While NextElement(_ac_Main()) 
      If _ac_Main()\Gadget = Gadget : SetWindowLong_(Handle, #GWL_WNDPROC, _ac_Main()\CallBack) : Break : EndIf 
   Wend 
   ; 
EndProcedure 
Procedure ac_DestroyAutocomplete() 
   ; Remove all autocomplete items. 
   If CountList(_ac_Main()) = 0 : ProcedureReturn : EndIf 
   ; No need to destroy if no items in the list. 
   ResetList(_ac_Main()) 
   While NextElement(_ac_Main()) 
      SetWindowLong_(Handle, #GWL_WNDPROC, _ac_Main()\CallBack) 
   Wend 
   ; 
EndProcedure
See if that works for you. I also fixed a huge (but very simple) bug that was fixed in private versions but not here :)
User avatar
Progi1984
Addict
Addict
Posts: 806
Joined: Fri Feb 25, 2005 1:01 am
Location: France > Rennes
Contact:

Post by Progi1984 »

It's cool. thank you.

have you tested in an editor gadget ?

That runs but just for the first word. Any solution for running that when we want or with a comboboxgadget like in the IDE of Purebasic ?
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

Post by Xombie »

I guess I don't know exactly what you're asking. Could you explain again, please?
dige
Addict
Addict
Posts: 1254
Joined: Wed Apr 30, 2003 8:15 am
Location: Germany
Contact:

Post by dige »

@Xombie: please try the word 'jumped'. It will only find 'Jack' ....
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

Post by Xombie »

Well, that depends on where you're doing it. The combobox doesn't have the word 'Jack' added and the only 'j' word is 'jumped' so if you start typing 'Jack' there, it will only find 'jumped'.

There are three separate completion tests. The combobox and each of the stringgadgets have their own lists. The first string gadget is the one that contains the word 'Jack' - but no 'jumped'. You can do a 'AddGadgetItem(#ComboTest, -1, "Jack")' with the rest of them to test whether it will find 'Jack' or 'jumped'.

Hope that helps ^_^
User avatar
Progi1984
Addict
Addict
Posts: 806
Joined: Fri Feb 25, 2005 1:01 am
Location: France > Rennes
Contact:

Post by Progi1984 »

Sorry Xombie, I was in holidays.

In fact, i want to add to my lib LibEditorPlus, the autocompletion functionnality. So i want to create the same autocompletion system which is in PB IDE. Have you some Idea ?
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

Post by Xombie »

You mean autocompleting individual words with a popup word selector as you type? Or what? Maybe if you gave me an example?
User avatar
Progi1984
Addict
Addict
Posts: 806
Joined: Fri Feb 25, 2005 1:01 am
Location: France > Rennes
Contact:

Post by Progi1984 »

You mean autocompleting individual words with a popup word selector as you type? => It's exactly that !
kinglestat
Enthusiast
Enthusiast
Posts: 732
Joined: Fri Jul 14, 2006 8:53 pm
Location: Malta
Contact:

Post by kinglestat »

The routine is fantastic and I tried to use it
as typical of me I stumbled onto a bug immidiately
I've taken the liberty to slightly modify your test example to illustrate the bug

basically once destroyed the list never works again
combogadget and and list are create on the fly using button create and destroyed with the kill button

I'm still rather green with programming with windows so I have no clue as how to fix it


; By Xombie - 12/12/2005
; Slightly modified by kinglestat 10/August/2006

Enumeration ; Window List
#WindowMain
EndEnumeration
Enumeration ; Menu List
#MenuMain
EndEnumeration
Enumeration ; Control List
#ButtonKill
#ButtonCreate
#StringTest
#StringTest2
#ComboTest
EndEnumeration
;- Global Variables
Dim Array01.s(10)
Dim array02.s(20)
;- Includes
XIncludeFile "Autocomplete.pb"
;- Main Program
DoQuit.b

Procedure FillCombo ()

ComboBoxGadget(#ComboTest, GadgetX(#ButtonCreate), GadgetY(#ButtonCreate) + GadgetHeight(#ButtonCreate) + 1, 100, 200, #PB_ComboBox_Editable )

AddGadgetItem(#ComboTest, -1, "One")
AddGadgetItem(#ComboTest, -1, "Two")
AddGadgetItem(#ComboTest, -1, "Three")
AddGadgetItem(#ComboTest, -1, "Four")
AddGadgetItem(#ComboTest, -1, "Five")
AddGadgetItem(#ComboTest, -1, "Six")
AddGadgetItem(#ComboTest, -1, "Seven")
AddGadgetItem(#ComboTest, -1, "Eight")
AddGadgetItem(#ComboTest, -1, "Nine")
AddGadgetItem(#ComboTest, -1, "Ten")
AddGadgetItem(#ComboTest, -1, "The")
AddGadgetItem(#ComboTest, -1, "quick")
AddGadgetItem(#ComboTest, -1, "brown")
AddGadgetItem(#ComboTest, -1, "fox")
AddGadgetItem(#ComboTest, -1, "jumped")
AddGadgetItem(#ComboTest, -1, "over")
AddGadgetItem(#ComboTest, -1, "the")
AddGadgetItem(#ComboTest, -1, "two")
AddGadgetItem(#ComboTest, -1, "lazy")
AddGadgetItem(#ComboTest, -1, "dogs")

EndProcedure


;
If OpenWindow(#WindowMain, 100, 300, 300, 200, "Test", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget)
;
If CreateGadgetList(WindowID(#WindowMain))
StringGadget(#StringTest, 0, 0, 100, 20, "")
StringGadget(#StringTest2, 0, 21, 100, 20, "")
;ComboBoxGadget(#ComboTest, GadgetX(#StringTest2), GadgetY(#StringTest2) + GadgetHeight(#StringTest2) + 1, 100, 200, #PB_ComboBox_Editable )
ButtonGadget ( #ButtonKill, GadgetX(#StringTest2), GadgetY(#StringTest2) + GadgetHeight(#StringTest2) + 1, 100, 20, "Kill" )
ButtonGadget ( #ButtonCreate, GadgetX(#StringTest2) + 120, GadgetY(#StringTest2) + GadgetHeight(#StringTest2) + 1, 100, 20, "Create" )

EndIf
;
;{ Create autocomplete lists
;
;/ Create items for the combobox.

;/ Create items for the first string
Array01(0) = "Jack"
Array01(1) = "and"
Array01(2) = "Jill"
Array01(3) = "went"
Array01(4) = "up"
Array01(5) = "the"
Array01(6) = "hill"
Array01(7) = "to"
Array01(8) = "fetch"
Array01(9) = "a"
Array01(10) = "pail"
;/ Create items for the second string
array02(0) = "Jack"
array02(1) = "Sprat"
array02(2) = "could"
array02(3) = "eat"
array02(4) = "no"
array02(5) = "fat"
array02(6) = "and"
array02(7) = "his"
array02(8) = "wife"
array02(9) = "could"
array02(10) = "eat"
array02(11) = "no"
array02(12) = "lean"
array02(13) = "but"
array02(14) = "between"
array02(15) = "the"
array02(16) = "both"
array02(17) = "of"
array02(18) = "them"
array02(19) = "they"
array02(20) = "licked"
;}
;
ac_SetAutocomplete(#StringTest, @Array01())
ac_SetAutocomplete(#StringTest2, @array02())

;ac_SetAutocomplete(#ComboTest, 0)
;
Repeat
;
EventID.l = WaitWindowEvent()
;
If EventID = #PB_Event_CloseWindow ; If the user has pressed on the close button
;
DoQuit = #True
;
ElseIf EventID = #PB_Event_Gadget
;
If EventGadget() = #ButtonKill

If EventType() = #PB_EventType_LeftClick

ac_RemoveAutocomplete( #ComboTest )
FreeGadget ( #ComboTest )

EndIf

ElseIf EventGadget() = #ButtonCreate

If EventType() = #PB_EventType_LeftClick

;ac_SetAutocomplete(#StringTest, @Array01())
;ac_SetAutocomplete(#StringTest2, @array02())
FillCombo ()
;ComboBoxGadget ( #ComboTest, GadgetX(#StringTest2), GadgetY(#StringTest2) + GadgetHeight(#StringTest2) + 1, 100, 200, #PB_ComboBox_Editable ) ;
ac_SetAutocomplete(#ComboTest, 0)

EndIf
;
EndIf
;
EndIf
;
Until DoQuit = #True

EndIf

ac_DestroyAutocomplete()
; Remove all autocompleting items.
End

cheers

KingLestat
Post Reply