Integrating Zest with Xtext

Info This post has , please enjoy the discussion !

In this post I show you how yo use the Eclipse Zest project to graphically depict the elements of a DSL defined through Xtext

Zest is an Eclipse project providing built in components enabling an easy graphical representation of Graph.

In this tutorial I am going to show you how we have used Zest to create a view which graphically depicts the tree (which as you well know is a graph without cycles) structure of a Work Breakdown Structure defined through the WBS language provided by the MDPM project as described in link1 and link2.

in Defining a Work Breakdown Structure through the WBS Language in MDPM we have shown you how to use this view that we called WBSTreeView.

You can find the whole code in the MDPM home page in the com.lowcoupling.mdpm.ui.wbstreeview plugin project

We started creating a new Eclipse plugin project implementing the view entry point.

Here follows the code of the WBSTreeView class which extends the ViewPart class and implements the ISelectionListener in order to be signaled when selection occurs in the open editors/outline.

The createPartControl is the method responsible for the correct initialization of the ViewPart. It basically creates a GraphViewer object which is the SWT Composite class provided by the Zest framework in order to depict graph.  As you can see the GraphViewer instance shall be configured with specific objects, a content provider and a label provider, appositely defined to create content and labels for the elements of the wbs language. It finally retrieves the active page of the current workbench window and register itself as a selection listener. This implies the selectionChanged method will be automatically invoked any times a selection will be made in the editor or outline. 

The selectionChanged method has to verify what is the source of the current selection. This can be easily inferred by looking at the class of the selection object. If it is an ITextSelection it means the selection happened into an Xtext editor while if it is an IStructuredSelection it means it occurred in the Outline. 

Dependently on the kind of selection the related EObject is retrieved and set as input to the viewer (setInput method). The viewer will exploit the content and label providers to state how to depict the input object. 

public class WBSTreeView extends ViewPart implements ISelectionListener, IZoomableWorkbenchPart {

    /**
	 * The ID of the view as specified by the extension.
	 */
	public static final String ID = "com.lowcoupling.mdpm.ui.wbstreeview.views.WBSTreeView";

	private GraphViewer viewer;

	/**
	 * The constructor.
	 */
	public WBSTreeView() {
	}

	/**
	 * This is a callback that will allow us
	 * to create the viewer and initialize it.
	 */
	public void createPartControl(Composite parent) {
		viewer = new GraphViewer(parent, SWT.NONE);
		viewer.setContentProvider(new WBSContentProvider());
		viewer.setLabelProvider(new WBSLabelProvider());
		int style = LayoutStyles.NO_LAYOUT_NODE_RESIZING;
		DirectedGraphLayoutAlgorithm alg = new DirectedGraphLayoutAlgorithm(style);
		viewer.setLayoutAlgorithm(
				new CompositeLayoutAlgorithm(style,
						new LayoutAlgorithm[]{
						alg, 
						new HorizontalShift(style) 
				}
						));


		PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().addSelectionListener(this);
		fillToolBar();
	}



	/**
	 * Passing the focus request to the viewer's control.
	 */
	public void setFocus() {
		//viewer.getControl().setFocus();
	}

