3.2+53.6
Working with the Eclipse Graphical Editing Framework (GEF)

The Graphical Editing Framework (GEF) is a set of Eclipse plugins for creating editors that support visual editing of arbitrary models. This framework is very popular and QF-Test has supported recording and playback of GEF items for a long time (since about version 2.2). It is also a good example for the power of the ItemResolver concept (see section 53.4), because the gef Jython module contains an implementation of just that interface.

The gef module can deal with GEF editors at a generic level and even support several editors at once. Though reasonable item names are provided out of the box also for GMF applications, there are limits to what can be determined automatically. Depending on the underlying model classes, there might still remain some work for you: Implementing custom resolvers to provide useful names and values for your items.

Recording GEF items

The actual GEF component is the FigureCanvas. This control displays Figures which represent EditParts. When recording a mouse click on such an element, QF-Test does not register a pure 'Mouse event' node for the canvas component with the corresponding (x,y) position but tries to recognize the object under the mouse cursor. For example, the recorded 'QF-Test component ID' may look like

  canvas@/Diagram/My ship/ShipLargeCargo (wine)
  canvas@Connection-2
  canvas@/Diagram/Rectangle 16329001

where "canvas" is the 'QF-Test ID' of the FigureCanvas component, followed by the item index of the recognized EditPart (see section 5.9). EditParts reside in a tree like hierarchy which is reflected in the index by a path separator '/'. The names of the individual items are generated as follows:

  • The item name is getModel().toString() unless it contains a hash value (e.g. NodeImpl@2d862).
  • QF-Test tries to extract a name for the item from the model ("My ship" in the above examples).
  • The class name along with a description gets recorded, e.g. "ShipLargeCargo (wine)".
  • If there's no description, an index is appended to the class name when there's more than one item of that class, e.g. "Connection-2" for the third connection.
  • The root EditPart always reads "Diagram".

As one can imagine, those generated item names may not always be useful. For example, items might be deleted so that the recorded index is not longer valid. Or the generated item name is unstable as "Rectangle 16329001" in the GEF Shapes example: The number is random and when restarting the application a different one will be created. Three options exist to overcome the problem:

  • Instead of working with a textual index, you can try to go with a numerical one. To this end, open the recording options and set the 'Sub-Item format' to 'Number' (see subsection 40.2.4). This is probably not satisfying because a numerical index like /0/1 tells nothing about an item.
  • Get in touch with your developers and convince them to provide a useful implementation of the toString() method of the item's model. It would make live easy for you, but only if the developers are cooperative.
  • Write an ItemNameResolver2. This is the tough course but unfortunately the most likely scenario. It is covered in the next section.

Implementing a GEF ItemNameResolver2

As stated in section 53.1, an ItemNameResolver2 is the hook to change or provide names for items. To get started, insert a new Jython 'SUT script' in the 'Extras' node with the following code:

def getItemName(canvas, item, name):
    print "name: %s" %name
    print "item: %s" %(item.__class__)
    model = item.getModel()
    print "model: %s" %(model.__class__)

resolvers.addItemNameResolver2("myGefItemNames", getItemName,
    "org.eclipse.draw2d.FigureCanvas")
Example 53.35:  Get started with a GEF ItemNameResolver2

To ease the installation of the resolver we use the resolvers module described in section 53.1. The resolver gets registered for the FigureCanvas class where the items reside. The default item name provided by QF-Test is supplied as the last argument to our function getItemName(). Now run the script, press the record button and then simply move the mouse over your figures on the canvas - supposing you have created some of them previously. Note that this first resolver implementation does nothing but print out out some information into the terminal, something like

  name: Rectangle 16329001
  item: org.eclipse.gef.examples.shapes.parts.ShapeEditPart
  model: org.eclipse.gef.examples.shapes.model.RectangularShape

The question is now: Does the model of the GEF EditPart provide any property that might be used as name for the item? The answer in the case of the GEF Shapes example is "No", and hopefully you are in a better situation with your application. To find out insert a line

  print dir(model)

