Tutoriel sur la customisation des éditeurs Eclipse GMF

Auteur

Pierre Cressant

Le but de cet atelier est d'apprendre à manipuler le framework de surcouche graphique GMF. Ce tutoriel s'adresse aux personnes souhaitant développer une interface utilisateur pour leur modèle EMF. Nous couvrirons le développement basique d'une application GMF ainsi qu'un approfondissement dans la customisation des éléments du framework.

- Réutilisation de modèles EMF,

- Conception de la surcouche GMF,

- génération de code Java,

- manipulation d'API GMF pour instancier le modèle,

- Customisation des éléments principaux de GMF : Noeuds, liens, palette...

- Apperçu d'un élément avancé.

Ici : liens vers deux fichiers nécessaires au bon déroulement du tutoriel : AddressBookPopupBarEditPolicy.java et enveloppe.svg

- Titre : Tutoriel sur la customisation des éditeurs Eclipse GMF

- Auteur : Pierre Cressant

- Parution : 22 décembre 2013

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Pierre Cressant. 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'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- I-A. Prérequis

- I-B. Installation

- I-B-1. Version d'Eclipse

- I-B-2. Les différents plugins pour faire marcher GMF

- I-C. Exemple

- II. Une version basique de GMF

- II-A. Modification du modèle original

- II-B. Le premier résultat GMF

- II-B-1. Le .gmftool

- II-B-2. Le .gmfgraph

- II-B-3. Le .gmfmap

- III. Personnalisation d'un noeud

- III-A. Forme du noeud : Utilisation de draw2d et / ou d'image SVG

- III-B. Couleur du noeud

- III-C. Les labels et les icônes

- IV. Personnalisation d'un lien

- IV-A. Les différents attributs des liens

- IV-B. Ajouter un label

- V. Personnalisation de la fenêtre

- V-A. Grille d'alignement

- V-B. Règle, grille et unités

- VI. Personnalisation de la Palette

- VI-A. Changer les différentes icônes

- VI-B. Ranger pour mieux régner

- VII. Personnalisation avancée : la popup-bar

- VII-A. Qu'est-ce à dire que ceci ?

- VII-B. Marche à suivre

- VIII. Conclusion et liens

I - Introduction

Le but et l'intérêt premier de ce tutoriel est, à partir d'une version basique d'un éditeur GMF, d'obtenir quelque chose de visuellement agréable et possédant une utilisation facilitée.

I-A - Prérequis

Afin de savourer pleinement ce tutoriel avancé sur GMF, je recommande d'avoir une certaine expérience sur EMF et GMF. Pour cela, il est possible de trouver plusieurs tutoriels sur ce même site ou simplement en parcourant la toile.

I-B - Installation

I-B-1 - Version d'Eclipse

Le tutoriel a été développé sur la version 4.2 (Juno). http://www.eclipse.org/juno/

I-B-2 - Les différents plugins pour faire marcher GMF

Pour réaliser un éditeur exploitant la technologie GMF, il est obligatoire d'installer quelques plug-ins nécessaires au bon fonctionnement du framework. Il suffit d'aller dans
Help --> Install New Software.
Sélectionner
All site available
dans la liste
Work with.
Et enfin, taper
GMF
dans la barre de recherche. Sélectionner tous les plug-ins présents dans le sous-menu modeling. Nous sommes enfin prêt pour l'aventure GMF avancé.

I-C - Exemple

Ce tutoriel est une suite logique d'un précédent cours basé sur EMF :
http://mbaron.developpez.com/tutoriels/eclipse/emf/creation-instanciation-modeles/

Le modèle EMF ici utilisé est celui du carnet d'adresses. Voici son UML :

En suivant toutes les étapes de la partie II, nous obtiendrons éditeur simple, permettant de gérer des carnets d'adresses.

Cependant, cet éditeur n'est absolument pas utilisable. Le comprendre n'est pas évident, l'exploiter encore moins. C'est pour cela que nous allons y appliquer une surcouche via le framework GMF.

