Revenir au plan du site

Gestion clavier : Saisir du texte, gérer les touches spéciales


Comme vu dans les articles [ accéder au clavier ] et [ gestion de menu ], on sait lire l'état du clavier. Cela nous donne l'état d'une touche à un instant donné. Mais quand il s'agit de gérer des évènements, on peut se retrouver rapidement submergé si on ne gère pas différents états, à minima, qu'on n'attende pas que la touche soit remontée avant de continuer.

Dans la [ gestion de menu ], on a utilisé une solution simple mais bloquante! Comment faire si on veut continuer à faire d'autres choses qu'attendre que les touche soient remontées?

Et bien on pourrait commencer par réagir à des états!

L'idée serait d'agir immédiatement à l'appui de la touche mais de ne pas recommencer l'action tant que la touche n'est pas remontée. Pour ça on va avoir besoin d'un tableau conservant le résultat du scan précédent et aussi d'un tampon pour y envoyer les nouvelles touches (ou un autre tableau qu'il faudra acquitter d'une façon ou d'une autre).
matriceClavier defs 10 ; état des touches inversés pour avoir 1 quand enfoncée
matriceRemanence defs 10 ; état précédent des touches inversées
repetitionTouche defb 0 ; en cas de touche unique, compter le temps d'enfoncement
nouvelleTouche defb 0 ; une nouvelle touche maxi par itération

