août
2009
A l’étape du billet précédant [step15] nous avons mis en place la palette GEF qui contient les 2 outils de création de state et d’actions. Dans ce billet nous allons ajouter l’outil de création de connections qui permettra à partir de la palette GEF de connecter les actions aux states :
Vous pouvez télécharger le projet org.example.workflow_step16.zip présenté dans ce billet.
Connection Tool
Notre outil de création de connections s’utilisera en cliquant sur un noeud source (ex : state) puis sur un noeud cible (ex : action). Pour effectuer nos tests de l’outil de connection, ajoutez un state « s3″ et une action « a2″ dans le diagramme GEF que nous tenterons tout au long de ce billet de connecter via notre outil de création de connection :
Pour ajouter l’outil de connection entre states et actions, nous allons procéder de la même manière que ce que nous avons fait précédemment avec les outils de création de states et d’actions, autrement dit :
- Ajouter les icônes de connections utilisés dans la palette GEF.
- Ajouter une entrée de palette « Connection » à la palette GEF qui sera notre outil de création de connections.
Connection – Icones
Récupérer les 2 icônes des connections que nous allons utiliser dans la palette GEF :
- copiez l’image dans le fichier icons/full/obj16/connection.gif. Cet icône sera utilisé en mode normal.
- copiez l’image dans le fichier icons/full/obj24/connection.gif. Cet icône sera utilisé en mode Use Large Icons »
WorkflowEditorPaletteFactory
A cette étape nous allons ajouter l’outil de création de connections à notre palette GEF. Pour ajoutez dans la classe org.example.workflow.presentation.graphical.WorkflowEditorPaletteFactory la méthode WorkflowEditorPaletteFactory#createControlGroup() comme suit :
PaletteGroup controlGroup = new PaletteGroup("Control Group");
ToolEntry connectionTool = new ConnectionCreationToolEntry(
"Connection", "Creating connections", null,
ExtendedImageRegistry.INSTANCE
.getImageDescriptor(WorkflowEditorPlugin.INSTANCE
.getImage("full/obj16/connection.gif")),
ExtendedImageRegistry.INSTANCE
.getImageDescriptor(WorkflowEditorPlugin.INSTANCE
.getImage("full/obj24/connection.gif")));
controlGroup.add(connectionTool);
return controlGroup;
}
La nouvelle méthode WorkflowEditorPaletteFactory#createControlGroup() créé un groupe d’entrée de palettes org.eclipse.gef.palette.PaletteGroup qui est constitué à ce stade de l’outil de création de connections.
A la différence de PaletteDrawer, PaletteGroupe n’affiche pas une entrée avec un libéllé. L’outil de création de connections utilise la classe GEF org.eclipse.gef.palette.ConnectionCreationToolEntry où nous avons passé dans son constructeur :
- « Connection » qui est le libéllé de l’outil, qui affichera « Connection Creation » dans la palette.
- « Creating connections » qui est le tooltip de l’outil, qui affichera « Creating connections » dans un tooltip lorsque vous placerez la souris sur cette entrée de palette.
- null, pour indiquer que nous souhaitons pas utiliser de factory org.eclipse.gef.requests.CreationFactory dans cet outil.
On pourrait s’attendre à crééer une factory qui renverrait une instance EMF de org.example.workflow.model.Connection mais nous verrons par la suite que notre Command GEF n’a pas besoin de cette instance. - les 2 paramètres, indiquent que nous ne souhaitons afficher les icones de connections (grandes et petite) dans la palette GEF.
ajoutez la PaletteGroupe créé dans la palette racine comme ceci :
PaletteRoot palette = new PaletteRoot();
palette.add(createControlGroup());
...}
Voici le code en entier de WorkflowEditorPaletteFactory :
import org.eclipse.emf.edit.ui.provider.ExtendedImageRegistry;
import org.eclipse.gef.palette.ConnectionCreationToolEntry;
import org.eclipse.gef.palette.CreationToolEntry;
import org.eclipse.gef.palette.PaletteContainer;
import org.eclipse.gef.palette.PaletteDrawer;
import org.eclipse.gef.palette.PaletteGroup;
import org.eclipse.gef.palette.PaletteRoot;
import org.eclipse.gef.palette.ToolEntry;
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;
}
private static PaletteContainer createControlGroup() {
PaletteGroup controlGroup = new PaletteGroup("Control Group");
ToolEntry connectionTool = new ConnectionCreationToolEntry(
"Connection", "Creating connections", null,
ExtendedImageRegistry.INSTANCE
.getImageDescriptor(WorkflowEditorPlugin.INSTANCE
.getImage("full/obj16/connection.gif")),
ExtendedImageRegistry.INSTANCE
.getImageDescriptor(WorkflowEditorPlugin.INSTANCE
.getImage("full/obj24/connection.gif")));
controlGroup.add(connectionTool);
return controlGroup;
}
static PaletteRoot createPalette() {
PaletteRoot palette = new PaletteRoot();
palette.add(createControlGroup());
palette.add(createComponentDrawer());
return palette;
}
}
Relancez le plugin, et vous pourrez voir l’outil de création de connections s’afficher dans la palette :
Après avoir sélectionné l’outil de création de connections, cliquez sur le state « s3″ pour intialiser la source de la connection. Mais rien ne se passe? Ce comportement est normal, car nous n’avons pas encore implémenté les 2 concepts :
- L’EditPolicy GEF WorkflowNodeEditPolicy qui permet d’interpréter une Request GEF provenant de l’outil sélectionné en une Command GEF
- la Command GEF ConnectionCreateCommand qui permet d’exécuter un traitement. Dans notre cas il faudra :
- mettre à jour l’attribut fromState de l’ActionType « a2″ avec le StateType « s3″ dans le cas ou on sélectionne « s3″ puis « a2″.
- mettre à jour l’attribut toState de l’ActionType « a2″ avec le StateType « s3″ dans le cas ou on sélectionne « a2″ puis « s3″.
WorkflowNodeEditPolicy
Dans le billet précedant nous avons installé l’EditPolicy WorkflowLayoutEditPolicy dans l’EditPart WorkflowTypePart pour intepréter la Request GEF en Command GEF CreateCommand. Nous allons procéder de la même manière pour rendre opérationnel notre outil de création de connections mais cette fois nous allons installer un EditPolicy (WorkflowNodeEditPolicy) dans les EditPart StateTypePart et ActionTypePart car ce sont eux qui doivent interpréter la Request GEF (réagir lorsque la souris survole l’EditPart) en command GEF ConnectionCreateCommand, command qui permet de créer uen connection entre un state et une action.
Créez la classe org.example.workflow.presentation.graphical.policies.WorkflowNodeEditPolicy comme suit :
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.editpolicies.GraphicalNodeEditPolicy;
import org.eclipse.gef.requests.CreateConnectionRequest;
import org.eclipse.gef.requests.ReconnectRequest;
public class WorkflowNodeEditPolicy extends GraphicalNodeEditPolicy {
@Override
protected Command getConnectionCompleteCommand(
CreateConnectionRequest request) {
return null;
}
@Override
protected Command getConnectionCreateCommand(CreateConnectionRequest request) {
return null;
}
@Override
protected Command getReconnectSourceCommand(ReconnectRequest request) {
return null;
}
@Override
protected Command getReconnectTargetCommand(ReconnectRequest request) {
return null;
}
}
WorkflowNodeEditPolicy hérite de org.eclipse.gef.editpolicies.GraphicalNodeEditPolicy car d’après la javadoc :
A GraphicalNodeEditPolicy is responsible for creating and reconnecting connections graphically.
cet EditPolicy est utilisé pour la création des connections (dans notre cas les Connection EMF).
ATTENTION!!! Supprimez la méthode createEditPolicies() dans les EditPart StateTypePart et ActionTypePart car nous allons implémenter l’installation des EditPolicy dans la classe commune ConnectableNodePart.
Modifier la classe ConnectableNodePart pour installer l’EditPolicy :
protected void createEditPolicies() {
installEditPolicy(EditPolicy.GRAPHICAL_NODE_ROLE, new WorkflowNodeEditPolicy());
}
Relancez le plugin et mettez un point d’arrêt dans WorkflowNodeEditPolicy#getConnectionCreateCommand(CreateConnectionRequest request). Lorsque vous déplacerez la souris sur le state « s3″, le debug s’arrêtera dans cette méthode. L’étape suivante est de développer cette commande via la classe ConnectionCreateCommand.
ConnectionCreateCommand
Cette Command GEF doit s’occuper de mettre à jour la connection entre un state et une action. Plus précisement :
- mettre à jour l’attribut fromState de l’ActionType « a2″ avec le StateType « s3″ dans le cas ou on sélectionne « s3″ puis « a2″.
- mettre à jour l’attribut toState de l’ActionType « a2″ avec le StateType « s3″ dans le cas ou on sélectionne « a2″ puis « s3″.
Elle doit avant son exécution avoir les noeuds source et cible (target) qui doivent être initialisé correctement. Cette initialisation des source et target doit être géré par l’EditPolicy WorkflowNodeEditPolicy.
Créez la classe org.example.workflow.presentation.graphical.commands.ConnectionCreateCommand comme suit :
import org.eclipse.gef.commands.Command;
import org.example.workflow.model.ActionType;
import org.example.workflow.model.ConnectableNode;
import org.example.workflow.model.StateType;
public class ConnectionCreateCommand extends Command {
private ConnectableNode source;
private ConnectableNode target;
@Override
public void execute() {
if (source instanceof StateType) {
StateType state = (StateType) source;
ActionType action = (ActionType) target;
action.setFromState(state);
} else {
StateType state = (StateType) target;
ActionType action = (ActionType) source;
action.setToState(state);
}
}
public void setSource(ConnectableNode source) {
this.source = source;
}
public void setTarget(ConnectableNode target) {
this.target = target;
}
}
Cette Command GEF doit avoir ces noeuds ConnectableNode source et target initialisés (par l’EditPolicy) avant de pouvoir être éxécuté. Comme vous pouvez le constater la mise à jour des connections s’effectuent en modifiant les propriétés ActionType#setToState(StateType state) et ActionType#setFromState(StateType state). Nous n’avons pas besoin de Connection EMF ici pour lié une action à un state (ce sont nos Adapter EMF qui gèrent ceci). C’est pour cela que notre outil de création de connections n’a pas besoin de factory CreationFactory.
WorkflowNodeEditPolicy – ConnectionCreateCommand
Notre EditPolicy WorkflowNodeEditPolicy doit maintenant s’occuper de préparer la Command GEF ConnectionCreateCommand, autrement dit :
- créer une instance ConnectionCreateCommand et mettre à jour la propriété ConnectionCreateCommand#setSource(ConnectableNode source) avec le modèle (StateTye/ActionType) de l’EditPart (StateTypePart/ActionTypePart) qui est sélectionné en premier. Ceci doit s’effectuer dans la méthode WorkflowNodeEditPolicy#getConnectionCreateCommand(CreateConnectionRequest request) qui est appelé lorsqu’un EditPart source est survolé par la souris.
- mettre à jour la propriété ConnectionCreateCommand#setTarget(ConnectableNode target) avec le modèle (StateTye/ActionType) de l’EditPart (StateTypePart/ActionTYpePart) qui est sélectionné en deuxième. Ceci doit s’effectuer dans la méthode WorkflowNodeEditPolicy#getConnectionCompleteCommand(CreateConnectionRequest request) qui est appelé lorsqu’un EditPart target est survolé par la souris.
Modifiez le code de WorkflowNodeEditPolicy comme suit :
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.editpolicies.GraphicalNodeEditPolicy;
import org.eclipse.gef.requests.CreateConnectionRequest;
import org.eclipse.gef.requests.ReconnectRequest;
import org.example.workflow.model.ConnectableNode;
import org.example.workflow.presentation.graphical.commands.ConnectionCreateCommand;
public class WorkflowNodeEditPolicy extends GraphicalNodeEditPolicy {
@Override
protected Command getConnectionCompleteCommand(
CreateConnectionRequest request) {
ConnectionCreateCommand cmd = (ConnectionCreateCommand) request
.getStartCommand();
cmd.setTarget(getConnectableNode());
return cmd;
}
@Override
protected Command getConnectionCreateCommand(CreateConnectionRequest request) {
ConnectionCreateCommand cmd = new ConnectionCreateCommand();
cmd.setSource(getConnectableNode());
request.setStartCommand(cmd);
return cmd;
}
private ConnectableNode getConnectableNode() {
return (ConnectableNode) getHost().getModel();
}
@Override
protected Command getReconnectSourceCommand(ReconnectRequest request) {
return null;
}
@Override
protected Command getReconnectTargetCommand(ReconnectRequest request) {
return null;
}
}
La méthode WorkflowNodeEditPolicy#getConnectionCreateCommand(CreateConnectionRequest request) s’occupe d’instancier une Command GEF ConnectionCreateCommand et d’initialiser la source. Le code :
request.setStartCommand(cmd);
permet d’enregistrer la command GEF dans la Request GEF. La méthode WorkflowNodeEditPolicy#getConnectionCompleteCommand(CreateConnectionRequest request) peut ensuite récupérer l’instance ConnectionCreateCommand via le code :
ConnectionCreateCommand cmd = (ConnectionCreateCommand) request.getStartCommand();
et peut ensuite s’occuper de mettre à jour la target.
Relancez le plugin, il est maintenant possible de lier le state « s3″ avec l’action « a2″ à l’aide de notre outil de création de connections :
ConnectionCreateCommand#canExecute()
A ce state, l’utilisateur peut tenter de lier un noeud source (state/action) avec lui même et peut aussi tenter de lier 2 states entre eux ou 2 actions entre elles. Même si il tente d’effectuer ceci, ceci ne marchera pas car la méthode ConnectionCreateCommand#execute() s’attend à avoir un state (source/target) et une action(source/target). Il est cependant possible d’effectuer un test de validité des 2 noeuds à lier avant l’appel de la méthode ConnectionCreateCommand#execute() en implémentant la méthode ConnectionCreateCommand#canExecute() (qui retourne true par défaut). Les avantages d’effectuer ce test de validité des 2 noeuds dans cette méthode sont :
- évite d’appeler la méthode ConnectionCreateCommand#execute() si les 2 noeuds source et target ne sont pas valides.
- le curseur de la souris affiche un rond barré (qui interdit la création de connection) si vous tentez de lier 2 noeuds qui ne sont pas valides.
Ajoutez la méthode ConnectionCreateCommand#canExecute() dans la classe ConnectionCreateCommand comme suit :
public boolean canExecute() {
// 1) source & target cannot be the same node
if (source.equals(target))
return false;
// 2) Target & Source cannot be StateType & ActionType
if (target != null && source.getClass().equals((target.getClass())))
return false;
if (target instanceof ActionType) {
// 3) action target must not have target connections
ActionType t = (ActionType) target;
if (t.getTargetConnections().size() > 0)
return false;
} else {
if (source instanceof ActionType) {
// 4) action source must not have source connections
ActionType t = (ActionType) source;
if (t.getSourceConnections().size() > 0)
return false;
}
}
// 5) Check for existence of connection already
List<Connection> transistions = source.getTargetConnections();
for (Connection connection : transistions) {
if (connection.getTarget().equals(target))
return false;
}
return true;
}
Relancez le plugin et essayer de lier 2 states entre eux, le curseur de la sourit affichera un ron barré qui vous indique qu’il est impossible de lier 2 states entre eux.
2 Commentaires + Ajouter un commentaire
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
Bonjour Thibault,
Merci beaucoup de tes encouragements et commentaires. Je t’avoues que le code que j’avais fait me parait très loin et il faudrait que je m’y replonge pour te donner uen reponse plus pertinente. Je ne me rappelle plus d’ou vient ce code (mon propre code ou un des examples de GEF?)
Etant donne que tu es dans le sujet tu dois avoir surement raison.
Angelo
Bonjour Angelo,
je vois le bout arriver (j’ai fait une petite pause depuis le step 12, d’autres priorités)! Merci encore pour ce super tuto! Même si ton modèle est assez simple, on s’en sort avec un xsd beaucoup plus compliqué (enfin, à peu près…).
Juste un petit commentaire pour parler des dernières lignes de code de ce billet: pour vérifier l’existence de la connexion que l’on veut créer, ça ne serait pas plutôt la méthode getSourceConnections() qu’il faudrait utiliser?
// 5) Check for existence of connection already <br />
List transistions = source.getSourceConnections(); <br />
for (Connection connection : transistions) { <br />
if (connection.getTarget().equals(target)) <br />
return false; <br />
} <br />
Sinon, il me semble bien que la target de toutes les connexions sur lesquelles on itère sera la source elle-même, et donc le equals(target) sera toujours faux!
Meilleures salutations
Thibault