Eclipse GEF Editor

  1. Eclipse GEF Editor
    1. RCP GEF Project
    2. GEF Editor
    3. Editor Mechanic
  2. GEF Block Diagram Editor
    1. GEF Empty Base Editor
    2. Gef Editor With Blocks
  3. Events On Visual Components
    1. Notification with Property Change Listener
    2. Change Block Constraints
    3. Undo / Redo Support
  4. Complete the tutorial (Zeigarnik effect)
  5. Source Code
  6. 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 components
  • org.eclipse.ui.editors to use editor stuffs
  • org.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:

  • ModelModel or Hierarchy Model representing data. It must have a notification mechanism to communicate state changes
  • EditPartController that manage interactions. It receives notifications from the model, and is responsible for view updates.
  • FigureView is the graphic representation of the model

In addition to standard MVC, we introduce two GEF Specific concepts:

  • EditPartFactoryObject 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.
  • GraphicalViewerDrawing 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: ModelViewControllerEditPartFactory 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 by PropertyChangeListener, 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 !

References


0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *