Comment gérer les touches de clavier dans une interface graphique XPCE.

- Titre : Technique XPCE, saisie d'une IP ou d'un mot de passe

- Auteur : Joël Foutelet

- Parution : 13 janvier 2013

- Licence : Les sources présentés sur cette page sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre cette page de présentation de ces sources constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2013 Joël Foutelet. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 € de dommages et intérêts. Cette page est déposée à la SACD.

Sommaire

- I. Présentation

- I-1. Le "Key_binding"

- II. Création d'un contrôle de saisie d'une IP

- II-1. Création de la classe "ip_item".

- II-2. Création du contrôle de saisie d'une IP

- III. Création d'un contrôle de saisie de mot de passe.

- III-1. Création de la classe "mdp_item"

- III-2. Création d'une boîte de saisie de mot de passe.

I - Présentation

XPCE n'est pas Prolog mais permet d' utiliser Prolog (en l'occurence SWI-Prolog) dans des programmes avec des fenêtres ou des boîtes de dialogue.
XPCE possède un utilitaire pour les créations de boîtes de dialogue, il permet d'utiliser des contrôles prédéfinis, on sait moins qu'on peut définir ses propres contrôles puis les insérer dans le code des boîtes de dialogue créées à l'aide de l'utilitaire. Le but de cet article est de décrire la création des contrôles de saisie d'une IP et d'un mot de passe puis de montrer l'insertion de ces contrôles dans le code des boîtes de dialogue.
Les codes ont été testés avec la version 6.3.11 de la version 64 bits de SWI-Prolog.

I-1 - Le "Key_binding"

Les contrôles de saisie d'une IP ou d'un mot de passe sont dérivés de la classe text_item. Cette classe permet d'utiliser la classe key_binding qui analyse les évènements clavier et souris et lance des actions en fonctions des touches pressées ou des clics effectués.
Un utilitaire très intéressant fournit par SWI-Prolog est XPCE Event-viewer qui permet de connaître les noms des évènements clavier qui seront utilisés dans les key_bindings associés aux contrôles de saisie.

On peut voir sur cette image les évènements associés au curseur, ces évènements devront être traités par le key_binding des contrôles. Les lettres et chiffres seront aussi traités.
Les évènements de touches doivent être reportés dans les buffers de saisie, qui sont de type "text". Les actions correspondantes sont décrites dans l'utilitaire Class Browser à la classe text.

On peut voir ainsi que l'évènement clavier cursor_home devra être transformé en beginning_of_line pour le buffer de saisie, et l'évènement clavier end en end_of_line, etc, etc.

II - Création d'un contrôle de saisie d'une IP

II-1 - Création de la classe "ip_item".

Ici on travaille directemement sur le buffer de saisie du text_item, il n'y pas besoin de créer un champ spécial comme ce sera nécessaire pour la création du contrôle de saisie du mot de passe, car ce qui est affiché est ce qui est saisi. La classe ip_item se décrit ainsi:

classe ip_item

:- pce_begin_class(ip_item, text_item, "saisie d'une ip").

variable(kb, recogniser, both, "gestionnaire de touches").

initialise(P, Title) :->
% on initialise le champ de saisie
send_super(P, initialise, Title, '000.000.000.000'),
% 15 caractères uniquement
send(P, width, 15),
send(P, recogniser, new(KB, key_binding(@nil, argument))),
send(P, slot, kb, KB),
send(P, make_key_binding).

% création des données du gestionnaires de touches
make_key_binding(P) :->
get(P, slot, kb, K),

% ¨Par défaut, tout est interdit !
send(K, default_function, alert),

% on n'autorise que les chiffres
send_list(P, init_key_binding, [0,1,2,3,4,5,6,7,8,9]),

% on gère maintenant les touches reconnues par le text_item

% les deux "fonctions" suivantes doivent être écrites
send(K, function, 'backspace', message(P, execute_backward)),
send(K, function, 'DEL',message(P,execute_del)),

% les autres sont prédéfinies
send(K, function, 'cursor_home',message(P,beginning_of_line)),
send(K, function, 'cursor_left',message(P,backward_char)),
send(K, function, 'cursor_right',message(P,forward_char)),
send(K, function, 'end',message(P,end_of_line)),

% ici le message envoyé "next" est un message de fenêtre
% on passe au contrôle suivant
send(K, function, 'TAB', message(P,next)),
send(K, function, 'RET', enter).

% programmation des touches de chiffres
init_key_binding(P, V) :->
get(P, slot, kb, K),
atom_number(AV, V),
% obtention du code de touche
Ascii is V + 48,
%creation du key_binding associé au chiffre
send(K, function, AV, message(P, execute_insert, Ascii)).

% fonction a exécuter lorsque le chiffre est tapé
execute_insert(W, V) :->
% celà dépend de la position du curseur
get(W, caret, Pos),
% est-on sur un point ou en fin de ligne ?
(   (member(Pos, [3,7,11]); Pos > 14)
->  (   Pos > 14
->    send(W, alert)

% si on n'et pas en fin de ligne
% on avance d'un caractère
;    send(W, forward_char),

% et on reessaye d'insérer le caractère
send(W, execute_insert, V))

% si on est en début de séquence
% on n'autorise que 0 et 1
;   (member(Pos, [0,4,8,12]), V > 50)
->  send(W, alert)

% sinon on efface le caractère courant
;   send(W, delete_char),
% et on insère le caractère tapé
send(W, insert_self, 1, V)).

% on a appuyé sur la touche 'BackSpace'
% on efface le caractère à gauche du curseur
execute_backward(W) :->
% celà dépend de la position du curseur
get(W, caret, Pos),
% est-on sur un point ?
(   member(Pos, [4,8,12])

% oui on recule simplement
->  send(W, backward_char)

% sinon, on recule en effaçant le caractère
;   Pos > 0
->  send(W, backward_delete_char),
% on insere un 0
send(W, insert_self, 1, 48),
% et on revient d'un caractère
send(W, backward_char)).

% on veut effacer le caractère à droite du curseur
execute_del(W) :->
% celà dépend de la position du curseur
get(W, caret, Pos),
% est-on sur un point ?
(   member(Pos, [3,7,11])

% inpossible dans ce cas
->  send(W, alert)

% on efface le caractère
;   send(W,delete_char),
% on insere un 0
send(W, insert_self, 1, 48),
% et on revient d'un caractère
send(W, backward_char)).

:- pce_end_class(ip_item).

II-2 - Création du contrôle de saisie d'une IP

Supposons que nous ayons besoin d'une boîte de dialogue pour saisir une IP. On crée la boîte de dialogue en positionnant un text_item au bon endroit. Une fois glissé la boîte de dialogue dans le code du programme, on remplace simplement text_item par ip_item ! Code de la boîte de dialogue :

Boîte de dialogue de saisie d'IP

dialog(ip,
[ object        :=
Ip,
parts         :=
[ Ip        := dialog('Ip'),
Nom_item := text_item('Votre nom'),
IP_item := ip_item('Votre IP '),
Button := button(ok)
],

modifications :=
[],
behaviour     :=
[ Button := [ message := message(@pce, write_ln,
Nom_item?selection,
' a pour IP ',
IP_item?selection)
]],
layout        :=
[ area(Nom_item,
area(50, 10, 253, 24)),
area(IP_item,
area(50, 50, 253, 24)),
area(Button,
area(86, 86, 50, 24))
]
]).

La boîte de dialogue obtenue :
Le message associé au bouton Ok correspond plus ou moins au writeln de SWI-Prolog.

III - Création d'un contrôle de saisie de mot de passe.

III-1 - Création de la classe "mdp_item"

Ici, ce qui est affiché n'est évidemment pas ce qui est saisi. De plus un test personnalisé de contrôle de la validité du mot de passe est prévu, le nom du prédicat de test (qui doit être d'arité 1) est passé en paramètre d'initialisation.
On n'autorise dans la saisie que les chiffres et les lettres majuscules ou minuscules.
La classe mdp_item se décrit ainsi:

