III) L'éditeur de byte code
Vous pouvez trouver une version compilée (en Java 8) sous forme de jar exécutable ici :Jar compilé de l'éditeur
Le code source se trouve ici : Code source de l'éditeur
Pour compiler, il est directement dépendant de : Utilitaires pour la créations d'interfaces pour l'interface graphique, Compilateur de byte code pour la partie qui compile et de Divers utilitaires
A noté que Utilitaires pour la créations d'interfaces a en théorie besoin de deux autres projets pour compiler : Utilitaires pour jouer des sons et Utilitaires pour la manipulation du XML
Vous pouvez soit récupérer ces deux projets en plus, soit supprimer de JHelpGUI les parties suivantes (Elles ne sont pas utilisés par l'éditeur) :
- Le package jhelp.gui.twoD, cela enlèvera la dépendance à JHelpSound
- Le package jhelp.gui.xml, cela enlèvera la dépendance à JHelpXML
III.1) Tour de l'éditeur
L'explication sur le type de fichier attendu est expliqué ici : I) Le byte code.
L'écran se divise en quatre parties :
En haut on trouve les différentes actions de base. Au milieu la zone de saisie du code. A droite une zone de messages que l'on peut cacher ou montrer. A gauche la liste des classes compilées.
Écrivons une classe pour additionner deux nombres. A la première lettre tapée apparaît ceci :
C'est un menu d'aide à la saisie avec à gauche la liste des suggestions et à droite plus d'informations sur l'instruction où le mot clef.
Il est possible de naviguer dedans avec les flèches du haut du bas. On peut choisir le mot sélectionné en bleu avec la touche entrée ou cliquer sur un mot avec la souris.
Il est aussi possible de continuer d'écrire au fur et à mesure que l'on écrit la liste change, voir disparaît.
Entrons notre code de test : class jhelp.example.Add
method add
parameter int first
parameter int second
return int
{
ILOAD first
ILOAD second
IADD
IRETURN
}
Appuyons ensuite sur le bouton Compile. L'interface devient :
Déployons l'arbre à gauche sous la classe jhelp.example.Add pour faire apparaître la méthode add(int, int):int. Double cliquons sur celle-ci apparaît alors :
Cette interface permet de tester une méthode d'une classe. Chaque zone de saisie qui apparaît correspond à un paramètre de la méthode.
Le type de zone de saisie dépendra du type du paramètre. La première zone de saisie en haut à gauche sous le nom de la méthode est le premier paramètre, ensuite les suivants vont de la droite vers la gauche puis de haut en bas si il y a plusieurs lignes.
Saisissez une valeur pour les paramètres et appuyez sur test pour voir le résultat juste à coté.
A noté que si vous faites un code qui écrite un texte avec System.out celui-ci sera écrit dans la zone de message à droite de l'interface.
Bon ceci est ce qui se passe si tout va bien.
Modifions le code pour créer une erreur: class jhelp.example.Add
method add
parameter int first
parameter int second
return int
{
ILOAD first
ILOAD
IADD
IRETURN
}
On a oublié de mettre le paramètre du second ILOAD. Appuyons sur Compile :
On voit sur la ligne 9 que le fond du numéro est devenu rouge. De plus sur la droite le message d'erreur à droite décrit ce que nous avons oublié.
Pour en finir avec l'interface provoquons une erreur qui ne respecte pas la pile :class jhelp.example.Add
method add
parameter int first
parameter int second
return int
{
ILOAD first
ILOAD second
IADD
IADD
IRETURN
}
Après avoir compiler, on a :
Les numéros soulignée en bleu sont le chemin parcouru pour atteindre l'erreur. Le numéro souligné en rouge est la ligne d'erreur.
On voit aussi à droite de chaque instruction du parcours leur effet sur la pile d'exécution. Enfin à droite le message d'erreur nous dit que IADD à besoin de trouver sur la pile deux int alors qu'il n'y en avait qu'un. Il nous rappel le chemin parcouru et la ligne d'erreur.
Il a été mis également en place des raccourcis claviers :
- Control+Shift+C : Compile
- Control+S : Save
- Control+Shift+S : Save as ...
- Control+O : Open
- Control+N : New
Les Control+C, Cotrol+V e Control+A fonctionnent déjà "naturellement" en Swing. Par contre désolé mais pas de Control+Z pour le moment.
Dans les points suivants nous allons décrire comment cette interface à été écrite du macro vers le détail.
Nous ne détailleront pas la partie qui compile ici, puisque déjà fait ici : Compilation du code.
III.2) Code de l'éditeur
Rappelons que le code source est ici : Code source de l'éditeur et que nous avons déjà expliqué au début du chapitre ce dont vous aurez besoin pour le compiler.
jhelp.asm.editor.ui.CompilerEditorPanel est un panneau indépendant qui affiche ce qui sert à la compilation, c'est à direr les boutons d'actions, la zone de sasie, et la zone de message à droite.
Pour la zone de saisie du code au milieu, il contient une instance de jhelp.asm.editor.ui.ComponentEditor.
jhelp.asm.editor.ui.ComponentEditor est la zone de saisie, elle contient la description des mots clefs et la coloration syntaxique.
jhelp.asm.editor.ui.ClassesTree affiche l'arbre des classes sur la gauche.
jhelp.asm.editor.ui.DialogLaunchMethod est la boîte de dialogue qui permet le test d'une méthode. Elle affiche zéro ou plusieurs jhelp.asm.editor.ui.ParameterEditor pour la saisie de paramètres.
jhelp.asm.editor.ui.ParameterEditor choisit le composant graphique à afficher selon le type de paramètre.
jhelp.asm.editor.ui.FrameEditorASM est une simple fenêtre qui se contente d'afficher jhelp.asm.editor.ui.ClassesTree et jhelp.asm.editor.ui.CompilerEditorPanel.
jhelp.asm.editor.MainEditorASM Lance l'éditeur.
Maintenant les présentations faites, rentrons dans les détails.
On ne détaillera pas jhelp.asm.editor.MainEditorASM, jhelp.asm.editor.ui.FrameEditorASM ni jhelp.asm.editor.ui.ParameterEditor, nous pensons que leur code est suffisamment simple pour se comprendre de lui même.
III.2.a) jhelp.asm.editor.ui.DialogLaunchMethod
C'est la boîte de dialogue qui permet le test d'une méthode.
Nous avons choisit d'afficher la boîte de dialogue grâce à la méthode :
public static void showDialog(Component component, ClassManager classManager, String className, Method method)
La première fois où cette méthode est appelée, elle utilise le Component en paramètre pour connaître la fenêtre parent à qui s'attacher.
Pour que cela fonctionne il faut que le Component soit lui même une fenêtre où qu'il soit attaché à une fenêtre.
Dans notre cas le composant sera attaché depuis le début du lancement de l'application et la boîte de dialogue elle n'apparaît que l'orsque l'utilisateur va double cliquer sur une méthode de l'arbre. La contrainte est donc respectée.
Trouver la fenêtre où est attacher un composant est simple il suffit de remonter les parents jusqu'a trouver une fenêtre.
Nous avons choisit de rendre la fenêtre visible dans un thread, pour ne pas bloqué la GUI quelque soit l'endroit où on appel la méthode show.
La méthode :
void initializeDialog(ClassManager classManager, String className, Method method)
va initialiser la boîte de dialogue. La première fois elle crée les composant fixes et les places. A chaque fois, la première aussi, elle va ensuite vider la zone réservée à la saisie des paramètres pour la remplir selon les paramètres de la méthode à tester.
La méthode :
void test()
est appelée par le bouton Test quand l'utilisateur clique dessus. Elle collecte les valeurs saisie par l'utilisateur, invoque la méthode grâce à jhelp.compiler.instance.ClassManager (Voir II.2) Utilisation de jhelp.compiler.instance.ClassManager).
Elle affiche le résultat ou l'erreur.
III.2.b) jhelp.asm.editor.ui.ClassesTree
Affiche l'arbre des classes. Pour se faire elle hérite de javax.swing.JTree. Elle utilise comme modèle : jhelp.asm.editor.model.ClassesTreeModel et comme "renderer" pour les cellules de l'arbre : jhelp.asm.editor.renderer.ClassesTreeCellRenderer.
De plus elle écoute les compilations pour modifier l'arbre quand une compilation à réussit, ceci en implémentant : jhelp.asm.editor.event.CompilationListener.
Elle écoute aussi les événements souris pour réagir aux double clique.
jhelp.asm.editor.model.ClassesTreeModel est un javax.swing.tree.TreeModel. C'est lui qui gère la hiérarchie dans l'arbre, il détient des instances de jhelp.asm.editor.model.ClassInformation.
jhelp.asm.editor.model.ClassInformation contient la description d'une classe et ses méthodes.
jhelp.asm.editor.renderer.ClassesTreeCellRenderer est un jhelp.gui.JHelpLabel qui implémente javax.swing.tree.TreeCellRenderer.
Il met à jour son affichage selon la cellule à dessinée et si celle-ci est sélectionnée ou non.
III.2.c) jhelp.asm.editor.ui.ComponentEditor
C'est l'éditeur où le texte est saisie. Il affiche les numéro de lignes, fait de la coloration syntaxique et peut afficher une information à la droite des textes.
La majorité des fonctions sont hérités de jhelp.gui.JHelpAutoStyledTextArea (que nous expliqueront plus loin).
Ici est choisit la coloration syntaxique et la liste des mots clef.
Elle associe un jhelp.gui.JHelpSuggestion (que nous expliqueront plus loin) qu'elle remplit pour faire apparaître la liste des suggestions.
Ici on utilise la réflexion pour récupérer de help.compiler.compil.CompilerConstants la liste des mots clefs de déclarations. Même chose avec jhelp.compiler.compil.OpcodeConstants pour le liste des instructions de code.
Pour remplir les suggestions, on "parse" le fichier : jhelp/asm/editor/resources/keywords.txt dans jhelp.asm.editor.resources.EditorResources et on lui demande les informations pour chaque mot clef/instruction trouvé.
Le format du fichier jhelp/asm/editor/resources/keywords.txt est assez simple :
- Un(e) mot clef/instruction par ligne
- Si la ligne est de la forme <keyWord>|<informatio>|<details>
- ALORS on utilisera <keyWord> comme mot clef/instruction, <informatio> comme information supplémentaire et <details> comme explications sur le/la mot clef/instruction
- SINON la ligne est ignorée
Les détails peuvent être sous forme HTML ou de simple textes. Il est possible de mettre des images. Normalement Swing demande que le chemin des images soit absolue, mais nous avons résolu ce soucis en utilisant jhelp.util.text.UtilText et sa méthode public static String resolveImagesLinkInHTML(String html) à l'intérieur de jhelp.gui.JHelpSuggestion.
III.2.d) jhelp.asm.editor.ui.CompilerEditorPanel
C'est un panneau permettant l'édition du code, ouvrir un existant, sauvegarder compiler et afficher des messages.
Pour les boutons nous passons par le système des actions. Les actions sont pratique dans une interface graphique, elle permettent de centralisé le comportement pour une même tâche.
Par exemple si un bouton, un menu, un raccourcis clavier, ... partage la même action, on l'écrit une seule fois. De plus il est une bonne idée que chaqu'un partage la même instance de l'action générique.
Non seulement c'est bien pour la mémoire, mais aussi si on veut désactiver l'action, changer le texte, ... bref modifier un état de l'action il suffit de le modifier sur notre instance et les modifications se répercutent automatiquement sur tous les composants qui l'utilisent.
Pour créer une action nous héritons de jhelp.gui.action.GenericAction qui lui même hérite de javax.swing.AbstractAction.
Le panneau contient un jhelp.compiler.instance.ClassManagerListener pour réagir aux événements de compilation.
Pour déclarer les raccourcis clavier nous utilisons le couple javax.swing.ActionMap et javax.swing.InputMap.
javax.swing.ActionMap associe une clef à une action.
javax.swing.InputMap associe un raccourcis clavier à un mot clef.
Quand un événement clavier arrive, Swing va regarder dans javax.swing.InputMap si la combinaison tapée correspond à une clef.
Si une clef est trouvée, il utilise alors javax.swing.ActionMap pour savoir qu'elle action il doit jouer. Si une action est trouvée, l'exécute.
Pour stocker les préférence nous utilisons jhelp.util.preference.Preferences, cela nous permet ici de se souvenir du dernier fichier ouvert et du dernier répertoire utilisé.
Il contient le jhelp.asm.editor.ui.ComponentEditor pour l'afficher, récupérer le code saisie lors de la compilation et le piloter pour souligner les numéros de lignes et afficher l'état de la pile en cas d'erreur de compilation.
Pour pouvoir cacher/montrer la zone de message nous utilisons un jhelp.gui.JHelpFoldablePanel.
Nous redirigeons la sortie de System.out vers notre zone de message grâce à jhelp.gui.ConsolePrintStream.
void reportCompilerException(final CompilerException compilerException)
est appelée quand une erreur de compilation est parfaitement identifiée.
Elle obéit à algorithme suivant :
Afficher le message d'erreur
Positionner le curseur sur la ligne d'erreur
SI l'erreur est de type non respect de la pile
Souligner en bleu les numéros de lignes du parcours pour arriver à l'erreur
Ajouter l'effet sur la pile pour chaque étape du parcours
FIN-SI
Souligner en rouge le numéro de la ligne d'erreur
public boolean compile()
Lance la compilation du code saisie. Le boolean retournée indique si la compilation c'est effectivement lancée ou pas. Ici on empêche deux compilations en même temps.
Après avoir nettoyé la zone d'édition, et vérifié qu'il y ait au minimum la déclaration de classe, elle vérifie si la classe à déjà été résolu ou pas.
Cette vérification est obligatoire voir II.2) Utilisation de jhelp.compiler.instance.ClassManager.
Puis lance la compilation elle même.
Les autres méthodes sont simples à comprendre d'elle même.
III.3) Le package jhelp.gui
Comme nous l'avons vu l'éditeur utilise des classes de Utilitaires pour la créations d'interfaces.
Le but ici n'est pas de vous parler de toutes les classes du package, mais seulement des principales qui ont servit pour l'éditeur.
III.3.a) jhelp.gui.JHelpAutoStyledTextArea
C'est un éditeur de texte avec coloration syntaxique. Il affiche les numéros de ligne à sa gauche. Il a la capacité d'ajouté du texte d'information à la droite des lignes.
Il est basé sur javax.swing.JEditorPane.
III.3.a.1) La coloration syntaxique
Le principe de la coloration syntaxique est d'associer à une chaîne de caractère ou une expression régulière un style particulier.
On peut aussi choisir le style des symboles. Et bien sur changer le style par défaut quand rien ne match.
Pour se faire nous avons deux choses : une table d'association qui associe aux mots clef/expression régulière le style à y appliquer et un thread qui va mettre à jour le style dans le document de manière régulière.
Maintenant que ceci est expliqué, expliquons comment les styles fonctionnent dans un javax.swing.JEditorPane.
Les styles javax.swing.text.Style sont appliqués à des zones de textes. Il faut déterminer dans le texte où commence la zone à changer de style commence et où elle se termine. Une fois cette zone connue il faut dire au document que cette zone aura le style voulu.
Pour cela, la méthode setCharacterAttributes de javax.swing.text.DefaultStyledDocument peut être utilisée. Cette méthode est pratique dans notre cas, car nous voulons pouvoir avoir des styles différents au sein de la même lignes.
Nous vous invitons à regarder les autres méthodes de javax.swing.text.DefaultStyledDocument si votre besoin est d'appliquer sur toute une ligne ou tout un paragraphe. C'est d'ailleurs ce que nous utilisons pour changer la couleur des numéros de lignes et passer le texte supplémentaire. Nous en parleront dans le point suivant.
Maintenant pour comprendre le fonctionnement du thread RefreshStyle (Celui qui rafraîchit les styles) il faut dire en plus qu'il va calculer les zones où appliquer les styles
La logique suivit pour régler les conflits est : le plus grand gagne.
C'est à dire admettons, comme dans l'éditeur on est le mot int comme mot clef et qu'on est également une expression régulière pour définir les commentaires // (Commente toue ce qui suit jusqu'à la fin de la ligne.
Si on se retrouve dans la situation suivante :
// This int defines ...
On voit ici que le commentaire englobe le int, c'est donc le commentaire qui va s'appliquer, car il s'applique sur plus de caractères et est donc plus grand.
III.3.a.2) Les numéros de lignes et le texte supplémentaire à droite
Par défaut un javax.swing.JEditorPane n'a pas de numéro de ligne ni la capacité d'afficher de texte supplémentaire à droite.
Nous allons expliquer comment nous les avons ajoutés.
Pour rendre (dessiner) un document un javax.swing.JEditorPane utilise un javax.swing.text.StyledEditorKit à qui il demande un javax.swing.text.ViewFactory pour savoir quel composant il doit afficher les différentes sections d'un document.
Une section de document peut être :
- Un paragraphe, qui contient une liste de sections enfant.
- Une section qui est une boîte délimitant un paragraphe ou une ligne de texte à l'intérieur d'un paragraphe
- Un composant générique
- Un icône
- Une zone de texte
Ici nous allons nous occuper que des paragraphes, car c'est à eux qu'il faut ajouter les numéro de lignes et le texte additionnel
Nous avons donc notre propre javax.swing.text.StyledEditorKit : jhelp.gui.lineNumber.LineNumberEditorKit.
Il va renvoyer notre propre javax.swing.text.ViewFactory : jhelp.gui.lineNumber.LineNumberViewFactory.
Lui même va renvoyer les vues par défaut pour les parties qu'on ne modifie pas, et notre propre javax.swing.text.ParagraphView : jhelp.gui.lineNumber.LineNumberParagraphView pour dessiner les paragraphes avec numéro et texte supplémentaire.
Avant de parler du code de jhelp.gui.lineNumber.LineNumberParagraphView, rappelons notre but de manière précise :
- Marquer à gauche le numéro de lignes et pouvoir ajouter du texte à droite de lignes.
- Une ligne doit être marqué par un retour à la ligne ('\n')
- Ce n'est pas parce que visuellement on est sur une autre ligne, du à un manque de place en largeur, que l'on a changé de ligne
- Si c'est la même ligne qui continue on ne veut pas répéter le numéro de ligne, juste l'afficher à la première ligne visuelle de la vraie ligne. De même pour le texte additionnel
Après plusieurs tests, nous avons observé la chose suivante sur le paragraphe :
--> Il contient toujours une et une seule vraie ligne. Ces enfants sont les lignes visuelles.
Donc il suffira d'écrire le numéro de la ligne et le texte supplémentaire à coté du premier enfant et de ne rien faire, autre que le comportement par défaut, pour les autres enfants.
Reste à savoir à quel numéro de ligne on est. Pour cela on sait que les paragraphes sont dans une liste du haut vers le bas dans leur parent. Il suffit donc de savoir à quel index se trouve le paragraphe en cours et de rajouter 1 (En effet un numéro de ligne commence à 1).
Fort de ces éléments nous pouvons maintenant parler du code lui même.
Il nous faut réserver de la place pour notre numéro de ligne et notre texte supplémentaire si il existe.
Pour cela on surcharge la méthode :
Ce qui ajoute une marge à gauche pour le numéro et une à droite si le texte supplémentaire est à afficher.
Nous avons également surchargé la méthode :
Pour la redirigée vers notre méthode afin d'ajouter la marge.
Et pour dessiner on surcharge la méthode :
Vous remarquerez, comme dit plus haut, on ne dessine que sur le premier enfant.
Pour calculer l'index dans le parent nous utilisons :
Voilà pour l'affichage lui même. Nous avons voulu pouvoir nous passer des paramètres pour pouvoir changer la couleur du fond du nombre et le texte afficher à droite.
Pour cela nous avons créer nos propre clef dans la description d'un paragraphe.
Afin de récupérer ces valuers nous avons surchargés la méthode :
Elle est appelée à chaque fois que les attribut de paragraphe change, c'est donc exactement ce qu'il nous faillait.
Revenons maintenant à jhelp.gui.JHelpAutoStyledTextArea pour voir comment utiliser ces nouveaux attributs.
Ce sont les méthodes : public void addTemporaryTextInformation(int lineNumber, String text)
et public void changeTemporaryLineNumberBackground(int lineNumber, Color color)
qui vont nous intéresser ici. Elles fonctionnent toutes deux de la même façon :
- Trouver le paragraphe à qui appliquer le changement
- Définir le nouvel attribut au paragraphe trouvé
Pour récupérer la paragraphe on utilise la méthode getParagraphElement de javax.swing.text.DefaultStyledDocument.
Mais cette méthode demande une position dans le texte et nous avons un numéro de ligne. dans ce cas utilisons la méthode lineNumberToPosition de jhelp.gui.JHelpAutoStyledTextArea et le tour sera joué.
Pour modifier les attributs de l'élément paragraphe trouvé, nous les récupérons. Mais ils ne sont pas modifiables directement.
Pour contourner ce problème nous créons une copie modifiable de attributs, que nous modifions et nous redéfinissons le style du paragraphe.
Ce qui donne :
Le code de public void changeTemporaryLineNumberBackground(int lineNumber, Color color)
est quasiment le même.
Si vous avez l'intention de faire votre propre javax.swing.JEditorPane nous vous conseillons de regarder le code de :public void scrollToPosition(int position)
III.3.b) jhelp.gui.JHelpSuggestion
Dans l'éditeur c'est la fenêtre de suggestion qui apparaît quand on tape du texte.
Cette fenêtre se lie à un javax.swing.text.JTextComponent, qui est classe parent des zone de saisie texte de swing.
C'est une dépendance forte dans le sens où on ne peut pas changer de composant de texte en cours de vie.
Elle contient une liste de jhelp.gui.model.SuggestionElement. Ces éléments décrivent une suggestion.
Une suggestion est composée d'un mot clef qui sera inséré si l'utilisateur sélectionne la suggestion. Ce mot clef sert aussi au filtre pour savoir si une suggestion doit être visible ou non.
Une suggestion peut éventuellement possédée une information dont la signification dépend du développeur qui utilise la classe. Dans l'éditeur on s'en sert pour afficher une petite description juste à coté du mot clef
Une suggestion peut éventuellement possédée un texte de détails. Dans l'éditeur on s'en sert pour afficher la description d'un mot clef.
Le texte peut être au format HTML. Pour le image il faudrait en théorie leur chemin absolu de l'image. Pour simplifier les choses nous avons ajouter deux protocoles :
- Le protocole dit de classe. Il suffit de spécifier une classe et le chemin relatif de l'image par rapport à la classe pour pouvoir l'atteindre .Le format est :
class:<classeCompletName>/<relativePathOfImage>
Pour le chemin relatif, la notation .. (Remonte au parent) est autorisé. Par exemple si vous avez la hiérarchie suivante dans votre projet :org
|- company
| |-resources
| | |-ResourcesManager.java
| | | |- images
| | | | |- icon.png
...
il suffit d'utiliser le chemin : class:org.company.resources.ResourcesManager/images/icon.png pour atteindre icon.png
- Le protocole dit externe. Le but cette fois est d'atteindre non pas une image intérieure au jar de votre projet, mais une image stockée "à cotée" de lui. Il suffit de saisir le chemin relatif au jar. Le format est :
external:<relativePathOfImage>
Comme tout à l'heure, la notation .. (Remonte au parent) est autorisé. Par exemple si votre jar est déployé ainsi :...|- greatApplication
| |- CoolApplication.jar
| | |- resources
| | | |- images
| | | | |-icon.png
...
il suffit d'utiliser le chemin : external:/resources/images/icon.png pour atteindre icon.png
Pour afficher la liste des suggestions et l'éventuel détail associé à une suggestion, nous utilisons un javax.swing.JPopupMenu.
Dans ce javax.swing.JPopupMenu nous avons à gauche la liste des suggestions et à droite la zone d'aide.
Voici les différentes étapes que suit le code.
Tout d'abord on est enregistré aux événements de changement de curseur pour savoir si on la popup doit se montrée ou pas.
A chaque fois que le curseur change on récupère le début de mot formé entre la position du curseur et l'espace, la tabulation ou le début de ligne juste avant le curseur (Le premier rencontré gagne).
On vérifie que ce n'est pas une chaîne vide, si c'est pas le cas, on regarde combien de suggestions correspondes a ce début de mot.
Si il n'y a pas de suggestion, on ferme la popup (si il est déjà ouvert) et on s'en va.
Si il y a une seule suggestion on regarde si le mot sous le curseur (On tient en compte aussi cette fois les caractère après le curseur). Si ce mot est exactement celui de la suggestion alors on n'affichera pas la popup. Sinon elle s'affichera
Si il y a plus d'une suggestion la popup s'affichera.
Si la popup doit s'afficher (Plus d'une suggestion ou si une ce n'est pas le mot sous le curseur), alors on calcul où doit s'afficher le popup.
Ce calcul peut être un peu pénible. Il faut d'abord savoir où est le curseur à l'écran. La plus part du temps faire :
Point position = this.attachedComponent.getCaret().getMagicCaretPosition();
nous donne la bonne réponse. Malheureusement quelques fois elle nous répond null. Nous sommes alors obligé d'utilisé une méthode plus lourde :
Rectangle box = this.attachedComponent.modelToView(caretPosition);
Avoir la position du curseur ne suffit pas, nous voulons afficher la suggestion sous le caractère tapé, pas au dessus de lui.
Il faut donc calculer la hauteur de la ligne, pour cela on récupère la police de caractère ou se trouve le curseur et on mesure sa hauteur. Maintenant on sait de combien décaler la position en y pour positionner la popup au bon endroit.
Vous remarquerez, dans le code de void positionCaretChange(), que l'on fait :if(this.popupMenu.isShowing() == true)
{
// If already show, just change the position
final Point location = this.attachedComponent.getLocationOnScreen();
this.popupMenu.setLocation(position.x + location.x, position.y + location.y);
}
else
{
// Shows the suggestion list
this.popupMenu.show(this.attachedComponent, position.x, position.y);
}
La différence entre setLocation et show est que setLocation attends des coordonnées de l'écran tandis que show des coordonnées relative au composant au dessus duquel la popup est montrée.
Une autre difficulté qui se présente est que la popup prend le focus, il est normalement pas possible pour l'utilisateur de continuer de taper du texte tant qu'elle visible.
Pour contourner ce problème nous écoutons les événements claviers dans la popup et nous envoyons à la main les caractères saisies au composant texte.
La méthode void keyTyped(int keyCode, char keyChar) va se charger des caractères ordinaires.
La méthode void keyCommand(final int keyCode, final int modifiers) quand à elle s'occupe des touche du clavier qui ne sont pas des caractères normaux. Comme les touches Delete, BackSpace, flèche du haut, du bas, ...
On en a profiter pour offrir une navigation au sein de la liste avec le clavier.
Pour gérer la liste qui change selon le filtre nous utilisons un jhelp.gui.model.ListFiterableModel .
III.3.c) Deux derniers pour la route
Ces classes ne méritent pas un points à elles seules, alors on a décidé de les mettre ici.
jhelp.gui.JHelpFoldablePanel. Il s'agit d'un composant que l'utilisateur peut cacher/montrer à l'aide d'une zone cliquable. C'est le composant utiliser dans l'éditeur pour cacher/montrer la zone de message ou l'arbre des classes compilée.
Pour cacher/montrer le composant nous utilisons tout simplement la méthode setVisible.
Vous pouvez choisir ou se trouvera la zone de clique par rapport au composant à cacher/montrer et aussi changer la police ou les couleurs de textes et de fond de la zone de clique.
jhelp.gui.model.ListFiterableModel. C'est le modèle de liste qu'utilise JHelpSuggestion pour filtrer les éléments à cacher/montrer dans la liste.
C'est une modèle de liste générique, donc réutilisable ailleurs, avec en plus la capacité de filtrées des éléments.
Pour filtrer les éléments on lui fournit un filtre qu'il utilise pour cacher/montrer les éléments.
Son intérêt est qu'il évite de reconstruire une liste à chaque fois que le filtre change. La liste reste la même, il marque seulement les éléments qu'il doit montrer ou cacher et fait croire à la liste qu'il n'y a que les éléments à montrer dans le modèle.
Le filtre donné peut changé, le modèle en sera avertit et donc se mettra à jour et avertira la liste du changement.