Draw2d Intro

practical introduction to eclipse draw2d project.

eclipse, draw2d, graphics, java, 2d, 2d graphics

  1. Draw2d Intro
    1. Draw2D Description
    2. Draw2 installation
  2. Draw2d Project
    1. Sample Draw2d Java Application
    2. Drawing Draw2d simple figures
    3. Connection
  3. Draw2D Advanced
    1. Draw2D Events
    2. Draw2D Class Figure
    3. Draw2D Connection Decoration
  4. Source Code
  5. References and Further readings

Draw2D Description

Draw2D is a layout and rendering toolkit built on top of SWT. It may be used standalone (outside Eclipse) or in combination with the GEF or Zest component.

Draw2D is a ligheweight toolkit, for displayng graphical components on SWT canvas. Lightweight means that each graphical component, called figure is a simple java object and has no corresponding resource in the Operating System.

Draw2 installation

To use Draw2D you need to install the GEF framework into Eclipse.
From menu, select Help > Install new Software.
in the wizard select the update site1; type gef sdk into the search box, select Eclipse GEF SDK, and complete the installation, that requires a restart.

1 in my case Update Site is Juno- http://download.eclipse.org/releases/juno.

Draw2d Project

The full Eclipse RCP framework is not needed to use Draw2D, so you can create a simple Java Application, in eclipse, and include: org.eclipse.draw2d, org.eclipse.swt and org.eclipse.swt.win32.win32.x86 jars, taken from ECLIPSE_HOME/plugins folder.

Note The minor number depends on the Eclipse version running

Alternatively you can create an elcipse plug-in project; and add the dependency to org.eclipse.draw2d

Sample Draw2d Java Application

With SWT and Draw2d libraries added to your build path you can build a very simple application window, containing theskeleton for ant Draw2D development.

We build a class with three methods:

  • main(..) the static application entry point
  • open() the method that instantiate the graphical window
  • buildDiagram(..) the method that draw the graphical content

Look at the code

/** class for testing SWT with Draw2d */
public class MainView {
	
	/** Application entry point */
	public static void main(String[] args) {
		new MainView().open();
	}
	
	/** Open a Shell call a method for drawing */
	public void open(){
		Shell shell = new Shell(new Display());
		shell.setSize(400, 250);
		shell.setText("My Main View");
		shell.setLayout( new GridLayout() );
		
		// build diagram
		Canvas canvas = buildDiagram( shell );
		canvas.setLayoutData(new GridData(GridData.FILL_BOTH));
		
		Display display = shell.getDisplay();
		// open and wait until closing
		shell.open();
		while( !shell.isDisposed() )
			while( !display.readAndDispatch() )
				display.sleep();
	}
	
	/** Instantiate the root figure, where we draw figures*/
	private Canvas buildDiagram( Composite parent ){
		
		// instantiate root figure
		Figure root = new Figure();
		root.setFont(parent.getFont());
		root.setLayoutManager( new XYLayout() );
		
		// insantiate a canvas on which to draw
		Canvas canvas = new Canvas(parent, SWT.DOUBLE_BUFFERED);
		canvas.setBackground(ColorConstants.white);
		LightweightSystem lws = new LightweightSystem(canvas);
		lws.setContents(root);
		
		// 
		return canvas;
	}

}

As you can see buildDiagram(..) draws nothing, for the moment. If you launch the main(..) you will see an empty window, build on top of SWT which contains an invisible Draw2D root figure

Drawing Draw2d simple figures

Now is time to put graphics on the window. My scope is to draw some blocks on on top of the SWT window.

To start I need a method that Build a Figure, containing a label.

/** specific method to draw a block figure */
public Figure myBlockFigure(String name){
	RectangleFigure f = new RectangleFigure();
	f.setBackgroundColor(ColorConstants.lightGreen);
	f.setLayoutManager( new ToolbarLayout() );
	f.setPreferredSize( 100, 100 );
	f.add( new Label( name ) );
	return f;
}

The code instantiate a RectangleFigure, with a lightGreen background, that contains a Label..

Now, to draw the Figure on our window, we add an hook: a _method call at the end of buildDiagram(..)

private Canvas buildDiagram( Composite parent ){
	// .. previous code remains above
	// this code for drawing
	draw( root );
	// 
	return canvas;
}

