Home
.. Links
.. Search
.. Plugins
.. Help
.. Irc Faq

Projects
.. Platform/Faq
.. JDT/Faq/Plan
.. PDE/Faq
.. SWT/Faq
.. RCP/Faq

Tools Projects
.. CDT/Faq
.. GEF/Faq
.. EMF/Faq

Wiki Tutorials

Hosted Projects
.. MTJ
.. Google Summer Of Code 2007
.. Update Manager 2.0
.. EasyEclipse
.. Stylebase for Eclipse

Archives

Movable Connection Labels


Resizable and movable connection-labels

 

by Ivo Vigan Move

vigan (at) ifi.unizh.ch

 

Abstract

 

This tutorial discusses the topic of developing labels which belong to a connection and can be moved and resized by user interactions. Since GEF doesn't offer a solution for this concern, everything has to be implemented at your own. At first glance one might think this problem doesn't require a whole tutorial. But if one delves into this task, several problems appear which are discussed in the next sections. It must emphasized, that the following tutorial doesn't explain the general steps of creating an GEF application, but only the steps associated with the label.

Suppose that we want to create a graphical editor in which we can draw class or objects connected by associations. The associations are annotated with two role names and the corresponding cardinalities. Each cardinality/role pair should be movable and resizable in an independent way (but is also useful if one likes to create direct editable labels of a connection). Therefore, suppose the following class diagram shows the part of an architecture for such an editor which implements these cardinality/role pairs as labels.

 

classdiagram

  

1. Creating a suitable Locator for controlling the lables location

The cause of the first obstacle lies in the fact, that a PolyLineConnection only offers the possibility to place figures added to the connection by using a delegating layout manager, which is not capable of moving and resizing figures by user interaction. To place a figure, a layout manager needs to know the constraints of the figure; constraints are data attached to each figure that gives additional guidance to the layout manager. While the delegating layout manager expects its figures to have a constraint which implements the Locator interface, the XYLayoutmanager - which is used to move and resize figures - requires the figures to have its constraint in form of a rectangle.

What we therefore need, is an object which handles both types of constraints. This class is called DelegatingLocator.

 

1.1 DelegatingLocator

The main function this object is placing the labels according to the endpoints of the polyline and placing the labels according to the offset, the user determined by dragging the label. This class can handle constraint changes in rectangle and locator form and sets the correct bounds of the label.

Therefore, 3 Rectangles are required:

  • offsetRect: Stores the displacement - the user made by moving the label - to the Connection's endpoint.
  • polylineRect: Stores position of the Connection's endpoint (= Coordinates the BlindLocator assesses).
  • bounds: The sum of the two vectors (polylineRect+offsetRect)

 

Methods:

The setConstraint method is being called from the refreshVisuals() method of the RoleEditPart. The constraints to be set - in from of a Rectangle - are retrieved from the RoleModel by the getLocation method.

The calculateOffset sets the coordinates of the polylineRect. Therefore, the endPointLocator, an instance of the BlindLocator, is used to calculate the location. The BlindLocator knows the Connection and the end to which it is associated:

 

endPointLocator = new BlindLocator(association, isTo);

private Rectangle calculateOffset() {

Figure dummyFigure = new Figure();

endPointLocator.relocate(dummyFigure);

return new Rectangle(dummyFigure.getBounds());

}

 

The relocate method is called from the layout manager each time something relevant changes. This method relocates the label according to the offset Rectanlge and the polyline rectangle, both stored in the DelegatingLocator object. The actual relocating is done by the setBounds method of the IFigure class which is called in this relocate mehod.

It's important, that the relocate method fires a property change Message which is caught by the RoleEditPart. Eachtime the the RoleEditPart receives a this Message, the RoleModels location has to be updated. This is done in the updateModelLocation() method of the RoleEditPart by calling the setLocation() method of the RoleModel.

 

public void relocate(IFigure target){

Rectangle bounds = new Rectangle();

Point location = new Point();

bounds = getConstraint();

target.setBounds(bounds);

location.x = bounds.x;

location.y = bounds.y;

pcsDelegate.firePropertyChange(MOVE_INDIRECT_PROP , null, location);

}

 

 

1.2 BlindLocator

The DelegatingLocator uses a BlindLocator to keep the labels position aware of the association endpoint positions. A ConnectionEndpointLocator has a method private IFigure getConnectionOwner() which wants to know both the start- and endpoint anchor of the connection. The locator used in the DelegatingLocator doesn't know anything about anchors. Therefore the Class BlindLocator is simply a copy of the Class ConnectionEndpointLocator with the only difference, that getConnectionOwner() returns null.

Unfortunately, this has to be solved like this, since too many methods used for relocating are private methods of the ConnectionEndpointLocator.

 

private IFigure getConnectionOwner() {

return null;

}

 

 

1.3 AssociationEditPart

Introducing DelegatingLocator and BlindLocator solves the problem of different constraint types (Locator/Rectangle). The things that are left to do, are implementing the EditParts of the corresponding Association and the corresponding EditPolicy, so the user can interact with the label. This EditPart instantiates two RoleEditPart objects which act as children. This is done in the createChild method.

public EditPart createChild(Object model){

RoleEditPart part=null;

part = new RoleEditPart();

part.setModel(model);

return part;

}

 

public List getModelChildren(){

return ((Association)getModel()).getRoles();

}

 

Furthermore, a DelegatingLayoutEditPolicy has to be installed by the Key of EditPolicy.LAYOUT_ROLE in the createEditPolicies() method. This policy handles the move and resize requests.

