août
2009
A l’étape du billet précédant [step10] nous avons modifié les modèles EMF ActionType et StateType pour qu’ils gèrent la notion de Connection. Pour rappel, une Connection est le modèle (EMF) qui représente :
- le lien entre un StateType et une ActionType : cette connection est mise à jour en fonction de la propriété fromState de l’action.
- ou le lien entre un ActionType et un StateType : cette connection est mise à jour en fonction de la propriété toState de l’action
Dans ce billet nous allons représenter graphiquement ces connections EMF à l’aide d’un EditPart GEF (AbstractConnectionEditPart) capable de gérer des connections :
Vous pouvez télécharger le projet org.example.workflow_step11.zip présenté dans ce billet.
Connection & EditPart
Maintenant que nous avons un modèle Connection entre les StateType et ActionType, nous pouvons le représenter graphiquement avec GEF. Pour cela nous allons gérer le modèle EMF Connection comme les autres modèles EMF WorkflowType, ActionType et StateType via un EditPart spécial qui s’occupe de gérer des connections. Nous allons créer la classe org.example.workflow.presentation.graphical.parts.ConnectionPart qui devra implémenter l’interface org.eclipse.gef.ConnectionEditPart. Voici le code de l’interface org.eclipse.gef.ConnectionEditPart :
public interface ConnectionEditPart
extends GraphicalEditPart
{
/**
* @return the EditPart at the <i>source</i> end of this connection.
*/
EditPart getSource();
/**
* @return the EditPart at the <i>target</i> end of this connection.
*/
EditPart getTarget();
/**
* Sets the <i>source</i> of this connection.
* @param source the source of this connection
*/
void setSource(EditPart source);
/**
* Sets the<i>target</i> of this connection.
* @param target the target of this connection
*/
void setTarget(EditPart target);
}
Comme vous pouvez le constater cette interface hérite de l’interface de base des EditPart GEF org.eclipse.gef.GraphicalEditPart et définit les EditPart source et cible (target) auquel est lié l’EditPart qui gère la connection. Dans notre cas les EditPart source et cible seront des ActionTypePart ou StateTypePart.
GEF fournit la classe abstraite org.eclipse.gef.editparts.AbstractConnectionEditPart qui implémente ces méthodes et ou GEF conseille vivement d’utiliser cette classe au lieu d’implémenter soit même l’interface org.eclipse.gef.ConnectionEditPart.
Création classe ConnectionPart
Dans le projet org.example.workflow.editor, créer la classe org.example.workflow.presentation.graphical.parts.ConnectionPart comme suit :
import org.eclipse.gef.editparts.AbstractConnectionEditPart;
public class ConnectionPart extends AbstractConnectionEditPart {
@Override
protected void createEditPolicies() {
}
}
ConnectionPart & WorkflowPartFactory
Nous pouvons maintenant associer l’EditPart ConnectionPart au modèle EMF Connection via la factory d’EditPart org.example.workflow.presentation.graphical.parts.WorkflowPartFactory. Pour cela ajoutez le code suivant dans la méthode WorkflowPartFactory#getPartForElement(Object modelElement) :
return new ConnectionPart();
}
Voici le code entier de org.example.workflow.presentation.graphical.parts.WorkflowPartFactory :
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPartFactory;
import org.example.workflow.model.ActionType;
import org.example.workflow.model.Connection;
import org.example.workflow.model.StateType;
import org.example.workflow.model.WorkflowType;
public class WorkflowPartFactory implements EditPartFactory {
public static final EditPartFactory INSTANCE = new WorkflowPartFactory();
public EditPart createEditPart(EditPart context, Object modelElement) {
// get EditPart for model element
EditPart part = getPartForElement(modelElement);
// store model element in EditPart
part.setModel(modelElement);
return part;
}
/**
* Maps an object to an EditPart.
*
* @throws RuntimeException
* if no match was found (programming error)
*/
private EditPart getPartForElement(Object modelElement) {
if (modelElement instanceof WorkflowType) {
return new WorkflowTypePart();
}
if (modelElement instanceof ActionType) {
return new ActionTypePart();
}
if (modelElement instanceof StateType) {
return new StateTypePart();
}
if (modelElement instanceof Connection) {
return new ConnectionPart();
}
throw new RuntimeException("Can't create part for model element: "
+ ((modelElement != null) ? modelElement.getClass().getName()
: "null"));
}
}
ConnectableNodePart
A cette étape nous avons créé un EditPart ConnectionPart qui permet d’afficher des connections, mais il ne sera jamais utilisé (la factory d’EditPart ne sera jamais sollicité avec le modèle Connection). En effet les EditPart qui souhaitent gérer des connections doivent indiquer :
- la liste des connections modèles sources (dans notre cas Connection EMF) du modèle de l’EditPart (dans notre cas StateType, ActionType) via la méthode org.eclipse.gef.GraphicalEditPart#getModelSourceConnections().
- la liste des connections modèles cibles (dans notre cas Connection EMF) du modèle de l’EditPart (dans notre cas StateType, ActionType) via la méthode org.eclipse.gef.GraphicalEditPart#getModelTargetConnections().
Par défaut org.eclipse.gef.editparts.AbstractGraphicalEditPart implémente GraphicalEditPart#getModelSourceConnections() en retournant une liste vide :
return Collections.EMPTY_LIST;
}
Il est en de même pour GraphicalEditPart#getModelTargetConnections() :
return Collections.EMPTY_LIST;
}
Nous devons surcharger ces 2 méthodes dans les EditPart StateTypePart et ActionTypePart pour indiquer comment récupérer les liste de connections sources et cibles des modèle EMF StateType et ActionType.
Les StateType et ActionType étendent tous les 2 le modèle EMF ConnectableNode qui contient les connections sources et cibles (target). Il apparaît logique de créer une classe abstraite ConnectableNodePart qui gère le modèle EMF ConnectableNode. Autrement dit nous allons
- créer la classe abstraite ConnectableNodePart qui gère les modèles EMF ConnectableNode.
- faire étendre StateTypePart de ConnectableNodePart.
- faire étendre ActionTypePart de ConnectableNodePart.
Création classe ConnectableNodePart
Créer la classe abstraite org.example.workflow.presentation.graphical.parts.ConnectableNodePart comme suit :
import java.util.List;
import org.example.workflow.model.ConnectableNode;
import org.example.workflow.model.Connection;
public abstract class ConnectableNodePart extends
AbstractEMFModelGraphicalEditPart {
@Override
protected List<Connection> getModelSourceConnections() {
return getConnectableNode().getSourceConnections();
}
@Override
protected List<Connection> getModelTargetConnections() {
return getConnectableNode().getTargetConnections();
}
public ConnectableNode getConnectableNode() {
return (ConnectableNode) super.getModel();
}
}
Comme vous pouvez le remarquer, la méthode AbstractGraphicalEditPart#getModelSourceConnections() est surchargée pour retourner la liste des connection EMF sources du noeud (StateType ou ActionType) :
protected List<Connection> getModelSourceConnections() {
return getConnectableNode().getSourceConnections();
}
Modification StateTypePart – ConnectableNodePart
Modifiez la classe StateTypePart pour quelle étende ConnectableNodePart :
}
Modification ActionTypePart – ConnectableNodePart
Modifiez la classe ActionTypePart pour quelle étende ConnectableNodePart :
}
ConnectableNodePart & GEF NodeEditPart
Relancez le plugin, et vous pourrez constater que les connections s’affichent :
Mais selon ou se positionnent (aléatoirement) les figures dans la page graphiques GEF, il arrive que les connections soient coupées avant d’atteindre la figure. La copie d’écran ci dessus montre ce problème (voir cercle rouge). Autrement dit la connection ne se « fixe » (anchor) pas correctement sur la figure. Pour régler ce problème, les EditPart StateTypePart et ActionTypePart doivent indiquer comment les connections doivent se fixer aux figures en implémentant l’interface org.eclipse.gef.NodeEditPart qui indique comment fixer (anchor) les connections cibles (target) et sources à la figure. Cette interface est utilisée par l’EditPart GEF qui gèrent les connections AbstractConnectionEditPart.
Voici le code de l’interface NodeEditPart :
import org.eclipse.draw2d.ConnectionAnchor;
public interface NodeEditPart
extends GraphicalEditPart
{
ConnectionAnchor getSourceConnectionAnchor(ConnectionEditPart connection);
ConnectionAnchor getTargetConnectionAnchor(ConnectionEditPart connection);
ConnectionAnchor getSourceConnectionAnchor(Request request);
ConnectionAnchor getTargetConnectionAnchor(Request request);
}
L’interface org.eclipse.draw2d.ConnectionAnchor permet de préciser comment la connection doit se fixer à la figure. Il existe plusieurs implémentations de cette interface :
Modification ConnectableNodePart – NodeEditPart
Modifiez la classe abstraite org.example.workflow.presentation.graphical.parts.ConnectableNodePart pour quelle implémente l’interface org.eclipse.gef.NodeEditPart :
import java.util.List;
import org.eclipse.gef.NodeEditPart;
import org.example.workflow.model.ConnectableNode;
import org.example.workflow.model.Connection;
public abstract class ConnectableNodePart extends
AbstractEMFModelGraphicalEditPart implements NodeEditPart {
...
}
Modification StateTypePart – NodeEditPart
Modifiez l’EditPart StateTypePart pour implémenter les méthodes de NodeEditPart en ajoutant le code suivant à la classe :
org.eclipse.gef.ConnectionEditPart connection) {
return new EllipseAnchor(getFigure());
}
public ConnectionAnchor getSourceConnectionAnchor(Request request) {
return new EllipseAnchor(getFigure());
}
public ConnectionAnchor getTargetConnectionAnchor(
org.eclipse.gef.ConnectionEditPart connection) {
return new EllipseAnchor(getFigure());
}
public ConnectionAnchor getTargetConnectionAnchor(Request request) {
return new EllipseAnchor(getFigure());
}
...
Les figures StateTypeFigure sont des ellipses, nous utilisons donc org.eclipse.draw2d.EllipseAnchor dans le cas des EditPart StateTypePart.
Voici le code en entier de la classe StateTypePart :
import org.eclipse.draw2d.ConnectionAnchor;
import org.eclipse.draw2d.EllipseAnchor;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.Request;
import org.example.workflow.model.StateType;
import org.example.workflow.model.WorkflowPackage;
import org.example.workflow.presentation.graphical.figures.StateTypeFigure;
public class StateTypePart extends ConnectableNodePart {
private int x = new Double(Math.random() * 400).intValue();
private int y = new Double(Math.random() * 400).intValue();
@Override
protected IFigure createFigure() {
return new StateTypeFigure();
}
@Override
protected void createEditPolicies() {
}
@Override
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());
}
public StateType getState() {
return (StateType) super.getModel();
}
public void notifyChanged(Notification notification) {
int type = notification.getEventType();
int featureId = notification.getFeatureID(WorkflowPackage.class);
switch (type) {
case Notification.SET:
switch (featureId) {
case WorkflowPackage.STATE_TYPE__NAME:
refreshVisuals();
break;
}
}
}
public ConnectionAnchor getSourceConnectionAnchor(
org.eclipse.gef.ConnectionEditPart connection) {
return new EllipseAnchor(getFigure());
}
public ConnectionAnchor getSourceConnectionAnchor(Request request) {
return new EllipseAnchor(getFigure());
}
public ConnectionAnchor getTargetConnectionAnchor(
org.eclipse.gef.ConnectionEditPart connection) {
return new EllipseAnchor(getFigure());
}
public ConnectionAnchor getTargetConnectionAnchor(Request request) {
return new EllipseAnchor(getFigure());
}
}
Modification ActionTypePart – NodeEditPart
Modifiez l’EditPart ActionTypePart pour implémenter les méthodes de NodeEditPart en ajoutant le code suivant à la classe :
public ConnectionAnchor getSourceConnectionAnchor(
org.eclipse.gef.ConnectionEditPart connection) {
return new ChopboxAnchor(getFigure());
}
public ConnectionAnchor getSourceConnectionAnchor(Request request) {
return new ChopboxAnchor(getFigure());
}
public ConnectionAnchor getTargetConnectionAnchor(
org.eclipse.gef.ConnectionEditPart connection) {
return new ChopboxAnchor(getFigure());
}
public ConnectionAnchor getTargetConnectionAnchor(Request request) {
return new ChopboxAnchor(getFigure());
}
Les figures ActionTypeFigure sont des carrées, nous utilisons donc org.eclipse.draw2d.ChopboxAnchor dans le cas des EditPart ActionTypePart.
Voici le code en entier de la classe ActionTypePart :
import org.eclipse.draw2d.ChopboxAnchor;
import org.eclipse.draw2d.ConnectionAnchor;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.Request;
import org.example.workflow.model.ActionType;
import org.example.workflow.model.WorkflowPackage;
import org.example.workflow.presentation.graphical.figures.ActionTypeFigure;
public class ActionTypePart extends ConnectableNodePart {
private int x = new Double(Math.random() * 400).intValue();
private int y = new Double(Math.random() * 400).intValue();
@Override
protected IFigure createFigure() {
return new ActionTypeFigure();
}
@Override
protected void createEditPolicies() {
}
@Override
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());
}
public ActionType getAction() {
return (ActionType) super.getModel();
}
public void notifyChanged(Notification notification) {
int type = notification.getEventType();
int featureId = notification.getFeatureID(WorkflowPackage.class);
switch (type) {
case Notification.SET:
switch (featureId) {
case WorkflowPackage.ACTION_TYPE__NAME:
refreshVisuals();
break;
}
}
}
public ConnectionAnchor getSourceConnectionAnchor(
org.eclipse.gef.ConnectionEditPart connection) {
return new ChopboxAnchor(getFigure());
}
public ConnectionAnchor getSourceConnectionAnchor(Request request) {
return new ChopboxAnchor(getFigure());
}
public ConnectionAnchor getTargetConnectionAnchor(
org.eclipse.gef.ConnectionEditPart connection) {
return new ChopboxAnchor(getFigure());
}
public ConnectionAnchor getTargetConnectionAnchor(Request request) {
return new ChopboxAnchor(getFigure());
}
}
Relancez le plugin et vous pourrez constater que les connections sont bien « fixées » aux figures.
ConnectionPart – TargetDecoration
A cette étape, les connections sont représentées par un trait mais pas par une flêche qui indique le sens de la connection (fromState ou toState). Pour avoir une flêche indiquant le sens de la connection, ceci s’effectue lors de la création de la figure qui affiche la connection. Pour cela nous allons surcharger la méthode AbstractConnectionEditPart#createFigure() qui retourne une figure draw2d org.eclipse.draw2d.PolylineConnection et « décorer » la cible de la connection draw2d par une flêche.
Ajouter le code suivant dans la classe org.example.workflow.presentation.graphical.parts.ConnectionPart :
protected IFigure createFigure() {
PolylineConnection conn = (PolylineConnection) super
.createFigure();
conn.setTargetDecoration(new PolygonDecoration());
return conn;
}
Voici le code entier de la classe org.example.workflow.presentation.graphical.parts.ConnectionPart :
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PolygonDecoration;
import org.eclipse.draw2d.PolylineConnection;
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() {
}
}
Relancez le plugin et vous pourrez constater que les connections sont représentées par des flêches :
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