II - Une version basique de GMF

Ce framework va permettre de mettre en place un éditeur graphique utilisable et compréhensible par la majorité des utilisateurs. Dans cette partie, nous allons brièvement rappeler les étapes permettant d'obtenir l'éditeur de base de GMF. Si vous souhaitez obtenir plus de détails sur cette étape primordiale du développement, référez-vous à ces différents tutoriels : [1], [2] et [3]

II-A - Modification du modèle original

Afin que le modèle puisse être parfaitement adapté à GMF, il est nécessaire d'y apporter quelques modifications :

- Changer le lien location de la classe Person en 0..1,

- La relation entre Person et Adress devient une agrégation,

- Créer un lien entre AddressBook et Address. En effet, nous souhaitons modéliser un seul carnet d'adresses, et la représentation par GMF des liens entre classes nécessite une connection entre la racine et chaque classe que l'on souhaite afficher.

II-B - Le premier résultat GMF

Pour les absents, voici un bref rappel des différentes étapes pour obtenir votre premier éditeur graphique.
Créer un nouveau projet dans Graphical Modeling Framework --> Graphical Editor Project, choisir un nom et cocher la case permettant d'afficher le
gmf dashboard
. Cette fenêtre est l'outil principal de génération.

Récupérer le fichier ecore du précédent tutoriel sur EMF. Nous allons en faire notre fichier Domain Model. Déplacer le fichier dans le dossier model du projet. Retour sur le dashboard.
Cliquer sur Select de la case Domain Model et choisir le modèle EMF importé via précédent tutoriel.
Après cette étape, nous devons générer le fichier .genmodel. Pour cela, il suffit de cliquer sur Derive à gauche du Domain Model.

Nous obtenons un fichier .genmodel qui permettra de générer les codes d'implémentation du modèle. Pour cela, il nous faut ouvrir le fichier et effectuer un clique droit sur la racine.

Cliquer sur Generate All. Cela aura pour effet de générer 3 nouveaux projets et le code d'implémentation du modèle ecore de l'adressBook.

Cette étape passée, nous pouvons attaquer les attributs propres à GMF. Pour organiser au mieux le projet, je conseille de créer un nouveau dossier afin d'y stocker les fichiers propres à la modélisation graphique (view par exemple).

II-B-1 - Le .gmftool

Maintenant, nous pouvons générer l'aspect que va prendre les noeuds ainsi que les outils de création d'instances EMF. Nous allons commencer par les outils.

Nous obtenons donc un fichier .gmftool. Ce dernier permet de définir 3 outils permettant de créer une personne, une adresse et un lien entre eux. Nous y reviendrons plus tard pour y apporter quelques modifications.

II-B-2 - Le .gmfgraph

Maintenant, il faut générer le fichier permettant de définir la forme et l'apparence des noeuds et Labels du futur diagramme.

Après tout ça, nous obtenons donc un fichier permettant de générer l'aspect de l'éditeur. Tout est modifiable, nous y reviendrons au prochain chapitre.

II-B-3 - Le .gmfmap

La prochaine étape concerne la liason des 3 principaux fichiers. En effet, toutes les informations sont stockées dans le modèle ecore, le .gmftool et .gmfgraph.

Il existe un souci lors de la génération du .gmfmap. En effet, il faut bien vérifier que les outils choisis soit les bons pour les différentes représentations. Regardez bien dans les propriétés que les bons outils sont liés aux bonnes figures (Node Mapping <Address/Address> --> creation tool Address).
Maintenant que vous avez toutes les pièces en main, il vous suffit de les transformer en un générateur de code unique.

