Page 1 of 1

GadgetString's Autocomplete list

Posted: Tue Jan 15, 2019 10:03 am
by thyphoon
I am currently working on a big project. I needed an autocompletion list for string gadget
My goal was also that it be the most possible crossplatform
And this morning I had an idea ....
What do you think? I am looking for new improvements

2019-16-01 11h05 : Update Code
2019-16-01 15h22 : Works Fine :mrgreen:
2019-16-01 15h52 Fix when move Window !
2019-16-01 16h28 Fix move Windows with no open autocomplete window
2019-16-01 16h43 hide autocomplete window if empty
2019-16-01 21h15 use Srod advice ! Better code

Code: Select all

; ******************************************************************** 
; Program:           AutoComplete List
; Description:       Add an AutoComplete list to a String gadget 

; Author:            Thyphoon
; Date:              January, 2019
; License:           Free, unrestricted, credit 
;                    appreciated but not required.
; Note:              Please share improvement !
; Thanks to:         Srod
; ******************************************************************** 

EnableExplicit

DeclareModule AutoComplete
  Declare AddAutocompleteWindow(Window.i,Gadget.i,*CallBack,FirstEventKey.l=5000)
  Declare CheckEvent(Event.i)
EndDeclareModule  

Module AutoComplete
  Declare MoveWindow()

  Prototype.s CallBack(String.s)
  
  Structure ac
    Window.i                  ;Original Window
    Gadget.i                  ;Original Gadget
    FirstEventKey.l           ;First Eventkey=Up +1=Down +2=Enter +3=Escape
    CallBack.CallBack         ;CallBack who return choice
  EndStructure
  
  Structure params
    acWindow.i                ;Autocomplete window
    acGadget.i                ;AutoComplete GadgetList
    CurrentName.s             ;CurrentName at last open autocomplete windows
    Map AutoCompleteGadget.ac()
    FirstEventKey.i
  EndStructure
  
  Global params.params
  
  
  Procedure AddAutocompleteWindow(Window.i,Gadget.i,*CallBack,FirstEventKey.l=5000)
    Name.s=Str(Window)+"-"+Str(Gadget)
    params\AutoCompleteGadget(Name)\CallBack=*CallBack
    params\AutoCompleteGadget(Name)\Gadget=Gadget
    params\AutoCompleteGadget(Name)\Window=Window
    params\AutoCompleteGadget(Name)\FirstEventKey=FirstEventKey
    AddKeyboardShortcut(Window, #PB_Shortcut_Up, FirstEventKey)
    AddKeyboardShortcut(Window, #PB_Shortcut_Down, FirstEventKey+1)
    AddKeyboardShortcut(Window, #PB_Shortcut_Return, FirstEventKey+2)
    AddKeyboardShortcut(Window, #PB_Shortcut_Escape, FirstEventKey+3)
  EndProcedure
  
  Procedure RefreshAutocompleteWindow(Name.s)
    Gadget.i=params\AutoCompleteGadget(Name)\Gadget
    X.l=GadgetX(Gadget,#PB_Gadget_ScreenCoordinate)
    Y.l=GadgetY(Gadget,#PB_Gadget_ScreenCoordinate)+GadgetHeight(Gadget)
    W.l=GadgetWidth(Gadget)
    H.l=150
    ResizeWindow(params\acWindow,X,Y,W,H)
    ResizeGadget(params\acGadget,0,0,W,H)
    HideWindow(params\acWindow, #False,#PB_Window_NoActivate)
    StickyWindow(params\acWindow,#True)
  EndProcedure
  
  Procedure UpdateAutocompleteList(Name.s)
    ClearGadgetItems(params\acGadget)
    If params\AutoCompleteGadget(Name)\CallBack<>0
      BackString.s=params\AutoCompleteGadget(Name)\CallBack(GetGadgetText(params\AutoCompleteGadget(Name)\Gadget))
      For n=1 To CountString(BackString,Chr(10))+1
        line.s=StringField(BackString,n,Chr(10))
        If LCase(Left(line,Len(GetGadgetText(params\AutoCompleteGadget(Name)\Gadget))))=LCase(GetGadgetText(params\AutoCompleteGadget(Name)\Gadget))
          AddGadgetItem(params\acGadget,-1,line)
        EndIf 
      Next
      ;Hide Autocomplete Window if no Choice
      If CountGadgetItems(params\acGadget)=0
        HideWindow(params\acWindow,#True)
      Else
        HideWindow(params\acWindow,#False,#PB_Window_NoActivate)
      EndIf 
    Else
      Debug "Error with callback"
    EndIf
  EndProcedure
  
  Procedure OpenAutocompleteWindow(Name.s)
    If params\acGadget>0 And IsGadget(params\acGadget):FreeGadget(params\acGadget):EndIf
    If params\acWindow>0 And IsWindow(params\acWindow):CloseWindow(params\acWindow):EndIf
    params\acWindow=OpenWindow(#PB_Any,0,0,50,50,"AutoComplete",#PB_Window_BorderLess|#PB_Window_NoActivate,WindowID(params\AutoCompleteGadget(Name)\Window));
    If params\acWindow
      params\CurrentName=Name
      SetWindowData(params\acWindow,params\AutoCompleteGadget(Name)\Gadget)
      params\acGadget=ListViewGadget(#PB_Any, 0, 0, 50,50)
    EndIf
    RefreshAutocompleteWindow(Name)
    UpdateAutocompleteList(Name)
    BindEvent(#PB_Event_MoveWindow, @MoveWindow(), params\AutoCompleteGadget(Name)\Window)
  EndProcedure
  
  Procedure CloseAutocompleteWindow(Name.s)
      If params\acGadget>0 And IsGadget(params\acGadget):FreeGadget(params\acGadget):EndIf
      If params\acWindow>0 And IsWindow(params\acWindow):CloseWindow(params\acWindow):EndIf
      UnbindEvent(#PB_Event_MoveWindow, @MoveWindow(), Windows)
  EndProcedure
  
  Procedure MoveWindow()
    If EventWindow()=params\AutoCompleteGadget()\Window And params\acWindow>0 And IsWindow(params\acWindow)
      RefreshAutocompleteWindow(params\CurrentName)
    EndIf
  EndProcedure
  

  Procedure CheckEvent(Event.i)
    Static bbclick.b
    ;-Orignal Gadget Event
    Protected Name.s=Str(EventWindow())+"-"+Str(EventGadget())   
    If FindMapElement(params\AutoCompleteGadget(),Name)
      Select Event
        Case  #PB_Event_Gadget
          Select EventGadget()
              ; Main gadget Event  
            Case params\AutoCompleteGadget()\Gadget
              Select EventType()
                Case #PB_EventType_Focus
                  If params\acWindow=0 Or IsWindow(params\acWindow)=#False
                    If bbclick=#False
                      OpenAutocompleteWindow(Name)
                    Else
                      bbclick=#True
                    EndIf 
                  EndIf 
                Case #PB_EventType_LostFocus
                  If GetActiveGadget()<>params\acGadget
                    CloseAutocompleteWindow(Name)
                    bbclick=#False
                  EndIf  
                Case #PB_EventType_Change
                  If bbclick=#False 
                    If IsWindow(params\acWindow)
                      UpdateAutocompleteList(Name)
                    Else
                      Debug "pas de windows"
                    EndIf
                  Else
                    OpenAutocompleteWindow(Name)
                    bbclick=#False
                  EndIf 
              EndSelect 

          EndSelect 
     EndSelect
    EndIf
    
    Select Event
      ;if Keyword  
      Case #PB_Event_Menu
        Select EventMenu()
          Case params\AutoCompleteGadget(params\CurrentName)\FirstEventKey;UP
            SetGadgetState(params\acGadget,GetGadgetState(params\acGadget)-1)
          Case params\AutoCompleteGadget(params\CurrentName)\FirstEventKey+1;Down
            SetGadgetState(params\acGadget,GetGadgetState(params\acGadget)+1)
          Case params\AutoCompleteGadget(params\CurrentName)\FirstEventKey+2;Enter
            SetGadgetText(params\AutoCompleteGadget()\Gadget,GetGadgetItemText(params\acGadget,GetGadgetState(params\acGadget)))
          Case params\AutoCompleteGadget(params\CurrentName)\FirstEventKey+3;
            CloseAutocompleteWindow(params\CurrentName)
        EndSelect
      Case  #PB_Event_Gadget
        If EventGadget()=params\acGadget
          If EventType()=#PB_EventType_LeftDoubleClick
            SetGadgetText(params\AutoCompleteGadget(params\CurrentName)\Gadget,GetGadgetItemText(params\acGadget,GetGadgetState(params\acGadget)))
            CloseAutocompleteWindow(params\CurrentName)
            bbclick=#True
          EndIf
        EndIf 
    EndSelect  
    
    
    
  EndProcedure  
  
  
EndModule

;- MAIN TEST

CompilerIf #PB_Compiler_IsMainFile 
  
  ; Callback to return keyword list. you must use a database en filter if you want ^_^ 
  Procedure.s ToDisplayInList(String.s)
    ProcedureReturn "Amiga"+Chr(10)+"Amstrad"+Chr(10)+"Atari"+Chr(10)+"Commodore"+Chr(10)+"BeBox"+Chr(10)+"Macintosh"+Chr(10)+"Spectrum"
  EndProcedure
  
  Procedure.s ToDisplayInListB(String.s)
    ProcedureReturn "Alain"+Chr(10)+"Bernard"+Chr(10)+"Charly"+Chr(10)+"David"+Chr(10)+"Eric"+Chr(10)+"Filipe"+Chr(10)+"Horace"+Chr(10)+"Jérome"+Chr(10)+"Kevin"+Chr(10)+"Laurent"+Chr(10)+"Marc"
  EndProcedure
  
  If OpenWindow(0, 200, 200, 800, 100, "AutoComplete Teste", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    
    StringGadget(1, 10, 10, 380, 26, "")
    StringGadget(2, 410, 10, 380, 26, "")
    
    AutoComplete::AddAutocompleteWindow(0,1,@ToDisplayInList()) ;Just init the gadget who must support AutoComplete list
    AutoComplete::AddAutocompleteWindow(0,2,@ToDisplayInListB())
    Repeat
      Define Event.i = WaitWindowEvent()
      
      
      AutoComplete::CheckEvent(Event)
      
      Select Event
        Case #PB_Event_Gadget
      EndSelect
    Until Event=#PB_Event_CloseWindow
  EndIf
  End
CompilerEndIf

Re: GadgetString's Autocomplete list

Posted: Tue Jan 15, 2019 1:28 pm
by STARGÅTE
thyphoon wrote:1) First problem is how to find the height of the window title bar ? and also thickness of the window to correctly position.
You have to use #PB_Window_InnerCoordinate:

Code: Select all

    MainAC(Name)\X=WindowX(Windows, #PB_Window_InnerCoordinate)+GadgetX(Gadget)
    MainAC(Name)\Y=WindowY(Windows, #PB_Window_InnerCoordinate)+GadgetY(Gadget)+GadgetHeight(Gadget)

Re: GadgetString's Autocomplete list

Posted: Tue Jan 15, 2019 4:52 pm
by Kukulkan
Maybe also interesting: viewtopic.php?f=12&t=52079

Re: GadgetString's Autocomplete list

Posted: Tue Jan 15, 2019 4:56 pm
by thyphoon
STARGÅTE wrote:
thyphoon wrote:1) First problem is how to find the height of the window title bar ? and also thickness of the window to correctly position.
You have to use #PB_Window_InnerCoordinate:

Code: Select all

    MainAC(Name)\X=WindowX(Windows, #PB_Window_InnerCoordinate)+GadgetX(Gadget)
    MainAC(Name)\Y=WindowY(Windows, #PB_Window_InnerCoordinate)+GadgetY(Gadget)+GadgetHeight(Gadget)
YEEEEEEEEEEEEEEEEEEES :mrgreen: Thanks
Kukulkan wrote:Maybe also interesting: viewtopic.php?f=12&t=52079
Very interesting. May be a mix of both is a goos idea :idea: :D

Re: GadgetString's Autocomplete list

Posted: Wed Jan 16, 2019 3:24 pm
by thyphoon
Big update 8)

Code: Select all

code updated on the first post

Re: GadgetString's Autocomplete list

Posted: Wed Jan 16, 2019 3:28 pm
by srod
The auto complete seems to work well. However, if I drag the window then any visible autocomplete box is left behind and the program crashes when I release the mouse on line 56 (Gadget# is not initialised).

Re: GadgetString's Autocomplete list

Posted: Wed Jan 16, 2019 3:47 pm
by thyphoon
srod wrote:The auto complete seems to work well. However, if I drag the window then any visible autocomplete box is left behind and the program crashes when I release the mouse on line 56 (Gadget# is not initialised).
thanks ! I will correct it !

Re: GadgetString's Autocomplete list

Posted: Wed Jan 16, 2019 3:54 pm
by thyphoon
I fix the problem when move the window :D Thanks Srod

Code: Select all

Code updated on the first Post

Re: GadgetString's Autocomplete list

Posted: Wed Jan 16, 2019 3:56 pm
by srod
Nearly! :) Still crashes on the same line. Drag the window as soon as you run the program - before opening one of the autocomplete boxes.

Re: GadgetString's Autocomplete list

Posted: Wed Jan 16, 2019 4:27 pm
by thyphoon
srod wrote:Nearly! :) Still crashes on the same line. Drag the window as soon as you run the program - before opening one of the autocomplete boxes.
:lol: Oups!!!!

Code: Select all

; ******************************************************************** 
; Program:           AutoComplete List
; Description:       Add an AutoComplete list to a String gadget 

; Author:            Thyphoon
; Date:              January, 2019
; License:           Free, unrestricted, credit 
;                    appreciated but not required.
; Note:              Please share improvement !
; ******************************************************************** 

EnableExplicit

DeclareModule AutoComplete
  Declare AddAutocompleteWindow(Window.i,Gadget.i,*CallBack,FirstEventKey.l=5000)
  Declare CheckEvent(Event.i)
EndDeclareModule  

Module AutoComplete
  
  Prototype.s CallBack(String.s)
  
  Structure ac
    Window.i                  ;Original Window
    Gadget.i                  ;Original Gadget
    FirstEventKey.l           ;First Eventkey=Up +1=Down +2=Enter +3=Escape
    CallBack.CallBack         ;CallBack who return choice
  EndStructure
  
  Structure params
    acWindow.i                ;Autocomplete window
    acGadget.i                ;AutoComplete GadgetList
    CurrentName.s             ;CurrentName at last open autocomplete windows
    Map AutoCompleteGadget.ac()
    FirstEventKey.i
  EndStructure
  
  Global params.params
  
  
  Procedure AddAutocompleteWindow(Window.i,Gadget.i,*CallBack,FirstEventKey.l=5000)
    Name.s=Str(Window)+"-"+Str(Gadget)
    params\AutoCompleteGadget(Name)\CallBack=*CallBack
    params\AutoCompleteGadget(Name)\Gadget=Gadget
    params\AutoCompleteGadget(Name)\Window=Window
    params\AutoCompleteGadget(Name)\FirstEventKey=FirstEventKey
    AddKeyboardShortcut(Window, #PB_Shortcut_Up, FirstEventKey)
    AddKeyboardShortcut(Window, #PB_Shortcut_Down, FirstEventKey+1)
    AddKeyboardShortcut(Window, #PB_Shortcut_Return, FirstEventKey+2)
    AddKeyboardShortcut(Window, #PB_Shortcut_Escape, FirstEventKey+3)
  EndProcedure
  
  Procedure RefreshAutocompleteWindow(Name.s)
    Gadget.i=params\AutoCompleteGadget(Name)\Gadget
    Windows.i=params\AutoCompleteGadget(Name)\Window
    Debug Gadget
    X.l=WindowX(Windows,#PB_Window_InnerCoordinate)+GadgetX(Gadget)
    Y.l=WindowY(Windows,#PB_Window_InnerCoordinate)+GadgetY(Gadget)+GadgetHeight(Gadget)
    W.l=GadgetWidth(Gadget)
    H.l=150
    ResizeWindow(params\acWindow,X,Y,W,H)
    ResizeGadget(params\acGadget,0,0,W,H)
    HideWindow(params\acWindow, #False,#PB_Window_NoActivate)
    StickyWindow(params\acWindow,#True)
  EndProcedure
  
  Procedure UpdateAutocompleteList(Name.s)
    ClearGadgetItems(params\acGadget)
    If params\AutoCompleteGadget(Name)\CallBack<>0
      BackString.s=params\AutoCompleteGadget(Name)\CallBack(GetGadgetText(params\AutoCompleteGadget(Name)\Gadget))

      For n=1 To CountString(BackString,Chr(10))+1
        line.s=StringField(BackString,n,Chr(10))
        If LCase(Left(line,Len(GetGadgetText(params\AutoCompleteGadget(Name)\Gadget))))=LCase(GetGadgetText(params\AutoCompleteGadget(Name)\Gadget))
          AddGadgetItem(params\acGadget,-1,line)
        EndIf 
      Next
      ;Hide Autocomplete Window if no Choice
      If CountGadgetItems(params\acGadget)=0
        HideWindow(params\acWindow,#True)
      Else
        HideWindow(params\acWindow,#False,#PB_Window_NoActivate)
      EndIf 
    Else
      Debug "Error with callback"
    EndIf
  EndProcedure
  
  Procedure OpenAutocompleteWindow(Name.s)
    If params\acGadget>0 And IsGadget(params\acGadget):FreeGadget(params\acGadget):EndIf
    If params\acWindow>0 And IsWindow(params\acWindow):CloseWindow(params\acGadget):EndIf
    params\acWindow=OpenWindow(#PB_Any,0,0,50,50,"AutoComplete",#PB_Window_BorderLess|#PB_Window_NoActivate,WindowID(Window));
    If params\acWindow
      params\CurrentName=Name
      SetWindowData(params\acWindow,params\AutoCompleteGadget(Name)\Gadget)
      params\acGadget=ListViewGadget(#PB_Any, 0, 0, 50,50)
    EndIf
    RefreshAutocompleteWindow(Name)
    UpdateAutocompleteList(Name)
  EndProcedure
  
  Procedure CloseAutocompleteWindow(Name.s)
      Gadget.i=params\AutoCompleteGadget(Name)\Gadget
      Windows.i=params\AutoCompleteGadget(Name)\Window
      If params\acGadget>0 And IsGadget(params\acGadget):FreeGadget(params\acGadget):EndIf
      If params\acWindow>0 And IsWindow(params\acWindow):CloseWindow(params\acWindow):EndIf
  EndProcedure
  
  Procedure CheckEvent(Event.i)
    Static bbclick.b
    ;-Orignal Gadget Event
    Protected Name.s=Str(EventWindow())+"-"+Str(EventGadget())   
    If FindMapElement(params\AutoCompleteGadget(),Name)
      Select Event
        Case  #PB_Event_Gadget
          Select EventGadget()
              ; Main gadget Event  
            Case params\AutoCompleteGadget()\Gadget
              Select EventType()
                Case #PB_EventType_Focus
                  If params\acWindow=0 Or IsWindow(params\acWindow)=#False
                    If bbclick=#False
                      OpenAutocompleteWindow(Name)
                    Else
                      bbclick=#True
                    EndIf 
                  EndIf 
                Case #PB_EventType_LostFocus
                  If GetActiveGadget()<>params\acGadget
                    CloseAutocompleteWindow(Name)
                    bbclick=#False
                  EndIf  
                Case #PB_EventType_Change
                  If bbclick=#False 
                    If IsWindow(params\acWindow)
                      UpdateAutocompleteList(Name)
                    Else
                      Debug "pas de windows"
                    EndIf
                  Else
                    OpenAutocompleteWindow(Name)
                    bbclick=#False
                  EndIf 
              EndSelect 

          EndSelect 
     EndSelect
    EndIf
    
    Select Event
      ;If Original Window Move  
      Case #PB_Event_MoveWindow,#PB_Event_SizeWindow 
        If EventWindow()=params\AutoCompleteGadget()\Window And params\acWindow>0 And IsWindow(params\acWindow)
          RefreshAutocompleteWindow(params\CurrentName)
        EndIf
      ;if Keyword  
      Case #PB_Event_Menu
        Select EventMenu()
          Case params\AutoCompleteGadget(params\CurrentName)\FirstEventKey;UP
            SetGadgetState(params\acGadget,GetGadgetState(params\acGadget)-1)
          Case params\AutoCompleteGadget(params\CurrentName)\FirstEventKey+1;Down
            SetGadgetState(params\acGadget,GetGadgetState(params\acGadget)+1)
          Case params\AutoCompleteGadget(params\CurrentName)\FirstEventKey+2;Enter
            SetGadgetText(params\AutoCompleteGadget()\Gadget,GetGadgetItemText(params\acGadget,GetGadgetState(params\acGadget)))
          Case params\AutoCompleteGadget(params\CurrentName)\FirstEventKey+3;
            CloseAutocompleteWindow(params\CurrentName)
        EndSelect
      Case  #PB_Event_Gadget
        If EventGadget()=params\acGadget
          If EventType()=#PB_EventType_LeftDoubleClick
            SetGadgetText(params\AutoCompleteGadget(params\CurrentName)\Gadget,GetGadgetItemText(params\acGadget,GetGadgetState(params\acGadget)))
            CloseAutocompleteWindow(params\CurrentName)
            bbclick=#True
          EndIf
        EndIf 
    EndSelect  
    
    
    
  EndProcedure  
  
  
EndModule

;- MAIN TEST

CompilerIf #PB_Compiler_IsMainFile 
  
  ; Callback to return keyword list. you must use a database en filter if you want ^_^ 
  Procedure.s ToDisplayInList(String.s)
    ProcedureReturn "Amiga"+Chr(10)+"Amstrad"+Chr(10)+"Atari"+Chr(10)+"Commodore"+Chr(10)+"BeBox"+Chr(10)+"Macintosh"+Chr(10)+"Spectrum"
  EndProcedure
  
  Procedure.s ToDisplayInListB(String.s)
    ProcedureReturn "Alain"+Chr(10)+"Bernard"+Chr(10)+"Charly"+Chr(10)+"David"+Chr(10)+"Eric"+Chr(10)+"Filipe"+Chr(10)+"Horace"+Chr(10)+"Jérome"+Chr(10)+"Kevin"+Chr(10)+"Laurent"+Chr(10)+"Marc"
  EndProcedure
  
  If OpenWindow(0, 200, 200, 800, 300, "AutoComplete Teste", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    
    StringGadget(1, 10, 10, 380, 26, "")
    StringGadget(2, 410, 10, 380, 26, "")
    
    
    AutoComplete::AddAutocompleteWindow(0,1,@ToDisplayInList()) ;Just init the gadget who must support AutoComplete list
    AutoComplete::AddAutocompleteWindow(0,2,@ToDisplayInListB())
    Repeat
      Define Event.i = WaitWindowEvent()
      
      
      AutoComplete::CheckEvent(Event)
      
      Select Event
        Case #PB_Event_Gadget
      EndSelect
      
    Until Event=#PB_Event_CloseWindow
  EndIf
  End
CompilerEndIf

Re: GadgetString's Autocomplete list

Posted: Wed Jan 16, 2019 5:01 pm
by srod
Yep - that's better.

I have made a simple modification so that when you drag the window, the auto complete box moves at the same time. Just a simple BindEvent(). You should use a suitable UnbindEvent() at some point.

At the moment this code is not easily used in a different program because of the way you have effectively hard coded parent window info into the module. You could easily change this, however, and create a library which had no dependency whatsoever on the parent window. GadgetX and GadgetY calculations would need to use the #PB_Gadget_ScreenCoordinate flag. Also, you would need to use a hidden window as the parent window of your 'pop up' to stop a taskbar entry appearing. Your host app would need to call a suitable library function everytime a #PB_Event_MoveWindow event was received. I've done this sort of thing a few times.

One thing I have spotted is that you are relying upon #PB_EventType_LostFocus events firing for ListView gadgets. This is undocumented for this gadget which is why you will need to test this on the other platforms.

Anyhow, I post your program with the move window alteration :

Code: Select all

; ******************************************************************** 
; Program:           AutoComplete List
; Description:       Add an AutoComplete list to a String gadget 

; Author:            Thyphoon
; Date:              January, 2019
; License:           Free, unrestricted, credit 
;                    appreciated but not required.
; Note:              Please share improvement !
; ******************************************************************** 

EnableExplicit

DeclareModule AutoComplete
  Declare AddAutocompleteWindow(Window.i,Gadget.i,*CallBack,FirstEventKey.l=5000)
  Declare CheckEvent(Event.i)
EndDeclareModule  

Module AutoComplete
  Declare MoveWindow()

  Prototype.s CallBack(String.s)
  
  Structure ac
    Window.i                  ;Original Window
    Gadget.i                  ;Original Gadget
    FirstEventKey.l           ;First Eventkey=Up +1=Down +2=Enter +3=Escape
    CallBack.CallBack         ;CallBack who return choice
  EndStructure
  
  Structure params
    acWindow.i                ;Autocomplete window
    acGadget.i                ;AutoComplete GadgetList
    CurrentName.s             ;CurrentName at last open autocomplete windows
    Map AutoCompleteGadget.ac()
    FirstEventKey.i
  EndStructure
  
  Global params.params
  
  
  Procedure AddAutocompleteWindow(Window.i,Gadget.i,*CallBack,FirstEventKey.l=5000)
    Name.s=Str(Window)+"-"+Str(Gadget)
    params\AutoCompleteGadget(Name)\CallBack=*CallBack
    params\AutoCompleteGadget(Name)\Gadget=Gadget
    params\AutoCompleteGadget(Name)\Window=Window
    params\AutoCompleteGadget(Name)\FirstEventKey=FirstEventKey
    AddKeyboardShortcut(Window, #PB_Shortcut_Up, FirstEventKey)
    AddKeyboardShortcut(Window, #PB_Shortcut_Down, FirstEventKey+1)
    AddKeyboardShortcut(Window, #PB_Shortcut_Return, FirstEventKey+2)
    AddKeyboardShortcut(Window, #PB_Shortcut_Escape, FirstEventKey+3)
    BindEvent(#PB_Event_MoveWindow, @MoveWindow(), Window)

  EndProcedure
  
  Procedure RefreshAutocompleteWindow(Name.s)
    Gadget.i=params\AutoCompleteGadget(Name)\Gadget
    Windows.i=params\AutoCompleteGadget(Name)\Window
    X.l=WindowX(Windows,#PB_Window_InnerCoordinate)+GadgetX(Gadget)
    Y.l=WindowY(Windows,#PB_Window_InnerCoordinate)+GadgetY(Gadget)+GadgetHeight(Gadget)
    W.l=GadgetWidth(Gadget)
    H.l=150
    ResizeWindow(params\acWindow,X,Y,W,H)
    ResizeGadget(params\acGadget,0,0,W,H)
    HideWindow(params\acWindow, #False,#PB_Window_NoActivate)
    StickyWindow(params\acWindow,#True)
  EndProcedure
  
  Procedure UpdateAutocompleteList(Name.s)
    ClearGadgetItems(params\acGadget)
    If params\AutoCompleteGadget(Name)\CallBack<>0
      BackString.s=params\AutoCompleteGadget(Name)\CallBack(GetGadgetText(params\AutoCompleteGadget(Name)\Gadget))
      For n=1 To CountString(BackString,Chr(10))+1
        line.s=StringField(BackString,n,Chr(10))
        If LCase(Left(line,Len(GetGadgetText(params\AutoCompleteGadget(Name)\Gadget))))=LCase(GetGadgetText(params\AutoCompleteGadget(Name)\Gadget))
          AddGadgetItem(params\acGadget,-1,line)
        EndIf 
      Next
    Else
      Debug "Error with callback"
    EndIf
  EndProcedure
  
  Procedure OpenAutocompleteWindow(Name.s)
    If params\acGadget>0 And IsGadget(params\acGadget):FreeGadget(params\acGadget):EndIf
    If params\acWindow>0 And IsWindow(params\acWindow):CloseWindow(params\acGadget):EndIf
    params\acWindow=OpenWindow(#PB_Any,0,0,50,50,"AutoComplete",#PB_Window_BorderLess|#PB_Window_NoActivate,WindowID(Window));
    If params\acWindow
      params\CurrentName=Name
      SetWindowData(params\acWindow,params\AutoCompleteGadget(Name)\Gadget)
      params\acGadget=ListViewGadget(#PB_Any, 0, 0, 50,50)
    EndIf
    RefreshAutocompleteWindow(Name)
    UpdateAutocompleteList(Name)
  EndProcedure
  
  Procedure CloseAutocompleteWindow(Name.s)
      Gadget.i=params\AutoCompleteGadget(Name)\Gadget
      Windows.i=params\AutoCompleteGadget(Name)\Window
      If params\acGadget>0 And IsGadget(params\acGadget):FreeGadget(params\acGadget):EndIf
      If params\acWindow>0 And IsWindow(params\acWindow):CloseWindow(params\acWindow):EndIf
  EndProcedure
  
  Procedure MoveWindow()
    If EventWindow()=params\AutoCompleteGadget()\Window And params\acWindow>0 And IsWindow(params\acWindow)
      RefreshAutocompleteWindow(params\CurrentName)
    EndIf
  EndProcedure
  

  Procedure CheckEvent(Event.i)
    Static bbclick.b
    ;-Orignal Gadget Event
    Protected Name.s=Str(EventWindow())+"-"+Str(EventGadget())   
    If FindMapElement(params\AutoCompleteGadget(),Name)
      Select Event
        Case  #PB_Event_Gadget
          Select EventGadget()
              ; Main gadget Event  
            Case params\AutoCompleteGadget()\Gadget
              Select EventType()
                Case #PB_EventType_Focus
                  If params\acWindow=0 Or IsWindow(params\acWindow)=#False
                    If bbclick=#False
                      OpenAutocompleteWindow(Name)
                    Else
                      bbclick=#True
                    EndIf 
                  EndIf 
                Case #PB_EventType_LostFocus
                  If GetActiveGadget()<>params\acGadget
                    CloseAutocompleteWindow(Name)
                    bbclick=#False
                  EndIf  
                Case #PB_EventType_Change
                  If bbclick=#False 
                    If IsWindow(params\acWindow)
                      UpdateAutocompleteList(Name)
                    Else
                      Debug "pas de windows"
                    EndIf
                  Else
                    OpenAutocompleteWindow(Name)
                    bbclick=#False
                  EndIf 
              EndSelect 

          EndSelect 
     EndSelect
    EndIf
    
    Select Event
      ;If Original Window Move  
      Case #PB_Event_MoveWindow,#PB_Event_SizeWindow 
;        If EventWindow()=params\AutoCompleteGadget()\Window And params\acWindow>0 And IsWindow(params\acWindow)
;          RefreshAutocompleteWindow(params\CurrentName)
;        EndIf
      ;if Keyword  
      Case #PB_Event_Menu
        Select EventMenu()
          Case params\AutoCompleteGadget(params\CurrentName)\FirstEventKey;UP
            SetGadgetState(params\acGadget,GetGadgetState(params\acGadget)-1)
          Case params\AutoCompleteGadget(params\CurrentName)\FirstEventKey+1;Down
            SetGadgetState(params\acGadget,GetGadgetState(params\acGadget)+1)
          Case params\AutoCompleteGadget(params\CurrentName)\FirstEventKey+2;Enter
            SetGadgetText(params\AutoCompleteGadget()\Gadget,GetGadgetItemText(params\acGadget,GetGadgetState(params\acGadget)))
          Case params\AutoCompleteGadget(params\CurrentName)\FirstEventKey+3;
            CloseAutocompleteWindow(params\CurrentName)
        EndSelect
      Case  #PB_Event_Gadget
        If EventGadget()=params\acGadget
          If EventType()=#PB_EventType_LeftDoubleClick
            SetGadgetText(params\AutoCompleteGadget(params\CurrentName)\Gadget,GetGadgetItemText(params\acGadget,GetGadgetState(params\acGadget)))
            CloseAutocompleteWindow(params\CurrentName)
            bbclick=#True
          EndIf
        EndIf 
    EndSelect  
    
    
    
  EndProcedure  
  
  
EndModule

;- MAIN TEST

CompilerIf #PB_Compiler_IsMainFile 
  
  ; Callback to return keyword list. you must use a database en filter if you want ^_^ 
  Procedure.s ToDisplayInList(String.s)
    ProcedureReturn "Amiga"+Chr(10)+"Amstrad"+Chr(10)+"Atari"+Chr(10)+"Commodore"+Chr(10)+"BeBox"+Chr(10)+"Macintosh"+Chr(10)+"Spectrum"
  EndProcedure
  
  Procedure.s ToDisplayInListB(String.s)
    ProcedureReturn "Alain"+Chr(10)+"Bernard"+Chr(10)+"Charly"+Chr(10)+"David"+Chr(10)+"Eric"+Chr(10)+"Filipe"+Chr(10)+"Horace"+Chr(10)+"Jérome"+Chr(10)+"Kevin"+Chr(10)+"Laurent"+Chr(10)+"Marc"
  EndProcedure
  
  If OpenWindow(0, 200, 200, 800, 100, "AutoComplete Teste", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    
    StringGadget(1, 10, 10, 380, 26, "")
    StringGadget(2, 410, 10, 380, 26, "")
    
    AutoComplete::AddAutocompleteWindow(0,1,@ToDisplayInList()) ;Just init the gadget who must support AutoComplete list
    AutoComplete::AddAutocompleteWindow(0,2,@ToDisplayInListB())
    Repeat
      Define Event.i = WaitWindowEvent()
      
      
      AutoComplete::CheckEvent(Event)
      
      Select Event
        Case #PB_Event_Gadget
      EndSelect
    Until Event=#PB_Event_CloseWindow
  EndIf
  End
CompilerEndIf

Re: GadgetString's Autocomplete list

Posted: Wed Jan 16, 2019 5:12 pm
by thyphoon
srod wrote:Yep - that's better.

I have made a simple modification so that when you drag the window, the auto complete box moves at the same time. Just a simple BindEvent(). You should use a suitable UnbindEvent() at some point.

At the moment this code is not easily used in a different program because of the way you have effectively hard coded parent window info into the module. You could easily change this, however, and create a library which had no dependency whatsoever on the parent window. GadgetX and GadgetY calculations would need to use the #PB_Gadget_ScreenCoordinate flag. Also, you would need to use a hidden window as the parent window of your 'pop up' to stop a taskbar entry appearing. Your host app would need to call a suitable library function everytime a #PB_Event_MoveWindow event was received. I've done this sort of thing a few times.

One thing I have spotted is that you are relying upon #PB_EventType_LostFocus events firing for ListView gadgets. This is undocumented for this gadget which is why you will need to test this on the other platforms.

Anyhow, I post your program with the move window alteration :
Big Thanks :D I will analyse all your tips and try to update my code.

Re: GadgetString's Autocomplete list

Posted: Wed Jan 16, 2019 5:17 pm
by srod
Don't get me wrong, that's a nice utility which could be very handy indeed. Very useful. Thanks. :)

Re: GadgetString's Autocomplete list

Posted: Wed Jan 16, 2019 9:13 pm
by thyphoon
srod wrote:Don't get me wrong, that's a nice utility which could be very handy indeed. Very useful. Thanks. :)
Big Thanks to you for your Help :D I love Purebasic and I love to share my work if can help somebody
srod wrote: At the moment this code is not easily used in a different program because of the way you have effectively hard coded parent window info into the module. You could easily change this, however, and create a library which had no dependency whatsoever on the parent window.
I can't remove all parent window dependency. I must use AddKeyboardShortcut() who ask the parend window. And i don't found how to found Window with only Gadget Id.
srod wrote: One thing I have spotted is that you are relying upon #PB_EventType_LostFocus events firing for ListView gadgets. This is undocumented for this gadget which is why you will need to test this on the other platforms.
I don't understant , In documentation EventType() speak about #PB_EventType_LostFocus and is compatible with StringGadget()

Last Version

Code: Select all

; ******************************************************************** 
; Program:           AutoComplete List
; Description:       Add an AutoComplete list to a String gadget 

; Author:            Thyphoon
; Date:              January, 2019
; License:           Free, unrestricted, credit 
;                    appreciated but not required.
; Note:              Please share improvement !
; Thanks to:         Srod
; ******************************************************************** 

EnableExplicit

DeclareModule AutoComplete
  Declare AddAutocompleteWindow(Window.i,Gadget.i,*CallBack,FirstEventKey.l=5000)
  Declare CheckEvent(Event.i)
EndDeclareModule  

Module AutoComplete
  Declare MoveWindow()

  Prototype.s CallBack(String.s)
  
  Structure ac
    Window.i                  ;Original Window
    Gadget.i                  ;Original Gadget
    FirstEventKey.l           ;First Eventkey=Up +1=Down +2=Enter +3=Escape
    CallBack.CallBack         ;CallBack who return choice
  EndStructure
  
  Structure params
    acWindow.i                ;Autocomplete window
    acGadget.i                ;AutoComplete GadgetList
    CurrentName.s             ;CurrentName at last open autocomplete windows
    Map AutoCompleteGadget.ac()
    FirstEventKey.i
  EndStructure
  
  Global params.params
  
  
  Procedure AddAutocompleteWindow(Window.i,Gadget.i,*CallBack,FirstEventKey.l=5000)
    Name.s=Str(Window)+"-"+Str(Gadget)
    params\AutoCompleteGadget(Name)\CallBack=*CallBack
    params\AutoCompleteGadget(Name)\Gadget=Gadget
    params\AutoCompleteGadget(Name)\Window=Window
    params\AutoCompleteGadget(Name)\FirstEventKey=FirstEventKey
    AddKeyboardShortcut(Window, #PB_Shortcut_Up, FirstEventKey)
    AddKeyboardShortcut(Window, #PB_Shortcut_Down, FirstEventKey+1)
    AddKeyboardShortcut(Window, #PB_Shortcut_Return, FirstEventKey+2)
    AddKeyboardShortcut(Window, #PB_Shortcut_Escape, FirstEventKey+3)
  EndProcedure
  
  Procedure RefreshAutocompleteWindow(Name.s)
    Gadget.i=params\AutoCompleteGadget(Name)\Gadget
    X.l=GadgetX(Gadget,#PB_Gadget_ScreenCoordinate)
    Y.l=GadgetY(Gadget,#PB_Gadget_ScreenCoordinate)+GadgetHeight(Gadget)
    W.l=GadgetWidth(Gadget)
    H.l=150
    ResizeWindow(params\acWindow,X,Y,W,H)
    ResizeGadget(params\acGadget,0,0,W,H)
    HideWindow(params\acWindow, #False,#PB_Window_NoActivate)
    StickyWindow(params\acWindow,#True)
  EndProcedure
  
  Procedure UpdateAutocompleteList(Name.s)
    ClearGadgetItems(params\acGadget)
    If params\AutoCompleteGadget(Name)\CallBack<>0
      BackString.s=params\AutoCompleteGadget(Name)\CallBack(GetGadgetText(params\AutoCompleteGadget(Name)\Gadget))
      For n=1 To CountString(BackString,Chr(10))+1
        line.s=StringField(BackString,n,Chr(10))
        If LCase(Left(line,Len(GetGadgetText(params\AutoCompleteGadget(Name)\Gadget))))=LCase(GetGadgetText(params\AutoCompleteGadget(Name)\Gadget))
          AddGadgetItem(params\acGadget,-1,line)
        EndIf 
      Next
      ;Hide Autocomplete Window if no Choice
      If CountGadgetItems(params\acGadget)=0
        HideWindow(params\acWindow,#True)
      Else
        HideWindow(params\acWindow,#False,#PB_Window_NoActivate)
      EndIf 
    Else
      Debug "Error with callback"
    EndIf
  EndProcedure
  
  Procedure OpenAutocompleteWindow(Name.s)
    If params\acGadget>0 And IsGadget(params\acGadget):FreeGadget(params\acGadget):EndIf
    If params\acWindow>0 And IsWindow(params\acWindow):CloseWindow(params\acWindow):EndIf
    params\acWindow=OpenWindow(#PB_Any,0,0,50,50,"AutoComplete",#PB_Window_BorderLess|#PB_Window_NoActivate,WindowID(params\AutoCompleteGadget(Name)\Window));
    If params\acWindow
      params\CurrentName=Name
      SetWindowData(params\acWindow,params\AutoCompleteGadget(Name)\Gadget)
      params\acGadget=ListViewGadget(#PB_Any, 0, 0, 50,50)
    EndIf
    RefreshAutocompleteWindow(Name)
    UpdateAutocompleteList(Name)
    BindEvent(#PB_Event_MoveWindow, @MoveWindow(), params\AutoCompleteGadget(Name)\Window)
  EndProcedure
  
  Procedure CloseAutocompleteWindow(Name.s)
      If params\acGadget>0 And IsGadget(params\acGadget):FreeGadget(params\acGadget):EndIf
      If params\acWindow>0 And IsWindow(params\acWindow):CloseWindow(params\acWindow):EndIf
      UnbindEvent(#PB_Event_MoveWindow, @MoveWindow(), Windows)
  EndProcedure
  
  Procedure MoveWindow()
    If EventWindow()=params\AutoCompleteGadget()\Window And params\acWindow>0 And IsWindow(params\acWindow)
      RefreshAutocompleteWindow(params\CurrentName)
    EndIf
  EndProcedure
  

  Procedure CheckEvent(Event.i)
    Static bbclick.b
    ;-Orignal Gadget Event
    Protected Name.s=Str(EventWindow())+"-"+Str(EventGadget())   
    If FindMapElement(params\AutoCompleteGadget(),Name)
      Select Event
        Case  #PB_Event_Gadget
          Select EventGadget()
              ; Main gadget Event  
            Case params\AutoCompleteGadget()\Gadget
              Select EventType()
                Case #PB_EventType_Focus
                  If params\acWindow=0 Or IsWindow(params\acWindow)=#False
                    If bbclick=#False
                      OpenAutocompleteWindow(Name)
                    Else
                      bbclick=#True
                    EndIf 
                  EndIf 
                Case #PB_EventType_LostFocus
                  If GetActiveGadget()<>params\acGadget
                    CloseAutocompleteWindow(Name)
                    bbclick=#False
                  EndIf  
                Case #PB_EventType_Change
                  If bbclick=#False 
                    If IsWindow(params\acWindow)
                      UpdateAutocompleteList(Name)
                    Else
                      Debug "pas de windows"
                    EndIf
                  Else
                    OpenAutocompleteWindow(Name)
                    bbclick=#False
                  EndIf 
              EndSelect 

          EndSelect 
     EndSelect
    EndIf
    
    Select Event
      ;if Keyword  
      Case #PB_Event_Menu
        Select EventMenu()
          Case params\AutoCompleteGadget(params\CurrentName)\FirstEventKey;UP
            SetGadgetState(params\acGadget,GetGadgetState(params\acGadget)-1)
          Case params\AutoCompleteGadget(params\CurrentName)\FirstEventKey+1;Down
            SetGadgetState(params\acGadget,GetGadgetState(params\acGadget)+1)
          Case params\AutoCompleteGadget(params\CurrentName)\FirstEventKey+2;Enter
            SetGadgetText(params\AutoCompleteGadget()\Gadget,GetGadgetItemText(params\acGadget,GetGadgetState(params\acGadget)))
          Case params\AutoCompleteGadget(params\CurrentName)\FirstEventKey+3;
            CloseAutocompleteWindow(params\CurrentName)
        EndSelect
      Case  #PB_Event_Gadget
        If EventGadget()=params\acGadget
          If EventType()=#PB_EventType_LeftDoubleClick
            SetGadgetText(params\AutoCompleteGadget(params\CurrentName)\Gadget,GetGadgetItemText(params\acGadget,GetGadgetState(params\acGadget)))
            CloseAutocompleteWindow(params\CurrentName)
            bbclick=#True
          EndIf
        EndIf 
    EndSelect  
    
    
    
  EndProcedure  
  
  
EndModule

;- MAIN TEST

CompilerIf #PB_Compiler_IsMainFile 
  
  ; Callback to return keyword list. you must use a database en filter if you want ^_^ 
  Procedure.s ToDisplayInList(String.s)
    ProcedureReturn "Amiga"+Chr(10)+"Amstrad"+Chr(10)+"Atari"+Chr(10)+"Commodore"+Chr(10)+"BeBox"+Chr(10)+"Macintosh"+Chr(10)+"Spectrum"
  EndProcedure
  
  Procedure.s ToDisplayInListB(String.s)
    ProcedureReturn "Alain"+Chr(10)+"Bernard"+Chr(10)+"Charly"+Chr(10)+"David"+Chr(10)+"Eric"+Chr(10)+"Filipe"+Chr(10)+"Horace"+Chr(10)+"Jérome"+Chr(10)+"Kevin"+Chr(10)+"Laurent"+Chr(10)+"Marc"
  EndProcedure
  
  If OpenWindow(0, 200, 200, 800, 100, "AutoComplete Teste", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    
    StringGadget(1, 10, 10, 380, 26, "")
    StringGadget(2, 410, 10, 380, 26, "")
    
    AutoComplete::AddAutocompleteWindow(0,1,@ToDisplayInList()) ;Just init the gadget who must support AutoComplete list
    AutoComplete::AddAutocompleteWindow(0,2,@ToDisplayInListB())
    Repeat
      Define Event.i = WaitWindowEvent()
      
      
      AutoComplete::CheckEvent(Event)
      
      Select Event
        Case #PB_Event_Gadget
      EndSelect
    Until Event=#PB_Event_CloseWindow
  EndIf
  End
CompilerEndIf
And excuse me for my bad english :P