StringGadget: also use "-" in numeric only

Got an idea for enhancing PureBasic? New command(s) you'd like to see?
User avatar
marcoagpinto
Addict
Addict
Posts: 1045
Joined: Sun Mar 10, 2013 3:01 pm
Location: Portugal
Contact:

StringGadget: also use "-" in numeric only

Post 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
----------------
User avatar
glomph
User
User
Posts: 48
Joined: Tue Apr 27, 2010 1:43 am
Location: St. Elsewhere / Germany
Contact:

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

Post by glomph »

+1
User avatar
Derren
Enthusiast
Enthusiast
Posts: 316
Joined: Sat Jul 23, 2011 1:13 am
Location: Germany

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

Post 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)
IdeasVacuum
Always Here
Always Here
Posts: 6426
Joined: Fri Oct 23, 2009 2:33 am
Location: Wales, UK
Contact:

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

Post by IdeasVacuum »

.... and let's support the decimal point too for floats/doubles
IdeasVacuum
If it sounds simple, you have not grasped the complexity.
User avatar
Derren
Enthusiast
Enthusiast
Posts: 316
Joined: Sat Jul 23, 2011 1:13 am
Location: Germany

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

Post 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^^
User avatar
skywalk
Addict
Addict
Posts: 4211
Joined: Wed Dec 23, 2009 10:14 pm
Location: Boston, MA

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

Post 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
The nice thing about standards is there are so many to choose from. ~ Andrew Tanenbaum
User avatar
Derren
Enthusiast
Enthusiast
Posts: 316
Joined: Sat Jul 23, 2011 1:13 am
Location: Germany

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

Post 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
User avatar
Danilo
Addict
Addict
Posts: 3036
Joined: Sat Apr 26, 2003 8:26 am
Location: Planet Earth

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

Post 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
User avatar
TI-994A
Addict
Addict
Posts: 2705
Joined: Sat Feb 19, 2011 3:47 am
Location: Singapore
Contact:

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

Post 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.
Texas Instruments TI-99/4A Home Computer: the first home computer with a 16bit processor, crammed into an 8bit architecture. Great hardware - Poor design - Wonderful BASIC engine. And it could talk too! Please visit my YouTube Channel :D
PB
PureBasic Expert
PureBasic Expert
Posts: 7581
Joined: Fri Apr 25, 2003 5:24 pm

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

Post 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.
I compile using 5.31 (x86) on Win 7 Ultimate (64-bit).
"PureBasic won't be object oriented, period" - Fred.
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3942
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

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

Post 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
Windows (x64)
Raspberry Pi OS (Arm64)
Post Reply