Et voilà, nous pouvons générer le diagramme. Nous obtenons un nouveau projet : AdressBook.diagram. Pour jeter un oeil à l'application de base, éxecuter le diagramme. Pour cela, créer une nouvelle application Eclipse.
Dans l'onglet plug-ins, launch with : plug-ins selected below only. Sélectionner tous les plug-ins AdressBook du workspace. Ensuite, cliquer sur Add Required Plug-ins. Il est probable que l'application ne fonctionne pas. Ayant eu des problèmes avec mon application aussi, ajouter org.eclipse.ui.ide.application et org.eclipse.core.net
Dans la nouvelle instance d'Eclipse, créer un nouveau projet. Dans ce projet, y ajouter un nouvelle exemple et choisir AddressBook Diagram.

Et là, révélation : "Mon dieu, que c'est laid ! Comment puis-je faire pour améliorer mon interface et la rendre merveilleusement utilisable ?". La suite de ce tutoriel repose sur des trucs et astuces que j'ai découvert pendant la réalisation d'un projet. La liste que je propose est loin d'être exhaustive. Mais ce sont des choses que j'ai découverte au fur et à mesure que j'utilisais le framework GMF. Ainsi, je ne peux que vous conseiller de mettre les mains dans le cambouis et essayer de découvrir par vous même. Cependant, je mets à disposition quelques idées et quelques méthodes pour mettre en place un éditeur correct et pas désagréable à regarder.

III - Personnalisation d'un noeud

Comme vous le savez sûrement, les figures utilisées par GMF sont issus de l'API Draw2d. Donc avant toute chose, je vous conseille d'aller jeter un oeil sur cette API et d'explorer par vous même les possibilités de cette partie. Le framework GMF peut s'utiliser de deux façons :

- La méthode de génération automatique depuis les fichiers évoqués dans la partie précédente finalisés par des retouches dans le code.

- Écrire tout le code à la main (seulement pour les vrais barbus).

III-A - Forme du noeud : Utilisation de draw2d et / ou d'image SVG

Pour cela, il suffit de changer le type de forme dans le gmfgraph. Changer le Rectangle PersonFigure en ce que vous souhaitez. Il est également possible de dessiner la forme qui nous intéresse le plus. Pour cela, je vous conseille le site suivant : [4].
Il est aussi possible d'utiliser des images SVG pour mettre en forme votre noeud. Ici, nous allons changer notre rectangle d'adresse par une enveloppe. Pour cela, il suffit de se servir du plugin org.eclipse.gmf.runtime.lite.svg [5]. Dans le fichier gmfgraph, remplacer le rectangle AddressFigure par un SVGFigure pour obtenir une nouvelle forme.

Transformer et générer le diagramme. Vous devriez obtenir le résultat suivant :

III-B - Couleur du noeud

Il est également possible de customiser les couleurs des noeuds. Pour cela, il suffit de rajouter un fils à l'élément de figure de votre choix :

III-C - Les labels et les icônes

Nous avons envie de modifier le noeud représentant une personne. L'objectif ici est d'obtenir un affichage des différentes informations considérées pertinentes de l'objet Personne. Pour cela, direction le gmfgraph. Je conseille fortement de créer un rectangle pour chaque sous partie d'un noeud afin de ne pas compliquer chaque layout :

Il est important d'appliquer un layout à tout les labels sans quoi ils ne seront pas affichés dans la figure. Il est également nécessaire de modifier les propriétés des layout des labels PersonIdentifierFigure et PersonAgeFigure.
Passer les paramètres de Grab Excess Horizontal Space et Grab Excess Vertical Space à true. Modifier les Horizontal Alignement de FamilyName et FirstName dans leur GridLayout Data : respectivement BEGINNING et END.
Les labels permettent aussi d'afficher les icônes. Par défaut, l'affichage est activé pour tous les labels. Pour les désactiver, il suffit de passer la propriété Element Icon des éléments suivant à false :

Indiquer à GMF dans le GMFMap l'affichage souhaité :