in the getItemName() function and run the script again. Now you will also see the methods of the model when moving the mouse over the items in record mode. With a bit of luck you will find methods like getId() or getLabel() and can create a resolver like this:

def getItemName(canvas, item, name):
    model = item.getModel()
    return model.getId()

resolvers.addItemNameResolver2("myGefItemNames", getItemName,
    "org.eclipse.draw2d.FigureCanvas")
Example 53.36:  A simple ItemNameResolver2

Let's go back to the GEF Shapes example where we don't have such useful methods. Only geometry information is available for the shapes and that is not really helpful. At least we can distinguish between rectangles and ellipses. To make the item names unique we simply add a child index as shown in the following resolver:

def getItemName(canvas, item, name):
    name = None
    shapes = "org.eclipse.gef.examples.shapes"
    diagrammEditPart = shapes + ".parts.DiagramEditPart"
    shapeEditPart = shapes + ".parts.ShapeEditPart"
    connectionEditPart = shapes + ".parts.ConnectionEditPart"
    ellipticalShape = shapes + ".model.EllipticalShape"
    rectangularShape = shapes + ".model.RectangularShape"
    if qf.isInstance(item, shapeEditPart):
        siblings = item.getParent().getChildren()
        for i in range(len(siblings)):
            if (item == siblings[i]):
                if qf.isInstance(item.getModel(), ellipticalShape):
                    name = "Ellipse " + str(i)
                elif qf.isInstance(item.getModel(),
                                                 rectangularShape):
                    name = "Rectangle " + str(i)
    elif qf.isInstance(item, connectionEditPart):
        source = item.getSource()
        target = item.getTarget()
        sourceName = getItemName(canvas, source, str(source.getModel()))
        targetName = getItemName(canvas, target, str(target.getModel()))
        name = "Connection " + sourceName + " " + targetName
    elif qf.isInstance(item, diagrammEditPart):
        name = "Diagram"
    return name

resolvers.addItemNameResolver2("shapesItemNames", getItemName,
    "org.eclipse.draw2d.FigureCanvas")
Example 53.37:  An ItemNameResolver2 for GEF Shapes

With this resolver in place, the item index for a rectangle becomes

  /Diagram/Rectangle 1

where the trailing number is the child index of the item. The above implementation also provides names for the connections by calling getItemName() recursively for the source and the target item of the connection. Checking the types with qf.isInstance() (see section 49.6) will save you the need to import the GEF classes, something that is not trivial.

Once your resolver is working fine you should move the script into your 'Setup' sequence right behind the 'Wait for client to connect' node. This way the resolver will be registered automatically when the SUT starts.

Implementing a GEF ItemValueResolver2

Usually a GEF editor consists of two parts. Having focused so far on the canvas where you draw the figures, we now take a look at the palette where you select the kind of figure to draw (e.g. 'Rectangle', 'Ellipse' or 'Connection'). Its entries look like tool buttons but actually the palette is a FigureCanvas too. You will be glad to know that this one works out of the box, that is without implementing an ItemNameResolver2. When you click for example on the 'Rectangle' button, QF-Test recognizes a

  /Palette Root/Palette Container (Shapes)/Palette Entry (Rectangle)

item. What will happen when you record a check (cf. section 4.3) for the 'Object value' for this button? You may expect to get the button text 'Rectangle' but in fact the value of this item is

  Palette Entry (Rectangle)

The reason is that by default name and value of an item are the same. To alter this behavior and provide customized values you need to implement an ItemValueResolver2. This interface is very similar to the ItemNameResolver2 above. For the palette we can code the following one:

def getItemValue(canvas, item, value):
    value = None
    paletteEditPart = \
        "org.eclipse.gef.ui.palette.editparts.PaletteEditPart"
    if qf.isInstance(item, paletteEditPart):
        value = item.getModel().getLabel()
    return value

resolvers.addItemValueResolver2("shapesItemValues", getItemValue,
    "org.eclipse.draw2d.FigureCanvas")
Example 53.38:  An ItemValueResolver2 for the GEF Shapes palette

The method getLabel() returns the text as displayed in the palette.