classe mdp_item

:- pce_begin_class(mdp_item, text_item, "saisie de mots de passe").
variable(kb, recogniser, both, "gestionnaire de touches").
variable(mdp, string, both, "mot de passe saisi").
variable(test, object, both, "pour verifier que le mot de passe est acceptable").

% initialisation sans test de validité de mot de passe
% on passe @nil comme prédicat
initialise(P, Title) :->
send(P, initialise, Title, @nil).

% initialisation avec test de validité de mot de passe
initialise(P, Title, Passe) :->
send_super(P, initialise, Title),
string_to_list(Lst, ""),
send(P, slot, mdp, Lst),
send(P, slot, test, Passe),
send(P, recogniser, new(KB, key_binding(@nil, argument))),
send(P, slot, kb, KB),
send(P, make_key_binding, KB).

make_key_binding(P, K) :->
send(K, default_function, alert),

% les codes ASCII des majuscules
numlist(65, 90, Maj),
% les codes ASCII des minuscules
numlist(97, 122, Min),
% les codes ASCII des chiffres
numlist(48, 57, Chiffres),
append([Maj, Min, Chiffres], Codes),
send_list(P, init_key_binding, Codes),

% les touches de déplacement
send(K, function, 'backspace', message(P, execute_backward)),
send(K, function, 'DEL',message(P,execute_del)),
send(K, function, 'cursor_home',message(P,beginning_of_line)),
send(K, function, 'cursor_left',message(P,backward_char)),
send(K, function, 'cursor_right',message(P,forward_char)),
send(K, function, 'end',message(P,end_of_line)),

