Flame Maker – Etape 8 - Application des modifications affines
Introduction
Le but principal de cette étape est de rendre active l'interface de modification affine mise en page lors de l'étape précédente. En d'autres termes, il s'agit de faire en sorte que les boutons de l'interface de modification affine agissent sur la fractale.
Activation des boutons de modification
Les boutons de l'interface de modification affine sont pour l'instant inactifs car aucun auditeur ne leur est attaché. Pour les rendre actifs, il faut ajouter un auditeur à chacun d'entre-eux, de manière à ce que la transformation spécifiée par l'interface soit composée avec la partie affine de la transformation Flame sélectionnée.
L'effet des 14 boutons sur la paire de flèches représentant la partie affine de la transformation actuellement sélectionnée est décrit ci-dessous. Dans cette description, la variable \(x\) représente le paramètre de la transformation, c-à-d la valeur contenue dans le champ textuel formaté associé à la transformation. De plus, lorsqu'on parle de l'origine de la paire de flèches, on désigne l'intersection de ces deux flèches.
- Translation
- La paire de flèches est translatée de \(x\) unités dans la direction spécifiée par l'étiquette du bouton.
- Rotation
- La paire de flèches subit une rotation autour de son origine de \(x\) degrés dans le sens spécifié par l'étiquette du bouton.
- Dilatation
- La paire de flèches subit une dilatation d'un facteur \(x\) (si le bouton est étiqueté avec un
+
) ou \(\tfrac{1}{x}\) (si le bouton est étiqueté avec un-
), parallèlement à l'axe désigné par l'étiquette du bouton. Les problèmes de division par zéro lorsque \(x\) est nul sont évités en empêchant l'introduction de cette valeur, comme décrit plus bas. - Transvection
- La paire de flèches subit une transvection d'un facteur \(x\) ou \(-x\) (en fonction de la direction de la flèche étiquetant le bouton), parallèlement à l'axe désigné par l'étiquette du bouton. L'origine de la transvection est celle de la paire de flèches.
Format des champs textuels
Les champs textuels formatés utilisent un objet séparé, nommé formateur et de type JFormattedTextField.AbstractFormatter
, pour effectuer la conversion entre les valeurs textuelles affichées et les valeurs non textuelles manipulées par le programme, ici des nombres.
Lorsqu'on passe une valeur initiale au constructeur d'un champ textuel formaté, comme cela a été fait dans l'étape précédente, un formateur convenant au type de cette valeur est automatiquement attaché au champ. Dans notre cas, ce formateur sait convertir des valeurs textuelles en nombres—c-à-d des instances de Number
—et inversément.
Malheureusement, ce formateur par défaut a la caractéristique d'arrondir les nombres à une décimale. Vous pouvez vous en rendre compte en cliquant sur le champ associé aux dilatations, contenant initialement 1,05, et en constatant que cette valeur se transforme immédiatement en 1.
La manière la plus simple de résoudre ce problème consiste à spécifier un format—et pas un formateur—à la construction du champ textuel. Un formateur sera ensuite automatiquement créé par le champ à partir de ce format. Pour formater des nombres avec deux décimales, il convient d'utiliser une instance de DecimalFormat
construite ainsi : new DecimalFormat("#0.##")
. La chaîne passée au constructeur spécifie que deux décimales sont désirées. Pour les curieux, la syntaxe de ces chaînes est décrite dans la documentation de la classe DecimalFormat
.
Notez que le constructeur de JFormattedTextField
acceptant un format n'accepte pas de valeur initiale. Pour fournir cette valeur, il faut donc faire un appel à setValue
après la création.
Validation du facteur de dilatation
Comme dit plus haut, il est clairement préférable d'éviter la présence de la valeur 0 dans le champ textuel associé aux dilatations. En effet, cette valeur produit des transformations affines dégénérées ou provoque une division par zéro.
Les champs textuels offrent la possibilité au programme de valider, et éventuellement refuser, une valeur entrée par l'utilisateur. Une validation est d'ailleurs effectuée automatiquement par le formateur attaché aux champs textuels formatés. Vous pouvez vous en rendre compte en essayant d'entrer une valeur qui n'est pas un nombre dans l'un d'entre-eux. Vous constaterez alors que la dernière valeur valide est restaurée dès que le curseur quitte le champ.
Pour le champ textuel associé aux dilatations, il faut pousser la validation plus loin pour détecter l'entrée de la valeur 0 et la remplacer par la dernière valeur valide entrée.
Cela peut se faire en attachant au champ un validateur au moyen de la méthode setInputVerifier
. Un vérificateur—qui, au passage, est une instance du patron Strategy—est un objet contenant une méthode nommée verify
prenant en argument le composant dont la valeur doit être validée et retournant un booléen. Contrairement à ce qu'on pourrait penser—et souhaiter—le booléen retourné n'indique pas si la valeur doit être acceptée ou non. Elle indique simplement si le curseur est autorisé à quitter le champ, et vous devez donc toujours retourner vrai.
La vérification de la validité de la valeur entrée par l'utilisateur, et son éventuel remplacement par la dernière valeur valide, peut se faire au moyen des méthodes ci-dessous, à vous de comprendre comment les enchaîner :
getText
, du champ textuel, qui permet d'obtenir la valeur à valider sous forme textuelle.getFormatter
, du champ textuel, qui permet d'obtenir le formateur attaché au champ, grâce auquel il est possible d'essayer de transformer la valeur textuelle obtenue précédemment en une valeur non textuelle.stringToValue
, du formateur, qui permet de (tenter de) convertir une valeur textuelle en une valeur non textuelle (ici numérique). Cette méthode lève l'exceptionParseException
si la chaîne reçue n'est pas un nombre.valueToString
, du formateur, qui permet de convertir une valeur (ici numérique) en une valeur textuelle.setText
, du champ textuel, qui permet de changer la valeur textuelle du champ afin de rétablir la dernière valeur valide.getValue
, du champ textuel, qui permet d'obtenir la dernière valeur valide.
Il est très important de comprendre qu'au moment de la validation, la valeur textuelle (retournée par getText
) du champ est celle entrée par l'utilisateur tandis que sa valeur non textuelle (retournée par getValue
) est la dernière valeur valide. On peut donc restaurer cette dernière si cette première n'est pas valide en forçant sa valeur.
Interface Nimbus (optionnel)
Swing offre la possibilité de changer l'apparence des composants affichés à l'écran, en choisissant un look and feel différent de celui utilisé par défaut. Toutes les copies d'écran des énoncés ont été faites en utilisant un look and feel ajouté récemment à Java et nommé Nimbus.
Si vous désirez obtenir la même apparence, vous pouvez forcer votre programme à utiliser ce look and feel en suivant les instructions de la section Nimbus Look and Feel du Swing Tutorial.
Résumé
Pour cette étape, vous devez :
- Attacher à chaque bouton de l'interface de modification affine un auditeur qui compose la transformation spécifiée par l'interface avec la partie affine de la transformation Flame actuellement sélectionnée.
- Permettre l'introduction de valeurs avec deux décimales dans les champs textuels formatés associés aux translations, dilatations et transvections, en leur passant un format approprié lors de la construction.
- Interdire l'introduction de la valeur 0 dans le champ textuel associé aux dilatations, en lui attachant un validateur.
Cette étape est conceptuellement simple, mais la quantité de code à écrire pour les auditeurs peut être importante si vous ne prenez pas soin de bien le factoriser au maximum. Il est donc fortement conseillé de passer un moment à réfléchir à la manière optimale d'organiser le code pour éviter autant que possible la duplication. Notez en particulier que le patron Strategy peut être utilisé dans ce but.