Page 1 of 1

StringGadget: also use "-" in numeric only

Posted: Fri Sep 12, 2014 10:43 pm
by marcoagpinto
Hello!

Freddy, could you enhance the stringgadgets in order that it also accepts negative values in numeric mode?

For example, when I press "-" it gives a warning saying it only accepts numbers.

Thanks!

Kind regards,
>Marco A.G.Pinto
----------------

Re: StringGadget: also use "-" in numeric only

Posted: Fri Sep 12, 2014 11:33 pm
by glomph
+1

Re: StringGadget: also use "-" in numeric only

Posted: Sat Sep 13, 2014 1:58 am
by Derren
Or how about natively supporting a callback, so you can decide valid inputs yourself. Perhaps with a mask like Date() (e.g. "00-aaa" would allow 2 numbers followed by a minus/dash then 3 letters. 00[-]aaa would make the dash optional and "0(2)-a(3)" would accept 4-x and 51-abc but would not allow you to enter more than 2 digits)

Re: StringGadget: also use "-" in numeric only

Posted: Sat Sep 13, 2014 2:36 am
by IdeasVacuum
.... and let's support the decimal point too for floats/doubles

Re: StringGadget: also use "-" in numeric only

Posted: Sat Sep 13, 2014 3:02 am
by Derren
IdeasVacuum wrote:.... and let's support the decimal point too for floats/doubles
And the decimal comma, for the non-english speaking world :wink:
How about a colon for times like 3:15^^

Re: StringGadget: also use "-" in numeric only

Posted: Sat Sep 13, 2014 3:10 am
by skywalk
Using BindGadgetEvent() and some old code. Numbers only, but you can modify for dates and commas if needed.

Code: Select all

EnableExplicit
Procedure.i SF_IsNumeric(InString.s, DecimalCharacter.c='.', ThousandsSeparator.c=',')
  ; REV:  100405, skywalk
  ;       modified PureBasic Forum: Xombie(20061129) + Demivec(20100323)
  ;       removed 3 constants, add Lcase, add 'd' exponent support
  ; REV:  110311
  ;       enabled leading '.' To Define a numeric: -.1 = -0.1
  ; Return: 0 = non-numeric values = 0
  ;         1 = positive integers
  ;        -1 = negative integers
  ;         2 = positive floats 
  ;        -2 = negative floats
  InString = LCase(Trim(InString))  ; eliminate comparisons for E & D
  Protected.i IsDecimal, CaughtThousand, CaughtDecimal, CaughtE
  Protected.i Sgn = 1, IsSignPresent, IsSignAllowed = 1, CountNumeric
  Protected.i *CurrentChar.Character = @InString
  While *CurrentChar\c
    Select *CurrentChar\c
    Case '0' To '9'
      CountNumeric + 1
      If CaughtThousand And CountNumeric > 3
        ProcedureReturn 0
      EndIf
      IsSignAllowed = 0
    Case ThousandsSeparator
      If CaughtDecimal Or CaughtE Or CountNumeric = 0 Or (CaughtThousand And CountNumeric <> 3)
        ProcedureReturn 0
      EndIf
      CaughtThousand = 1
      CountNumeric = 0
    Case DecimalCharacter
      ;If CaughtDecimal Or CaughtE Or CountNumeric = 0 Or (CaughtThousand And CountNumeric <> 3)
      If CaughtDecimal Or CaughtE Or (CaughtThousand And CountNumeric <> 3) ; Allow leading decimal to signify a numeric
        ProcedureReturn 0
      EndIf
      CountNumeric = 0
      CaughtDecimal = 1
      IsDecimal = 1
      CaughtThousand = 0
    Case '-'
      If IsSignPresent Or Not IsSignAllowed
        ProcedureReturn 0
      EndIf 
      If Not CaughtE: Sgn = -1: EndIf
      IsSignPresent = 1
    Case '+'
      If IsSignPresent Or Not IsSignAllowed
        ProcedureReturn 0
      EndIf 
      IsSignPresent = 1
    Case 'e', 'd' ; Lcase = Don't Care -> 'E', 'D'
      If CaughtE Or CountNumeric = 0 Or (CaughtThousand And CountNumeric <> 3)
        ProcedureReturn 0
      EndIf
      CaughtE = 1
      CountNumeric = 0
      CaughtDecimal = 0
      CaughtThousand = 0
      IsSignPresent = 0
      IsSignAllowed = 1
    Default
      ProcedureReturn 0
    EndSelect
    *CurrentChar + SizeOf(Character)
  Wend
  If CountNumeric = 0 Or (CaughtThousand And CountNumeric <> 3)
    ProcedureReturn 0
  EndIf
  If IsDecimal
    ProcedureReturn 2 * Sgn ; -> Float
  EndIf
  ProcedureReturn Sgn       ; -> Integer
