Page 1 of 1

Masked edit/string gadget, all OS

Posted: Tue Feb 28, 2017 5:10 pm
by Keya
I needed to be able to accept floating point numbers in a StringGadget eg "3.123", but the #PB_String_Numeric OS flag doesn't allow the "." character. Additionally I needed to ensure only one "." is used - "3...123" is invalid.

So I came up with this simple masked edit that works for each OS; it simply reacts to #PB_EventType_Change, reads the caret position, strips any unwanted characters out, if any, and resets the caret position. Resetting the caret position after stripping the illegal character gives the illusion of a typical masked edit. Not quite as slick as filtering KeyDown events, but still reasonably effective and works on all OS without any special tricks or hooks etc.
Special thanks to Shardik for supporting code.

StringGadgetFilter.pbi

Code: Select all

;##### CARET FUNCTIONS ##########################################################################
;// Special thanks to Shardik for his excellent caret/selection-related code which is the basis for this:
;// http://www.purebasic.fr/english/viewtopic.php?f=13&t=51950&start=3

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 ;'sele'   
    Structure ControlEditTextSelectionRec
      SelStart.W
      SelEnd.W
    EndStructure
  CompilerElse
    Global NSRangeZero.NSRange
    Procedure.i TextEditor(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


Procedure SetStringGadgetSelection(GadgetID.i, Start.i, Length.i=0)
  SetActiveGadget(gadgetid)
  CompilerSelect #PB_Compiler_OS
    CompilerCase #PB_OS_Windows
      SendMessage_(GadgetID(GadgetID), #EM_SETSEL, Start-1, Start-1)
    CompilerCase #PB_OS_Linux
      gtk_editable_select_region_(GadgetID(gadgetid), Start-1, Start-1)
    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 
        SetControlData(GadgetID(GadgetID), #kControlEditTextPart, #kControlEditTextSelectionTag, SizeOf(ControlEditTextSelectionRec), @TextSelection)
      CompilerElse
        Protected Range.NSRange\location = Start - 1 : Range\length = 0
        CocoaMessage(0, TextEditor(GadgetID), "setSelectedRange:@", @Range)
      CompilerEndIf
  CompilerEndSelect
EndProcedure


Procedure.i GetCaretPosition(GadgetID.i)
  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 Stop + 1
    CompilerCase #PB_OS_Linux
      Stop = gtk_editable_get_position_(GadgetID(gadgetid))
      ProcedureReturn Stop + 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\End + 1
      CompilerElse
        Protected Range.NSRange
        CocoaMessage(@Range, TextEditor(GadgetID), "selectedRange")        
        ProcedureReturn Range\location + Range\length + 1
      CompilerEndIf
  CompilerEndSelect
EndProcedure


;##### STRING FILTER ###############################################################################

;RestrictChars - Takes an input string like "123ab45" and returns "12345"
Procedure RestrictChars(*pchr.Ascii, slen)
  If slen
  *pout.Ascii = *pchr
  For i = 1 To slen
    Select *pchr\a
      Case '0' To '9'  ;Allow 0-9
        *pchr+SizeOf(Character): *pout+SizeOf(Character)
      Case '.':        ;Allow only one instance of a '.' period character
        dotcnt+1
        If dotcnt=1
          *pchr+SizeOf(Character): *pout+SizeOf(Character)
        Else
          Goto BadChar
        EndIf
      Default:         ;Block all other characters
        BadChar:
        *pchr+SizeOf(Character): changed=1
    EndSelect 
    *pout\a = *pchr\a
  Next
  EndIf
  ProcedureReturn changed
EndProcedure


;StringFilter - called when #PB_EventType_Change triggers in the StringGadget
Procedure StringFilter(gadget)
  Static Changing
  If Not Changing
    Changing=1
    sTxt$ = GetGadgetText(gadget)
    lastcaretpos = GetCaretPosition(gadget)
    If RestrictChars(@sTxt$, Len(sTxt$))
      SetGadgetText(gadget, sTxt$)
      SetStringGadgetSelection(gadget, lastcaretpos-1)  ;must be called from the Dialog loop (which this is)
    EndIf
    Changing=0
  EndIf
EndProcedure
Example.pb

Code: Select all

;### EXAMPLE
IncludeFile("StringGadgetFilter.pbi")

#Dlg1=0
#String1=1

Procedure Dlg1_Events(event)
  Select event
      
    ;#####################
    Case #PB_Event_Gadget
      If EventGadget() = #String1 And EventType() = #PB_EventType_Change
        StringFilter(#String1)
      EndIf
    ;#####################
 
  EndSelect
  ProcedureReturn #True
EndProcedure


Procedure OpenDlg1(x = 0, y = 0, width = 308, height = 148)
  OpenWindow(#Dlg1, x, y, width, height, "Restricted Characters", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_ScreenCentered)
  StringGadget(#String1, 52, 60, 200, 24, "")
  TextGadget(69, 30,30, 200,30, "Allowed chars: 0-9 and one '.'")
EndProcedure

OpenDlg1()

Repeat
  Define event.i = WaitWindowEvent()
  Dlg1_Events(event)
Until event = #PB_Event_CloseWindow

Re: Masked edit/string gadget, all OS

Posted: Tue Feb 28, 2017 5:15 pm
by Kwai chang caine
Thanks for sharing 8)

Re: Masked edit/string gadget, all OS

Posted: Tue Feb 28, 2017 7:52 pm
by davido
@Keya,
Some nice code. Particularly as you've taken the trouble to make it cross-platform.
Thank you for sharing. :D