Then we add the code to Draw two figures on the root Figure. Basically we draw twice the save figure, with different labels, in different points.

/** This method draws figures on the root */
private void draw(Figure root ) {
	Figure first= myBlockFigure("First");
	root.add(first, 
		new Rectangle( new Point(10, 10),  
			first.getPreferredSize() ) );
	
	Figure second= myBlockFigure("Second");
	root.add(second, 
		new Rectangle( new Point(200, 100),  
			second.getPreferredSize() ) );
}

Launching again the Application we can see two blocks inside our window.

Connection

Now we connect the two figures, by using a specialized IFigure called Connection. Simply, this figure draws a line between two points of a canvas, connecting two figures. There are different types of connections, but now we need a simple PolylineConnection connecting figures with ChopboxAnchors.
To make a connection we add a myConnection(..) method to our class

/** return a connection figure with chopbox between two figures */
public Connection myConnection(IFigure fig1, IFigure fig2){
	PolylineConnection conn = new PolylineConnection();
	conn.setSourceAnchor( new ChopboxAnchor( fig1 ) );
	conn.setTargetAnchor( new ChopboxAnchor( fig2 ) );
	return conn;
}

To complete the test we need to add the connection to our root figure; so we add a line of code at the end of our draw(..) method

private void draw(Figure root, XYLayout layout) {
	// .. previous code here	
	root.add( myConnection( first, second ) );
}

Now, we can test the application by launching again the main(..). The result id visible in the figure below

At this point we have seen basics of Draw2D: Canvas, Figures and Connection.

Draw2D Advanced

Now we proceed with examples on advanced topics of Draw2D, with Events and Complex Figures.

Draw2D Events

We would like to move figure on the canvas, reacting to user’s drag. For this we need a Draw2D event listener, that intercepts user events: Mouse Click, Mouse Move and Mouse Release.

To to this, we need a class implementing MouseListener and MouseMotionListener interfaces.

/** This Listener implements drag and drop for Draw2D Figure */
public class MyListener 
	implements MouseListener, MouseMotionListener{
	
	Figure figure;
	Point location;
	
	/** constructor save reference to figure, then add listeners */
	public MyListener(Figure figure) {
		this.figure = figure;
		figure.addMouseListener(this);
		figure.addMouseMotionListener(this);
	}
	
	@Override
	public void mousePressed(MouseEvent me) {
		location = me.getLocation();
		me.consume();
	}
	
	@Override
	public void mouseDragged(MouseEvent me) {
		Point newLocation = me.getLocation();
		if( location==null || newLocation == null)
			return;
		// calculate offset wrt last location
		Dimension offset = newLocation.getDifference( location );
		if( offset.width==0 && offset.height==0 )
			return;
		// exchange location
		location = newLocation;
		
		// old Bounds are dirty
		figure.getUpdateManager()
			.addDirtyRegion(figure.getParent(), figure.getBounds()); 
		
		// translate figure  
		figure.translate( offset.width, offset.height );
		
		// new Bounds are dirty
		figure.getUpdateManager()
			.addDirtyRegion( figure.getParent(), figure.getBounds() );
		
		// new Bounds: set parent constraint
		figure.getParent().getLayoutManager()
			.setConstraint(figure, figure.getBounds() );
		//
		me.consume();
	}
	
	@Override
	public void mouseReleased(MouseEvent me) {
		if( location==null )
			return;
		location = null;
		me.consume();
	}

	@Override
	public void mouseEntered(MouseEvent me) {}

	@Override
	public void mouseExited(MouseEvent me) {}

	@Override
	public void mouseHover(MouseEvent me) {}

	@Override
	public void mouseMoved(MouseEvent me) {}

	@Override
	public void mouseDoubleClicked(MouseEvent me) {}

}

Looking at the code we note

  • MyListener(.) constructor save a reference to the figure, then adds listeners
  • mousePressed(.) method store the location at the first click on the Figure, then consume the event.
  • mouseDragged(.) method calculate the difference from last movement, translate the figure, and make the repaint happens, by marking dirty the old regon, the new region and notifyng the parent layout manager.
  • mouseReleased(.) method set to null the location and consumes the event.

Finally we need to hook the listener to figure creation. It’s really simple, we need only to put one line on the myBlockFigure(.) method, so that each built figure has its own mouse listener.

public Figure myBlockFigure(String name){
	// .. previous code here
	new MyListener( f );
	return f;
} 

