Page 1 of 1

Cross plattform multiple email address autocomplete

Posted: Mon Nov 12, 2012 7:55 am
by Kukulkan
Hi,

I just want to share my multiple email address autocomplete code. To enter, simply type the address. To finish some input (dividing addresses) type comma (,), semicolon (;) or a blank ( ).

It works by using a ordinary StringGadget and simply adding the following to your event loop (adapt to your needs):

Code: Select all

If EventGadget() = MyStringGadgetID.i And EventType() = #PB_EventType_Change
  AutoCompleteStringGadget(MyStringGadgetID.i, MailAddressesLinkedList())
EndIf
Complete code and example implementation:

Code: Select all

EnableExplicit

CompilerIf #PB_Compiler_OS = #PB_OS_MacOS
  CompilerIf #PB_Compiler_Version < 470 Or (#PB_Compiler_Version >= 500 And Subsystem("Carbon"))
    ImportC ""
      GetControlData(ControlRef.L, ControlPartCode.L, TagName.L, BufferSize.L, *Buffer, *ActualSize)
      SetControlData(ControlRef.L, ControlPartCode.L, TagName.L, BufferSize.L, *Buffer)
    EndImport
    
    #kControlEditTextPart = 5
    #kControlEditTextSelectionTag = $73656C65
    
    Structure ControlEditTextSelectionRec
      SelStart.W
      SelEnd.W
    EndStructure
  CompilerElse
    Global NSRangeZero.NSRange
    Procedure.i acTextEditor(Gadget.i)
      Protected TextField.i = GadgetID(Gadget)
      Protected Window.i = CocoaMessage(0, TextField, "window")
      ProcedureReturn CocoaMessage(0, Window, "fieldEditor:", #YES, "forObject:", TextField)
    EndProcedure
  CompilerEndIf
CompilerEndIf

; Sets the selection in a StringGadget() independent of Operating System
; If Start.i < 1, every selection is removed.
; If Length.i = 0, only the cursor is set.
Procedure SetStringGadgetSelection(GadgetID.i, Start.i, Length.i)
  If Start.i > 0 And Length.i > -1
    ; Set selection
    CompilerSelect #PB_Compiler_OS
      CompilerCase #PB_OS_Windows
        SendMessage_(GadgetID(GadgetID), #EM_SETSEL, Start.i - 1, Start.i + Length.i - 1)
      CompilerCase #PB_OS_Linux
        gtk_editable_select_region_(GadgetID(GadgetID), Start - 1, Start -1 + Length)
      CompilerCase #PB_OS_MacOS
        CompilerIf #PB_Compiler_Version < 470 Or (#PB_Compiler_Version >= 500 And Subsystem("Carbon"))
          Protected TextSelection.ControlEditTextSelectionRec
          TextSelection\selStart = Start - 1
          TextSelection\selEnd = Start -1 + Length
          SetControlData(GadgetID(GadgetID), #kControlEditTextPart, #kControlEditTextSelectionTag, SizeOf(ControlEditTextSelectionRec), @TextSelection)
        CompilerElse
          Protected Range.NSRange\location = Start - 1 : Range\length = Length
          CocoaMessage(0, acTextEditor(GadgetID), "setSelectedRange:@", @Range)
        CompilerEndIf
    CompilerEndSelect
  Else
    ; Deselect all
    CompilerSelect #PB_Compiler_OS
      CompilerCase #PB_OS_Windows
        SendMessage_(GadgetID(GadgetID.i), #EM_SETSEL, -1, 0)
      CompilerCase #PB_OS_Linux
        gtk_editable_delete_selection_(GadgetID(GadgetID))
      CompilerCase #PB_OS_MacOS
        CompilerIf #PB_Compiler_Version < 470 Or (#PB_Compiler_Version >= 500 And Subsystem("Carbon"))
          TextSelection\selStart = -1
          TextSelection\selEnd = -1
          SetControlData(GadgetID(GadgetID), #kControlEditTextPart, #kControlEditTextSelectionTag, SizeOf(ControlEditTextSelectionRec), @TextSelection)
        CompilerElse
          CocoaMessage(0, acTextEditor(GadgetID), "setSelectedRange:@", @NSRangeZero)
        CompilerEndIf
    CompilerEndSelect
  EndIf
EndProcedure

Procedure.i GetStringGadgetSelectionStart(GadgetID.i)
  ; Get selection
  Protected Start.i = 0
  Protected Stop.i = 0
  CompilerSelect #PB_Compiler_OS
    CompilerCase #PB_OS_Windows
      SendMessage_(GadgetID(GadgetID.i), #EM_GETSEL, @Start.i, @Stop.i)
      ProcedureReturn Start.i + 1
    CompilerCase #PB_OS_Linux
      gtk_editable_get_selection_bounds_(GadgetID(GadgetID), @Start, @Stop)
      ProcedureReturn Start + 1
    CompilerCase #PB_OS_MacOS
      CompilerIf #PB_Compiler_Version < 470 Or (#PB_Compiler_Version >= 500 And Subsystem("Carbon"))
        Protected TextSelection.ControlEditTextSelectionRec
        GetControlData(GadgetID(GadgetID), #kControlEditTextPart, #kControlEditTextSelectionTag, SizeOf(ControlEditTextSelectionRec), @TextSelection.ControlEditTextSelectionRec, 0)
        ProcedureReturn TextSelection\SelStart + 1
      CompilerElse
        Protected Range.NSRange
        CocoaMessage(@Range, acTextEditor(GadgetID), "selectedRange")
        ProcedureReturn Range\location + 1
      CompilerEndIf
  CompilerEndSelect
EndProcedure

Procedure.i GetStringGadgetSelectionLength(GadgetID.i)
  ; Get selection
  Protected Start.i = 0
  Protected Stop.i = 0
  CompilerSelect #PB_Compiler_OS
    CompilerCase #PB_OS_Windows
      SendMessage_(GadgetID(GadgetID), #EM_GETSEL, @Start.i, @Stop.i)
      ProcedureReturn Stop.i - Start.i
    CompilerCase #PB_OS_Linux
      gtk_editable_get_selection_bounds_(GadgetID(GadgetID), @Start, @Stop)
      ProcedureReturn Stop - Start
    CompilerCase #PB_OS_MacOS
      CompilerIf #PB_Compiler_Version < 470 Or (#PB_Compiler_Version >= 500 And Subsystem("Carbon"))
        Protected TextSelection.ControlEditTextSelectionRec
        GetControlData(GadgetID(GadgetID), #kControlEditTextPart, #kControlEditTextSelectionTag, SizeOf(ControlEditTextSelectionRec), @TextSelection.ControlEditTextSelectionRec, 0)
        ProcedureReturn TextSelection\SelEnd - TextSelection\SelStart
      CompilerElse
        Protected Range.NSRange
        CocoaMessage(@Range, acTextEditor(GadgetID), "selectedRange")
        ProcedureReturn Range\length
      CompilerEndIf
  CompilerEndSelect
EndProcedure

Procedure.i AutoCompleteFindStringRev(String$, StringToFind$, StringToFind2$ = "", StringToFind3$ = "")
  Protected x.i = 0
  String$ = UCase(String$)
  StringToFind$ = UCase(StringToFind$)
  StringToFind2$ = UCase(StringToFind2$)
  StringToFind3$ = UCase(StringToFind3$)
  For x.i = Len(String$) To 1 Step -1
    If Mid(String$, x.i, Len(StringToFind$)) = StringToFind$
      ProcedureReturn x.i
    EndIf
    If Mid(String$, x.i, Len(StringToFind2$)) = StringToFind2$
      ProcedureReturn x.i
    EndIf
    If Mid(String$, x.i, Len(StringToFind3$)) = StringToFind3$
      ProcedureReturn x.i
    EndIf
  Next
  ProcedureReturn 0
EndProcedure

Procedure AutoCompleteStringGadget(strGadgID.i, List Options.s())
  Static NewMap LastLength.i()
  Static NewMap LastAddress.s()
  Protected StandardDivider.s = ","
  Protected TempText.s        = ""
  Protected RightChar.s       = ""
  Protected Offset.i          = 1
  Protected Left.i            = 0
  Protected Part.s            = ""
  Protected Content.s         = GetGadgetText(strGadgID.i)
  
  Left.i = AutoCompleteFindStringRev(Content.s, ",", ";", " ")
  If Left.i > 0: Offset.i = Left.i + 1: EndIf
  Part.s = UCase(Trim(Mid(Content.s, Offset.i)))
  
  If Len(Content.s) > LastLength(Str(strGadgID.i))
    RightChar.s = Right(Content.s, 1)
    If LastAddress(Str(strGadgID.i)) <> "" And (RightChar.s = "," Or RightChar.s = ";" Or RightChar.s = " ")
      Left.i = AutoCompleteFindStringRev(Left(Content.s, Left.i - 1), ",", ";", " ")
      Offset.i  = 1
      If Left.i > 0: Offset.i = Left.i + 1: EndIf
      TempText.s = Left(Content.s, Offset.i - 1) + Options() + StandardDivider.s
      SetGadgetText(strGadgID.i, TempText.s)
      SetStringGadgetSelection(strGadgID.i, Len(TempText.s) + 1, 0)
      LastAddress(Str(strGadgID.i)) = ""
    EndIf
    If Len(Part.s) > 0
      LastAddress(Str(strGadgID.i)) = ""
      ForEach Options()
        If Left(UCase(Options()), Len(Part.s)) = Part.s
          TempText.s = Left(Content.s, Offset.i - 1) + Options()
          SetGadgetText(strGadgID.i, TempText.s)
          SetStringGadgetSelection(strGadgID.i, Offset.i + Len(Part.s), 500)
          LastAddress(Str(strGadgID.i)) = Options()
          Break
        EndIf
      Next
    EndIf
  EndIf
  LastLength(Str(strGadgID.i)) = Len(Content.s)  
EndProcedure

; EXAMPLE USAGE:
; ================================================================================

NewList MailAddresses.s()
AddElement(MailAddresses())
MailAddresses() = "apple@company.com"
AddElement(MailAddresses())
MailAddresses() = "apple2@company.com"
AddElement(MailAddresses())
MailAddresses() = "berry@company.com"
AddElement(MailAddresses())
MailAddresses() = "peach@company.com"
AddElement(MailAddresses())
MailAddresses() = "banana@company.com"

If OpenWindow(0, 200, 300, 395, 160, "PureBasic Window", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
 
  Define strGadgID.i = StringGadget(#PB_Any, 10, 10, 380, 26, "")
  Define btnGadgID.i = ButtonGadget(#PB_Any, 10, 40, 380, 26, "Button")
 
  SetActiveGadget(strGadgID.i)
  
  Repeat
    Define Event.i = WaitWindowEvent()
    
    If Event.i = #PB_Event_CloseWindow
      Define Quit.i = 1
    EndIf
    If Event.i = #PB_Event_Gadget
      Define et.i = EventType()
      Define gd.i = EventGadget()
      
      If gd.i = strGadgID.i And et.i = #PB_EventType_Change
        AutoCompleteStringGadget(gd.i, MailAddresses())
      EndIf
    EndIf
  Until Quit.i = 1
EndIf
End
It works on Windows, MacOS X and Linux. I tested on Windows 7, MacOS 10.7 and Ubuntu 11.04.

Any enhancements welcome :-)

Kukulkan

Re: Cross plattform multiple email address autocomplete

Posted: Mon Nov 12, 2012 3:30 pm
by jassing
Nicely done....Thanks for sharing.