3.2+39.4
Das Eclipse Graphical Editing Framework (GEF)

Das Graphical Editing Framework (GEF) besteht aus mehreren Eclipse Plugins, mit deren Hilfe graphische Editoren erstellt werden können, um damit beliebige Datenmodelle zu visualisieren. Diese Bibliothek ist sehr populär, so dass QF-Test die Aufnahme und Wiedergabe von GEF Elementen bereits seit Version 2.2 unterstützt. Dies ist auch ein gutes Beispiel für die Stärke des ItemResolver Konzepts (siehe Abschnitt 39.2), denn das gef Jython Modul enthält eine Implementierung genau dieser Schnittstelle. In älteren QF-Test Versionen musste das gef Modul noch explizit mit Hilfe eines Skriptknotens eingebunden werden, doch mittlerweile geschieht dies automatisch.

Das gef Modul verwendet GEF Editoren in generischer Weise und kann sogar mit mehreren Editoren gleichzeitig umgehen. Allerdings gibt es Grenzen, was die automatische Erkennung angeht: Je nachdem wie die zugrunde liegenden Modell-Klassen aussehen, bleibt noch etwas Arbeit zu tun, nämlich die Implementierung eigener Resolver, die brauchbare Namen und Werte für die Elemente liefern.

39.4.1
Aufnahme von GEF Elementen

Die folgenden Abschnitte nehmen Bezug auf das GEF Shapes Beispiel. Dieses erlaubt es, Rechtecke, Ellipsen sowie Verbindungen zwischen den Figuren auf den FigureCanvas zu zeichnen. Die Art der Figur wird dabei aus der Palette ausgewählt, die ebenfalls zum GEF Editor gehört. Nehmen wir an, es wurde ein Rechteck auf den Canvas gezeichnet und man nimmt mit QF-Test einen Mausklick darauf auf. Ohne GEF ItemResolver erhält man einen 'Mausevent' Knoten für die Canvas Komponente. Die Klickposition ist relativ zum Canvas und es gibt nicht den kleinsten Hinweis, dass es das Rechteck war, das angeklickt wurde. Im Gegensatz dazu wird mit registriertem GEF Resolver das Element als Ziel des Klicks aufgenommen:

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

Dem pfad-artigen Index kann man entnehmen, dass auf ein 'Rectangle' geklickt wurde, welches selbst Teil eines 'ShapesDiagram' ist. Dieser Index ist nicht wirklich schön. Viel schlimmer ist aber, dass er vollkommen wertlos ist. Warum ist das so? Die Zahlen, die hier im Index auftauchen, sind beides Hashwerte, die sich ändern, wenn die Anwendung neu gestartet wird und man die gespeicherte Zeichnung wieder öffnet. Wenn man dann versucht, den Mausklick abzuspielen, läuft man in eine IndexNotFoundException.

Mit etwas Glück sorgt Ihre GEF Anwendung jedoch für saubere Elementnamen, so dass etwa ein Index wie folgt herauskommt:

  /ShapesDiagram/My blue rectangle

Wenn das nicht der Fall ist, gibt es drei Möglichkeiten:

39.4.2
Implementierung eines ItemNameResolver2 für GEF

Im letzten Abschnitt haben wir gesehen, dass die Elemente im GEF Shapes Beispiel keine brauchbaren Namen haben. Wie in Abschnitt 39.1 ausgeführt wird, ist ItemNameResolver2 die Schnittstelle, um Namen für Elemente zu ändern oder überhaupt erst zu definieren.

Um anzufangen - sei es mit dem Shapes Beispiel oder der eigenen GEF Anwendung -, füge man ein Jython 'SUT Skript' mit dem folgenden Code in 'Extrasequenzen' ein:

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")
Beispiel 39.12:  Ein erster GEF ItemNameResolver2

Um die Installation des Resolvers zu vereinfachen, wird das in Abschnitt 39.1.10 beschriebene resolvers Modul verwendet. Der Resolver wird auf die Klasse FigureCanvas registiert, dort liegen die Elemente. Wie oben erwähnt liefert QF-Test normalerweise item.getModel().toString() als Elementnamen. Diesen Wert erhält die Funktion getItemName() beim Aufruf als drittes Argument. Wenn man das Skript nun ausführt und dann den Aufnahmeknopf drückt, werden beim Überfahren der Elemente - man sollte zuvor davon ein paar gezeichnet haben - Informationen im Terminal von QF-Test ausgegeben, etwa so etwas:

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

