Eclipse GEF Editor
- Eclipse GEF Editor
- GEF Block Diagram Editor
- Events On Visual Components
- Complete the tutorial (Zeigarnik effect)
- Source Code
- References and Further readings
This article explains how to create a graphical editor with Eclipse GEF
Keywords: Eclipse, GEF, Editor, Graphical Editor, RCP
RCP GEF 3.x Project
We start from scratch by creating a new Eclipse RCP 3.x application with one editor, that subclasses GraphicalEditorWithFlyoutPalette
.
Create a new Plugin-project called my.rcp.gefeditor
Select to develop an RCP application, then click next
Complete the wizard by selecting the Hello RCP template and clicking on finish
GEF Editor
At this point we’ll edit the plugin.xml to add the graphical editor.
First open the plugin.xml‘s Depencencies tab and Add needed dependencies:
org.eclipse.gef
to use GEF libraries.org.eclipse.ui.ide
for basic ide componentsorg.eclipse.ui.editors
to use editor stuffsorg.eclipse.core.filesystem
for accessing filesystem
As dependencies are ready, we add a new editor extension in the Extensions. In the Extension Element Details we define basic property values, as in the image below.
- The id is the unique identifier for editor. We write
my.rcp.gefeditor.editors.MyEditor
and remember this id, since it will be used afterwards, to reference the editor. - The icon is a decorator corresponding to a file accessible in the project filesystem
- The name is used for editor Decoration
- The extensions indicates what filetypes are managed by the editor
- The class defines the java class implementing the editor. We write
my.rcp.gefeditor.editors.MyEditor
, and, by clicking on the class: Hyperlink we open the New Class Wizard to create “a concrete” implementation of the editor.
In the Wizard, We change the proposed EditorPart
superclas; instead we subclass the GraphicalEditorWithFlyoutPalette
.
To make the editor working, simply we put the editor Id, set the EditDomain
and instantiate the PaletteRoot
methods.
/** This class implements the concrete editor */ public class MyEditor extends GraphicalEditorWithFlyoutPalette { /** this is the public ID for the editor */ public static final String ID = "my.rcp.gefeditor.editors.MyEditor"; public MyEditor() { // the edit domain is necessary setEditDomain( new DefaultEditDomain(this) ); } @Override protected PaletteRoot getPaletteRoot() { return new PaletteRoot(); } @Override public void doSave(IProgressMonitor monitor) { } }
Now, to see the editor on the application, we need to add a Command that opens it. For the command we take a note of the id : my.rcp.gefeditor.commands.OpenEditor
, that will be used afterwards.
Clicking on defaultHandler hyperlink opens a the New Class Wizard, to define the Handler Class corresponding to the command. To the new wizard window, we add a particular: our Handler extends the AbstractHandler
class.
In the handler class we simply create a temp file “define a tell Eclipse to open a new instance of editor, by passing the ID of the editor, that annotated before.
/** This handler opens the editor with temp file input */ public class OpenEditor extends AbstractHandler implements IHandler { @Override public Object execute(ExecutionEvent event) throws ExecutionException { // IWorkbenchPage page = PlatformUI.getWorkbench() .getActiveWorkbenchWindow().getActivePage(); try{ // write a new temp file deleted on JVM exit File tempFile = File.createTempFile("tempEditor", "myed"); tempFile.deleteOnExit(); IFileStore fileStore = EFS.getStore( tempFile.toURI() ); // instantiate input then open MyEditor IEditorInput input = new FileStoreEditorInput(fileStore); page.openEditor( input, MyEditor.ID ); } catch (Exception e) { e.printStackTrace(); } return null; } }
After creating the command, we add a menuContribution containing a toolbar, then we add a button linked to the above command. Note that
- the toolbar menuContribution has
toolbar:org.eclipse.ui.main.toolbar
as locationUri. Note that by splitting the uri with “:” we get a first part denoting a toolbar and the second denoting the main toolbar id - the Open Editor command is linked to the command defined above, by the
my.rcp.gefeditor.commands.OpenEditor
command id. Furthermore it has a decaorator icon and a name
Now we see the result, by launching an eclipse application from the Overview tab of the plugin.xml.
The application looks like the image above, in which, you can see the button that opens the editor. Notice that the editor has a flyout palette on the right side. Moreover out application can open nore than one single file.
Editor Mechanic
To use GEF for Graphical editing, we need to get familiar with different base concepts:
- Model: Model or Hierarchy Model representing data. It must have a notification mechanism to communicate state changes
- EditPart: Controller that manage interactions. It receives notifications from the model, and is responsible for view updates.
- Figure: View is the graphic representation of the model
In addition to standard MVC, we introduce two GEF Specific concepts:
- EditPartFactory: Object Factory that builds controller hierarchy. It is responsible to build a specific controller depending on the passed model instance. This factory helps with scalability and granularity.
- GraphicalViewer: Drawing Area on which the user interacts. This component is offertd by the framework and contains editor’s graphical elements.
Given all presented elements, in the next section we build a basic block diagram editor.
GEF Block Diagram Editor
We would develop an editor for drawing block diagrams; briefly we can think to a Schema, containing several blocks, variously interconnected. we have to proceed step by step:
- build a base editor for an empty Schema. t
- extend
GEF Empty Base Editor
We write five basic classes to implement an empty Schema Editor: Model, View, Controller, EditPartFactory and GraphicalViever
First we create a model object called MySchema
.
/** schema model */ public class MySchema { }
Then we write MySchemaFigure
class, that is the View for the schema model. It extends the FreeFormLayer
class, to allow containment of other object that we will use in next sections.
public class MySchemaFigure extends FreeformLayer { public MySchemaFigure() { super(); setLayoutManager( new FreeformLayout() ); setBorder( new LineBorder(1) ); } }
Now we need a Controller for model. The GEF infrastructure implies a specific knowledge of the model. So we define an adequate constructor. Furthermore is expected that we provide a createFigure()
method that instantiate the specific view.
public class MySchemaEditPart extends AbstractGraphicalEditPart { /** instantiate controller by passing a model instance */ public MySchemaEditPart( MySchema model) { setModel( model ); } @Override protected IFigure createFigure() { return new MySchemaFigure(); } @Override protected void createEditPolicies() { } }
At this point is possibile to implement the EditPartFactory
delegated to the construction of controllers hierarchy. This is used by the framework to build the whole infrastructure.
public class MyEditPartFactory implements EditPartFactory { @Override public EditPart createEditPart(EditPart context, Object model) { if( model instanceof MySchema ){ return new MySchemaEditPart( (MySchema) model ); } return null; } }
At the end we modify the editor:
- by overriding the
configureGraphicalViewer()
method, that initializize the whole architecture. - by overriding the
initializeGraphicalViewer()
, where we set the Root model instance
/** This class implements the concrete editor */ public class MyEditor extends GraphicalEditorWithFlyoutPalette { // .. previous code here @Override protected void configureGraphicalViewer() { super.configureGraphicalViewer(); super.configureGraphicalViewer(); // viewer <-- factory + root edit part GraphicalViewer viewer = getGraphicalViewer(); viewer.setEditPartFactory( new MyEditPartFactory() ); viewer.setRootEditPart( new ScalableFreeformRootEditPart() ); } @Override protected void initializeGraphicalViewer() { super.initializeGraphicalViewer(); // set the root model to be rendered getGraphicalViewer().setContents( new MySchema() ); } }
We launch the application to test what is happened. In the following figure we see a 1px border around the editor area; it tell us that our infrastructure is working, and we can proceed with next steps.
Gef Editor With Blocks
Now, we have the architecture in place, and we would like to add block to the schema. The general architecture should looks like the following UML Class diagram.
We have already many of components in place, but we need to add Block classes, and adjust some in other classes.
First we add the MyBlock
model class. It simply keep a reference to their dimensions.
public class MyBlock { private Rectangle constraint; public Rectangle getConstraint(){ return constraint; } public void setConstraint( Rectangle constraint ){ this.constraint = constraint; } }
Then we modify the MySchema
model class, by adding the containtment of blocks
public class MySchema { List<MyBlock> blocks; public List<MyBlock> getBlocks(){ if( blocks == null ) blocks = new ArrayList<MyBlock>(); return blocks; } public void addBlock( MyBlock block ){ getBlocks().add(block); } }
Now we add the MyBlockFigure
view to represent graphically the model
public class MyBlockFigure extends Figure { private RectangleFigure rectangle; public MyBlockFigure(){ setLayoutManager(new XYLayout()); rectangle = new RectangleFigure(); rectangle.setBackgroundColor( ColorConstants.lightBlue ); add( rectangle ); } @Override protected void paintFigure(Graphics graphics) { //super.paintFigure(graphics); Rectangle r = getBounds().getCopy(); // constraint the child RectangleFigure of this figure setConstraint( rectangle, new Rectangle(0, 0, r.width, r.height)); rectangle.invalidate(); } }
Then finally we define the MyBlockEditPart
controller, that is constructed with an instance of the model, and can instantiate the specific view.
public class MyBlockEditPart extends AbstractGraphicalEditPart { public MyBlockEditPart( MyBlock model ){ setModel(model); } @Override protected IFigure createFigure() { return new MyBlockFigure(); } @Override protected void createEditPolicies() { } @Override protected void refreshVisuals() { // super.refreshVisuals(); MyBlockFigure figure = (MyBlockFigure) getFigure(); MyBlock block = (MyBlock) getModel(); MySchemaEditPart parent = (MySchemaEditPart) getParent(); // Rectangle r = new Rectangle( block.getConstraint() ); parent.setLayoutConstraint(this, figure, r); } }
Now, is fundamental to override the getModelChildren()
for the parent MySchemaEditPart
controller. This allows the controller hierarchy to be built.
public class MySchemaEditPart extends AbstractGraphicalEditPart { // .. previous code here @Override protected List getModelChildren() { return ( (MySchema) getModel() ).getBlocks(); } }
Good, now we change the editpart factory, so that it will be aware of the new MyBlock
component
public class MyEditPartFactory implements EditPartFactory { @Override public EditPart createEditPart(EditPart context, Object model) { if( model instanceof MySchema ){ return new MySchemaEditPart( (MySchema) model ); } if( model instanceof MyBlock ){ return new MyBlockEditPart( (MyBlock) model ); } return null; } }
And finally we write a ContentProvider
class, that buils a sample instance of the Schema.
public class MyContentProvider { public static MyContentProvider INSTANCE = new MyContentProvider(); /** Get a sample instance of {@link MySchema}*/ public MySchema sample() { MySchema s = new MySchema(); // instantiate 3 blocks MyBlock b1, b2, b3; b1 = newBlock(10, 10, 30, 30); b2 = newBlock(100, 30, 50, 50); b3 = newBlock(200, 10, 90, 90); // add blocks to the schema s.addBlock(b1); s.addBlock(b2); s.addBlock(b3); return s; } private MyBlock newBlock(int x, int y, int width, int height) { MyBlock b = new MyBlock(); b.setConstraint(new Rectangle( new Point(x, y), new Dimension(width,height))); return b; } }
The ContentProvider.INSTANCE.sample()
is used into the createPartControl()
method of the Editor
/** This class implements the concrete editor */ public class MyEditor extends GraphicalEditorWithFlyoutPalette { // .. previous code same as above @Override protected void initializeGraphicalViewer() { super.initializeGraphicalViewer(); // set the root model to be rendered getGraphicalViewer().setContents( MyContentProvider.INSTANCE.sample() ); } }
Now we launch the editor again and we can see how our sample looks like.
Events On Visual Components
Now we add behaviour to objects inside our diagram
to
Notification with Property Change Listener
EditParts Controllers have to receive notifications when the Model change. So we implement a notification infrastructure based on property-change-listeners, that make use of java.beans.PropertyChangeListener
.
We introduce two Base classes: one for models, and another for controllers
The ElementModel
, base class for models, can fire property changes.
/** Generic model superclass referencing PropertyListener */ public class ElementModel { transient protected PropertyChangeSupport listeners = new PropertyChangeSupport(this); public void addPropertyChangeListener(PropertyChangeListener listener){ listeners.addPropertyChangeListener(listener); } public void removePropertyChangeListener(PropertyChangeListener listener){ listeners.removePropertyChangeListener(listener); } protected void firePropertyChange( String prop, Object oldVal, Object newVal ){ listeners.firePropertyChange(prop, oldVal, newVal); } }
The ElementEditPart
represents our Abstract Controller. It has to:
- register model for changes, overriding
activate()
: - de-register model for changes overriding
deactivate()
: - react to property changes, implementing the
propertyChange()
method. The implementation is required byPropertyChangeListener
, and subclasses must implement this.
/** Generic Element superclass implementing the property change listener */ public abstract class ElementEditPart extends AbstractGraphicalEditPart implements PropertyChangeListener { @Override public void activate() { if (isActive()) return; super.activate(); // subscription ((ElementModel) getModel()).addPropertyChangeListener(this); } @Override public void deactivate() { if (!isActive()) return; super.deactivate(); // de-registration ((ElementModel) getModel()).removePropertyChangeListener(this); } }
Thus we need to transform MyBlock
model, to extends the ElementModel
.
public class MyBlock extends ElementModel { //.. previous code here }
Consequently we Change the MyBlockEditPart
controller, to extend the abstract ElementEditPart
class. Note that the class requires the propertyChange(.)
method. For the moment, we leave it empty. The property-change logic will be implemented in next section.
public class MyBlockEditPart extends ElementEditPart { //.. previous code here /** method for managing property changes notification */ @Override public void propertyChange(PropertyChangeEvent evt) { // leave empty unless we have to manage prop-changes } }
Change Block Constraints
We go to implement a simple action, that consists in changing constraints; that means changing dimension and position of a block.
The GEF Framework does the great part of the work, by providing a good infrastructure. Simply we need to leverage the infrastructure, described by following UML class diagram
First we add a class BlockChangeConstraintCommand
, extending Command
. The execute()
method of our command class Our command class changes the Rectan
/** Command that changes Constraints for a block */ public class BlockChangeConstraintCommand extends Command { // actual and old constraint private Rectangle newConstraint; private Rectangle oldConstraint; // object of the command private MyBlock block; /** Public method that executes the command. */ public void execute(){ // save previous value if( oldConstraint == null ) oldConstraint = new Rectangle(block.getConstraint()); // set actual value block.setConstraint(newConstraint); } /** Undo method that reset to old value */ public void undo(){ block.setConstraint(oldConstraint); } /** Set new constraint for block. */ public void setNewConstraint(Rectangle newConstraint){ this.newConstraint = newConstraint; } /** Set the Block obejct of the command. */ public void setBlock(MyBlock block){ this.block = block; } }
Then we add a LayoutEditPolicy, that defines a particular policy for editing diagram objects
/** define the policy for layouting */ public class MySchemaXYLayoutEditPolicy extends XYLayoutEditPolicy { @Override protected Command createChangeConstraintCommand(EditPart child, Object constraint) { // get instance for the command BlockChangeConstraintCommand changeConstraintCommand = new BlockChangeConstraintCommand(); // set obejct and parameters for command changeConstraintCommand.setBlock( (MyBlock) child.getModel() ); changeConstraintCommand.setNewConstraint( (Rectangle) constraint ); // return changeConstraintCommand; } @Override protected Command getCreateCommand(CreateRequest request) { return null; } }
To make things working we need to hook the policy to the schema, by defining the MySchemaEditPart#createEditPolicies()
method.
public class MySchemaEditPart extends AbstractGraphicalEditPart { //.. previous code /** this define edit policies for the controller */ @Override protected void createEditPolicies() { installEditPolicy( EditPolicy.LAYOUT_ROLE, new MySchemaXYLayoutEditPolicy()); } }
At this point the editor is ready, and me miss only a notification from the model to controller. So we use the notification system seen before, in two steps.
First we edit the MyBlock
model class, by adding the call to firePropertyChange()
.
public class MyBlock extends ElementModel { // .. previous code public void setConstraint( Rectangle constraint ){ Rectangle oldVal = this.constraint; this.constraint = constraint; firePropertyChange("constraint", oldVal, constraint); } }
Then change MyBlockEditPart
controller class, to react to the model change, by invoking the refreshVisuals()
method, that repaints the component.
public class MyBlockEditPart extends ElementEditPart { // .. previous code /** method for managing property changes notification */ @Override public void propertyChange(PropertyChangeEvent evt) { // call the refresh visuals of the component refreshVisuals(); } }
Now we launch again the application, and we can test that now is possible to mode and resize blocks.
Undo / Redo Support
The usage of the Command design pattern in GEF simplifies to make a simple implementation of undo /redo policies.
In Eclipse any executed command passes inside a CommandStack
that memorizes last executed actions, and allow an easy management of saving.
The command stak is and application object that can be used even by more than one editor. The access of undo / redo operations is executed through predefined actions offererd by the framework. To activate such actions we use a component called ActionRegistry, that mantain in memory the list of executed Actions.
Now we add the support to undo and redo actions. For this, we get the editor’s current instance of ActionRegistry
and add register Undo and Redo actions.
From plugin.xml select the editor node, write the name of our contributor class, then click on contributorClass hyperlink. This will opens a the new class wizard.
We create our Contributor class as subclass of ActionBarContributor
. The code follows
public class MyEditorActionBarContributor extends ActionBarContributor { @Override protected void buildActions() { addRetargetAction(new UndoRetargetAction()); addRetargetAction(new RedoRetargetAction()); } @Override public void contributeToToolBar(IToolBarManager toolBarManager) { toolBarManager.add(getAction(ActionFactory.UNDO.getId())); toolBarManager.add(getAction(ActionFactory.REDO.getId())); } @Override protected void declareGlobalActionKeys() { // TODO Auto-generated method stub } }
Then we test again our application. Now we can see UNDO / REDO buttons on the toolbar. Testing the application we see that the undo / redo behaviour works.
Further steps
These are my suggestions on how to proceed with this tutorial, in order to have a professional looking prototype/product:
- add behaviour that allow to delete blocks
- add palette that allow to put blocks and connections
- add support for CTRL-C, CTRL-V, CTRL-X, and for saving edited content
The Zeigarnik effectPeople tend to remember unfinished things better. This is the Zeigarnik effect. This tutorial is quite good to start with GEF, and if you want to go deeper, just take a look at the references and further readings.
Source Code
You can download the source code of the example
Notes
- The zipped archive (88KB) contain a project without dependencies. You only need to use Eclipse for RCP development added of GEF dependencies.
Have Fun !
0 Comments