	@Override
	public void selectionChanged(IWorkbenchPart part, ISelection selection) {
		EObject eobject = null;
		if(selection != null && !selection.isEmpty()){

			if (selection instanceof ITextSelection){
				final ITextSelection textSel = (ITextSelection) selection;
				IEditorPart ie = (IEditorPart)PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor();
				XtextEditor editor = (XtextEditor)ie;
				if(editor==null){
					System.out.println("null editor");
				}else{
					eobject = editor.getDocument().readOnly(
					new IUnitOfWork<EObject, XtextResource>() {
								public EObject exec(XtextResource resource) throws Exception {
									EObjectAtOffsetHelper eObjectAtOffsetHelper = new EObjectAtOffsetHelper();
									EObject eObject = eObjectAtOffsetHelper.resolveElementAt(resource, textSel.getOffset());
									return eObject;
								}
							});
				}
			}
			if (selection instanceof IStructuredSelection) {
				Object first = ((IStructuredSelection)selection).getFirstElement();
				if (first!=null && first instanceof EObjectNode){
					EObjectNode enode = (EObjectNode)first;
					ResourceSet resourceSet = new ResourceSetImpl();
					Resource inResource = resourceSet.getResource(enode.getEObjectURI(),true);
					//System.out.println(inResource);
					eobject = (EObject)inResource.getEObject(enode.getEObjectURI().fragment());
				}
			}
			if (eobject instanceof WBSNode){
				updateCanvas((WBSNode)eobject);
			}
			if (eobject instanceof WBSProject){
				updateCanvas((WBSProject)eobject);
			}
			if (eobject instanceof WBSProgram){
				updateCanvas((WBSProgram)eobject);
			}

		}
	}

	public void updateCanvas(WBSProject project){
		viewer.setInput(project);
	}
	public void updateCanvas(WBSProgram program){
		viewer.setInput(program);
	}
	public void updateCanvas(WBSNode activity){
		//ModelNode node = new ModelNode(activity);
		//viewer.setContents(node);
	}

	private void fillToolBar() {
		ZoomContributionViewItem toolbarZoomContributionViewItem = new ZoomContributionViewItem(this);
		IActionBars bars = getViewSite().getActionBars();
		bars.getMenuManager().add(toolbarZoomContributionViewItem);

	}

	@Override
	public AbstractZoomableViewer getZoomableViewer() {
		// TODO Auto-generated method stub
		return viewer;
	}

The content provider has to override a set of getElements methods each returning the whole set of nodes to depict when the object passed as parameter is the input of the viewer. In our case we are interested on three kinds of objects: WBSProgram, WBSProject and WBSNode. Dependently on the kind of object the invoked method is implemented in order to return all its children obtained by means of a recursive invokation of an accessory method. The content provider is also responsible to state what objects shall be connected. The viewer retrieve this information by invoking its getConnectedTo method which shall return the list of objects that shall be connected to the object passed as parameter. In our case the method returns the list of the direct children nodes of the object passed as parameter.

public class WBSContentProvider implements IGraphEntityContentProvider {

    public ArrayList<Object> getElements(WBSNode activity){
		ArrayList<Object> results = new ArrayList<Object>();
		Iterator<WBSNode> actIt = activity.getWbsNodes().iterator();
		results.addAll(activity.getWbsNodes());
		//results.addAll(activity.getWorkpackages());
		while(actIt.hasNext()){
			WBSNode act = actIt.next();
			results.addAll(getElements(act));
		}
		return results;
	}

	public ArrayList<Object> getElements(WBSProject project){
		ArrayList<Object> results = new ArrayList<Object>();
		results.addAll(project.getWbsNodes());
		Iterator<WBSNode>actIt = project.getWbsNodes().iterator();
		while(actIt.hasNext()){
			WBSNode act = actIt.next();
			results.addAll(getElements(act));
		}
		results.add(project); 
		return results;
	}

	@Override
	public Object[] getElements(Object inputElement) {
		ArrayList<Object> results = new ArrayList<Object>();
		if (inputElement instanceof WBSProgram) {
			WBSProgram program = (WBSProgram) inputElement;
			Iterator<WBSProject> prjIt = program.getProjects().iterator();
			while(prjIt.hasNext()){
				WBSProject prj = prjIt.next();
				results.addAll(getElements(prj));
			}
			results.add(program);
		}

		if (inputElement instanceof WBSProject) {
			WBSProject project = (WBSProject) inputElement;
			//results.addAll(project.getName());
			results.addAll(project.getWbsNodes());
			Iterator<WBSNode>actIt = project.getWbsNodes().iterator();
			while(actIt.hasNext()){
				WBSNode act = actIt.next();
				results.addAll(getElements(act));
			}
			results.add(project);
		}
		if (inputElement instanceof WBSNode) {
			WBSNode activity = (WBSNode) inputElement;
			//results.addAll(project.getName());
			results.addAll(activity.getWbsNodes());
			//results.addAll(activity.getWorkpackages());
			//results.add(activity);
		}
		if (inputElement instanceof WBSNode) {
			WBSNode wp = (WBSNode) inputElement;
			//results.addAll(project.getName());
			results.add(wp);
		}
		return results.toArray();
	}