% ici le message envoyé "next" est un message de fenêtre
% il passe au controle suivant
send(K, function, 'TAB', message(P,next)),
send(K, function, 'RET', message(P,next)).

init_key_binding(P, V) :->
get(P, slot, kb, K),
atom_codes(Atm, [V]),
send(K, function, Atm, message(P, execute_insert, Atm)).

execute_insert(P, V) :->
get(P, caret, Pos),
get(P, slot, mdp, Str),
% on affiche une étoile (code ASCII 42)
send(P, insert_self, 1, 42),
% on insere le bon caractère dans la chaîne en mémoire
send(Str, insert, Pos, V).

execute_backward(P) :->
send(P, backward_char),
get(P, caret, Pos),
% on efface le caractère à l'écran
send(P,delete_char),
get(P, slot, mdp, Str),
% on efface le caractère correspondant de la chaîne
send(Str, delete, Pos, 1).

execute_del(P) :->
get(P, caret, Pos),
send(P,delete_char),
get(P, slot, mdp, Str),
send(Str, delete, Pos, 1).

% verifie la validité du mot de passe saisi
validate(P) :->
get(P, slot, test, Test),
(   Test \= @nil
->  get(P, slot, mdp, Passe),
get(Passe, value, V),
(    % le mot de passe est-il valide ?
\+call(Test, V)
->    send(@display, inform, 'Passe incorrect'),
fail
;    true)
;   true).

:- pce_end_class(mdp_item).

III-2 - Création d'une boîte de saisie de mot de passe.

La boîte de saisie doit vérifier que le mot de passe est valide. Si le mot de passe est correct, il est retourné dans MDP, sinon un message d'erreur est affiché. Si on clique sur la croix, ou abandon, la prédicat échoue.
Le code de la boîte de saisie est :

Boite de saisie de mot de passe

ask(MDP) :-
new(D, dialog('Saisie de mot de passe')),
send(D, append(new(NameItem, mdp_item('Votre mot de passe', my_test)))),
send(D, append(button(ok, and(message(NameItem, validate),
message(D, return, NameItem?mdp?value))))),
send(D, append(button(abandon, message(D, return, @nil)))),
send(D, default_button(ok)),
get(D, confirm, Rval),
free(D),
Rval \== @nil,
MDP = Rval.

% le mot de passe doit comporter
% des lettres minuscules
% des lettres majuscules
% des chiffres
my_test(Passe) :-
atom_codes(Passe, Codes),
partition(test_char, Codes, Chiffres, Maj, Min) ,
Chiffres \= [], Maj \= [], Min \= [].

% la on teste les chiffres
test_char(Ch, <) :-
Ch >= 48, Ch =< 57.

% la on teste les majuscules
test_char(Ch, =) :-
Ch >= 65, Ch =< 90.

% la on teste les minuscules
test_char(Ch, >) :-
Ch >= 97, Ch =< 122.

Des renseignements sur partition/5 peuvent être trouvés ici.

La boîte de dialogue obtenue :

Technique XPCE, saisie d'une IP ou d'un mot de passe

Auteur

Joël Foutelet