Simulation GetKey() without flush?

Blitz3D Forums/Blitz3D Programming/Simulation GetKey() without flush?

Hi,

I´m doing a little textbox for user input using getkey().
And now I want to implement keypress delays and keyrepeats while the user is holding down a button. Is this possible using Getkey()? It flushes the buffer after each read which causes major problems for me.
There´s no problem if you use Keydown() for this, but then you get scancodes, and not ascii. And converting them correctly on all languages and all keyboards is probably impossible.

So how can I solve this? Any ideas?

The best way would be to have a parameter to GetKey() that tells if the buffer should be flushed or not, but I guess that´s up to Mark to implement.. ;)

There's infact a problem with country keyboard settings.

What you can do is: Use GetKey to determine the keys in the correct language. Immediately after getting a key you have to test Keydown in a for 0 to 255 loop to see what (scancode) key was pressed.
Then use your custom keyrepeat stuff, based on millisecs and Keydown.

Frequently check for keydown independently from GetKey as well, to see if eg. a key of the number block was pressed. These keys are not country specific, AFAIK. So you may add support for them without using GetKey.

Thanks,
Checking for a keydown immediately after the getkey could solve some of the problems.
I´ll try to implement this and see if it can work.
I already use keydown for the numpad and home, end, delete, backspace and the arrows, so it shouldn´t take to long.
I think I need to "remember" if the shift keys or AltGr also are pressed..

I have some text input code in a project of mine that I could probably rip out for you, if you want?

big10p: yes please, just email me your code. thanks!

I started doing some GetKey() followed by KeyDown() and it could work, though it´s rather messy(atleast at the moment ;)) code.

If it´s too much trouble to do this, then I might consider dropping keyrepeats for letters. It works for del, backspace and so on which are much more important.
If a user want to type "ggggg", they can press "g" five times.. ;)

If it´s too much trouble to do this, then I might consider dropping keyrepeats for letters. It works for del, backspace and so on which are much more important.
Actually, having just revisted my code, it doesn't do key repeats for alphanumeric keys, either. It's a tricky problem to get around, I guess it just wasn't worth the effort. :)

Anyway, if you still want it, the code's here:
(sorry about the weird tabs - I use 2-space tabs in my IDE)

	Graphics 640,480,0,2
	SetBuffer BackBuffer()
	ClsColor 200,200,200
	
	Const REPEAT_DELAY%				= 250
	Const REPEAT_RATE%				= 25
	Const CURSOR_BLINK_RATE%	= 500

	Const GUI_FONT_NAME$		= "verdana"
	Const GUI_FONT_SIZE%		= 16

	; Key IDs.
	Const ID_KEY_NONE%			= 0			
	Const ID_KEY_LEFT%			= 1
	Const ID_KEY_RIGHT%			= 2
	Const ID_KEY_HOME%			= 3
	Const ID_KEY_END%				= 4
	Const ID_KEY_BACKSPACE%	= 5	
	Const ID_KEY_DELETE%		= 6
	Const ID_KEY_UP%				= 7
	Const ID_KEY_DOWN%			= 8
	Const ID_KEY_PAGE_UP%		= 9
	Const ID_KEY_PAGE_DOWN%	= 10

	fps_timer = CreateTimer(60) ; Set to desired FPS.

	gui_font = LoadFont(GUI_FONT_NAME$,GUI_FONT_SIZE,True)
	SetFont gui_font

	; Text input field control vars.
	Global i_text$
	Global i_selected%
	Global i_x%
	Global i_y%
	Global i_width%
	Global i_height%
	Global i_first_char%
	Global i_cursor_pos%
	Global i_cursor_on%
	Global i_cursor_timeout%
	Global i_title$
	Global i_title_x_off%
	Global i_old_key%
	Global i_key_delay%
	Global i_key_timeout%
	Global i_backup_width%
	Global i_max_chars%
	Global i_alt_char$

	; Initialize text input field.
	i_text$						= "This is the default text"
	i_title$					=	"Input"
	i_title_x_off			= StringWidth(i_title$) + 5
	i_selected				= True
	i_width						= 280
	i_height					= GUI_FONT_SIZE
	i_x								=	100
	i_y								= 100
	i_first_char			= 1
	i_cursor_pos			= Len(i_text$)
	i_cursor_on				= True
	i_cursor_timeout	= MilliSecs() + CURSOR_BLINK_RATE
	i_old_key					= ID_KEY_NONE
	i_key_delay				= 0
	i_key_timeout			= 0
	i_backup_width		= i_width / 4
	i_max_chars				= 1000
	i_alt_char$				= ""
	
	; Main loop.
	While Not KeyHit(1)
		Cls
		
		check_input_field()
		draw_input_field()
		
		WaitTimer(fps_timer)
		VWait : Flip False
	Wend

	End