Les feature labels permettent d'afficher les labels contenant un attribut. Ici, nous souhaitons afficher le nom de famille, le prénom et l'âge. Ce seront trois labels automatiques générés par GMF. L'identifiant sera fait à la main.
Pour l'âge, passer le read only à true. Il est également possible de changer l'affichage d'un texte via ces labels. Pour le feature label de l'âge, modifier le view pattern : {0} ans. Le {0} correspond au premier attribut qui sera affiché par le label.
Transformer et ouvrir le fichier gmfgen. Modifier les noms de EditPart :

Transformer et générer à nouveau l'éditeur. Accéder au fichier PersonEditPart. Il faut vérifier les placements dans le BorderLayout, GMF ne sachant pas les placer lors de la génération. Changer l'assignation de chaque sous rectangle. Dans la fonction createContents() :

1. this.add(rectangleTopFigure0, BorderLayout.TOP);
2. this.add(rectangleCenterFigure0, BorderLayout.CENTER);
3. this.add(rectangleBottomFigure0, BorderLayout.BOTTOM);

Rajouter également la ligne suivante :

1. fFigurePersonIdentifierFigure.setText("Identifier : nullnull0");

Pour avoir un renouvellement automatique de l'identifiant, overrider la fonction handleNotificationEvent de PersonEditPart :

1. /**
2.  * @generated NOT
3.  */
4. @Override
5. protected void handleNotificationEvent(Notification notification) {
6.     if (notification.getNotifier() instanceof Person) {
7.         if (notification.getFeature() instanceof EAttributeImpl) {
8.             String name = ((EAttributeImpl) notification.getFeature())
9.                     .getName();
10.             if (name.equals("age") || name.equals("firstName")
11.                     || name.equals("familyName")) {
12.                 this.getPrimaryShape()
13.                         .getFigurePersonIdentifierFigure()
14.                         .setText("Identifier : "
15.                             + ((Person) notification
16.                                     .getNotifier())
17.                                     .getIdentifier());
18.             }
19.         }
20.     }
21.     super.handleNotificationEvent(notification);
22. }

Pour modifier l'icône, se placer dans PersonFamilyNameEditPart et changer la fonction getLabel pour retourner une image.

1. /**
2.  * @generated NOT
3.  */
4. protected Image getLabel() {
5.     Bundle bundle = Platform.getBundle("AdressBook.diagram");
6.     IPath path = new Path("icons/node/person.gif");
7.
8.     URL url = FileLocator.find(bundle, path, null);
9.     ImageDescriptor desc = ImageDescriptor.createFromURL(url);
10.     return desc.createImage();
11. }

IV - Personnalisation d'un lien

IV-A - Les différents attributs des liens

GMF est un outil permettant une customisation très avancée d'un éditeur graphique. Il est donc possible d'améliorer l'apparence d'un lien entre deux noeuds. Ces modifications peuvent être apportées directement dans l'éditeur. Cependant, il est souvent souhaitable de changer les paramètres par défaut. Par exemple, nous désirons obtenir des liens orthogonaux et assez intelligent pour éviter les autres formes présentes sur le graphique.
Pour cela, ouvrir le fichier PersonLocationEditPart et modifier la fonction createConnectionFigure() :

1. protected Connection createConnectionFigure() {
2.     PersonLocationFigure figure = new PersonLocationFigure();
3.     AbstractEMFOperation emfOp = new AbstractEMFOperation(
4.             getEditingDomain(), "line routing setting") {
5.
6.         @Override
7.         protected IStatus doExecute(IProgressMonitor monitor,
8.                 IAdaptable info) throws ExecutionException {
9.             RoutingStyle style = (RoutingStyle) ((View) getModel())
10.                     .getStyle(NotationPackage.Literals.ROUTING_STYLE);
11.             style.setRouting(Routing.RECTILINEAR_LITERAL); // or
12.                                                             // Routing.TREE_LITERAL
13.             style.setAvoidObstructions(true);
14.             style.setClosestDistance(true);
15.             return Status.OK_STATUS;
16.         }
17.
18.     };
19.
20.     try {
21.         OperationHistoryFactory.getOperationHistory().execute(emfOp, null,
22.                 null);
23.     } catch (ExecutionException e) {
24.     throw e;
25.     }
26.
27.     return figure;
28. }