	@Override
	public void dispose() {
		// TODO Auto-generated method stub

	}

	@Override
	public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
		// TODO Auto-generated method stub

	}


	@Override
	public Object[] getConnectedTo(Object entity) {
		// TODO Auto-generated method stub
		Collection<Object> results = new ArrayList<Object>();

		if (entity instanceof WBSProgram) {
			WBSProgram p = (WBSProgram) entity;
			results.addAll(p.getProjects());
		}

		if (entity instanceof WBSProject) {
			WBSProject p = (WBSProject) entity;
			results.addAll(p.getWbsNodes());
		}
		if (entity instanceof WBSNode) {
			WBSNode a = (WBSNode) entity;
			results.addAll(a.getWbsNodes());
			//results.addAll(a.getWorkpackages());
		}
		return results.toArray();
	}

}

The label provider is finally responsible to state how the different nodes shall be depicted, i.e. what labels should they have and what colors (foreground, background, ect.). Connections and edges can be customized as well.

public class WBSLabelProvider extends LabelProvider implements ISelfStyleProvider{
    @Override

	public String getText(Object element) {
		// TODO Auto-generated method stub
		StringBuilder builder = new StringBuilder();
		if (element instanceof WBSProgram) {
			WBSProgram program = (WBSProgram) element;
			builder.append(program.getName());
		}
		if (element instanceof WBSProject) {
			WBSProject project = (WBSProject) element;
			builder.append(project.getName());
		}
		if (element instanceof WBSActivity) {
			WBSActivity act = (WBSActivity) element;
			builder.append(act.getName());
		}
		if (element instanceof WBSDeliverable) {
			WBSDeliverable act = (WBSDeliverable) element;
			builder.append(act.getName());
		}
		if (element instanceof WBSWorkPackage) {
			WBSWorkPackage wp = (WBSWorkPackage) element;
			builder.append(wp.getName());
		}
		return builder.toString();
	}

	@Override
	public void selfStyleConnection(Object element, GraphConnection connection) {
		// TODO Auto-generated method stub
		connection.setCurveDepth(2);

		//connection.setConnectionStyle(ZestStyles.CONNECTIONS_DOT);
		//connection.setLineStyle(ZestStyles.CONNECTIONS_DASH_DOT);
	}

	@Override
	public void selfStyleNode(Object element, GraphNode node) {
		// TODO Auto-generated method stub
		
		node.setForegroundColor(ColorConstants.black);
		node.setBackgroundColor(null);
		if (node.getSize().width<100){
			node.setSize(100, node.getSize().height);
		}
		if (node.getSize().height<30){
			node.setSize(node.getSize().width, 30);
		}
		if (element instanceof WBSProgram){
			node.setBackgroundColor(ColorConstants.darkBlue);
			node.setForegroundColor(ColorConstants.white);
		}
		if (element instanceof WBSProject){
			node.setBackgroundColor(ColorConstants.blue);
			node.setForegroundColor(ColorConstants.white);
		}
		if (element instanceof WBSActivity){
			node.setBackgroundColor(ColorConstants.lightBlue);
		}
		if (element instanceof WBSDeliverable){
			node.setBackgroundColor(ColorConstants.cyan);
		}
		if (element instanceof WBSWorkPackage){
			node.setBackgroundColor(ColorConstants.lightGreen);
		}
	}
}

Heads Up!

If you have found this post useful please consider to share it with your friends

and to reccomend it on Google

thank you !!!

blog comments powered by Disqus