In Chapter 10 we took a step back from source code to discuss GEF architecture and concepts. Now we put that knowledge to work building the Genealogy GEF view using the figures and model developed in earlier chapters. We create a GEF view, then load model elements from an XML file (see Section 8.2.1 on page 116) for display in that view.
To use GEF, first we install the GEF feature using the update site, and then we modify our plug-in to depend on the GEF plug-in.
If you do not already have the GEF features installed, go to Help > Install New Software... and use either the main Eclipse update site or the GEF specific update site (see Section 2.1 on page 7) to install the GEF features.
In the MANIFEST.MF
file add the following dependency:
• org.eclipse.gef
In addition, if you want to run the GEF view as a standalone application, you’ll need to import the following package in your MANIFEST.MF
:
• com.ibm.icu.text
Similarly to our earlier Draw2D-based view (see Section 2.4 on page 15), we construct a GEF-based view by declaring the new view in the plugin.xml
.
<view
category="com.qualityeclipse.gef"
class="com.qualityeclipse.genealogy.view.GenealogyViewGEF"
id="com.qualityeclipse.genealogy.view.gef"
name="Genealogy (GEF)"
restorable="true">
</view>
Next, create the GenealogyViewGEF
class as a subclass of ViewPart
and put it into the com.qualityeclipse.genealogy.view
package. The following methods create a scrollable GEF viewer (see Section 10.2 on page 178), set the root EditPart
(see Section 10.2.2 on page 179), and set the background to be white.
public class GenealogyViewGEF extends ViewPart
{
private ScrollingGraphicalViewer viewer;
public void createPartControl(Composite parent) {
createDiagram(parent);
}
private FigureCanvas createDiagram(Composite parent) {
viewer = new ScrollingGraphicalViewer();
viewer.createControl(parent);
viewer.setRootEditPart(new ScalableFreeformRootEditPart());
viewer.getControl().setBackground(ColorConstants.white);
return (FigureCanvas) viewer.getControl();
}
public void setFocus() {
}
}
To show this view as a standalone window, copy the following methods from the GenealogyView
class defined in earlier chapters.
• createMenuBar
—see Section 7.4.3 on page 105
• createOpenFileMenuItem
—see Section 8.2.4 on page 125
• createSaveFileMenuItem
—see Section 8.3.2 on page 127
• main
—see Section 2.3 on page 9
• openFile
—see Section 8.2.4 on page 125
• run
—see Section 2.3 on page 9
• saveFile
—see Section 8.3.2 on page 127
Modify the createMenuBar
method to remove the four statements that call methods creating zoom-related menu items. In addition, you’ll need to make the modifications in the next section that set the viewer’s contents. As mentioned earlier, you will need to import the com.ibm.icu.text
package to successfully launch the standalone GEF application (see Section 11.1.2 on page 185).
The GEF viewer will display a blank canvas until the viewer’s setContents(
...)
method is called. Add the following statement to the createPartControl(
...)
method to set the viewer’s model:
readAndClose(getClass().getResourceAsStream("genealogy.xml"));
The statement above makes a call to the readAndClose(
...)
method, which must be copied from the GenealogyView
class defined in earlier chapters (see Section 8.2.1 on page 116). The readAndClose(
...)
method in turn makes a call to the setModel(
...)
method as defined below.
private GenealogyGraph graph;
private void setModel(GenealogyGraph newGraph) {
graph = newGraph;
viewer.setContents(graph);
}
If you open the view or run the stand-alone application, you will get an exception, which is addressed in the next section.
Now if the GEF Genealogy view is opened, an exception is thrown indicating that we must set an EditPartFactory
(see Section 10.2.1 on page 179) before calling setContents(
...)
. Our EditPartFactory
must construct and return EditParts
for each type of element in our genealogy model. Add the following statement in the GenealogyGEFView createPartControl
method before the call to setContents(
...)
:
viewer.setEditPartFactory(new GenealogyEditPartFactory());
Next, create the new GenealogyEditPartFactory
class in the com.qualityeclipse.genealogy.parts
package.
public class GenealogyEditPartFactory
implements EditPartFactory
{
public EditPart createEditPart(EditPart context, Object model) {
if (model instanceof GenealogyGraph)
return new GenealogyGraphEditPart((GenealogyGraph) model);
if (model instanceof Person)
return new PersonEditPart((Person) model);
if (model instanceof Marriage)
return new MarriageEditPart((Marriage) model);
if (model instanceof Note)
return new NoteEditPart((Note) model);
throw new IllegalStateException("No EditPart for "
+ model.getClass());
}
}
There are better, more polymorphic ways to implement the method above for more complex models, but using instanceof
works for our simple model. In addition, the method above references some new EditPart
classes which we define in the next several sections.
Each element in the model, including the top-level GenealogyGraph
model object, needs an associated EditPart
(see Section 10.1.3 on page 177) that constructs the visual representation of the model element. In this case, the model element represents the entire genealogy graph, so the figure returned by this EditPart
is a layer in which all other genealogy figures can be displayed. EditParts
also create the appropriate EditPolicies
(see Section 10.3.4 on page 182), but for now we leave createEditPolicies()
as an empty method to be implemented later (see Section 12.3.2 on page 209). Create a new GenealogyGraphEditPart
class in the com.qualityeclipse.genealogy.parts
package as shown below.
public class GenealogyGraphEditPart
extends AbstractGraphicalEditPart
{
public GenealogyGraphEditPart(GenealogyGraph genealogyGraph) {
setModel(genealogyGraph);
}
public GenealogyGraph getModel() {
return (GenealogyGraph) super.getModel();
}
protected IFigure createFigure() {
Figure figure = new FreeformLayer();
figure.setBorder(new MarginBorder(3));
figure.setLayoutManager(new FreeformLayout());
return figure;
}
protected void createEditPolicies() {
}
}
In addition, an EditPart
determines what child model elements should be displayed. Add the following method to our new GenealogyGraphEditPart
class to return all the top-level elements in the genealogy model:
protected List<GenealogyElement> getModelChildren() {
List<GenealogyElement> allObjects = new ArrayList<GenealogyEle-
ment>();
allObjects.addAll(getModel().getMarriages());
allObjects.addAll(getModel().getPeople());
allObjects.addAll(getModel().getNotes());
return allObjects;
}
As in the prior section, the EditPart
for the Person
model object must create the figure to represent that model object. As before, we leave createEditPolicies()
as an empty method to be implemented later (see Section 12.3.2 on page 209). Create a new PersonEditPart
class in the com.qualityeclipse.genealogy.parts
package as shown below.
public class PersonEditPart extends AbstractGraphicalEditPart
{
public PersonEditPart(Person person) {
setModel(person);
}
public Person getModel() {
return (Person) super.getModel();
}
protected IFigure createFigure() {
Person m = getModel();
Image image = m.getGender() == Person.Gender.MALE ?
PersonFigure.MALE : PersonFigure.FEMALE;
return new PersonFigure(m.getName(), image,
m.getBirthYear(), m.getDeathYear());
}
protected void createEditPolicies() {
}
}
Since Person
model elements can have embedded notes, we must override the getModelChildren()
method to return the child model elements to be displayed as we do similarly for the GenealogyGraphEditPart
method of the same name.
protected List<Note> getModelChildren() {
return getModel().getNotes();
}
In addition to creating a figure to represent the model object, each EditPart
should update that figure as the underlying model changes. To keep the visual representation of the model in sync with the model itself, we override the refreshVisuals(
...)
method to update the figure bounds based upon the model state. Since this same method must be implemented for the MarriageEditPart
and NoteEditPart
classes, add this method to a new abstract GenealogyElementEditPart
and make it the superclass of PersonEditPart
.
protected void refreshVisuals() {
GenealogyElement m = getModel();
Rectangle bounds = new Rectangle(m.getX(), m.getY(),
m.getWidth(), m.getHeight());
((GraphicalEditPart) getParent()).setLayoutConstraint(this,
getFigure(), bounds);
super.refreshVisuals();
}
The MarriageEditPart
and NoteEditPart
are trivial variations of PersonEditPart
and thus are left as an exercise for the reader. When the GEF Genealogy view is opened (Window > Show View > Other..., and select GEF Book > Genealogy (GEF)), it shows all of the model objects currently returned by the various EditParts
previously defined (see Figure 11–1).
Figure 11–1. GEF-based Genealogy view showing all of the model objects.
The current implementation places the notes above the person’s image, name, and birth information. This occurs because AbstractGraphicalEditPart
adds the child EditPart
figures to the figure returned by its getContentPane()
method, which by default is its own figure.
public IFigure getContentPane() {
return getFigure();
}
Override this method in PersonEditPart
to return a new nested figure within the PersonFigure
that contains only notes.
public IFigure getContentPane() {
return ((PersonFigure) getFigure()).getNotesContainer();
}
This new method calls the getNotesContainer()
method which we must add to the PersonFigure
class as follows:
private final IFigure notesContainer;
public IFigure getNotesContainer() {
return notesContainer;
}
The notesContainer
must be initialized in PersonFigure
by appending the following lines to the constructor:
notesContainer = new Figure();
final ToolbarLayout notesLayout = new ToolbarLayout();
notesLayout.setSpacing(1);
notesContainer.setLayoutManager(notesLayout);
add(notesContainer);
Once these modifications are complete, the notes are correctly placed below the person’s image, name, and birth information (see Figure 11–2).
Figure 11–2. Genealogy view showing correct note placement.
Currently, our genealogy graph contains people and marriages but no connections between them (see Figure 11–2). Rather than explicitly constructing the connection figures as we did earlier when using only Draw2D (see Section 2.3 on page 9), we override the following two methods in each EditPart
subclass to return the appropriate connection model objects:
• getModelSourceConnections()
—returns the connection model objects for connections that originate with the EditPart
’s figure
• getModelTargetConnections()
—returns the connection model objects for connections that terminate with the EditPart
’s figure
Our genealogy model does not currently have elements that represent connections. We could add first-class elements to our model that represent connections and are persisted along with the rest of the model. Instead, we add transient connection model objects that are placeholders representing the connection information already encoded in our model. To represent a connection between a parent and the marriage that joins the parent and his or her spouse, create a new GenealogyConnection
class.
public class GenealogyConnection
{
public final Person person;
public final Marriage marriage;
public GenealogyConnection(Person person, Marriage marriage) {
this.person = person;
this.marriage = marriage;
}
}
We make this new class immutable and override the equals(
...)
and hashCode()
methods so that instances of this class that connect the same person with the same marriage will be considered the same model object.
public boolean equals(Object obj) {
if (!(obj instanceof GenealogyConnection))
return false;
GenealogyConnection conn = (GenealogyConnection) obj;
return conn.person == person && conn.marriage == marriage;
}
public int hashCode() {
int hash = 0;
if (person != null)
hash += person.hashCode();
if (marriage != null)
hash += marriage.hashCode();
return hash;
}
Now, override the PersonEditPart getModelSourceConnections()
method to return instances of this new class representing the connection to the marriage.
public List<GenealogyConnection> getModelSourceConnections() {
Person person = getModel();
Marriage marriage = person.getMarriage();
ArrayList<GenealogyConnection> marriageList =
new ArrayList<GenealogyConnection>(1);
if (marriage != null)
marriageList.add(new GenealogyConnection(person, marriage));
return marriageList;
}
In addition, override the MarriageEditPart getModelTargetConnections()
method to return instances of GenealogyConnection
representing the connections to the spouses in the marriage.
protected List<GenealogyConnection> getModelTargetConnections() {
Marriage marriage = getModel();
ArrayList<GenealogyConnection> marriageList = new ArrayList<Gene-
alogyConnection>(1);
Person husband = marriage.getHusband();
if (husband != null)
marriageList.add(new GenealogyConnection(husband, marriage));
Person wife = marriage.getWife();
if (wife != null)
marriageList.add(new GenealogyConnection(wife, marriage));
return marriageList;
}
We need an EditPart
associated with the new GenealogyConnection
model element for GEF to properly display that model element in the genealogy graph. Create a new GenealogyConnectionEditPart
class to manage the connection representing the GenealogyConnection
model element. The createEditPolicies()
method is left as an empty method now but is fully implemented in a later chapter (see Section 12.3.2 on page 209).
public class GenealogyConnectionEditPart
extends AbstractConnectionEditPart
{
public GenealogyConnectionEditPart(
GenealogyConnection GenealogyConnection) {
setModel(GenealogyConnection);
}
public GenealogyConnection getModel() {
return (GenealogyConnection) super.getModel();
}
protected void createEditPolicies() {
}
}
This new GenealogyConnectionEditPart
is responsible for constructing the connection figure that visually represents the connection between person and marriage. To create a connection figure similar to the Draw2D connection described earlier in the book (see Section 2.3 on page 9 and see Section 6.3.2 on page 78), add the following static field and instance method to GenealogyConnectionEditPart
.
private static final PointList ARROWHEAD =
new PointList(new int[] { 0, 0, -2, 2, -2, 0, -2, -2, 0, 0 });
protected IFigure createFigure() {
PolylineConnection connection = new PolylineConnection();
PolygonDecoration decoration = new PolygonDecoration();
decoration.setTemplate(ARROWHEAD);
decoration.setBackgroundColor(ColorConstants.darkGray);
connection.setTargetDecoration(decoration);
return connection;
}
The EditFactory
needs to know how and when to construct an instance of this new EditPart
. Insert the following lines into the GenealogyEditPartFactory
’s createEditPart(
...)
method to construct new instances of GenealogyConnectionEditPart
:
if (model instanceof GenealogyConnection)
return new GenealogyConnectionEditPart(
(GenealogyConnection) model);
Now the genealogy graph has connections between parents and marriage object (see Figure 11–3), but the connection terminates at the MarriageFigure
’s bounding box rather than along the outside of the MarriageFigure
’s shape.
Figure 11–3. Genealogy view showing defaut connections.
Having seen this problem in earlier chapters (see Section 6.2.2 on page 73), we add the following method to GenealogyConnectionEditPart
so that the connection uses the MarriageAnchor
rather than the default ChopboxAnchor
.
protected ConnectionAnchor getTargetConnectionAnchor() {
if (getTarget() instanceof MarriageEditPart) {
MarriageEditPart editPart = (MarriageEditPart) getTarget();
return new MarriageAnchor(editPart.getFigure());
}
return super.getTargetConnectionAnchor();
}
Be careful not to pass the connection figure as the anchor’s owner. Doing so results in an infinite revalidation loop.
protected ConnectionAnchor getTargetConnectionAnchor() {
return new MarriageAnchor(getFigure()); // WRONG!
}
Now the GenealogyConnection
terminates along the outside of MarriageFigure
’s shape rather than at the MarriageFigure
’s bounding box (see Figure 11–4).
Figure 11–4. Genealogy view showing correct connections.
Adding the connections from marriage to offspring involves a similar process. We can reuse the GenealogyConnection
model element but need to add the following method for other classes to distinguish between connections between parent and marriage and connections between marriage and offspring:
public boolean isOffspringConnection() {
return marriage != null &&
marriage.getOffspring().contains(person);
}
Connections between parent and marriage have a dark gray arrow fill color. For connections between marriage and offspring, we want a white arrow fill color. Modify the GenealogyConnectionEditPart createFigure()
method as follows to change the arrow fill color depending upon the value returned by the isOffspringConnection
method defined above:
protected IFigure createFigure() {
PolylineConnection connection = new PolylineConnection();
PolygonDecoration decoration = new PolygonDecoration();
decoration.setTemplate(ARROWHEAD);
decoration.setBackgroundColor(getModel().isOffspringConnection()
? ColorConstants.white
: ColorConstants.darkGray);
connection.setTargetDecoration(decoration);
return connection;
}
Create a MarriageEditPart getModelSourceConnections()
method to return a collection of GenealogyConnection
model elements representing the marriage offspring similar to the PersonEditPart getModelSourceConnections()
method defined earlier.
protected List<GenealogyConnection> getModelSourceConnections() {
Marriage model = getModel();
ArrayList<GenealogyConnection> offspringList =
new ArrayList<GenealogyConnection>();
for (Person offspring : model.getOffspring())
offspringList.add(new GenealogyConnection(offspring, model));
return offspringList;
}
In addition, add a new PersonEditPart getModelTargetConnections()
method to return a collection of model elements representing the marriage offspring similar to the MarriageEditPart getModelTargetConnections()
method defined earlier.
protected List<GenealogyConnection> getModelTargetConnections() {
ArrayList<GenealogyConnection> offspringList =
new ArrayList<GenealogyConnection>();
Person person = getModel();
Marriage parentsMarriage = person.getParentsMarriage();
if (parentsMarriage != null)
offspringList.add(new GenealogyConnection(person,
parentsMarriage));
return offspringList;
}
To specify the connection source and target anchor points, we could make GenealogyConnectionEditPart
override the superclass methods getSourceConnectionAnchor()
and getTargetConnectionAnchor()
. Rather than placing this knowledge of anchors in the EditPart
for the connection, the preferred approach is for the EditParts
that are the source and target of the connection to determine where the connection should anchor. Modify MarriageEditPart
to implement the NodeEditPart
interface and add the following methods. The getSourceConnectionAnchor(ConnectionEditPart)
and getTargetConnectionAnchor(ConnectionEditPart)
methods are called to determine the source and target anchor points respectively for a given connection. The getSourceConnectionAnchor(Request)
and getTargetConnectionAnchor(Request)
methods are used during the connection creation process as discussed later in the book (see Section 13.3.6 on page 240).
public ConnectionAnchor getSourceConnectionAnchor(
ConnectionEditPart connection
) {
return new MarriageAnchor(getFigure());
}
public ConnectionAnchor getSourceConnectionAnchor(Request request) {
return new MarriageAnchor(getFigure());
}
public ConnectionAnchor getTargetConnectionAnchor(
ConnectionEditPart connection
) {
return new MarriageAnchor(getFigure());
}
public ConnectionAnchor getTargetConnectionAnchor(Request request) {
return new MarriageAnchor(getFigure());
}
Now the genealogy graph has connections between parents and marriage and between marriage and children (see Figure 11–5).
Figure 11–5. Genealogy view showing all parent and child connections.
With a well-defined model and set of figures, it is easy to pull together the first iteration of your GEF application. All of the techniques discussed in this chapter for building a GEF view are applicable when building a GEF Editor as discussed in the next chapter.
Chapter source (see Section 2.6 on page 20).
Clayberg, Eric, and Dan Rubel, Eclipse Plug-ins, Third Edition. Addison-Wesley, Boston, 2009.
GEF and Draw2D Plug-in Developer Guide, Eclipse Documentation (see http://help.eclipse.org/).
Moore, Bill, David Dean, Anna Gerber, Gunnar Wagenknecht, and Philippe Vanderheyden, Eclipse Development Using the Graphical Editing Framework and the Eclipse Modeling Framework. IBM Redbooks, February 2004.
3.135.216.75