août
2009
A l’étape du billet précédant [step12] nous avons finalisé la gestion des connections entre les actions et states. Les figures states et actions se positionnent aléatoirement dans la page Graphics GEF. Dans ce billet nous allons mettre en place le positionnement automatique de ces figures (layout automatique). Le contenu XML du workflow suivant :
<workflow xmlns="http://www.example.org/workflow">
<state name="s1" />
<state name="s2" />
<state name="s3" />
<action toState="s2" name="a1" fromState="s1"/>
<action toState="s3" name="a1" fromState="s1"/>
</workflow>
sera représenté graphiquement comme ceci :
Les states et actions se positionneront automatiquement dans la page Graphics GEF.
Vous pouvez télécharger le projet org.example.workflow_step13.zip présenté dans ce billet.
Example Flow – Layout
Dans la page de Téléchargement de GEF, vous pouvez télécharger des Exemples sous formes d’un zip GEF-examples-*.zip. Vous pouvez y trouver l’exemple org.eclipse.gef.examples.flow qui permet de gérer un diagramme constitué d’élements « activity » qui se positionnent automatiquement (layout automatique) :
Le projet org.eclipse.gef.examples.flow est constitué de plusieurs EdiPart qui sont similaires à celles de notre projet de workflow :
- org.eclipse.gef.examples.flow.parts.SimpleActivityPart qui est l’EditPart qui affiche des activités simples (ex Sleep…, Alarme…). Cet EditPart est similaire à notre EditPart org.example.workflow.presentation.graphical.parts.ConnectableNodePart.
- org.eclipse.gef.examples.flow.parts.TransitionPart qui est l’EditPart qui gèrent les connections entre les activity. Cet EditPart est similaire à notre EditPart org.example.workflow.presentation.graphical.parts.ConnectionPart.
- org.eclipse.gef.examples.flow.parts.ActivityDiagramPart qui est l’EditPart racine qui contient entre autres les EditPart SimpleActivityPart. Cet EditPart est similaire à notre EditPart org.example.workflow.presentation.graphical.parts.WorkflowTypePart.
Les figures « activity » (comme SimpleActivityPart) du projet org.eclipse.gef.examples.flow se positionnent automatiquement dans le diagramme (classe ActivityDiagramPart). Ce positionnement automatique, s’effectue via le layout GEF org.eclipse.gef.examples.flow.parts.GraphLayoutManager, qui est le layout de la figure draw2d de l’EditPart ActivityDiagramPart (containeur des elements « activity »).
Le positionnement des figures « activity » s’effectue via une petite animation qui lorsque le diagramme est modifié (ajout, suppression d’élements « activity ») déplace les figures jusqu’à ce qu’elles soient bien positionnées. Ceci s’effectue via la classe org.eclipse.gef.examples.flow.parts.GraphAnimation.
L’animation est déclenchée lorsque le graphique change, autrement dit lorsque l’utilisateur utilise la palette d’outil GEF pour ajouter/supprimer… des élement « activity ». Pour détecter les modifications effectuées sur le graphique, l’EditPart ActivityDiagramPart utilise la stack de commandes GEF et déclenche l’animation lorsque celle-ci est modifiée :
...
CommandStackListener stackListener = new CommandStackListener() {
public void commandStackChanged(EventObject event) {
if (!GraphAnimation.captureLayout(getFigure()))
return;
while(GraphAnimation.step())
getFigure().getUpdateManager().performUpdate();
GraphAnimation.end();
}
};
...
public void activate() {
super.activate();
getViewer().getEditDomain().getCommandStack().addCommandStackListener(stackListener);
}
...
public void deactivate() {
getViewer().getEditDomain().getCommandStack().removeCommandStackListener(stackListener);
super.deactivate();
}
...
Workflow – Layout
Dans ce billet, nous nous inspirerons du projet org.eclipse.gef.examples.flow pour gérer le layout automatique, mais en supprimant l’effet d’animation (autrement dit la classe GraphAnimation et ses appels). En effet la stack de commande GEF n’est pas encore opérationnelle, car nous n’avons pas mis en place de palette GEF et celle-ci n’est pas synchronisées avec notre stack de commandes de SSE ou de EMF. Il nous est impossible à ce stade de déclencher le positionnement automatique avec animation à l’aide de la commande de stack GEF.
GraphLayoutManager
Copier la classe org.eclipse.gef.examples.flow.parts.GraphLayoutManager dans org.example.workflow.presentation.graphical.parts.GraphLayoutManager puis effectuez les modifications suivantes :
- modifiez le package org.eclipse.gef.examples.flow.parts par org.example.workflow.presentation.graphical.parts.
- mettez la classe en public.
- mettez la constructeur de la classe en public.
- mettez en commentaire les appels à GraphAnimation.
- remplacez ActivityDiagramPart par WorkflowTypePart. En effet notre WorkflowTypePart joue le même rôle que ActivityDiagramPart.
Voici le code de org.example.workflow.presentation.graphical.parts.GraphLayoutManager :
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.draw2d.AbstractLayout;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.draw2d.graph.CompoundDirectedGraph;
import org.eclipse.draw2d.graph.CompoundDirectedGraphLayout;
public class GraphLayoutManager extends AbstractLayout {
private WorkflowTypePart diagram;
public GraphLayoutManager(WorkflowTypePart diagram) {
this.diagram = diagram;
}
protected Dimension calculatePreferredSize(IFigure container, int wHint,
int hHint) {
container.validate();
List children = container.getChildren();
Rectangle result = new Rectangle().setLocation(container
.getClientArea().getLocation());
for (int i = 0; i < children.size(); i++)
result.union(((IFigure) children.get(i)).getBounds());
result.resize(container.getInsets().getWidth(), container.getInsets()
.getHeight());
return result.getSize();
}
public void layout(IFigure container) {
// GraphAnimation.recordInitialState(container);
// if (GraphAnimation.playbackState(container))
// return;
CompoundDirectedGraph graph = new CompoundDirectedGraph();
Map partsToNodes = new HashMap();
diagram.contributeNodesToGraph(graph, null, partsToNodes);
diagram.contributeEdgesToGraph(graph, partsToNodes);
new CompoundDirectedGraphLayout().visit(graph);
diagram.applyGraphResults(graph, partsToNodes);
}
}
GraphLayoutManger est un layout GEF qui étend la classe abstraite de layout GEF org.eclipse.draw2d.AbstractLayout qui implémente l’interface GEF de layout org.eclipse.draw2d.LayoutManager. GraphLayoutManger implémente la méthode LayoutManager#(IFigure container) qui s’occupe de calculer le layout de la figure container comme ceci :
// GraphAnimation.recordInitialState(container);
// if (GraphAnimation.playbackState(container))
// return;
CompoundDirectedGraph graph = new CompoundDirectedGraph();
Map partsToNodes = new HashMap();
diagram.contributeNodesToGraph(graph, null, partsToNodes);
diagram.contributeEdgesToGraph(graph, partsToNodes);
new CompoundDirectedGraphLayout().visit(graph);
diagram.applyGraphResults(graph, partsToNodes);
}
L’algoritme utilisé pour le positionnement des figures est décrit dans la javadoc de la classe org.eclipse.draw2d.graph.CompoundDirectedGraphLayout qui est utilisé dans la méthode layout. Cette classe calcule les positions des noeuds et edges (connections) stockés dans la structure org.eclipse.draw2d.graph.CompoundDirectedGraph.
Le calcul du layout consiste à :
- alimenter la structure CompoundDirectedGraph en utilisant les informations des figures (ex : taille de préférences des figures) du diagramme. Ces informations sont ensuite stockées dans des structures org.eclipse.draw2d.graph.Node (informations des noeuds figures) et org.eclipse.draw2d.graph.Edge (informations des connections figures).
- effectuer le calcul du layout en appelant la méthode CompoundDirectedGraphLayout#visit(CompoundDirectedGraph graph). Cette dernière met à jour la structure CompoundDirectedGraph (structures org.eclipse.draw2d.graph.Node et structures org.eclipse.draw2d.graph.Edge) avec les positions calculées.
- appliquer le resultats du calcul du layout au figures draw2d, autrement dit utiliser les informations de positions de CompoundDirectedGraph (structures org.eclipse.draw2d.graph.Node et org.eclipse.draw2d.graph.Edge) pour mettre à jour les positions des figures draw2d.
WorkflowTypePart
A ce stade, nous avons des erreurs de compilation sur les 3 méthodes manquantes de WorkflowTypePart nécessaires pour collecter les noeuds/connections (mise à jour de CompoundDirectedGraph) qui interviennent dans le calcul du layout puis après calcul du layout, appliquer les positions calculées aux figures draw2d (provenant de CompoundDirectedGraph) :
- WorkflowTypePart#contributeNodesToGraph(CompoundDirectedGraph graph, Object object, Map partsToNodes) collecte les noeuds (org.eclipse.draw2d.graph.Node) qui doivent être pris en compte pour le calcul du layout.
- WorkflowTypePart#contributeEdgesToGraph(CompoundDirectedGraph graph, Object object, Map partsToNodes) collecte les connections (org.eclipse.draw2d.graph.Edge) qui doivent être prises en compte pour le calcul du layout.
- WorkflowTypePart#applyGraphResults(CompoundDirectedGraph graph, Object object, Map partsToNodes) applique le résultat de calcul aux noeuds/connnections qui ont contribués au calcul du layout.
Notre classe WorkflowTypePart joue le même rôle que la classe org.eclipse.gef.examples.flow.parts.ActivityDiagramPart. Nous allons nous inspirer de cette classe pour implémenter les 3 méthodes ci dessus.
WorkflowTypePart#contributeNodesToGraph
La méthode WorkflowTypePart#contributeNodesToGraph(CompoundDirectedGraph graph, Object object, Map partsToNodes) collecte les noeuds (Nodes) qui doivent être pris en compte pour le calcul du layout. Pour implémenter cette méthode je me suis inspiré de celle de org.eclipse.gef.examples.flow.parts.StructuredActivityPart#contributeNodesToGraph(CompoundDirectedGraph graph, Object object, Map partsToNodes) dont org.eclipse.gef.examples.flow.parts.ActivityDiagramPart hérite.
Ajoutez le code suivant à la classe WorkflowTypePart :
static final Insets INNER_PADDING = new Insets(0);
et le code suivant :
Map map) {
Subgraph me = new Subgraph(this, s);
me.outgoingOffset = 5;
me.incomingOffset = 5;
me.innerPadding = INNER_PADDING;
me.setPadding(PADDING);
map.put(this, me);
graph.nodes.add(me);
for (int i = 0; i < getChildren().size(); i++) {
ConnectableNodePart activity = (ConnectableNodePart) getChildren()
.get(i);
activity.contributeNodesToGraph(graph, me, map);
}
}
Ce code initialise un sous-graphs org.eclipse.draw2d.graph.Subgraph qui est ajouté à CompoundDirectedGraph, puis itère sur chacun des EditPart enfant ConnectableNodePart pour appeler ConnectableNodePart#contributeNodesToGraph qui permet de collecter les informations de tailles des ConnectableNode.
WorkflowTypePart#contributeEdgesToGraph
La méthode WorkflowTypePart#contributeEdgesToGraph(CompoundDirectedGraph graph, Object object, Map partsToNodes) collecte les connections (Edges) qui doivent être pris en compte pour le calcul du layout. Pour implémenter cette méthode je me suis inspiré de celle de org.eclipse.gef.examples.flow.parts.StructuredActivityPart#contributeEdgesToGraph(CompoundDirectedGraph graph, Object object, Map partsToNodes) dont org.eclipse.gef.examples.flow.parts.ActivityDiagramPart hérite.
Ajoutez le code suivant à la classe WorkflowTypePart :
for (int i = 0; i < getChildren().size(); i++) {
ConnectableNodePart child = (ConnectableNodePart) children.get(i);
child.contributeEdgesToGraph(graph, map);
}
}
Ce code itère sur chacun des noeuds enfants ConnectableNodePart (ActionTypePart et StateTypePart) constituant le workflow et appèle pour chaque noeud sa méthode ConnectableNodePart#contributeEdgesToGraph.
WorkflowTypePart#applyGraphResults
La méthode WorkflowTypePart#applyGraphResults(CompoundDirectedGraph graph, Object object, Map partsToNodes) applique le résultat de calcul aux noeuds/connnections qui ont contribués au calcul du layout. Pour implémenter cette méthode je me suis inspiré de celle de org.eclipse.gef.examples.flow.parts.StructuredActivityPart#applyGraphResults(CompoundDirectedGraph graph, Object object, Map partsToNodes) dont org.eclipse.gef.examples.flow.parts.ActivityDiagramPart hérite.
Ajoutez le code suivant à la classe WorkflowTypePart :
applyOwnResults(graph, map);
applyChildrenResults(graph, map);
}
protected void applyOwnResults(CompoundDirectedGraph graph, Map map) {
Node n = (Node) map.get(this);
getFigure().setBounds(new Rectangle(n.x, n.y, n.width, n.height));
}
protected void applyChildrenResults(CompoundDirectedGraph graph, Map map) {
for (int i = 0; i < getChildren().size(); i++) {
ConnectableNodePart part = (ConnectableNodePart) getChildren().get(
i);
part.applyGraphResults(graph, map);
}
}
Ces méthodes récupèrent le Node org.eclipse.draw2d.graph.Subgraph qui est le résultat du calcul du layout et met à jour la figure draw2d WorkflowTypeFigure avec les nouvelles positions. Il y a ensuite itération sur les EditPart enfants ConnectableNodePart pour appeler sur chacune des EditaPart la méthode ConnectableNodePart#applyGraphResults qui permet d’appliquer le resultat du calcul du layout sur les figures draw2d StateTypeFigure et ActionTypeFigure.
Voici le code en entier de WorkflowTypePart :
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.draw2d.graph.CompoundDirectedGraph;
import org.eclipse.draw2d.graph.Node;
import org.eclipse.draw2d.graph.Subgraph;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.ecore.EObject;
import org.example.workflow.model.WorkflowPackage;
import org.example.workflow.model.WorkflowType;
import org.example.workflow.presentation.graphical.figures.WorkflowTypeFigure;
public class WorkflowTypePart extends AbstractEMFModelGraphicalEditPart {
static final Insets PADDING = new Insets(8, 6, 8, 6);
static final Insets INNER_PADDING = new Insets(0);
@Override
protected IFigure createFigure() {
return new WorkflowTypeFigure();
}
@Override
protected void createEditPolicies() {
}
@Override
protected List<EObject> getModelChildren() {
WorkflowType workflow = getWorkflow();
List<EObject> models = new ArrayList<EObject>(workflow.getState());
models.addAll(workflow.getAction());
return models;
}
public WorkflowType getWorkflow() {
return (WorkflowType) super.getModel();
}
public void notifyChanged(Notification notification) {
int type = notification.getEventType();
int featureId = notification.getFeatureID(WorkflowPackage.class);
if (type == Notification.ADD || type == Notification.REMOVE) {
switch (featureId) {
case WorkflowPackage.WORKFLOW_TYPE__ACTION:
case WorkflowPackage.WORKFLOW_TYPE__STATE:
refreshChildren();
break;
}
}
}
public void contributeNodesToGraph(CompoundDirectedGraph graph, Subgraph s,
Map map) {
Subgraph me = new Subgraph(this, s);
me.outgoingOffset = 5;
me.incomingOffset = 5;
me.innerPadding = INNER_PADDING;
me.setPadding(PADDING);
map.put(this, me);
graph.nodes.add(me);
for (int i = 0; i < getChildren().size(); i++) {
ConnectableNodePart activity = (ConnectableNodePart) getChildren()
.get(i);
activity.contributeNodesToGraph(graph, me, map);
}
}
public void contributeEdgesToGraph(CompoundDirectedGraph graph, Map map) {
for (int i = 0; i < getChildren().size(); i++) {
ConnectableNodePart child = (ConnectableNodePart) children.get(i);
child.contributeEdgesToGraph(graph, map);
}
}
protected void applyGraphResults(CompoundDirectedGraph graph, Map map) {
applyOwnResults(graph, map);
applyChildrenResults(graph, map);
}
protected void applyOwnResults(CompoundDirectedGraph graph, Map map) {
Node n = (Node) map.get(this);
getFigure().setBounds(new Rectangle(n.x, n.y, n.width, n.height));
}
protected void applyChildrenResults(CompoundDirectedGraph graph, Map map) {
for (int i = 0; i < getChildren().size(); i++) {
ConnectableNodePart part = (ConnectableNodePart) getChildren().get(
i);
part.applyGraphResults(graph, map);
}
}
}
ConnectableNodePart
Notre classe ConnectableNodePart joue le même rôle que la classe org.eclipse.gef.examples.flow.parts.SimpleActivityPart. Nous allons nous inspirer de cette classe.
ConnectableNodePar#contributeNodesToGraph
Pour implémenter cette méthode je me suis inspiré de celle de org.eclipse.gef.examples.flow.parts.SimpleActivityPart#contributeNodesToGraph(CompoundDirectedGraph graph, Object object, Map partsToNodes).
Ajoutez le code suivant à la classe ConnectableNodePart :
Node n = new Node(this, s);
n.outgoingOffset = getAnchorOffset();
n.incomingOffset = getAnchorOffset();
n.width = getFigure().getPreferredSize().width;
n.height = getFigure().getPreferredSize().height;
n.setPadding(new Insets(10,8,10,12));
map.put(this, n);
graph.nodes.add(n);
}
int getAnchorOffset() {
return 9;
}
Cette méthode met à jour CompoundDirectedGraph avec un org.eclipse.draw2d.graph.Node dont sa taille (width/height) est initialisé avec la taille de préférences de la figure (StateTypeFigure/ActionTypeFigure).
ConnectableNodePart#contributeEdgesToGraph
Pour implémenter cette méthode je me suis inspiré de celle de org.eclipse.gef.examples.flow.parts.SimpleActivityPart#contributeEdgesToGraph(CompoundDirectedGraph graph, Object object, Map partsToNodes).
Ajoutez le code suivant à la classe ConnectableNodePart :
List outgoing = getSourceConnections();
for (int i = 0; i < outgoing.size(); i++) {
ConnectionPart part = (ConnectionPart) getSourceConnections()
.get(i);
part.contributeToGraph(graph, map);
}
}
Ce code itère sur toutes les EditPart connections sources ConnectionPart et appèle pour chacune des ConnectionPart la méthode ConnectionPart#contributeToGraph pour mettre à jour la structure CompoundDirectedGraph avec des org.eclipse.draw2d.graph.Edge.
ConnectableNodePart#applyGraphResults
Pour implémenter cette méthode je me suis inspiré de celle de org.eclipse.gef.examples.flow.parts.SimpleActivityPart#applyGraphResults(CompoundDirectedGraph graph, Object object, Map partsToNodes).
Ajoutez le code suivant à la classe ConnectableNodePart :
Node n = (Node)map.get(this);
getFigure().setBounds(new Rectangle(n.x, n.y, n.width, n.height));
for (int i = 0; i < getSourceConnections().size(); i++) {
ConnectionPart trans = (ConnectionPart) getSourceConnections().get(i);
trans.applyGraphResults(graph, map);
}
}
Ce code récupère le Node org.eclipse.draw2d.graph.Node qui est le résultat du calcul du layout et met à jour la figure draw2d ActionTypefigure/StateTypeFigure avec les nouvelles positions. Il y a ensuite itération sur les EditPart connections sources ConnectionPart pour appeler sur chacune des EditaPart la méthode ConnectionPart#applyGraphResults qui permet d’appliquer le resultat du calcul du layout sur les connections draw2d.
Voici le code en entier de ConnectableNodePart :
import java.util.List;
import java.util.Map;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.draw2d.graph.CompoundDirectedGraph;
import org.eclipse.draw2d.graph.Node;
import org.eclipse.draw2d.graph.Subgraph;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.gef.NodeEditPart;
import org.example.workflow.model.ConnectableNode;
import org.example.workflow.model.Connection;
import org.example.workflow.model.WorkflowPackage;
public abstract class ConnectableNodePart extends
AbstractEMFModelGraphicalEditPart implements NodeEditPart {
@Override
protected List<Connection> getModelSourceConnections() {
return getConnectableNode().getSourceConnections();
}
@Override
protected List<Connection> getModelTargetConnections() {
return getConnectableNode().getTargetConnections();
}
public ConnectableNode getConnectableNode() {
return (ConnectableNode) super.getModel();
}
public void notifyChanged(Notification notification) {
int type = notification.getEventType();
int featureId = notification.getFeatureID(WorkflowPackage.class);
switch (type) {
case Notification.ADD:
case Notification.REMOVE:
switch (featureId) {
case WorkflowPackage.CONNECTABLE_NODE__SOURCE_CONNECTIONS:
refreshSourceConnections();
break;
case WorkflowPackage.CONNECTABLE_NODE__TARGET_CONNECTIONS:
refreshTargetConnections();
break;
}
}
}
public void contributeNodesToGraph(CompoundDirectedGraph graph, Subgraph s,
Map map) {
Node n = new Node(this, s);
n.outgoingOffset = getAnchorOffset();
n.incomingOffset = getAnchorOffset();
n.width = getFigure().getPreferredSize().width;
n.height = getFigure().getPreferredSize().height;
n.setPadding(new Insets(10, 8, 10, 12));
map.put(this, n);
graph.nodes.add(n);
}
int getAnchorOffset() {
return 9;
}
public void contributeEdgesToGraph(CompoundDirectedGraph graph, Map map) {
List outgoing = getSourceConnections();
for (int i = 0; i < outgoing.size(); i++) {
ConnectionPart part = (ConnectionPart) getSourceConnections()
.get(i);
part.contributeToGraph(graph, map);
}
}
public void applyGraphResults(CompoundDirectedGraph graph, Map map) {
Node n = (Node) map.get(this);
getFigure().setBounds(new Rectangle(n.x, n.y, n.width, n.height));
for (int i = 0; i < getSourceConnections().size(); i++) {
ConnectionPart trans = (ConnectionPart) getSourceConnections().get(
i);
trans.applyGraphResults(graph, map);
}
}
}
ConnectionPart
Notre classe ConnectionPart joue le même rôle que la classe org.eclipse.gef.examples.flow.parts.TransitionPart. Nous allons nous inspirer de cette classe.
ConnectionPart#contributeToGraph
Pour implémenter cette méthode je me suis inspiré de celle de org.eclipse.gef.examples.flow.parts.TransitionPart#contributeToGraph(CompoundDirectedGraph graph, Object object, Map partsToNode).
Ajoutez le code suivant à la classe ConnectionPart :
Node source = (Node)map.get(getSource());
Node target = (Node)map.get(getTarget());
Edge e = new Edge(this, source, target);
e.weight = 2;
graph.edges.add(e);
map.put(this, e);
}
ConnectionPart#applyGraphResults
Pour implémenter cette méthode je me suis inspiré de celle de org.eclipse.gef.examples.flow.parts.TransitionPart#applyGraphResults(CompoundDirectedGraph graph, Object object, Map partsToNode).
Ajoutez le code suivant à la classe ConnectionPart :
Edge e = (Edge)map.get(this);
NodeList nodes = e.vNodes;
PolylineConnection conn = (PolylineConnection)getConnectionFigure();
conn.setTargetDecoration(new PolygonDecoration());
if (nodes != null) {
List bends = new ArrayList();
for (int i = 0; i < nodes.size(); i++) {
Node vn = nodes.getNode(i);
int x = vn.x;
int y = vn.y;
if (e.isFeedback()) {
bends.add(new AbsoluteBendpoint(x, y + vn.height));
bends.add(new AbsoluteBendpoint(x, y));
} else {
bends.add(new AbsoluteBendpoint(x, y));
bends.add(new AbsoluteBendpoint(x, y + vn.height));
}
}
conn.setRoutingConstraint(bends);
} else {
conn.setRoutingConstraint(Collections.EMPTY_LIST);
}
}
Voici le code en entier de ConnectionPart :
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.eclipse.draw2d.AbsoluteBendpoint;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PolygonDecoration;
import org.eclipse.draw2d.PolylineConnection;
import org.eclipse.draw2d.graph.CompoundDirectedGraph;
import org.eclipse.draw2d.graph.Edge;
import org.eclipse.draw2d.graph.Node;
import org.eclipse.draw2d.graph.NodeList;
import org.eclipse.gef.editparts.AbstractConnectionEditPart;
public class ConnectionPart extends AbstractConnectionEditPart {
@Override
protected IFigure createFigure() {
PolylineConnection conn = (PolylineConnection) super.createFigure();
conn.setTargetDecoration(new PolygonDecoration());
return conn;
}
@Override
protected void createEditPolicies() {
}
public void contributeToGraph(CompoundDirectedGraph graph, Map map) {
Node source = (Node) map.get(getSource());
Node target = (Node) map.get(getTarget());
Edge e = new Edge(this, source, target);
e.weight = 2;
graph.edges.add(e);
map.put(this, e);
}
protected void applyGraphResults(CompoundDirectedGraph graph, Map map) {
Edge e = (Edge) map.get(this);
NodeList nodes = e.vNodes;
PolylineConnection conn = (PolylineConnection) getConnectionFigure();
conn.setTargetDecoration(new PolygonDecoration());
if (nodes != null) {
List bends = new ArrayList();
for (int i = 0; i < nodes.size(); i++) {
Node vn = nodes.getNode(i);
int x = vn.x;
int y = vn.y;
if (e.isFeedback()) {
bends.add(new AbsoluteBendpoint(x, y + vn.height));
bends.add(new AbsoluteBendpoint(x, y));
} else {
bends.add(new AbsoluteBendpoint(x, y));
bends.add(new AbsoluteBendpoint(x, y + vn.height));
}
}
conn.setRoutingConstraint(bends);
} else {
conn.setRoutingConstraint(Collections.EMPTY_LIST);
}
}
}
Modification Layout
A ce stade la figure draw2d WorkflowTypeFigure qui contient toutes les figures du workflow est géré par le layout GEF org.eclipse.draw2d.XYLayout:
super.setLayoutManager(new XYLayout());
}
Les coordonnées X, Y de chacune des figure sont positionnés lors de l’appel du refreshVisuals de l’EditPart :
protected void refreshVisuals() {
super.refreshVisuals();
Rectangle bounds = new Rectangle(x, y, 50, 50);
((GraphicalEditPart) getParent()).setLayoutConstraint(this,
getFigure(), bounds);
...
Nous souhaitons maintenant utiliser notre layout GraphLayoutManager qui permet de positionner automatiquement les figures.
WorkflowTypeFigure
Ici nous souhaitons utiliser notre layout GraphLayoutManager. Pour cela modifier la classe WorkflowTypeFigure comme suit :
import org.eclipse.draw2d.Figure;
import org.example.workflow.presentation.graphical.parts.GraphLayoutManager;
import org.example.workflow.presentation.graphical.parts.WorkflowTypePart;
public class WorkflowTypeFigure extends Figure {
public WorkflowTypeFigure(WorkflowTypePart diagram) {
super.setLayoutManager(new GraphLayoutManager(diagram));
//super.setLayoutManager(new XYLayout());
//super.setBorder(new LineBorder(ColorConstants.red, 2));
}
}
Et modifiez dans WorkflowTypePart :
protected IFigure createFigure() {
return new WorkflowTypeFigure(this);
}
StateTypePart
A ce stade nous utilisons le layout GraphLayoutManager au lieu de XYLayout. Même si ca ne géne pas, nous pouvons commenter le code qui gèrait le positionnement X, Y des figures. Dans la classe StateTypePart, commentez le code suivant :
//private int y = new Double(Math.random() * 400).intValue();
puis le code suivant :
protected void refreshVisuals() {
super.refreshVisuals();
// Rectangle bounds = new Rectangle(x, y, 50, 50);
// ((GraphicalEditPart) getParent()).setLayoutConstraint(this,
// getFigure(), bounds);
StateType State = getState();
StateTypeFigure figure = (StateTypeFigure) getFigure();
figure.setName(State.getName());
}
ActionTypePart
Nous pouvons aussi commenter le code de positionnement dans les ActionTypePart :
//private int y = new Double(Math.random() * 400).intValue();
puis le code suivant :
protected void refreshVisuals() {
super.refreshVisuals();
// Rectangle bounds = new Rectangle(x, y, 50, 50);
// ((GraphicalEditPart) getParent()).setLayoutConstraint(this,
// getFigure(), bounds);
ActionType action = getAction();
ActionTypeFigure figure = (ActionTypeFigure) getFigure();
figure.setName(action.getName());
}
Relancez le plugin et vous devriez avoir les states et actions positionnés automatiquement :
Invalidate
Lorsque l’on modifie les propriétés fromState et toState d’uen action, les connections se mettent à jour correctment mais le positionnement ne s’effectue pas correctement. Voici un scénario qui pose problème :
Charger le graphique à partir du contenu XML suivant :
<workflow xmlns="http://www.example.org/workflow">
<state name="s1"/>
<state name="s2"/>
<action name="a2" fromState="s1" toState="s2"/>
</workflow>
Ajoutez un state « s3″ :
<workflow xmlns="http://www.example.org/workflow">
<state name="s1"/>
<state name="s2"/>
<state name="s3"/>
<action name="a2" fromState="s1" toState="s2"/>
</workflow>
Modifiez le toState de l’action par « s3″, voici le graphique obtenu :
Comme vous pouvez le constater le state s3 n’a pas bougé pour qu’il soit mieux positionné. Pour régler le problème, ajouter dans la classe ActionTypePart le code suivant :
case WorkflowPackage.ACTION_TYPE__FROM_STATE:
case WorkflowPackage.ACTION_TYPE__TO_STATE:
// Causes Graph to re-layout
((GraphicalEditPart) (getViewer().getContents())).getFigure().revalidate();
break;
int type = notification.getEventType();
int featureId = notification.getFeatureID(WorkflowPackage.class);
switch (type) {
case Notification.SET:
switch (featureId) {
case WorkflowPackage.ACTION_TYPE__NAME:
refreshVisuals();
break;
case WorkflowPackage.ACTION_TYPE__FROM_STATE:
case WorkflowPackage.ACTION_TYPE__TO_STATE:
// Causes Graph to re-layout
((GraphicalEditPart) (getViewer().getContents())).getFigure().revalidate();
break;
}
default:
super.notifyChanged(notification);
}
}
Si vous relancez le plugin et que vous rejouez le scénario décrit ci-dessus, vous aurrez ce positionnemnt de figures :
Conclusion
Dans ce billet nous avons mis en place le layout qui permet de positionner automatiquement les figures actions et states. Le code est un peu intrusif car il nous a obligé à modifier les EditPart ActionTypePart, StateTypePart et WorkflowTypePart, mais c’est celui proposé dans le projet d’exemple GEF org.eclipse.gef.examples.flow, le seul example de layout automatique que j’ai pu trouver. Les figures states et actions apparaîssent toutes petites, mais elles ont l’avantage de se re-dimensionner en fonction du label de la figure (nom du state/action). Dans le prochain billet nous personnaliserons ces figures pour avoir un diagramme de workflow un peu plus joli.
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 nd786mar1,
Merci de tes encouragements.
Je pense que c’est possible d’effectuer ce qu etu souhaites faire, mais je ne suis pas allé aussi loin dan sles layout de GEF.
Bon courage
Angelo
Bonjour Angelo,
Merci pour ce tutoriel très intéressent.
j’ai l’intention de créer un éditeur HTML WYSIWY à l’aide de EMFGEF.
en se basant sur GEF (layout,…), est-il possible de mettre un positionnement automatique des figures (label,inputtext,…) comme dans le cas de web page editor et dreamweaver. l’important est de ne pas avoir l’air qu’on dessine une page HTML.
Merci d’avance.