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 :
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.
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 :
@Override
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.
Créez la factory de palettes GEF org.example.workflow.presentation.graphical.WorkflowEditorPaletteFactory comme suit :
package org.example.workflow.presentation.graphical;
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 :
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).
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 :
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 :
@Override
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) :

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.
Modifiez le code WorkflowEditorPaletteFactory#createComponentDrawer() comme ceci :
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;
}
}, 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 :
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 :
![]()
Il est possible d'afficher un icône dans la palette devant le libéllé de l'outil de creation de states. Pour cela :
Remplacez les 2 null du constructeur de CreationToolEntry pour indiquer les petites et grandes icônes à utiliser :
// 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")));
Relancez le plugin et vous pourrez constater que l'outil de création d'un state affiche le petit icône
:

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.
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 :
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.
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 :

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.
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.
Pour installer un EditPolicy dans un EditPart, il faut utiliser la méthode EditPart#installEditPolicy(Object role, EditPolicy editPolicy) de l'EditPart :
/** is a valid value for reserving a location.
* 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.
* <code>null
* @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 WorkflowLayoutEditPolicy qui hérite de org.eclipse.gef.editpolicies.LayoutEditPolicy, il est conseillé d'utiliser la constante EditPolicy.LAYOUT_ROLE :
installEditPolicy(EditPolicy.LAYOUT_ROLE, new WorkflowLayoutEditPolicy());
mais le code suivant fonctionne très bien aussi :
installEditPolicy("blablabla", new WorkflowLayoutEditPolicy());
Les appels de cette méthode pour installer plusieurs EditPolicy doivent s'effectuer dans la méthode AbstractEditPart#createEditPolicies().
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 WorkflowLayoutEditPolicy (qui transforme une Request GEF de création en Commande GEF CreateCommand (qui permet d'ajouter un StateType EMF au WorkflowType EMF)) étend la classe abstraite org.eclipse.gef.editpolicies.LayoutEditPolicy et pas une autre?
Tout d'abord voici la hiérachie des EditPolicy :

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 WorkflowLayoutEditPolicy étend org.eclipse.gef.editpolicies.LayoutEditPolicy, mais j'ai tenté d'étendre d'autres EditPolicy et la méthode EditPolicy#getCommand(Request request) n'était jamais appelé.
Pour savoir quel EditPolicy on doit étendre, la solution est de :
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.Command.
Créer la classe org.example.workflow.presentation.graphical.policies.WorkflowLayoutEditPolicy comme suit :
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 :
@Override
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.
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 :
package org.example.workflow.presentation.graphical.commands;
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 :
public void setConnectableNode(ConnectableNode connectableNode) {
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) :

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.
Tout d'abord vous devez récupérer les 2 icônes des actions :
Modifiez le code de WorkflowEditorPaletteFactory comme suit pour que la palette contienne les 2 outils de création de state et d'actions :
package org.example.workflow.presentation.graphical;
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;
}
}
Modifiez la classe CreateCommand comme suit, pour traiter les ajout d'ActionType EMF :
package org.example.workflow.presentation.graphical.commands;
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 :

Cet article n'a pas de Commentaires/Pingbacks pour le moment...
Vous devez être identifié pour poster un commentaire.
| Lun | Mar | Mer | Jeu | Ven | Sam | Dim |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 |
Copyright © 2000-2012 - www.developpez.com