3.2+39.4
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 ago (since about version 2.2). It is also a good example for the power of the ItemResolver concept (see section 39.2), because the gef Jython module contains an implementation of just that interface. In older QF-Test versions you had to import the gef module explicitly with the help of a script node, but now this is done automatically.

The gef module can deal with GEF editors at a generic level, even supporting several editors at once. However, 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.

39.4.1
Recording GEF items

The following paragraphs refer to the GEF Shapes example. It allows you to draw rectangles, ellipses, and connections between those figures on the FigureCanvas. The type of figure to be drawn is chosen from a palette which is also part of the GEF editor. Suppose you have drawn a rectangle on the canvas and then record a mouse click on that figure. Without the GEF resolver you will record a 'Mouse event' node for the canvas component. The click position is relative to the canvas and there is no clue at all that it was the rectangle figure you clicked at. In contrast, with the GEF resolver installed the recording gets targeted at an item of the canvas:

  canvas@/ShapesDiagram\\\@1e1968d/Rectangle 16329001

You can see from the path-like index that you clicked on a 'Rectangle' which itself is part of a 'ShapesDiagram'. This item index is not really beautiful. Even worse, it is completely useless. But why? Both numbers contained in the index are hash code values which will change when you restart your application and reopen the saved drawing. Consequently, when trying to replay that mouse click, you will run into an IndexNotFoundException.

If you are lucky, your GEF application comes with proper item names giving an index like

  /ShapesDiagram/My blue rectangle

If not, there are three options:

39.4.2
Implementing a GEF ItemNameResolver2

You have seen in the last section that the items in the GEF Shapes example do not have useful names by default. As stated in section 39.1, an ItemNameResolver2 is the hook to change or provide names for items.

To get started - be it the Shapes example or your own GEF application - 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 39.12:  Get started with a GEF ItemNameResolver2

To ease the installation of the resolver we use the resolvers module described in subsection 39.1.10. The resolver gets registered for the FigureCanvas class where the items reside. As mentioned above, what QF-Test does by default is providing item.getModel().toString() as item name. This default value 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 getName() or getLabel() and can create a resolver like this:

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

resolvers.addItemNameResolver2("myGefItemNames", getItemName,
    "org.eclipse.draw2d.FigureCanvas")
Example 39.13:  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:

from de.qfs.apps.qftest.extensions import ResolverRegistry

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 ResolverRegistry.isInstance(item, shapeEditPart):
        siblings = item.getParent().getChildren()
        for i in range(len(siblings)):
            if (item == siblings[i]):
                if ResolverRegistry.isInstance(item.getModel(),
                                               ellipticalShape):
                    name = "Ellipse " + str(i)
                elif ResolverRegistry.isInstance(item.getModel(),
                                                 rectangularShape):
                    name = "Rectangle " + str(i)
    elif ResolverRegistry.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 ResolverRegistry.isInstance(item, diagrammEditPart):
        name = "Diagram"
    return name

resolvers.addItemNameResolver2("shapesItemNames", getItemName,
    "org.eclipse.draw2d.FigureCanvas")
Example 39.14:  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 ResolverRegistry.isInstance (see subsection 39.1.11) 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.

39.4.3
Implementing a GEF ItemValueResolver2

We have mentioned that a GEF editor normally consists of two parts. Having so far focused on the canvas where you draw the figures, we now take a closer look at the palette where you select the kind of figure to draw ('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 if you record a check 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:

from de.qfs.apps.qftest.extensions import ResolverRegistry

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

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

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