protected void createEditPolicies() {

installEditPolicy(EditPolicy.LAYOUT_ROLE, new DelegatingLayoutEditPolicy());

......

}

 

 

1.4 RoleEditPart

The RoleEditPart allows users to perform operations on the RoleLabel.

The most important method is the refreshVisuals() method. This method is called as soon as the model has changed. In this case the setBounds() method of the DelegatingLocator is called, since the location of the label has to be updated. Beside the call of the refreshVisuals() method, the setLayoutConstraint(RoleEditpart, RoleLabel, DelegatingLocator) method of the AssociationEditPart has to be called. By this call, the RoleEditParts' constraints are applied to the LayoutManager. This is what causes the childs to get updated during the next revalidation cycle.

 

protected void refreshVisuals() {

DelegatingLocator locator;

//Depending on the role type (from/to) a different Locator has to ben updated

if(((Role)getModel()).isTo()){

locator = ((AssociationEditPart) getParent()).getCastedFigure().getTargetLocator();

}

else{

locator = ((AssociationEditPart) getParent()).getCastedFigure().getSourceLocator();

}

Rectangle bounds = new Rectangle(getCastedModel().getLocation(),

getCastedModel().getSize());

if(bounds.x!=0&&bounds.y!=0){

locator.setRectangle(bounds);

Point p = new Point();

p.x = locator.getConstraint().x;

p.y = locator.getConstraint().y;

/*The location saved in the model has to be updated manually, since the

responsible EndPointLocator only works as an internal layoutmanager in the

DelegatingLocatorImpl class.*/

getCastedRoleModel().setLocation(p);

}

((GraphicalEditPart) getParent()).setLayoutConstraint(this,getFigure(), locator);

}

 

The second method is needed to update the location saved in the model, each time the RoleLabel gets relocated by the BlindLocator. This is necessary, since the DelegatingLocator doens't know the RoleModel (according to the MVC pattern). Therefore the RoleEditPart (= Controller) updates the model in the propertyChange() method.

 

public void propertyChange(PropertyChangeEvent evt) {

String prop = evt.getPropertyName();

if(DelegatingLocator.MOVE_INDIRECT_PROP.equals(prop)){

model.setLocation(location);

}

......

super.propertyChange(evt);

}

 

1.5 DelegatingLayoutEditPolicy

As I denoted under 1.3, the DelegatingLayoutEditPolicy is needed to create the VisualizableModelElementSetConstraintCommand whenever a ChangeBoundsRequest occures.

This class extends the ConstrainedLayoutEditPolicy. The only overridden method is the createChangeConstraintCommand() method, which returns a new VisualizableModelElementSetConstraintCommand.

protected Command createChangeConstraintCommand(

ChangeBoundsRequest request, EditPart child, Object constraint) {

return new VisualizableModelElementSetConstraintCommand((VisualizableModelElement) child.getModel(), request, (Rectangle) constraint);

}


1.6 VisualizableModelElementSetConstraintCommand

This Command actually changes the location and/or size of the label. This is done by calling the setLocation() and setSize() methods of the role model.

 

public VisualizableModelElementSetConstraintCommand(VisualizableModelElement shape, ChangeBoundsRequest req,

Rectangle newBounds) {

if (shape == null || req == null || newBounds == null) {

throw new IllegalArgumentException();

}

this.shape = shape;

this.request = req;

this.newBounds = newBounds.getCopy();

setLabel("move / resize");

}

 

 

1.7 MyPanningSelectionTool

This class extends the PanningSelectionTool. The method handleButtonDown() is the only method which has to be overwritten, since we have to use our own DragTracker if we are moving a role label. handleButtonDown() is the only place where we can set the DragTracker. The reason for using our own DragTracker is the following:

By default, if a RoleLabel is moved, as soon as the mouse button is released, an AddCommand gets created since the parent of the role label changed from AssociationEditPart to the main diagramm container. But this is exactly what we don't want to happen. We want our RoleEditPart to always be the child of the AssociationEditPart.


So what we have to change is the criteria in the DragTracker by which it is decided if an operation is a move- or add operation. These changes are discussed under 1.8.

 

protected boolean handleButtonDown(int button) {

......

if (editpart != null) {

//Here the MyDragTracker has to be set, to change the

//have access to the isMove method.

if(editpart instanceof RoleEditPart){

setDragTracker(new MyDragTracker(editpart));

}

else{

setDragTracker(editpart.getDragTracker(getTargetRequest()));

}

lockTargetEditPart(editpart);

return true;

}

return false;

}


1.8 MyDragTracker

This class extends the DragTracker class. As mentioned above, the move/add criteria of the DragTracker has to be changed. This is done by overwriting the isMove() method.

This method has to return true under any circumstances if the source EditPart is an instance of the class RoleEditPart.

protected boolean isMove() {

EditPart part = getSourceEditPart();

while (part != getTargetEditPart() && part != null) {

//RoleEditParts are always movable

if (part instanceof RoleEditPart || part.getParent() == getTargetEditPart()

&& part.getSelected() != EditPart.SELECTED_NONE)

return true;

part = part.getParent();

}

return false;

}

 


NameVersionSizeDateUser
Klassendiagramm_2.jpg1708832/20/06 11:42 PMvigan
resize.jpg185572/20/06 1:46 AMvigan
resize2.jpg192432/20/06 2:16 AMvigan
resizeSmall.jpg246662/20/06 2:22 AMvigan



Last Modified 2/20/06 11:43 PM

Hide Tools