;
; Ensure first_char is valid in relation to the current cursor position.
;
Function update_input_settings()

	text_len = Len(i_text$)
	
	If i_cursor_pos = (i_first_char-1)
		; Cursor is at first_char position...

		If i_cursor_pos > 0
			; Set first_char to be i_backup_width in front of cursor.
			i_first_char = i_cursor_pos
			width = StringWidth(Mid$(i_text$,i_first_char,1))
			While i_first_char > 1
				width = width + StringWidth(Mid$(i_text$,i_first_char-1,1))
				If width > i_backup_width Then Exit
				i_first_char = i_first_char - 1
			Wend
		EndIf
	ElseIf i_cursor_pos < (i_first_char-1)
		; Cursor is before first_char...
		
		i_first_char = i_cursor_pos + 1
	Else
		; Cursor is after first_char...

		If i_cursor_pos = text_len
			; Cursor is at very end of input text...

			; Find character at end of text field.
			last_char = i_first_char
			width = StringWidth(Mid$(i_text$,last_char,1))
			While last_char < text_len
				width = width + StringWidth(Mid$(i_text$,last_char+1,1))
				If width > i_width Then Exit
				last_char = last_char + 1
			Wend
			
			If last_char < text_len
				; Last char is NOT visible so back up to fill the whole field.
				i_first_char = i_cursor_pos
				width = StringWidth(Mid$(i_text$,i_first_char,1))
				Repeat
					width = width + StringWidth(Mid$(i_text$,i_first_char-1,1))
					If width > i_width Then Exit
					i_first_char = i_first_char - 1
				Forever
			EndIf
		Else
			; Cursor is NOT at very end of input text...
			
			char_diff = ((i_cursor_pos + 1) - i_first_char)

			If StringWidth(Mid$(i_text$,i_first_char,char_diff+1)) > i_width
				; Cursor character won't fit at end of text field...
				
				; First, find the last character we want displayed in the text field.
				If i_old_key = ID_KEY_DELETE
					last_char = i_cursor_pos + 1
				Else
					; Set last_char to be i_backup_width in front of cursor.
					last_char = i_cursor_pos + 1
					width = StringWidth(Mid$(i_text$,last_char,1))
					While last_char < text_len
						width = width + StringWidth(Mid$(i_text$,last_char+1,1))
						If width > i_backup_width Then Exit
						last_char = last_char + 1
					Wend
				EndIf
				
				; Now, back up fron last_char to fill the whole field.
				i_first_char = last_char
				width = StringWidth(Mid$(i_text$,i_first_char,1))
				Repeat
					width = width + StringWidth(Mid$(i_text$,i_first_char-1,1))
					If width > i_width Then Exit
					i_first_char = i_first_char - 1
				Forever
			EndIf
		EndIf
	EndIf

End Function


;
;
;
Function draw_input_field()

	update_input_settings()

	; Draw input field.
	Color 0,0,0
	Rect i_x-2,i_y-2,i_width+4,i_height+4,True
	Text i_x-4-i_title_x_off,i_y,i_title$

	; Find cursor bar x coord.
	bar_x = i_x
	char_diff = ((i_cursor_pos + 1) - i_first_char)
	If char_diff > 0
		width = StringWidth(Mid$(i_text$,i_first_char,char_diff))
	
		bar_x = bar_x + width
	
		If i_selected
			; Draw 'selected text' background.
			Color 0,0,255
			Rect i_x,i_y,width,i_height,True
		EndIf
	EndIf

	; Draw field text.
	Color 255,255,255
	x = i_x
	width = 0
	For n = i_first_char To Len(i_text$)
		c$ = Mid$(i_text$,n,1)
		c_width = StringWidth(c$)
		
		width = width + c_width
		If width > i_width Then Exit
		
		Text x,i_y,c$
		x = x + c_width
	Next

	If i_cursor_on
		; Draw cursor bar.
		Color 255,255,255
		Rect bar_x,i_y,1,i_height,True
	EndIf

	; Cursor blink management.
	If i_cursor_timeout < MilliSecs()
		i_cursor_on = Not i_cursor_on
		i_cursor_timeout = MilliSecs() + CURSOR_BLINK_RATE
	EndIf
	