IV-B - Ajouter un label

Nous pouvons également choisir de rajouter un label à chaque lien pour mieux le détailler. Aller dans le fichier genmap, et supprimer le label mapping du numéro d'adresse pour le noeud correspondant. Ensuite, créer un fils pour le link mapping de Location. Nous pouvons également créer un label dynamique permettant l'affichage d'une variable. Pour ce dernier, je préconise d'utiliser une entité sous forme de lien plutôt qu'une relation entre deux objets [6].

V - Personnalisation de la fenêtre

V-A - Grille d'alignement

Afin d'afficher les lignes permettant d'aligner les figures entre elles, il est nécessaire rajouter la fonction suivante dans la classe DiagramRulersAndGridPreferencePage :

1. /**
2.  * @generated NOT
3.  * @param preferenceStore
4.  */
5. public static void initDefaults(IPreferenceStore preferenceStore) {
6.     preferenceStore.setDefault(IPreferenceConstants.PREF_SNAP_TO_GEOMETRY,
7.             true);
8.     preferenceStore.setDefault(IPreferenceConstants.PREF_SNAP_TO_GRID,
9.             false);
10. }

V-B - Règle, grille et unités

Afficher une règle ainsi qu'une grille est également possible. Pour cela, rajouter ces quelques lignes à la fonction précédente :

1. preferenceStore.setDefault(IPreferenceConstants.PREF_SHOW_RULERS, true);
2. preferenceStore.setDefault(IPreferenceConstants.PREF_GRID_SPACING, 1);
3. preferenceStore.setDefault(IPreferenceConstants.PREF_RULER_UNITS,
4.     RulerProvider.UNIT_CENTIMETERS);
5. preferenceStore.setDefault(IPreferenceConstants.PREF_SHOW_GRID, true);

VI - Personnalisation de la Palette

VI-A - Changer les différentes icônes

Modifier les paramètres du fichier gmftool. En effet, chacun des outils possède des enfants lui permettant de retrouver son icône. Afin de les modifier, supprimer les enfants déjà présents et les remplacer par Small Icon Bundle Image. Dans les propriétés, changer le Bundle par AdressBook.diagram. Ensuite, changer le chemin par celui de l'icône au sein du plug-in :

VI-B - Ranger pour mieux régner

Il est possible de créer des menus pour ranger les outils et ainsi mieux les ordonner. Créer un nouveau tool group, et modifier ses propriétés :

Déplacer les outils précédemments créés. Faire de même pour les liens :

VII - Personnalisation avancée : la popup-bar

VII-A - Qu'est-ce à dire que ceci ?

Le popup-bar est une des politiques d'attitudes des attributs GMF. En effet, l'exemple le plus simple serait de citer la petite popup que vous obtenez si vous laissez votre souris immobile dans la fenêtre princpale. Utile me direz-vous. Et vous auriez totalement raison. Si dans un cas bien précis, vous souhaitez créer plusieurs versions d'un noeud, par exemple, un homme ou une femme, vous devez d'abord créer le noeud par défaut puis le modifier. Et bien, je vais vous montrer comment, sur une popup, avoir les deux !
Tout d'abord, modifier le modèle Ecore :

Regénérer l'éditeur.

VII-B - Marche à suivre

Il existe une classe en Singleton dans GMF nommée ElementInitializer. Cette classe permet d'appliquer des changements à un objet avant qu'il ne soit afficher en tant que noeud dans l'éditeur. Ici, nous aimerions choisir homme ou femme à la création. Nous allons y ajouter deux fonctions et un attribut :

