Chapter 11. GEF View

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.

11.1 Setup

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.

11.1.1 Installation

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.

11.1.2 Plug-in Dependencies

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

11.2 GEF Viewer

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() {
   }
}

11.2.1 Standalone GEF View

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).

11.2.2 Viewer setContents

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.

11.3 EditPartFactory

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.

11.3.1 GenealogyGraphEditPart

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;
}

11.3.2 PersonEditPart

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.

image

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.

image

11.4 Connections

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.

image

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();
}

Tip

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.

image

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.

image

11.5 Summary

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.

References

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.239.41