End Function


;
;
;
Function check_input_field()

	ascii = GetKey()
	
	If KeyDown(56)
		; Left Alt key is being held down so check numeric keypad
		; for ASCII code being entered.
		Select True
		Case KeyHit(82)
			i_alt_char$ = i_alt_char$ + "0"
		Case KeyHit(79)
			i_alt_char$ = i_alt_char$ + "1"
		Case KeyHit(80)
			i_alt_char$ = i_alt_char$ + "2"
		Case KeyHit(81)
			i_alt_char$ = i_alt_char$ + "3"
		Case KeyHit(75)
			i_alt_char$ = i_alt_char$ + "4"
		Case KeyHit(76)
			i_alt_char$ = i_alt_char$ + "5"
		Case KeyHit(77)
			i_alt_char$ = i_alt_char$ + "6"
		Case KeyHit(71)
			i_alt_char$ = i_alt_char$ + "7"
		Case KeyHit(72)
			i_alt_char$ = i_alt_char$ + "8"
		Case KeyHit(73)
			i_alt_char$ = i_alt_char$ + "9"
		End Select
		
		Return
	ElseIf i_alt_char$ <> ""
		; Left Alt key has just been released with a value contained in
		; i_alt_char$ so attempt to convert it into a valid ASCII code.
		val = Int(i_alt_char$)
		i_alt_char$ = ""
		If (val >= 32) And (val <= 255) Then ascii = val
	EndIf
	
	If ascii > 31
		If i_selected
			; Delete initially selected text.
			i_text$ = ""
			i_cursor_pos = 0
			i_first_char = 1
			i_selected = False
		EndIf
		
		num_chars = Len(i_text$)

		If num_chars < i_max_chars
			t$ = ""
			If i_cursor_pos Then t$ = Mid$(i_text$,1,i_cursor_pos)
			t$ = t$ + Chr$(ascii)
			If num_chars > i_cursor_pos Then t$ = t$ + Mid$(i_text$,i_cursor_pos+1)
			i_text$ = t$
			i_cursor_pos = i_cursor_pos + 1
			i_cursor_on = True
		EndIf
	Else
		; Give ID to any valid pressed key.
		If KeyDown(203)
			new_key = ID_KEY_LEFT
		ElseIf KeyDown(205)
			new_key = ID_KEY_RIGHT
		ElseIf KeyDown(199)
			new_key = ID_KEY_HOME
		ElseIf KeyDown(207)
			new_key = ID_KEY_END
		ElseIf KeyDown(14)
			new_key = ID_KEY_BACKSPACE
		ElseIf KeyDown(211)
			new_key = ID_KEY_DELETE
		Else
			new_key = ID_KEY_NONE
		EndIf

		; Key delay/authorization management.
		If (new_key <> ID_KEY_NONE) And (new_key = i_old_key)
			If i_key_timeout
				If MilliSecs() > i_key_timeout
					i_key_timeout = 0
					i_key_delay = REPEAT_RATE
				EndIf
			Else
				i_key_timeout = MilliSecs() + i_key_delay
			EndIf
		Else
			i_key_timeout = 0
			i_key_delay = REPEAT_DELAY
		EndIf

		i_old_key = new_key
		
		If (new_key<>ID_KEY_NONE) And (i_key_timeout=0)
			; Carry out appropriate key action.
			Select new_key
			Case ID_KEY_LEFT
				If i_cursor_pos > 0
					i_cursor_pos = i_cursor_pos - 1
					i_cursor_on = True
				EndIf
				i_selected = False
			Case ID_KEY_RIGHT
				If i_cursor_pos < Len(i_text$)
					i_cursor_pos = i_cursor_pos + 1
					i_cursor_on = True
				EndIf
				i_selected = False
			Case ID_KEY_HOME
				If i_cursor_pos <> 0
					i_cursor_pos = 0
					i_cursor_on = True
				EndIf
				i_selected = False
			Case ID_KEY_END
				If i_cursor_pos <> Len(i_text$)
					i_cursor_pos = Len(i_text$)
					i_cursor_on = True
				EndIf
				i_selected = False
			Case ID_KEY_BACKSPACE
				If i_selected
					; Delete initially selected text.
					i_text$ = ""
					i_cursor_pos = 0
					i_first_char = 1
					i_selected = False
				ElseIf i_cursor_pos > 0
					If i_cursor_pos > 1 Then t$ = Left$(i_text$,i_cursor_pos-1)
					If i_cursor_pos < Len(i_text$) Then t$ = t$ + Mid$(i_text$,i_cursor_pos+1)
					i_text$ = t$
					i_cursor_pos = i_cursor_pos - 1
					i_cursor_on = True
				EndIf
			Case ID_KEY_DELETE
				If i_selected
					; Delete initially selected text.
					i_text$ = ""
					i_cursor_pos = 0
					i_first_char = 1
					i_selected = False
				ElseIf i_cursor_pos < Len(i_text$)
					If i_cursor_pos > 0 Then t$ = Left$(i_text$,i_cursor_pos)
					If i_cursor_pos < (Len(i_text$)-1) Then t$ = t$ + Mid$(i_text$,i_cursor_pos+2)
					i_text$ = t$
					i_cursor_on = True
				EndIf
			End Select
		EndIf
		
	EndIf
	