EndProcedure

Procedure gad_ForceNumeric_CB()
  Protected.i gN = EventGadget()
  If EventType() = #PB_EventType_Change
    ; Verify numeric entries...
    If SF_IsNumeric(GetGadgetText(gN))
      SetGadgetColor(gN, #PB_Gadget_BackColor, #Green)
    Else
      SetGadgetColor(gN, #PB_Gadget_BackColor, #Red)
    EndIf
  EndIf
EndProcedure

If OpenWindow(0, 434, 225, 193, 174, "Verify Numeric Entry",  #PB_Window_SystemMenu)
  TextGadget(0, 10, 10, 150, 20, "Enter Numeric Text Below", #PB_Text_Center)
  StringGadget(1, 10, 45, 150, 20, "-1.234e-12", #PB_Text_Center) ;#ES_CENTER)
  BindGadgetEvent(1, @gad_ForceNumeric_CB())
  SetGadgetText(1, "-1.234e-12")
  PostEvent(#PB_Event_Gadget, 0, 1, #PB_EventType_Change)
  SendMessage_(GadgetID(1), #EM_SETLIMITTEXT, 16, 0) ; Force character limit = 16
EndIf

Repeat
  Select WaitWindowEvent()
  Case #PB_Event_Gadget
    ;UNCOMMENT if not using BindGadgetEvent().
    ;Select EventGadget()
    ;Case 1
    ;  If EventType() = #PB_EventType_Change
    ;    gad_ForceNumeric_CB()
    ;  EndIf
    ;EndSelect
  Case #PB_Event_CloseWindow
    Break
  EndSelect
ForEver

Re: StringGadget: also use "-" in numeric only

Posted: Sat Sep 13, 2014 3:53 am
by Derren
Ah, somebody already posted something.

I just hacked this into my keyboard. Not very pretty, using string functions instead of memory, but it's flexible.
I don't think it's possible to prevent an input from being displayed in the gadget without API, so this code just turns the gadget red when there's an invalid character detected (not very sophisticated detection technique, though, I'm so bad it this...)

Code: Select all

Structure StringGadgetProperties
	exists.i
	List chars.s()
EndStructure 
NewMap gadgets.StringGadgetProperties()

Procedure AllowCharacters(gadget.i, characters.s)
	Shared gadgets()
	
	gadgets(Str(gadget))\exists = #True
	
	Protected i.i
	Protected length.i = Len(characters)
	For i=1 To length
		AddElement(gadgets(Str(gadget))\chars())
		gadgets(Str(gadget))\chars() = Mid(characters, i, 1)
	Next 	
EndProcedure 

Procedure CheckStringGadgetForInvalidCharacters(EventGadget.i)
	Shared gadgets()
	
	If gadgets(Str(EventGadget))\exists <> #True
		ProcedureReturn #False
	EndIf 
	
	Protected text.s = GetGadgetText(EventGadget)
	Protected i.i
	Protected length.i = Len(text)
	Protected char.s
	Protected result.i = 0	
	
	
	
	For i=1 To length
		char = Mid(text, i, 1)
		result=0
		ForEach gadgets(Str(EventGadget))\chars()
			
			If gadgets(Str(EventGadget))\chars() = char				
				result = 1				
			EndIf 			
		Next 
		
		
		If result=0
			ProcedureReturn #True
		EndIf 
	Next  
EndProcedure 

#CharacterSet_Alpha$ = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
#CharacterSet_Num$ = "0123456789"
#CharacterSet_AlphaNum$ = #CharacterSet_Alpha$ + #CharacterSet_Num$


; Shows possible flags of StringGadget in action...
If OpenWindow(0, 0, 0, 322, 205, "StringGadget Flags", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
	StringGadget(0, 108, 10, 206, 20, "") ;Regular Stringgadge
	StringGadget(1, 108, 35, 206, 20, "") : AllowCharacters(1, "abc")
	StringGadget(2, 108, 60, 206, 20, "") : AllowCharacters(2, #CharacterSet_Alpha$)
	StringGadget(3, 108, 85, 206, 20, "") : AllowCharacters(3, #CharacterSet_Num$)
	StringGadget(4, 108, 110, 206, 20, "") : AllowCharacters(4, #CharacterSet_AlphaNum$)
	StringGadget(5, 108, 140, 206, 20, "") : AllowCharacters(5, #CharacterSet_AlphaNum$+"-,.")
	
	TextGadget(6, 8, 13, 90, 14, "Regular (all chars)")
	TextGadget(7, 8, 35, 90, 14, "abc")
	TextGadget(8, 8, 60, 90, 14, "a-z/A-Z")
	TextGadget(9, 8, 85, 90, 14, "0-9")
	TextGadget(10, 8, 110, 90, 14, "a-z/A-Z/0-9")
	TextGadget(11, 8, 140, 90, 14, "AlphaNum+ '-' ',' '.'")
	Repeat
		event = WaitWindowEvent()
		If event = #PB_Event_Gadget
			If EventType()=#PB_EventType_Change
				
				
				If CheckStringGadgetForInvalidCharacters(EventGadget()) = #True
					SetGadgetColor(EventGadget(), #PB_Gadget_BackColor, $0000FF) ;Turn red
				Else
					SetGadgetColor(EventGadget(), #PB_Gadget_BackColor, -1) ;Default
				EndIf 
			EndIf 
		EndIf 

	Until event = #PB_Event_CloseWindow
EndIf

Re: StringGadget: also use "-" in numeric only

Posted: Sat Sep 13, 2014 6:26 am
by Danilo
Two examples for simple number/float (non-scientific notation) input:

Code: Select all

EnableExplicit

Procedure checkFloatInput(gadget)
    Protected start, count, pointcount, new$
    SendMessage_(GadgetID(gadget), #EM_GETSEL, @start, 0)
    Protected txt$ = GetGadgetText(gadget)
    Protected *p.Character = @txt$
   
    While *p\c ; <> 0
        If *p\c = '.'
            pointcount+1
            If pointcount < 2
                new$ + Chr(*p\c)
            Else
                If start>count : start-1 : EndIf
            EndIf
        ElseIf count = 0 And *p\c = '-'
          new$ + Chr('-')
        ElseIf *p\c >= '0' And *p\c <= '9'
            new$ + Chr(*p\c)
        Else
            start - 1
        EndIf
        *p + SizeOf(Character)
        count + 1
    Wend
    SetGadgetText(gadget, new$)
    SendMessage_(GadgetID(gadget), #EM_SETSEL, start, start)
EndProcedure

Define event
If OpenWindow(0, 0, 0, 322, 205, "StringGadget Flags", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    StringGadget(0, 8,  10, 306, 20, "")
    StringGadget(1, 8,  35, 306, 20, "1234567")
    Repeat
        event = WaitWindowEvent()
        If     event = #PB_Event_CloseWindow : End
        ElseIf event = #PB_Event_Gadget
            If EventGadget()=0 Or EventGadget()=1
                If EventType() = #PB_EventType_Change
                    checkFloatInput(EventGadget())
                EndIf
            EndIf
        EndIf
    ForEver
EndIf

; ts-soft: Habe Danilos Codebeispiel mal um Minus-Werte erweitert
The following was for german forum, so it uses comma ',' instead dot '.' => for example "567,88" (can be changed within the regular expression)

Code: Select all

;
; by Danilo
;
; http://www.purebasic.fr/german/viewtopic.php?f=16&t=26388&start=7
;
; Nummern-Eingabe Beispiele: 123  567,88  -12  -4,5
;
OpenWindow(0, #PB_Ignore, #PB_Ignore, 250, 80, "")
StringGadget(0, 10, 10, 200, 20, "")

CreateRegularExpression(0,"^\-{0,1}\d*$|^\-{0,1}\d+\,\d{0,2}$|^$") ; ^       = Anfang des Strings
                                                                   ; $       = Ende des Strings
                                                                   ; \d      = Dezimalzahl 0-9
                                                                   ; +       = 1 oder mehr Vorkommen
                                                                   ; *       = 0 oder mehr Vorkommen
                                                                   ; |       = alternative Moeglichkeit (Or in PureBasic)
                                                                   ; \,      = Komma
                                                                   ; \-{0,1} = Minus{0 oder 1 mal}
                                                                   ; \d{0,2} = Dezimalzahl 0-9 { 0 bis 2 mal }
Repeat
  Select WaitWindowEvent()
    Case #PB_Event_CloseWindow
      Break
    Case #PB_Event_Gadget
      If EventGadget() = 0 And EventType() = #PB_EventType_Change
        txt$ = GetGadgetText(0)
        If MatchRegularExpression(0,txt$)=0                           ; wenn kein Treffer, dann wieder vorherigen text setzen
          SendMessage_(GadgetID(0),#EM_GETSEL,0,@endpos) : endpos - 1 ; cursor position holen
          SetGadgetText(0,old$)                                       ; alten text wieder setzen
          SendMessage_(GadgetID(0),#EM_SETSEL,endpos,endpos)          ; cursor position wieder setzen
        Else
          old$ = txt$
        EndIf
      EndIf
  EndSelect
ForEver
Translated English version, uses dot '.' for float numbers:

Code: Select all

;
; by Danilo
;
; Original german version: http://www.purebasic.fr/german/viewtopic.php?f=16&t=26388&start=7
;
; Number-Input, English version
; Examples: 123  567.88  -12  -4.5
;
OpenWindow(0, #PB_Ignore, #PB_Ignore, 250, 80, "")
StringGadget(0, 10, 10, 200, 20, "")

CreateRegularExpression(0,"^\-{0,1}\d*$|^\-{0,1}\d+\.\d{0,2}$|^$") ; ^       = Begin of string
                                                                   ; $       = End of string
                                                                   ; \d      = Decimal number 0-9
                                                                   ; +       = 1 or more occurences
                                                                   ; *       = 0 or more occurences
                                                                   ; |       = alternative ('Or' in PureBasic)
                                                                   ; \.      = Dot
                                                                   ; \-{0,1} = Minus{0 or 1 times}
                                                                   ; \d{0,2} = Decimal number 0-9 { 0 to 2 times }
Repeat
  Select WaitWindowEvent()
    Case #PB_Event_CloseWindow
      Break
    Case #PB_Event_Gadget
      If EventGadget() = 0 And EventType() = #PB_EventType_Change
        txt$ = GetGadgetText(0)
        If MatchRegularExpression(0,txt$)=0                           ; If regular expression does not match: set old text
          SendMessage_(GadgetID(0),#EM_GETSEL,0,@endpos) : endpos - 1 ; get cursor position
          SetGadgetText(0,old$)                                       ; set old text
          SendMessage_(GadgetID(0),#EM_SETSEL,endpos,endpos)          ; set cursor position
        Else
          old$ = txt$
        EndIf
      EndIf
  EndSelect
ForEver
All examples use WinAPI messages #EM_GETSEL & #EM_SETSEL to save and restore the cursor position. Otherwise it would be platform-independent.
I already requested the possibility of handling the cursor and text selection numerous times, because of such example codes, but the team resists to add it.
Maybe it is too powerful, or just not useful for freak. :D

Re: StringGadget: also use "-" in numeric only

Posted: Sat Sep 13, 2014 8:12 am
by TI-994A
Great solutions guys. Here's a quick and dirty one:

Code: Select all

Procedure InputProc(hWnd, uMsg, wParam, lParam)
  Shared sysProc
  If uMsg = #WM_CHAR And (wParam < 48 Or wParam > 57) And 
     wParam ! 45 And wParam ! 46 And wParam ! 8 
    ProcedureReturn
  EndIf       
  ProcedureReturn CallWindowProc_(sysProc, hWnd, uMsg, wParam, lParam)
EndProcedure

wFlags = #PB_Window_SystemMenu | #PB_Window_ScreenCentered
OpenWindow(0, #PB_Any, #PB_Any, 350, 110, "Subclassed StringGadget", wFlags)
TextGadget(1, 25, 25, 300, 20, "Numeric StringGadget that accepts negatives & decimals:")
StringGadget(2, 25, 50, 270, 20, "")
sysProc = SetWindowLongPtr_(GadgetID(2), #GWL_WNDPROC, @InputProc())

While WaitWindowEvent() ! #PB_Event_CloseWindow : Wend
The string evaluation could be stepped up to avoid double minuses or decimals, and to ensure they're in the right places; but basically, the value could simply be extracted into a float variable and you're golden.

Re: StringGadget: also use "-" in numeric only

Posted: Sat Sep 13, 2014 9:29 am
by PB
Probably be easier just to use a normal StringGadget and then
use any good IsNumeric() procedure to its text; then report an
error to the user if it's not numeric. It'd be cross-platform with
this approach, too.

Re: StringGadget: also use "-" in numeric only

Posted: Sat Sep 13, 2014 2:40 pm
by wilbert
Does this work cross platform ?
Unfortunately caret position is lost when entering a wrong character

Code: Select all

Procedure IsNumeric(n.s)
  Protected *End.Ascii
  CompilerIf #PB_Compiler_Unicode
    PokeS(@n, n, -1, #PB_Ascii)  
  CompilerEndIf
  strtod_(@n, @*End)
  If *End > @n
    While *End\a
      If *End\a > 32
        ProcedureReturn #False
      EndIf
      *End + 1
    Wend
    ProcedureReturn #True
  Else
    ProcedureReturn #False
  EndIf
EndProcedure

Global NewMap OldStringGadgetValue.s()

Procedure NumericCheck(Gadget)
  Protected GadgetKey.s = Hex(Gadget)
  Protected GadgetText.s = GetGadgetText(Gadget)
  If IsNumeric(GadgetText)
    OldStringGadgetValue(GadgetKey) = GadgetText
  Else
    SetGadgetText(Gadget, OldStringGadgetValue(GadgetKey))
  EndIf
EndProcedure

If OpenWindow(0, 0, 0, 322, 205, "Numeric only", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  StringGadget(0, 8,  10, 306, 20, "")
  Repeat
    Event = WaitWindowEvent()
    If Event = #PB_Event_Gadget And EventGadget() = 0
      NumericCheck(0)
    EndIf
  Until Event = #PB_Event_CloseWindow
EndIf