22 December 2010

Notes for Starting Out with Eclipse GEF

I finally dove into Eclipse GEF. At first, I dug around a bit to find the documentation, which mostly points to examples, and found them somewhat helpful but required a bit of massaging to handle the cases I needed for my viewer. I already had a model and other view parts and workbench elements that would manipulate the model, and just wanted to visualize it with a GEF viewer and allow some manipulation.


I didn't need an editor part, so started from scratch adding a graphical viewer to a view part, figuring out what the built-in editor part subclass did along the way. I did't need the palette, the ability to create visually within the viewer, or the ability for a user to have a free-form XY layout - they would simply drag items around and the layout would auto-update. The model would be used as-is, and would store no visual information - including "position" or "bounds", because the layout would handle that.


So in looking through the examples, I started to realize what was there, exactly what the framework did in particular cases, and came up with a set of notes and self-proclaimed "best practices" for myself and my development team to use and contribute to. Without any more "ado", here are some helpful links and lessons learned that would be helpful to first-time users of GEF.


Links
If you are just starting into the world of GEF, these are must-read
  • The Draw2D Developers Guide - on help.eclipse.org/helios
    • GEF is built on top of Draw2D, so to understand figures, layout, and painting you will need to know this. It is a very high-level and short document on how Draw2D works, but at least lets you start to know what you do not know.
  • The GEF developers guide - can be found on help.eclipse.org/helios.
    • This is a similarly high-level and short document describing how GEF works in general.
  • A very good baby-step tutorial for starting from scratch from EclipseCon 2008 (by Koen Aers) http://www.eclipsecon.org/2008/?page=sub/&id=102
    • Covers from-scratch creation, including model, viewer, edit part factory, move/resize, palette, create/delete, undo, create/delete connections