End Function


I have done a working keyrepeat function using GetKey in combo with KeyDown, as jfk EO-11110 suggested.
It should work with all languages and keymaps/keyboards since it uses GetKey() to get the ascii code.

And here it is for those of you that want it.
Of course it can be improved. One thing is that it ignores shift and altgr presses for the repeat. These could be easily added if the need arises.

And if you only need keyrepeats for scancodes, just use the KeyRepeats() function and skip the stuff in KeyChecks() except the print lines at the bottom. It´s pretty generic.

Thanks guys for your help!

-------------------
Global glob_keypressed=False
Global glob_keypressed_timer=0
Global glob_keycheck_ok=True
Global glob_keyrepeat_start=False

Global glob_input_ascii
Global glob_input_scan

;-- Main loop --
Repeat

	KeyChecks()
	KeyRepeats()

Until KeyHit(1)
End
;--
Function KeyChecks()

	Local input_ascii_gk
	Local scan

	If glob_keypressed=False
		input_ascii_gk=GetKey()
		If input_ascii_gk>=32 And input_ascii_gk<>127	;allow ascii above 31, except 127(Del)
			glob_keypressed=True
			glob_input_ascii=input_ascii_gk		
			For scan=0 To 255
				If KeyDown(scan)			
					If scan<>42 Or scan<>54 Or scan<>184	;ignore RShift, LShift, AltGr
						glob_input_scan=scan
						Exit
					End If
				End If
			Next
		Else
			glob_input_ascii=0
		End If
	Else	
		For scan=0 To 255
			If KeyDown(scan)		
				input_scan=scan
				Exit
			End If
		Next
		If input_scan<>glob_input_scan
			glob_keypressed=False
		End If
	End If

	If glob_keypressed=True And glob_keycheck_ok=True
		;-- Do your printing here --
		Print Chr$(glob_input_ascii)
	End If

End Function
;--
Function KeyRepeats()
	If glob_keypressed=True
		If glob_keypressed_timer=0
			glob_keypressed_timer=MilliSecs()
			glob_keycheck_ok=False
		Else
			If (MilliSecs()-glob_keypressed_timer)>600 And glob_keyrepeat_start=False
				glob_keypressed_timer=0
				glob_keycheck_ok=True
				glob_keyrepeat_start=True
			Else If (MilliSecs()-glob_keypressed_timer)>40 And glob_keyrepeat_start=True
				glob_keypressed_timer=0
				glob_keycheck_ok=True
			End If
		End If
	Else
		glob_keypressed_timer=0
		glob_keycheck_ok=True
		glob_keyrepeat_start=False
	End If
End Function


That's looking good. For simplicity you may (as in "don't have to") wrap the entire thing in a function named "CollectKey()" that will return any key in ascii, including cursor keys, backspace, keyrepeat etc. Like the old InKey$.

But this depends on the situation. Guess this one is already working nicely. So thanks for sharing!

BTW you may add it to the code archies.