août
2009
A l’étape du billet précédant [step14] nous avons mis en place le layout automatique et finalisé la partie GEF traitant la représentation graphique du workflow XML. A partir de maintenant nous allons nous concentrer sur les fonctionnalités d’édition GEF (palette d’outils, intéraction avec le clavier (Ctrl+Z..)). Dans ce billet nous allons mettre en place la palette GEF qui proposera 2 outils :
- un outil de création de states qui permettra à partir de cette palette d’ajouter un modèle EMF StateType au modèle EMF WorkflowType.
- un outil de création d’actions qui permettra à partir de cette palette d’ajouter un modèle EMF ActionType au modèle EMF WorkflowType.
Voici une copie d’écran ou le state « s3″ a été ajouté via l’outil de création de states :
Vous pouvez télécharger le projet org.example.workflow_step15.zip présenté dans ce billet.
WorkflowEditorPaletteFactory
A ce stade, la palette d’outil GEF apparaît vide, car la méthode GraphicalWorkflowEditor#getPaletteRoot() de la classe org.example.workflow.presentation.graphical.GraphicalWorkflowEditor retourne null :
protected PaletteRoot getPaletteRoot() {
return null;
}
Nous allons ici implémenter cette méthode en utilisant la factory de palettes WorkflowEditorPaletteFactory. Nous pourrions directement implémenter cette méthode sans cette factory, mais celle-ci permet de rendre le code plus compréhensible et tous les exemples GEF se basent aussi sur cette idée de factory de palettes.
WorkflowEditorPaletteFactory
Créez la factory de palettes GEF org.example.workflow.presentation.graphical.WorkflowEditorPaletteFactory comme suit :
import org.eclipse.gef.palette.PaletteContainer;
import org.eclipse.gef.palette.PaletteDrawer;
import org.eclipse.gef.palette.PaletteRoot;
public class WorkflowEditorPaletteFactory {
private static PaletteContainer createComponentDrawer() {
PaletteDrawer componentsDrawer = new PaletteDrawer("Workflow");
return componentsDrawer;
}
static PaletteRoot createPalette() {
PaletteRoot palette = new PaletteRoot();
palette.add(createComponentDrawer());
return palette;
}
}
La méthode statique WorkflowEditorPaletteFactory#createPalette() retourne une instance de org.eclipse.gef.palette.PaletteRoot qui est la palette racine de notre éditeur. PaletteRoot :
- étend la classe de base d’une entrée de palette org.eclipse.gef.palette.PaletteEntry.
- a une méthode PaletteContainer#add(PaletteEntry entry) qui attend une entrée de palette.
Nous ajoutons a la palette racine, une instance org.eclipse.gef.palette.PaletteDrawer (étend org.eclipse.gef.palette.PaletteEntry) qui permet d’avoir une entrée « Workflow » dans la palette. Elle contiendra d’autres entrées de palettes (qui seront nos outil de création de states et nos outil de création d’actions).
PaletteEntry
Voici la hiérachie de org.eclipse.gef.palette.PaletteEntry :
On peut voir que l’on peut ajouter à la palette 2 grands types d’entrées :
- org.eclipse.gef.palette.PaletteContainer, classe de base des containeurs de palette qui permet d’ajouter d’autres entrées de palettes.
- org.eclipse.gef.palette.ToolEntry classe de base des outils GEF, comme la sélection des figures (org.eclipse.gef.palette.SelectionToolEntry), la création de figures (org.eclipse.gef.palette.CreationToolEntry)….
GraphicalWorkflowEditor
Nous pouvons maintenant utiliser notre factory de palettes dans la méthode GraphicalWorkflowEditor#getPaletteRoot(). Cette palette racine peut être un singleton. Pour cela ajoutez le champs static PALETTE_MODEL dans la classe org.example.workflow.presentation.graphical.GraphicalWorkflowEditor :
public class GraphicalWorkflowEditor extends GraphicalEditorWithFlyoutPalette {
...
private IEditorPart parent;
private static PaletteRoot PALETTE_MODEL;
...
}
puis implémentez la méthode GraphicalWorkflowEditor#getPaletteRoot() en appelant la factory de palettes pour initialiser le singleton palette racine :
protected PaletteRoot getPaletteRoot() {
if (PALETTE_MODEL == null)
PALETTE_MODEL = WorkflowEditorPaletteFactory.createPalette();
return PALETTE_MODEL;
}
Relancez le plugin, et vous pourrez constater que la palette est constituée d’une entrée « Workflow » (vide) :
State Tool
Maintenant que nous avons notre palette GEF initialisée, nous allons pouvoir ajouter l’outil de création d’un state à l’entrée « Workflow » de la palette. Comme nous avons vu dans la section PaletteEntry, un outil de création est une entrée de palette basé sur la classe org.eclipse.gef.palette.CreationToolEntry. Notre outil de création de states se basera sur cette classe.
State – CreationToolEntry
Modifiez le code WorkflowEditorPaletteFactory#createComponentDrawer() comme ceci :
PaletteDrawer componentsDrawer = new PaletteDrawer("Workflow");
// Creation tool of state
CreationToolEntry stateComponent = new CreationToolEntry("State",
"Create a state", new CreationFactory() {
public Object getNewObject() {
return WorkflowFactory.eINSTANCE.createStateType();
}
public Object getObjectType() {
return null;
}
}, null,
null);
componentsDrawer.add(stateComponent);
return componentsDrawer;
}
Ici nous créons un outil de création de state à l’aide de la classe org.eclipse.gef.palette.CreationToolEntry, où nous avons passé dans son constructeur :
- « State » qui est le libéllé de l’outil, qui affichera « State » dans la palette.
- « Create a state » qui est le tooltip de l’outil, qui affichera « Create a state » dans un tooltip lorsque vous placerez la souris sur cette entrée de palette.
- une instance org.eclipse.gef.requests.CreationFactory qui est la factory a utiliser pour instancier un StateType EMF (méthode CreationFactory#getNewObject() lorsque l’outil devra instancier un StateType EMF à deposer dans le WorkflowType (avant l’exécution de la commande).
- les 2 null, indiquent que nous ne souhaitons pas afficher d’icones dans la palette GEF.
Une fois l’outil créé, nous l’ajoutons à l’entrée de palette « Workflow », via le code :
componentsDrawer.add(stateComponent);
Relancez le plugin, et vous pourrez voir l’outil de création de states s’afficher dans la palette :
State – Icones
Il est possible d’afficher un icône dans la palette devant le libéllé de l’outil de creation de states. Pour cela :
- copiez l’image dans le fichier icons/full/obj16/state.gif. Cet icône sera utilisé en mode normal.
- copiez l’image dans le fichier icons/full/obj24/state.gif. Cet icône sera utilisé en mode Use Large Icons »
Remplacez les 2 null du constructeur de CreationToolEntry pour indiquer les petites et grandes icônes à utiliser :
CreationToolEntry stateComponent = new CreationToolEntry("State",
"Create a state", new CreationFactory() {
public Object getNewObject() {
return WorkflowFactory.eINSTANCE.createStateType();
}
public Object getObjectType() {
return null;
}
}, ExtendedImageRegistry.INSTANCE
.getImageDescriptor(WorkflowEditorPlugin.INSTANCE
.getImage("full/obj16/state.gif")),
ExtendedImageRegistry.INSTANCE
.getImageDescriptor(WorkflowEditorPlugin.INSTANCE
.getImage("full/obj24/state.gif")));
Palette Icônes – Mode « Normal »
Relancez le plugin et vous pourrez constater que l’outil de création d’un state affiche le petit icône :
Palette Icônes – Mode « Use Large Icons »
Cliquez avec le bouton droit de la souris, sur la palette. Ceci ouvre un menu contextuel et sélectionnez l’item « Use Large Icons », qui permet d’utiliser les icônes plus grand. Dans notre cas le grand icône doit s’afficher dans la palette.
WorkflowLayoutEditPolicy
Une fois l’outil de state sélectionné, si vous tentez de déplacer la souris dans le diagramme pour y ajouter un nouveau state, la souris affiche un rond barré qui indique que l’ajout du state au diagramme GEF est interdit. Ce comportement est normal, car nous n’avons pas encore implémenté 2 concepts :
- L’EditPolicy GEF WorkflowLayoutEditPolicy qui permet d’interpréter une Request GEF provenant de l’outil sélectionné en une Command GEF
- la Command GEF CreateCommand qui permet d’exécuter un traitement. Dans notre cas il faudra ajouter le StateType EMF créé par l’outil de création de state dans le WorkflowType EMF
Avant de commencer à développer quoique ce soit, je vais tenter d’expliquer ces 2 concepts, où j’ai passé pas mal de temps à comprendre.
Request – EditPolicy – Command GEF
Reprenons la vue d’ensemble de GEF :
Ici nous allons nous intéresser à la partie Requests/Commands de ce schéma qui montre qu’une action (ex : clic dans le diagramme GEF) à l’aide d’un outil sélectionné (ex : sélection de l’outil de création d’un state dans la palette GEF) génère une Request GEF (ex : org.eclipse.gef.requests.CreateRequest dans le cas d’une requête de création d’un élement). Cette dernière est ensuite interprétée par le contrôleur (EditPart?) pour créer une Command GEF org.eclipse.gef.commands.Command qui est ensuite exécutée (ex : ajout du StateType EMF (créé par l’outil de création de state) dans le modèle WorkflowType EMF de l’editPart WorkflowTypePart sur lequel le clic a eu lieu).
L’interprétation de la Request GEF en Command à executer est gérée par le contrôleur qui en GEF est l’EditPart. Mais cette première n’est en fait pas gérée directement par l’EditPart. En effet l’EditPart délègue cette action aux EditPolicy installés dans les EditPart.
Le redbook: ‘Eclipse Development using GEF and EMF’ décrit bien le mécanisme des EditPolicy et contient le schéma suivant qui montre les intéractions entre Request – EditPart – Command.
Le diagramme de séquence ci-dessous montre la même chose que le schéma précédant mais permet de mettre en évidence les méthodes qui sont appelées :
Request
Les Requests GEF sont créées par l’outil actif de la palette. Une Request maintient les informations nécessaires à la création d’une Command GEF (créé par l’EditPolicy).
Voici la hiérachie des Request GEF où l’on peut constater qu’il existe plusieurs type de Request :
Par exemple, notre outil de création de state, générera une Request GEF de création org.eclipse.gef.requests.CreateRequest.
EditPolicy
Editpolicies sont utilisés dans les EditPart pour réagir autant que possible aux actions utilisateurs. Chaque EditPart à une liste de Editpolicies installés. Un EditPlocy est représenté par l’interface org.eclipse.gef.EditPolicy. Il permet (dans notre cas) de transformer une Request GEF en Command GEF à executer.
Installation EditPolicy
Pour installer un EditPolicy dans un EditPart, il faut utiliser la méthode EditPart#installEditPolicy(Object role, EditPolicy editPolicy) de l’EditPart :
* Installs an EditPolicy for a specified <i>role</i>. A <i>role</i> is is simply an
* Object used to identify the EditPolicy. An example of a role is layout. {@link
* EditPolicy#LAYOUT_ROLE} is generally used as the key for this EditPolicy.
* <span>::CODECOLORER_BLOCK_2::</span> is a valid value for reserving a location.
* @param role an identifier used to key the EditPolicy
* @param editPolicy the EditPolicy
*/
void installEditPolicy(Object role, EditPolicy editPolicy);
Lors de l'installation d'un EditPolicy, un nom de rôle lui est assigné. Il faut savoir que ce rôle n'est pas du tout utilisé par le framework GEF pour que l'EditPolicy soit activé. EditPolicy fournit plusieurs constantes qu'il est conseillé d'utiliser. Par exemple pour installer notre <a href="#WorkflowLayoutEditPolicy" >WorkflowLayoutEditPolicy</a> qui hérite de org.eclipse.gef.editpolicies.<b>LayoutEditPolicy</b>, il est conseillé d'utiliser la constante <b>EditPolicy.LAYOUT_ROLE</b> :
<span>::CODECOLORER_BLOCK_3::</span>
mais le code suivant fonctionne très bien aussi :
<span>::CODECOLORER_BLOCK_4::</span>
Les appels de cette méthode pour installer plusieurs EditPolicy doivent s'effectuer dans la méthode <b>AbstractEditPart#createEditPolicies()</b>.
<h4>Implémentation EditPolicy</h4>
La difficulté que j'ai rencontré pour développer mes EditPolicy est de savoir quelle est la classe de base EditPolicy à utiliser pour gérer les commandes GEF adéquates. Par exemple l'EditPolicy <a href="#WorkflowLayoutEditPolicy" >WorkflowLayoutEditPolicy</a> (qui transforme une Request GEF de création en Commande GEF <a href="#CreateCommand" >CreateCommand</a> (qui permet d'ajouter un StateType EMF au WorkflowType EMF)) étend la classe abstraite org.eclipse.gef.editpolicies.<b>LayoutEditPolicy</b> et pas une autre?
Tout d'abord voici la hiérachie des EditPolicy :
<img src="http://blog.developpez.com/media/GEF_TypeHierarchyEditPolicy.png" width="530" height="510" alt="" />
On peut remarquer qu'il existe plusieurs implémentations de EditPolicy. La difficulté est de savoir quelle classe EditPolicy de base, on doit utiliser pour gérer le comportement souhaité. Notre EditPolicy <a href="#WorkflowLayoutEditPolicy" >WorkflowLayoutEditPolicy</a> étend org.eclipse.gef.editpolicies.<b>LayoutEditPolicy</b>, mais j'ai tenté d'étendre d'autres EditPolicy et la méthode <b>EditPolicy#getCommand(Request request)</b> n'était jamais appelé.
Pour savoir quel EditPolicy on doit étendre, la solution est de :
<ul>
<li>se baser sur les <a href="http://www.eclipse.org/gef/downloads/" >examples GEF à télécharger</a> (flow, logic....) et <a href="http://www.eclipse.org/gef/reference/documentation.php" >les exemples GEF documentés</a>.
</li>
<li>lire la javadoc de chacune des EditPolicy.
</li>
<li>lire le chapitre <b>4.2.7 Feedback techniques</b> de <a href="http://publib-b.boulder.ibm.com/Redbooks.nsf/RedpieceAbstracts/sg246302.html?Open" >Le redbook: 'Eclipse Development using GEF and EMF</a> qui liste et décrit les rôles de chacun des EditPolicy
</li>
</ul>
<h3 id="Command" >Command</h3>
Une Command GEF est la partie qui modifie toujours le modèle. Le fait de passer par une Commande pour modifier le modèle (alors que l'EditPolicy pourrait modifier le modèle directement) permet de gérer le undo/redo via la stack de commande GEF. Une Command GEF doit implémenter la classe abstraite org.eclipse.gef.commands.<b>Command</b>.
<h2 id="WorkflowLayoutEditPolicy" >WorkflowLayoutEditPolicy</h2>
Créer la classe org.example.workflow.presentation.graphical.policies.<b>WorkflowLayoutEditPolicy</b> comme suit :
<code>package org.example.workflow.presentation.graphical.policies;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.Request;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.editpolicies.LayoutEditPolicy;
import org.eclipse.gef.requests.CreateRequest;
public class WorkflowLayoutEditPolicy extends LayoutEditPolicy {
@Override
protected EditPolicy createChildEditPolicy(EditPart child) {
return null;
}
@Override
protected Command getCreateCommand(CreateRequest request) {
return null;
}
@Override
protected Command getMoveChildrenCommand(Request request) {
return null;
}
}
WorkflowLayoutEditPolicy hérite de org.eclipse.gef.editpolicies.LayoutEditPolicy car d’après la javadoc :
LayoutEditPolicies are responsible for moving, resizing, reparenting, and creating children.
cet EditPolicy est utilisé pour la création des enfants (dans notre cas les StateType/ActionType EMF).
Modifier la classe WorkflowTypePart pour installer l’EditPolicy :
protected void createEditPolicies() {
installEditPolicy(EditPolicy.LAYOUT_ROLE, new WorkflowLayoutEditPolicy());
}
Relancez le plugin et mettez un point d’arret dans WorkflowLayoutEditPolicy#getCreateCommand(). Lorsque vous déplacerez la souris dans le diagramme GEF, le debug s’arrêtera dans cette méthode. L’étape suivante est de développer cette commande via la classe CreateCommand.
CreateCommand
La classe CreateCommand est une Command GEF qui va permettre d’ajouter l’instance StateType EMF (créée par la factory CreationFactory de l’outil de création de state CreationToolEntry) à l’instance EMF WorkflowType (modèle de l’EditPart WorkflowTypePart auquel est lié l’EditPolicy WorkflowLayoutEditPolicy). Elle va s’occuper à la fois d’ajouter des StateType et des ActionType, c’est pour cela que nous allons utiliser ConnectableNode dans cette classe. Créer la classe org.example.workflow.presentation.graphical.commands.CreateCommand comme suit :
import org.eclipse.gef.commands.Command;
import org.example.workflow.model.ConnectableNode;
import org.example.workflow.model.StateType;
import org.example.workflow.model.WorkflowType;
public class CreateCommand extends Command {
private WorkflowType workflow;
private ConnectableNode connectableNode;
@Override
public void execute() {
if (getConnectableNode() instanceof StateType) {
getWorkflow().getState().add((StateType)getConnectableNode());
}
}
public WorkflowType getWorkflow() {
return workflow;
}
public void setWorkflow(WorkflowType workflow) {
this.workflow = workflow;
}
public ConnectableNode getConnectableNode() {
return connectableNode;
}
public void setConnectableNode(ConnectableNode connectableNode) {
this.connectableNode = connectableNode;
}
}
Cette classe implémente la méthode Command#execute() qui permet d’ajouter l’instance ConnectableNode (StateType/ActionType) au WorkflowType. Modifiez la méthode WorkflowLayoutEditPolicy#getCreateCommand(CreateRequest request) pour utiliser notre Commande comme suit :
@Override
protected Command getCreateCommand(CreateRequest request) {
CreateCommand command = new CreateCommand();
command.setWorkflow((WorkflowType)getHost().getModel());
command.setConnectableNode((ConnectableNode)request.getNewObject());
return command;
}
...
Relancez le plugin et vous pourrez constater que l’outil de création de states est opérationnel :
Il apparaît intéressant d’initialiser le nom du state ajouté par une valeur, comme « s » + nombre de states. Pour effectuer cela, modifiez le setter CreateCommande#setConnectableNode(ConnectableNode connectableNode) comme suit :
this.connectableNode = connectableNode;
if (connectableNode instanceof StateType) {
((StateType)connectableNode).setName("s" + (getWorkflow().getState().size() + 1));
}
}
Relancer le plugin, et vous pourrez constater que le nom du state ajouté par l’outil de création des states s’initialise avec le libéllé (« s » + nombre de states) :
Action Tool
Nous pouvons maintenant créer l’outil de création d’une action ActionType EMF qui suit la même logique que les celui des states. Ici je ne détaillerais pas le code. Je mettrais le code en entier des classes impactées par l’outil de création d’actions.
Action – Icones
Tout d’abord vous devez récupérer les 2 icônes des actions :
- copiez l’image dans le fichier icons/full/obj16/action.gif. Cet icône sera utilisé en mode normal.
- copiez l’image dans le fichier icons/full/obj24/action.gif. Cet icône sera utilisé en mode Use Large Icons »
Action – WorkflowEditorPaletteFactory
Modifiez le code de WorkflowEditorPaletteFactory comme suit pour que la palette contienne les 2 outils de création de state et d’actions :
import org.eclipse.emf.edit.ui.provider.ExtendedImageRegistry;
import org.eclipse.gef.palette.CreationToolEntry;
import org.eclipse.gef.palette.PaletteContainer;
import org.eclipse.gef.palette.PaletteDrawer;
import org.eclipse.gef.palette.PaletteRoot;
import org.eclipse.gef.requests.CreationFactory;
import org.example.workflow.model.WorkflowFactory;
import org.example.workflow.presentation.WorkflowEditorPlugin;
public class WorkflowEditorPaletteFactory {
private static PaletteContainer createComponentDrawer() {
PaletteDrawer componentsDrawer = new PaletteDrawer("Workflow");
// Creation tool of state
CreationToolEntry stateComponent = new CreationToolEntry("State",
"Create a state", new CreationFactory() {
public Object getNewObject() {
return WorkflowFactory.eINSTANCE.createStateType();
}
public Object getObjectType() {
return null;
}
}, ExtendedImageRegistry.INSTANCE
.getImageDescriptor(WorkflowEditorPlugin.INSTANCE
.getImage("full/obj16/state.gif")),
ExtendedImageRegistry.INSTANCE
.getImageDescriptor(WorkflowEditorPlugin.INSTANCE
.getImage("full/obj24/state.gif")));
componentsDrawer.add(stateComponent);
// Creation tool of action
CreationToolEntry actionComponent = new CreationToolEntry("action",
"Create a action", new CreationFactory() {
public Object getNewObject() {
return WorkflowFactory.eINSTANCE.createActionType();
}
public Object getObjectType() {
return null;
}
}, ExtendedImageRegistry.INSTANCE
.getImageDescriptor(WorkflowEditorPlugin.INSTANCE
.getImage("full/obj16/action.gif")),
ExtendedImageRegistry.INSTANCE
.getImageDescriptor(WorkflowEditorPlugin.INSTANCE
.getImage("full/obj24/action.gif")));
componentsDrawer.add(actionComponent);
return componentsDrawer;
}
static PaletteRoot createPalette() {
PaletteRoot palette = new PaletteRoot();
palette.add(createComponentDrawer());
return palette;
}
}
Action – CreateCommand
Modifiez la classe CreateCommand comme suit, pour traiter les ajout d’ActionType EMF :
import org.eclipse.gef.commands.Command;
import org.example.workflow.model.ActionType;
import org.example.workflow.model.ConnectableNode;
import org.example.workflow.model.StateType;
import org.example.workflow.model.WorkflowType;
public class CreateCommand extends Command {
private WorkflowType workflow;
private ConnectableNode connectableNode;
@Override
public void execute() {
if (getConnectableNode() instanceof StateType) {
getWorkflow().getState().add((StateType)getConnectableNode());
}
else {
if (getConnectableNode() instanceof ActionType) {
getWorkflow().getAction().add((ActionType)getConnectableNode());
}
}
}
public WorkflowType getWorkflow() {
return workflow;
}
public void setWorkflow(WorkflowType workflow) {
this.workflow = workflow;
}
public ConnectableNode getConnectableNode() {
return connectableNode;
}
public void setConnectableNode(ConnectableNode connectableNode) {
this.connectableNode = connectableNode;
if (connectableNode instanceof StateType) {
((StateType)connectableNode).setName("s" + (getWorkflow().getState().size() + 1));
}
else {
if (connectableNode instanceof ActionType) {
((ActionType)connectableNode).setName("a" + (getWorkflow().getAction().size() + 1));
}
}
}
}
Relancez le plugin et vous pourrez constater que l’outil de création d’actions est opérationnel :
Articles récents
- Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step5]
- Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step4]
- Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step3]
- Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step2]
- Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step1]
Commentaires récents
- Conception d’un Editeur Eclipse de workflow XML [step 0] dans
- Conception d’un Editeur Eclipse de workflow XML [step 19] dans
- Conception d’un Editeur Eclipse de workflow XML [step 7] dans
- Conception d’un Editeur Eclipse de workflow XML [step 7] dans
- Conception d’un Editeur Eclipse de workflow XML [step 7] dans