3.2+54.5
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 54.3),
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.
54.5.1 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 41.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.
54.5.2 Implementing a GEF ItemNameResolver2
As stated in section 54.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")
To ease the installation of the resolver we use the resolvers
module
described in section 54.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")
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")
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 50.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.
54.5.3 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")
The method getLabel()
returns the text as displayed in the palette.