On démarre le code de comparaison? 4 chemins logiques possibles :
- touche relâchée et était déjà relâchée, on ne fait rien.
- touche relâchée et était enfoncée. On arrête la répétition au besoin.
- touche enfoncée et était déjà enfoncée, on gère les répétitions si unique (besoin d'un compteur).
- touche enfoncée et était relâchée, on ajoute de l'activité sur cette touche.
- plusieurs touches enfoncées, on réinitialise les répétitions et on ne renvoie éventuellement qu'une nouvelle touche.

Mais d'abord, définissons quelques tables en accord avec le clavier français de France.

Pour faciliter le traitement, les touches de curseur, tab, esc, ... , seront relogées dans les 32 premiers caractères afin de les regrouper dans un seul intervale, ce qui nous permettra de les traiter plus facilement et les dissocier des caractères à afficher.

Pour compiler les tables suivantes qui contiennent certains caractères accentués (codés en UTF8), vous aurez besoin d'indiquer à RASM de convertir vos caractères UTF8 dans l'encodage du CPC (ça fonctionne aussi avec les caractères espagnols).
rasm.exe saisieTexte.asm -utf8

Le clavier FR est un peu particulier, en effet, il possède deux touches qui renvoient le même caractère, rapport à la fonte graphique située en ROM mais comme nous allons utiliser notre propre fonte, on va pouvoir remettre un vrai pipe, l'arobe et l'anti-slash qui étaient respectivement remplacés par le ù, le à et le ç...

Je place ici les trois extras en fin de fonte, sur les codes 126,127 et 128.
CODE_MORT equ 0
CODE_RETURN equ 1
CODE_BAS equ 2
CODE_DROITE equ 3
CODE_HAUT equ 4
CODE_GAUCHE equ 5
CODE_CLR equ 6
CODE_TAB equ 7
CODE_ESC equ 8
CODE_DEL equ 9
CODE_CTRL_BAS equ 10
CODE_CTRL_DROITE equ 11
CODE_CTRL_HAUT equ 12
CODE_CTRL_GAUCHE equ 13

conversionASCII
.ligne40 defb '.',CODE_RETURN,'369',CODE_BAS,CODE_DROITE,CODE_HAUT
.ligne41 defb '021587',CODE_RETURN,CODE_GAUCHE
.ligne42 defb CODE_MORT,'$',CODE_MORT,'4#',CODE_RETURN,'*',CODE_CLR
.ligne43 defb ':=mùp^)-'
.ligne44 defb ';,klioçà'
.ligne45 defb ' njhyuè!'
.ligne46 defb 'vbfgtr(]'
.ligne47 defb 'xcdsze"\''
.ligne48 defb 'w',CODE_MORT,'q',CODE_TAB,'a',CODE_ESC,'é&'
.ligne49 defb CODE_DEL,CODE_RETURN,CODE_RETURN,CODE_RETURN,CODE_DROITE,CODE_GAUCHE,CODE_BAS,CODE_HAUT

.shift40 defb '.',CODE_RETURN,'369',CODE_BAS,CODE_DROITE,CODE_HAUT
.shift41 defb '021587',CODE_RETURN,CODE_GAUCHE
.shift42 defb CODE_MORT,128,CODE_MORT,'4>',CODE_RETURN,'<',CODE_CLR ; patch anti-slash
.shift43 defb '/+M%P',126,'[_' ; patch pipe
.shift44 defb '.?KLIO90'
.shift45 defb ' NJHYU78'
.shift46 defb 'VBFGTR56'
.shift47 defb 'XCDSZE34'
.shift48 defb 'W',CODE_MORT,'Q',CODE_TAB,'A',CODE_ESC,'21'
.shift49 defb CODE_DEL,CODE_RETURN,CODE_RETURN,CODE_RETURN,CODE_DROITE,CODE_GAUCHE,CODE_BAS,CODE_HAUT

.ctrl40 defb CODE_MORT,CODE_RETURN,CODE_MORT,CODE_MORT,CODE_MORT,CODE_CTRL_BAS,CODE_CTRL_DROITE,CODE_CTRL_HAUT
.ctrl41 defb CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT,CODE_RETURN,CODE_CTRL_GAUCHE
.ctrl42 defb CODE_MORT,127,CODE_MORT,CODE_MORT,CODE_MORT,CODE_RETURN,CODE_MORT,CODE_CLR ; patch Arobe
.ctrl43 defb CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT
.ctrl44 defb CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT
.ctrl45 defb CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT
.ctrl46 defb CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT
.ctrl47 defb CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT,CODE_MORT
.ctrl48 defb CODE_MORT,CODE_MORT,CODE_MORT,CODE_TAB,CODE_MORT,CODE_ESC,CODE_MORT,CODE_MORT
.ctrl49 defb CODE_DEL,CODE_RETURN,CODE_RETURN,CODE_RETURN,CODE_DROITE,CODE_GAUCHE,CODE_BAS,CODE_HAUT

On aura défini 3 tables, selon qu'on appuie sur SHIFT et/ou CTRL en même temps qu'une autre touche. Ces tables serviront dans l'analyse des changements de touche à la fin de la gestion clavier.

Mais d'abord, il faut déterminer quelles touches ont été actionnées... On va doubler le tableau résultat de scan des touches pour conserver l'état précédent et pouvoir le comparer avec l'état courant. Une simple copie de l'un vers l'autre avant de scanner.
lectureMatriceClavier
ld hl,matriceClavier+9 : ld de,matriceRemanence+9 : ld bc,9 : lddr : ld a,(hl) : ld (de),a ; copier la rémanence
ld bc,#f782
out (c),c
ld bc,#f40e
ld e,b
out (c),c
ld bc,#f6c0
ld d,b
out (c),c
out (c),0
ld bc,#f792
out (c),c
ld a,#40
ld c,d
.loop ld b,d
out (c),a ; sélectionner la ligne
ld b,e
ini ; lire et stocker dans notre tableau
inc a
inc c
jr nz,.loop
ld bc,#f782
out (c),c
ret
confine 21 ; s'assurer que les 20 octets suivants ne chevauchent pas une incrémentation de poids fort
matriceClavier defs 10,#FF
matriceRemanence defs 10,#FF ; état précédent des touches inversées
compteurRepetition defb 0
sousRepetition defb 0
shiftCTRL defb 0

À partir de nos deux tableaux, on va analyer :
- les changements
- les états enfoncés
GererClavier
call lectureMatriceClavier
; on récupère SHIFT+CTRL qu'on reset pour ne pas interférer avec le scan
ld hl,matriceClavier+2 : ld a,(hl) : ld (shiftCTRL),a : or %10100000 : ld (hl),a
ld de,matriceClavier : ld hl,matriceRemanence
exx : ld hl,#FF : ld c,8 : exx
ld iy,10 ; YH=0 compteur de touches, YL=10 compteur de lignes
ld a,(compteurRepetition) : exa
;-----------------------------
gestionTouches
;-----------------------------
ld a,(de) : cp #FF : jr nz,.scanEtat : cp (hl) : jr z,.pasDeChangement

.scanEtat
ld c,(hl) ; état précédent des touches dans C
ld b,8
;-----------------------------
.scanBits
add a : jr c,.couranteRelachee
;----------------
.couranteEnfoncee
inc yh ; incrémenter le compteur de touches courantes
sla c : jr c,.ajouteTouche ; saute si relâchée
; était déjà enfoncée, on récupère le code courant dans B'
exx : ld b,h : exx
jr .suiteBits
.ajouteTouche
exx : ld l,h : exx : exa : xor a : exa ; reset des répétitions + nouvelle touche dans L'
jr .suiteBits
;----------------
.couranteRelachee
sla c : jr c,.suiteBits ; relâchée/relâchée RAS
; était enfoncée, reset du compteur de répétition
exa : xor a : exa
.suiteBits
exx : inc h : exx
djnz .scanBits
jr .reloop
;-----------------------------
.pasDeChangement
exx : ld a,h : add c : ld h,a : exx ; liste de conversion suivante
.reloop
inc e : inc l
dec yl : jr nz,gestionTouches

Une fois qu'on a comparé nos tableaux, on a un nombre d'appuis, éventuellement une nouvelle touche ou une touche de répétition, on renvoie dans tous les cas un pseudo-code ASCII (avec nos touches spéciales sous 32 et un code de non action de valeur zéro).
; analyse des résultats
ld a,yh : or a : jr z,.pasDeTouche

dec a : jp nz,.plusieursTouches
; une seule touche!
; si nouvelle touche alors L' ne vaut plus #FF et on récupère le code ASCII
exx : ld a,l : exx : cp #FF : jr nz,.getASCII

; une seule touche déjà enfoncée, augmenter la répétition!
; B'=code de la touche touche à répeter
exa : inc a : bit 5,a : jr z,.pasDeRepetition : dec a : ld (compteurRepetition),a
ld a,(sousRepetition) : inc a : and 3 : ld (sousRepetition),a : jr nz,.sortie
exx : ld l,b : exx
jr .getASCII

.pasDeRepetition ld (compteurRepetition),a : ld a,#FF : ld (sousRepetition),a : .sortie xor a : ret
;exx : ld a,l : inc a : ret z ; pas de nouvelle touche

.plusieursTouches
exx : ld a,l : exx : cp #FF : jr nz,.getASCII ; si il en existe au moins une nouvelle on l'envoie
.pasDeTouche xor a : ld (compteurRepetition),a : ret ; sinon reset comme aucune touche

.getASCII
exx : ld h,hi(conversionASCII) : ld c,0 : ld a,(shiftCTRL)
add a : jr c,.skipCtrl : ld c,160 : .skipCtrl
add a : add a : jr c,.skipShift : ld c,80 : .skipShift ; deuxième chance
ld a,l : add c : ld l,a : ld a,(hl) ; code ASCII
ret

On va mettre en place un code très simple pour voir qu'il se passe quelque chose, que la répétition est prise en compte et que les timings choisis pour la vitesse sont OK.
Visuellement c'est pas foufou mais on aura du vrai texte à l'explorateur mémoire :)
laBoucle
halt 6
call GererClavier
; code mort 0
; codes spéciaux < 32
; code ASCII sinon :)

or a : jr z,laBoucle ; code mort, on reboucle
cp 32 : jr c,dispFunc
; afficher l'octet correspondant à la touche
plug ld hl,#C000 : ld (hl),a : inc l : ld (plug+1),hl
jp laBoucle
; remplir sur une ligne différente les codes spéciaux (s'affichent et s'effacent)
dispFunc ld h,#D0 : ld l,a : ld a,(hl) : xor #AA : ld (hl),a
jp laBoucle


Et maintenant qu'on a une routine plutôt puissante comme celle là, on va pouvoir se fabriquer des petits modules qui s'en servent, à commencer par une [ checkbox ].