Here are some not as useful links, but included for completeness to help in your search for other information
Notes and Lessons Learned

  • Commands
    • They are constructed often without being invoked, so make them inexpensive to construct and to test ability to execute
    • They should encapsulate two states:
      • Store the state necessary to execute, e.g. ability to look up parent/child to be manipulated
      • Store the state modified on execute if the command canUndo(), i.e. only save child bounds within the execute() method if they have not been saved (occurs if execute-undo-redo) and do not save modified state upon command construction/initialization, since most of the time this will be thrown away.
    • Do not hold onto figures, editparts, or other GEF elements. For example, a delete command will have its edit part removed. If the command is un-done, a new editpart should be created and re-populated with state stored by the command.
    • Commands are closely related to Edit Policies, so it is helpful to understand those in depth to complete the picture on commands.
  • GraphicalViewer
    • It can be created in a view part just as well as using the GEF built-in viewer wrapped in an edit part, which adds a lot of boilerplate and may not be necessary for all uses.
  • Model Elements
    • When determining whether an EditPart exists or must be created for a model element's model children, identity equal (==) is used on the model element. Therefore, when returning results in EditPart#getModelChildren(), ensure the same model instances are returned every time for the same model children.
  • EditPartFactory
    • It need not be state-less, so the factory itself can store/maintain the visual information required of EditPart instances that cannot be stored in the data model elements, such as a cached icon, color, or position/bounds.
  • Figures
    • Updates
      • After updating an edit part due to a model change that must be reflected in the figure, make sure to call IFigure#revalidate() and IFigure#repaint()
      • The GEF UpdateManager handles painting and layout events, and processes the pending operations in batches for efficiency. Notify the UpdateManager that a layout has changed by marking a figure invalid via IFigure#revalidate() or notify that it needs to be re-drawn via IFigure#repaint().
    • When creating a figure, just set the things that are "constant". Use EditPart#refreshVisuals() to set render properties that vary depending on the model element or figure state, such as label text and background color.
  • Layouts
    • A layout "constraint" is analogous to "layout data" in SWT. Constraints are the specific data used to lay out a single entity in its parent.
    • Just as with SWT layouts, if things are pretty simple it can all be done in one place (i.e. one figure class or edit part class). Once it starts getting more complicated and decentralized, follow these principles to keep layout code properly decoupled
      • An element is in charge of only setting its own size and setting child element locations.
      • A child element should not try to set its own location because it should have no knowledge of its parent's layout type or constraints. Computing constraints on a child element should be done through a policy the same way the layout was set on the parent.
      • Note that "bounds constraints" contain two parts: size and location. This makes it difficult to distribute the work since they are usually combined into a single Rectangle
  • Edit Policies
    • An edit policy determines what a user can do to a figure (via an edit part) by responding to each user Request with a Command
    • An edit policy installed in an edit part is really a delegate for some responsibilities of the part.
      • It is possible for an edit part to not install any edit policies, and instead override and reimplement directly the methods used to generate commands. This is not recommended, but is noted because commands are requested from an edit part which delegates work to its edit policies.
      • Not all requests will go to edit policies, and some must be handled within the edit part or manually passed to its policies. See the notes regarding mouse click handling.
    • There are a few built-in edit policy hierarchies, each of which is specialized to handle a set of certain related request types, such as layout, container, component, feedback, and connections.
      • It is possible for an edit part to install a single edit policy to cover all the cases, but the specialized pre-built policies are intended to cover most cases, including nuances of special cases, and can be reused in pieces instead of having a new policy for each new edit part type for a different composition of cases.
      • Some edit policies are not installed by the host edit part. For example, the LayoutEditPolicy decorates a host's child edit parts added to the layout with "satellite" edit policies which augment the edit part added with additional functionality to perform during a drag, such as feedback.
    • In general, the built-in edit policy subclasses divide command creation into two parts; the first part to "get" a command, and the second part to "create" a command. The "get" version of a method is the entry point, and typically iterates over the request input calling the associated "create" for each one.
    • GEF treats all edit policies uniformly through getCommand(Request), so the hierarchies are only for implementation convenience and do not have special hooks back in to GEF's command evaluation service.
    • Edit policies are installed by multiple locations, including EditPart#createEditPolicies() and LayoutEditPolicy#decorateChild(EditPart).
    • When installed, GEF iterates over policies in the same order they were added (but I would not count on this always being the case)
    • Edit policies are considered using three different paradigms by their host edit part, depending on the type of request
      • Pool of responsibility: Each policy is considered for a result and the results are collected. If the collective result is valid, then a further action may be performed (by the caller). One "nay vote" will halt progress. See also the notes on Commands for why this is important. This pattern is only used by getCommand(Request)
      • Chain of responsibility: Each policy is considered in sequence and the first one to respond with a necessary value trumps the remaining policies and its value is used. This pattern is used by delegate methods of AbstractEditPolicy via AbstractEditPart, such as getTargetEditPart(Request)and understandsRequest(Request)
      • Broadcast: Each policy is notified in sequence of an event and no result is processed. This pattern is used to show/hide source/target feedback, and to activate/deactivate edit policies
      • It is important to know this detail regarding notification paradigms because the built-in edit policies override certain behavior differently to effect their changes. For example, simply moving the handling for an "add child" command from a LayoutEditPolicy to a ContainerEditPolicy will not work. The container policy does not have an implementation for getTargetEditPart(Request) which is used to determine the new parent for the drag operation.
    • There are three valid results for a command request from an edit policy: a valid command, null, or UnexecutableCommand.INSTANCE (which is also a valid command, but a concrete case).
      • A return of null means that the policy has no interest in the request. If all policies return null, the request is denied
      • A return value of UnexecutableCommand.INSTANCE (or any valid command with canExecute() of false) will veto any other request results from the pool. This is important to note so you do not implement policies that have no interest in a request by returning an unexecutable command as that will always cause the request to be disabled.
      • null or unexecutable command pool will result in a "no-can-do" cursor.
    • If no feedback at all is provided, then there is no edit policy handling the request to return null or an unexecutable command. This is an important corrolary to the notes for what to return for a request.
  • Clicking and mouse events
    • Mouse event dispatching is generally handled in one of two places: a figure or tool
    • This is the sequence for handling a mouse event
      • First, the mouse event is dispatched in the DomainEventDispatcher to the Draw2D SWTEventDispatcher, which routes to the figure
      • If the figure handles the event, it will call InputEvent#consume() to invalidate it from further processing.
        • This is done, for example, in ClickableEventHandler#mousePressed(MouseEvent)
      • If Draw2D does not consume the event, it is sent to the active tool via the EditDomain
      • The tool will attempt to create a DragTracker (for example, DragEditPartsTracker which "is-a" SelectEditPartTracker) to handle this and subsequent mouse events until any eventual drag operation is finished
        • If the tracker consumes an event, it returns true from the handle* handler method
      • The tracker is notified about the event, which is managed internally (e.g. DragEditPartsTracker#handleDragInProgress()) or causes a perform* method to be called, such as DragEditPartsTracker#performDrag() or SelectEditPartTracker#performOpen()
        • handleDragInProgress() builds a new Command from calling EditPart#getCommand(Request) on each EditPart of interest, and invokes the command if the drag completes normally
        • performOpen() passes a request to  EditPart#performRequest(Request). Note that it does not pass to EditPart#getCommand(Request), which would route to an edit policy.
    • It is important to realize that when using EditPart.performRequest(Request), it is up to the implementor to use a Command and update the CommandStack (obtainable from the EditDomain). If the Request is routed through the edit policy, the command stack is managed automatically.
      • With EditPart: override AbstractEditPart#performRequest(Request) to be notified of IRequestConstants.REQ_OPEN requests, which are fired on double-click
      • With IFigure: add various listener types directly to the figure (focus, key, layout, mouse, etc)
        • Do not override the figure's handle* method, just use its internal listener notification mechanisms.
        • They should probably be marked final to reduce temptation, but overrides may be necessary in certain cases.

2 comments:

  1. I linked to your article on http://wiki.eclipse.org/GEF/Examples

    I wonder if your article should be pasted there. Very useful article :)

    ReplyDelete
  2. @Hendy
    Thanks for the feedback and the wiki addition! Anyone is free to use the information here, and perhaps I could reorganize this to be a better fit for their documentation and help improve what the GEF team already has.

    ReplyDelete