Now, launching the application again we see the same two blocks. By dragging them, we see blocks moving, together with connection. Try yourself to see the Drag And Drop

Draw2D Class Figure

Now we want to transform our simple block figure into one more complex figure, similar to a class of a class diagram. Our class needs:

  • an icon near its name
  • a compartment for fiends
  • a compartment for methods

So we change our code, by adding a new method that builds the class figure. If you give a look at the conde you can see that is similar to previous myBlockFigure(.), except for few details.

/** this method build a Class image figure */
public Figure myClassFigure(String name, String[] fields, String[] methods) {
	RectangleFigure f = new RectangleFigure();
	f.setBackgroundColor(ColorConstants.lightGray);
	f.setLayoutManager(new ToolbarLayout());
	f.setPreferredSize(150, 110);
	//
	f.add( new Label( name, getImage("resources/img/class_obj.gif") ) );
	f.add(myListLabel(fields));
	f.add(myListLabel(methods));
	//
	new MyListener(f);
	return f;
}

Note we inserted into the project a resource/img folder that contains the class_obj.gif file.

Our method calls two helper methods; one that builds an Image given a path relative to the user dir.

/** build an image starting from relative path wrt user directory */
private static Image getImage(String path) {
	File f = new File(System.getProperty("user.dir"), path );
	return new Image( Display.getCurrent(), f.getAbsolutePath() );
}

And one that build a big label with a top line and a list of elements displayed.

/** builds a label with a list of strings */
public Label myListLabel(String[] list) {
	String text = "";
	for (String s : list) {
		text += s + "\n";
	}
	Label label = new Label(text) {
		protected void paintBorder(Graphics g) {
			Rectangle r = getBounds();
			/** paints horizontal line on top of label */
			g.drawLine(r.x, r.y, r.x + r.width, r.y);
		};
		public Insets getInsets() {
			// top, left, bottom, right
			return new Insets(5, 0, 0, 0);
		}
	};
	return label;
}

Finally we change the code into the draw(.) method, so that it can draw our class figures.

/** This method draws figures on the root */
private void draw(Figure root) {
	Figure first = myClassFigure("First", 
			new String[] { "- name:String" },
			new String[] { "+ First(String)", 
				"+ getName():String",
				"+ setName(String)" });
	root.add(first,
			new Rectangle(new Point(10, 10), first.getPreferredSize()));

	Figure second = myClassFigure("Second", 
			new String[]{"- email: String"},
			new String[]{"+ getEmail():String",
				"+ setEmail(String)"}
	);
	root.add(second,
			new Rectangle(new Point(200, 100), second.getPreferredSize()));

	root.add(myConnection(first, second));
}

Now the diagram looks like a class diagram. See the following image

Draw2D Connection Decoration

At this point we need only to decorate connections.

First we add a decoration that looks like a containment, with the following code

/** cerate a poligon decoration that looks like a containment */
public PolygonDecoration myPolygonDecoration(){
	PolygonDecoration deco = new PolygonDecoration();
	PointList pl = new PointList();
	pl.addPoint( 0, 0 );
	pl.addPoint(-2, 2);
	pl.addPoint(-4, 0);
	pl.addPoint(-2,-2);
	deco.setTemplate( pl );
	return deco;
}

Then we add a label representing multiplicity, attached to target endpoint, withh following code

/** adds a multiplicity label linked to targte endpoint */
public void addMultiplicity(PolylineConnection c){
	ConnectionEndpointLocator targetEL = new ConnectionEndpointLocator(c, true);
	targetEL.setVDistance(10);
	Label multiplicity = new Label("1..*");
	c.add(multiplicity, targetEL);
}

Finally we join the code, by changing the myConnection(..) method, as follows

/** return a connection figure with chopbox between two figures */
public Connection myConnection(IFigure fig1, IFigure fig2) {
	PolylineConnection conn = new PolylineConnection();
	conn.setSourceAnchor(new ChopboxAnchor(fig1));
	conn.setTargetAnchor(new ChopboxAnchor(fig2));
	conn.setSourceDecoration( myPolygonDecoration() );
	addMultiplicity(conn);
	return conn;
}

The result looks like the following image.

Source Code

You can download the source code of the example

Notes

  • The zipped archive (3MB) contain a project together with dependencies.

References and Further readings