Abgesehen von den Ausgaben ist der Resolver ohne Funktion. Die Frage ist nun: Gibt es im Datenmodell irgendeine Eigenschaft oder Methode, die einen vernüftigen Namen für das Element liefert? Für das GEF Shapes Beispiel lautet die Antwort: Nein. Hoffentlich sind Sie mit Ihrer Anwendung in einer besseren Lage. Um das herauszufinden, fügen Sie der Funktion getItemName() die Zeile

  print dir(model)

hinzu und führen das Skript erneut aus. Nun werden beim Bewegen der Maus über die Elemente (im Aufnahmenmodus) auch die Methoden des Modells angezeigt. Mit etwas Glück tauchen hier Methoden wie getName() oder getLabel() auf, so dass man einen Resolver wie den folgenden implementieren kann.

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

resolvers.addItemNameResolver2("myGefItemNames", getItemName,
    "org.eclipse.draw2d.FigureCanvas")
Beispiel 39.13:  Ein einfacher ItemNameResolver2

Zurück zum GEF Shapes Beispiel, wo es solche Methoden nicht gibt. Hier sind nur Informationen über die Geometrie verfügbar, doch das ist wenig hilfreich. Zumindest lassen sich aber Rechtecke und Ellipsen unterscheiden. Um die Elementnamen eindeutig zu machen, fügen wir einfach den Index der Figur (des Kind-Knotens) an, wie im folgenden Resolver gezeigt:

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")
Beispiel 39.14:  Ein ItemNameResolver2 für GEF Shapes

Mit diesem Resolver wird der Element-Index zu etwas wie

  /Diagram/Rectangle 1

wobei die abschließende Zahl der Index des Kind-Knotens ist. Diese Implementierung liefert auch Namen für die Verbindungen, indem getItemName() rekursiv für das Quell- und Zielelement aufgerufen wird. Die Typüberprüfung erfolgt mit der Methode ResolverRegistry.isInstance (siehe Abschnitt 39.1.11), wodurch einem das Importieren der GEF-Klassen (was nicht ganz einfach ist) erspart bleibt.

Sobald der Resolver funktionstüchtig ist, sollte man das Skript in die 'Vorbereitung' Sequenz packen, direkt hinter den 'Warten auf Client' Knoten. Auf diese Weise wird der Resolver automatisch registriert, wenn man das SUT startet.

39.4.3
Implementierung eines ItemValueResolver2 für GEF

Oben wurde bereits erwähnt, dass der GEF Editor normalerweise aus zwei Teilen besteht. Bislang hatten wir uns auf den Canvas konzentriert, in den die Figuren gezeichnet werden. Nun werfen wir einen Blick auf die Palette, in der die Art der zu zeichnenden Figur ausgewählt wird (Rechteck, Ellipse oder Verbindung). Die Einträge sehen zwar aus wie Buttons, doch tatsächlich ist die Palette ebenfalls ein FigureCanvas. Erfreulicherweise funktioniert hier alles, ohne dass besondere Vorkehrungen getroffen werden müssten, d.h. ohne einen ItemNameResolver2 zu implementieren. Wenn man etwa auf den 'Rectangle' Button klickt, erkennt QF-Test ein

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

Element. Was wird wohl passieren, wenn man einen 'Object value' Check für diesen Button aufnimmt? Man könnte erwarten, den Text 'Rectangle' zu erhalten, doch tatsächlich ist der Wert des Elements

  Palette Entry (Rectangle)

Der Grund dafür ist, dass Name und Wert eines Elements normalerweise gleich sind. Um dieses Verhalten zu ändern und selbstdefinierte Werte zu erhalten, muss ein ItemValueResolver2 implementiert werden. Diese Schnittstelle ist der ItemNameResolver2 Schnittstelle ganz ähnlich. Für die Palette können wir etwa das Folgende codieren:

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")
Beispiel 39.15:  Ein ItemValueResolver2 für die GEF Shapes Palette

Die Methode getLabel() liefert den Text des Elements, so wie er in der Palette angezeigt wird.