1. private Sex _sexe;
2.
3. /**
4.  * @generated NOT
5.  */
6. public void setSex(Sex value) {
7.     this._sexe = value;
8. }
9.
10. /**
11.  * @generated NOT
12.  */
13. public void initializePerson(Person newPerson) {
14.     newPerson.setSexe(this._sexe);
15.     this._sexe = Sex.HOMME;
16. }

Maintenant, nous devons indiquer à GMF d'appliquer cette modification à la création d'une personne. Pour cela, il faut modifier le ficher PersonCreateCommand et plus précisement, la fonction doExecutWithResult() :

1. /**
2.  * @generated NOT
3.  */
4. protected CommandResult doExecuteWithResult(IProgressMonitor monitor,
5.         IAdaptable info) throws ExecutionException {
6.     Person newElement = AddressbookFactory.eINSTANCE.createPerson();
7.
8.     ElementInitializers.getInstance().initializePerson(newElement);
9.
10.     AddressBook owner = (AddressBook) getElementToEdit();
11.     owner.getContainsPerson().add(newElement);
12.
13.     doConfigure(newElement, monitor, info);
14.
15.     ((CreateElementRequest) getRequest()).setNewElement(newElement);
16.     return CommandResult.newOKCommandResult(newElement);
17. }

Le modificateur est maintenant prêt. Le plus compliqué reste à faire. Télécharger le fichier AdressBookPopupBarEditPolicy. J'y ai rajouté plusieurs éléments :

- Rajout du sexe dans le PopupBarDescriptor. Ce descriptor contient l'image, le texte et le type d'objet à ajouter. Maintenant, il contient le sexe que nous pourrons, par la suite, envoyer à nos précédentes méthodes.

- Rajout du descriptor dans le PopupBarLabelHandle. Pourquoi ? Simplement pour pouvoir y retrouver notre sexe contenu dans ce même descriptor. Cette étape est importante puisque que le listener permettant de sélectionner notre sexe se trouve sur cette handle. Il se trouve dans la méthode handleMousePressed(MouseEvent event).

- Dans cette fonction, nous rajoutons la ligne suivante : ElementInitializers.getInstance().setSex(this.getDesc().getSex());

- Modification de la fonction populatePopupBars() :

1. protected void populatePopupBars() {
2.     fillPopupBarDescriptors();
3.     IElementType type = AddressbookElementTypes.Person_2001;
4.
5.     Bundle bundle = Platform.getBundle("AdressBook.diagram");
6.     for (Sex s : Sex.VALUES) {
7.         Path path = new Path("icons/popup/" + s.getValue() + ".gif");
8.         URL url = FileLocator.find(bundle, path, null);
9.         ImageDescriptor desc = ImageDescriptor.createFromURL(url);
10.         addPopupBarDescriptor(type, desc.createImage(), s);
11.     }
12. }

VIII - Conclusion et liens

Ce tutoriel est purement à titre indicatif. GMF est une technologie complexe et vaste. Je me permets de faire passer quelques liens, qui m'ont été très utiles, ils pourront sûrement aider dans la quête du parfait éditeur.
[1] http://sbachmann.developpez.com/eclipse/generer-editeurgraphique-gmf/
[2] http://gmfsamples.tuxfamily.org/wiki/doku.php
[3] http://wiki.eclipse.org/Graphical_Modeling_Framework/Tutorial#Get_started
[4] http://gmfsamples.tuxfamily.org/wiki/doku.php?id=gmf_tutorial4
[5] https://github.com/YattaSolutions/org.eclipse.mylyn.mft.taipan/tree/master/org.eclipse.gmf.runtime.lite.svg
[6] http://gmfsamples.tuxfamily.org/wiki/doku.php?id=gmf_tutorial8

Petits liens utiles :
http://www.jevon.org/wiki/GMF_Code_Samples
http://wiki.eclipse.org/GMF_Newsgroup_Q_and_A
http://wiki.eclipse.org/GMF/Tips
https://gist.github.com/snursmumrik/5532388 -- Ajouter des outils à une palette