NEW - bug 251055: [Article] BIRT Extension Mechanism : Part 2
https://bugs.eclipse.org/bugs/show_bug.cgi?id=251055
diff --git a/Article-BIRT-ExtensionTutorial2/extension-tutorial-2.zip b/Article-BIRT-ExtensionTutorial2/extension-tutorial-2.zip
new file mode 100644
index 0000000..56be45d
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/extension-tutorial-2.zip
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/border-effect.png b/Article-BIRT-ExtensionTutorial2/images/border-effect.png
new file mode 100644
index 0000000..8287c58
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/border-effect.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/builder-expression-support.png b/Article-BIRT-ExtensionTutorial2/images/builder-expression-support.png
new file mode 100644
index 0000000..4f17e59
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/builder-expression-support.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/builder-extension.png b/Article-BIRT-ExtensionTutorial2/images/builder-extension.png
new file mode 100644
index 0000000..f85e5b6
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/builder-extension.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/builder.png b/Article-BIRT-ExtensionTutorial2/images/builder.png
new file mode 100644
index 0000000..8289c49
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/builder.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/contextmenu.png b/Article-BIRT-ExtensionTutorial2/images/contextmenu.png
new file mode 100644
index 0000000..b07c6ed
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/contextmenu.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/expression-builder.png b/Article-BIRT-ExtensionTutorial2/images/expression-builder.png
new file mode 100644
index 0000000..c1afb44
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/expression-builder.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/figure-ui-extension.png b/Article-BIRT-ExtensionTutorial2/images/figure-ui-extension.png
new file mode 100644
index 0000000..58fa1ee
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/figure-ui-extension.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/image-ui-extension.png b/Article-BIRT-ExtensionTutorial2/images/image-ui-extension.png
new file mode 100644
index 0000000..6baf49c
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/image-ui-extension.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/image-ui-layout.png b/Article-BIRT-ExtensionTutorial2/images/image-ui-layout.png
new file mode 100644
index 0000000..03ed817
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/image-ui-layout.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/image-ui-preview.png b/Article-BIRT-ExtensionTutorial2/images/image-ui-preview.png
new file mode 100644
index 0000000..ac7f171
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/image-ui-preview.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/label-border.png b/Article-BIRT-ExtensionTutorial2/images/label-border.png
new file mode 100644
index 0000000..ce4c6e3
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/label-border.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/label-general.png b/Article-BIRT-ExtensionTutorial2/images/label-general.png
new file mode 100644
index 0000000..68703e4
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/label-general.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/linux_only.gif b/Article-BIRT-ExtensionTutorial2/images/linux_only.gif
new file mode 100644
index 0000000..7c135cf
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/linux_only.gif
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/menu-extension.png b/Article-BIRT-ExtensionTutorial2/images/menu-extension.png
new file mode 100644
index 0000000..9f008ac
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/menu-extension.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/properties.png b/Article-BIRT-ExtensionTutorial2/images/properties.png
new file mode 100644
index 0000000..d09e2b3
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/properties.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/property-page-expression-support.png b/Article-BIRT-ExtensionTutorial2/images/property-page-expression-support.png
new file mode 100644
index 0000000..ccb2dae
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/property-page-expression-support.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/property-page-extension-1.png b/Article-BIRT-ExtensionTutorial2/images/property-page-extension-1.png
new file mode 100644
index 0000000..d6c1c75
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/property-page-extension-1.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/property-page-extension-2.png b/Article-BIRT-ExtensionTutorial2/images/property-page-extension-2.png
new file mode 100644
index 0000000..152e570
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/property-page-extension-2.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/property-page-extension-3.png b/Article-BIRT-ExtensionTutorial2/images/property-page-extension-3.png
new file mode 100644
index 0000000..30d716f
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/property-page-extension-3.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/property-page-extension-4.png b/Article-BIRT-ExtensionTutorial2/images/property-page-extension-4.png
new file mode 100644
index 0000000..f7367d5
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/property-page-extension-4.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/property-page-extension-5.png b/Article-BIRT-ExtensionTutorial2/images/property-page-extension-5.png
new file mode 100644
index 0000000..f50e68c
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/property-page-extension-5.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/rotatedtext-border.png b/Article-BIRT-ExtensionTutorial2/images/rotatedtext-border.png
new file mode 100644
index 0000000..bb374fc
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/rotatedtext-border.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/rotatedtext-custom.png b/Article-BIRT-ExtensionTutorial2/images/rotatedtext-custom.png
new file mode 100644
index 0000000..25b6b82
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/rotatedtext-custom.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/rotatedtext-general.png b/Article-BIRT-ExtensionTutorial2/images/rotatedtext-general.png
new file mode 100644
index 0000000..33ee326
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/rotatedtext-general.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/tab-structure.png b/Article-BIRT-ExtensionTutorial2/images/tab-structure.png
new file mode 100644
index 0000000..e4cbc2e
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/tab-structure.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/test-expression-1.png b/Article-BIRT-ExtensionTutorial2/images/test-expression-1.png
new file mode 100644
index 0000000..8b969d1
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/test-expression-1.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/test-expression-2.png b/Article-BIRT-ExtensionTutorial2/images/test-expression-2.png
new file mode 100644
index 0000000..143b3bc
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/test-expression-2.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/test-expression-3.png b/Article-BIRT-ExtensionTutorial2/images/test-expression-3.png
new file mode 100644
index 0000000..deea48c
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/test-expression-3.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/text-expression-type.png b/Article-BIRT-ExtensionTutorial2/images/text-expression-type.png
new file mode 100644
index 0000000..6e270b5
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/text-expression-type.png
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/tip.gif b/Article-BIRT-ExtensionTutorial2/images/tip.gif
new file mode 100644
index 0000000..77b2451
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/tip.gif
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/tryit.gif b/Article-BIRT-ExtensionTutorial2/images/tryit.gif
new file mode 100644
index 0000000..f4927a4
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/tryit.gif
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/images/win_only.gif b/Article-BIRT-ExtensionTutorial2/images/win_only.gif
new file mode 100644
index 0000000..895f9ca
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/images/win_only.gif
Binary files differ
diff --git a/Article-BIRT-ExtensionTutorial2/index.html b/Article-BIRT-ExtensionTutorial2/index.html
new file mode 100644
index 0000000..ca0da19
--- /dev/null
+++ b/Article-BIRT-ExtensionTutorial2/index.html
@@ -0,0 +1,1767 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">

+<html>

+<head>

+  <title>BIRT Extension Mechanism : Part 2</title>

+  <meta http-equiv="Content-Type" content="text/html; charset=windows-1252">

+  <link href="../article.css" type="text/css" rel="stylesheet">

+</head>

+

+<body>

+

+<h1>BIRT Extension Mechanism : Part 2</h1>

+

+<div class="summary">

+<h2>Abstract</h2>

+

+<p>This article introduces the extension mechanism of BIRT report

+model, engine and designer, and shows how to create custom extended report

+items step-by-step.</p>

+

+<div class="author">By&nbsp;Zhiqiang&nbsp;Qian,&nbsp;Actuate Corporation</div>

+

+<div class="copyright">Copyright &copy; 2008

+Actuate Corporation.</div>

+

+<div class="date">September 27, 2008</div>

+

+</div>

+

+<div class="content">

+<h2>Introduction</h2>

+

+<p>For many people, <a href="http://eclipse.org/birt">BIRT(Business

+Intelligence and Reporting Tools)</a> may be only a reporting tool. But in fact, 

+the real strength of BIRT is not only its built-in reporting functionality, but 

+also its extension capabilities. Using extensions, a user can easily extend the 

+functionality of BIRT, creating custom extended report items, adding custom 

+emitters, and even having custom editor pages. The power of extensions enables 

+the user to create custom reporting features that can meet very specific requirements. 

+In this article, we explore how to leverage the BIRT extension mechanism to 

+extend the BIRT capabilities. The provided samples cover most of the essential 

+extension points that provided by BIRT report model, engine and designer.<br>

+</p>

+

+<p>In <a href="http://www.eclipse.org/articles/article.php?file=Article-BIRT-ExtensionTutorial1/index.html">Part 1</a>, 

+we already learned the basics for how to create a custom extended report item for BIRT. In this part, we will further 

+explore how to polish the extension UI, improving the usability, and enhancing the extension functionality.

+</p>

+

+<h2>Beyond the Basics</h2>

+

+<p>In this section, we will take a look at how to add additional UI features for custom  

+report items, i.e. report item builder, context menu, 

+property page, etc., which would undoubtedly improve the look and usability of your cusotm 

+report items.

+</p>

+

+<h3>1) WYSIWYG</h3>

+

+<p>Remember how we implemented the look in layout editor for the

+RotatedText item in previous part? Yes, we just chose the simplest Label

+UI

+provider. The disadvantage of this approach is obvious: the label in

+layout view always shows the horizontal text, while the preview shows the angled

+text. This inconsistency may prevent the end users from getting an intuitive picture when  

+designing the report. For most modern editors, WYSIWYG is already encompassed as a de facto

+standard. Ideally, we would also want to keep the consistent look between

+our layout view and

+preview. To achieve this, let's take a look at the 

+other two types of the UI

+providers:</p>

+<ul>

+	<li><em>Image UI Provider</em>

+	<li><em>Figure UI Provider</em>

+</ul>

+

+<p>The Image UI provider is a straightforward choice. It simply

+requests an image for the UI presentation. So what we need to do here 

+is just to render the rotated text to an image and return it. 

+This is very similar to what we already have done for the engine

+presentation output.</p>

+

+<p>To register the Image UI provider, we need remove the original Label

+UI provider first, adding the new Image UI provider extension,

+and specifying the implementor class which must implement

+<code>org.eclipse.birt.report.designer.ui.extensions.IReportItemImageProvider</code>.</p>

+

+<p><img style="width: 1000px; height: 226px;" alt="" src="images/image-ui-extension.png"></p>

+

+<p>Here we specify the class as <code>org.eclipse.birt.sample.reportitem.rotatedtext.RotatedTextImageUI</code>, 

+the code looks like this:</p>

+

+<pre>public class RotatedTextImageUI implements IReportItemImageProvider

+{

+	public void disposeImage( ExtendedItemHandle handle, Image image )

+	{

+		if ( image != null &amp;&amp; !image.isDisposed( ) )

+		{

+			image.dispose( );

+		}

+	}

+	

+	public Image getImage( ExtendedItemHandle handle )

+	{

+		try

+		{

+			IReportItem item = handle.getReportItem( );

+			

+			if ( item instanceof RotatedTextItem )

+			{

+				int angle = ( (RotatedTextItem) item ).getRotationAngle( );

+				String text = ( (RotatedTextItem) item ).getText( );

+			

+				return SwtGraphicsUtil.createRotatedTextImage( text, angle, null );

+			}

+		}

+		catch ( ExtendedElementException e )

+		{

+			e.printStackTrace( );

+		}

+		return null;

+	}

+}</pre>

+

+<p>The logic seems very trivial. Here we introduced a new class <code>SwtGraphicsUtil</code>.

+Since the Eclipse UI is based on SWT (Standard Widget

+Toolkit), to make it work, we need an SWT port for

+the original <code>SwingGraphicsUtil</code> class. Here is the code for the new SWT version:</p>

+

+<pre>public class SwtGraphicsUtil

+{

+	public static Image createRotatedTextImage( String text, int angle, Font ft )

+	{

+		GC gc = null;

+		try

+		{

+			if ( text == null || text.trim( ).length( ) == 0 )

+			{

+				return null;

+			}

+			

+			Display display = Display.getCurrent( );

+			

+			gc = new GC( display );

+			if ( ft != null )

+			{

+				gc.setFont( ft );

+			}

+			

+			Point pt = gc.textExtent( text );

+			

+			gc.dispose( );

+			

+			TextLayout tl = new TextLayout( display );

+			if ( ft != null )

+			{

+				tl.setFont( ft );

+			}

+			tl.setText( text );

+			

+			return createRotatedImage( tl, pt.x, pt.y, angle );

+		}

+		catch ( Exception e )

+		{

+			e.printStackTrace( );

+			

+			if ( gc != null &amp;&amp; !gc.isDisposed( ) )

+			{

+				gc.dispose( );

+			}

+		}

+		return null;

+	}

+	

+	/**

+	 * @return Returns as [rotatedWidth, rotatedHeight, xOffset, yOffset]

+	 */

+	public static double[] computedRotatedInfo( int width, int height, int angle )

+	{

+		angle = angle % 360;

+		

+		if ( angle &lt; 0 )

+		{

+			angle += 360;

+		}

+		

+		if ( angle == 0 )

+		{

+			return new double[]{ width, height, 0, 0 };

+		}

+		else if ( angle == 90 )

+		{

+			return new double[]{ height, width, -width, 0 };

+		}

+		else if ( angle == 180 )

+		{

+			return new double[]{ width, height, -width, -height };

+		}

+		else if ( angle == 270 )

+		{

+			return new double[]{ height, width, 0, -height };

+		}

+		else if ( angle &gt; 0 &amp;&amp; angle &lt; 90 )

+		{

+			double angleInRadians = ( ( -angle * Math.PI ) / 180.0 );

+			double cosTheta = Math.abs( Math.cos( angleInRadians ) );

+			double sineTheta = Math.abs( Math.sin( angleInRadians ) );

+			

+			int dW = (int) ( width * cosTheta + height * sineTheta );

+			int dH = (int) ( width * sineTheta + height * cosTheta );

+			

+			return new double[]{ dW, dH, -width * sineTheta * sineTheta, width * sineTheta * cosTheta };

+		}

+		else if ( angle &gt; 90 &amp;&amp; angle &lt; 180 )

+		{

+			double angleInRadians = ( ( -angle * Math.PI ) / 180.0 );

+			double cosTheta = Math.abs( Math.cos( angleInRadians ) );

+			double sineTheta = Math.abs( Math.sin( angleInRadians ) );

+			

+			int dW = (int) ( width * cosTheta + height * sineTheta );

+			int dH = (int) ( width * sineTheta + height * cosTheta );

+			

+			return new double[]{ dW, dH, -( width + height * sineTheta * cosTheta ), -height / 2 };

+		}

+		else if ( angle &gt; 180 &amp;&amp; angle &lt; 270 )

+		{

+			double angleInRadians = ( ( -angle * Math.PI ) / 180.0 );

+			double cosTheta = Math.abs( Math.cos( angleInRadians ) );

+			double sineTheta = Math.abs( Math.sin( angleInRadians ) );

+			

+			int dW = (int) ( width * cosTheta + height * sineTheta );

+			int dH = (int) ( width * sineTheta + height * cosTheta );

+			

+			return new double[]{ dW, dH, -( width * cosTheta * cosTheta ), -( height + width * cosTheta * sineTheta ) };

+		}

+		else if ( angle &gt; 270 &amp;&amp; angle &lt; 360 )

+		{

+			double angleInRadians = ( ( -angle * Math.PI ) / 180.0 );

+			double cosTheta = Math.abs( Math.cos( angleInRadians ) );

+			double sineTheta = Math.abs( Math.sin( angleInRadians ) );

+			

+			int dW = (int) ( width * cosTheta + height * sineTheta );

+			int dH = (int) ( width * sineTheta + height * cosTheta );

+			

+			return new double[]{ dW, dH, ( height * cosTheta * sineTheta ),	-( height * sineTheta * sineTheta ) };

+		}

+		

+		return new double[]{ width, height, 0, 0 };

+	}

+	

+	private static Image createRotatedImage( Object src, int width, int height, int angle )

+	{

+		angle = angle % 360;

+		

+		if ( angle &lt; 0 )

+		{

+			angle += 360;

+		}

+		

+		double[] info = computedRotatedInfo( width, height, angle );

+		

+		return renderRotatedObject( src, -angle, (int) info[0],	(int) info[1], info[2], info[3] );

+	}

+	

+	private static Image renderRotatedObject( Object src, double angle, int width, int height, double tx, double ty )

+	{

+		Display display = Display.getCurrent( );

+		

+		Image dest = null;

+		GC gc = null;

+		Transform tf = null;

+		

+		try

+		{

+			dest = new Image( Display.getCurrent( ), width, height );

+			gc = new GC( dest );

+			

+			gc.setAdvanced( true );

+			gc.setAntialias( SWT.ON );

+			gc.setTextAntialias( SWT.ON );

+			

+			tf = new Transform( display );

+			tf.rotate( (float) angle );

+			tf.translate( (float) tx, (float) ty );

+			

+			gc.setTransform( tf );

+			

+			if ( src instanceof TextLayout )

+			{

+				TextLayout tl = (TextLayout) src;

+				tl.draw( gc, 0, 0 );

+			}

+			else if ( src instanceof Image )

+			{

+				gc.drawImage( (Image) src, 0, 0 );

+			}

+		}

+		catch ( Exception e )

+		{

+			e.printStackTrace( );

+		}

+		finally

+		{

+			if ( gc != null &amp;&amp; !gc.isDisposed( ) )

+			{

+				gc.dispose( );

+			}

+			

+			if ( tf != null &amp;&amp; !tf.isDisposed( ) )

+			{

+				tf.dispose( );

+			}

+		}

+		return dest;

+	}

+}</pre>

+

+<p>As the SWT API and resource management are not

+100% same as SWING, you can see some differences between

+the code styles. Nevertheless, the core logic are the same.</p>

+

+<p>Now let's have a test.</p>

+

+<p>Insert a RotatedText item and specify the rotation angle as "45",</p>

+

+<img style="width: 721px; height: 169px;" alt="" src="images/properties.png"><br>

+

+<p>See, now the layout view looks same as the previewing result. The angle

+setting is reflected in the editor too. This kind of WYSIWYG can help the 

+end user to quickly spot and identify different report items that having

+different settings.</p>

+

+<p><img style="width: 861px; height: 204px;" alt="" src="images/image-ui-layout.png"></p>

+

+<img style="width: 554px; height: 115px;" alt="" src="images/image-ui-preview.png">

+

+<p>The Image UI provides the basic possibility for

+WYSIWYG support. For most simple extended report items, this is already

+sufficient. While for some custom extended report items that requiring more

+complicated UI behavior, the Figure UI may be another

+choice.</p>

+

+<h3>2) Image VS Figure</h3>

+

+<p>The Figure UI leverages the <code>IFigure</code> interface from <a href="http://www.eclipse.org/gef/">GEF 

+(Graphical Editing Framework)</a>. By using this interface, it can provide more flexibility 

+and interactive possibilities than the Image UI.</p>

+

+<p>The table below shows a brief summary for major differences between Figure UI and Image UI:</p>

+

+<table style="text-align: left;" border="1" cellpadding="2" cellspacing="2">

+  <tbody>

+    <tr>

+      <td></td>

+      <td style="text-align: center;"><span style="font-weight: bold;">Figure UI</span></td>

+      <td style="text-align: center;"><span style="font-weight: bold;">Image UI</span></td>

+    </tr>

+    <tr>

+      <td style="vertical-align: top;"><span style="font-weight: bold;">Life Cycle</span></td>

+      <td style="vertical-align: top;">The life cycle of Figure UI is divided into three parts:<br>

+      <ul>

+        <li><em>Create</em></li>

+        <li><em>Update</em></li>

+        <li><em>Dispose</em></li>

+      </ul>

+The  Figure UI normally creates the Figure object once. Each time

+any model state changed, the <em>Update</em> action will be invoked. <br>

+

+The advantage of this pattern is it can support incremental and selective

+update. For each time <em>Update</em> is called, the figure instance

+can check the internal model state and decide if it really needs the update 

+or only need update part of the UI.

+      </td>

+

+      <td style="vertical-align: top;">The life cycle of Image UI is divided into two parts:<br>

+      <ul>

+        <li><em>Create</em></li>

+        <li><em>Dispose</em></li>

+      </ul>

+Each time any model state changed, the Image UI will recreate the image presentation.

+      </td>

+    </tr>

+    <tr>

+      <td style="vertical-align: top;"><span style="font-weight: bold;">Presentation</span></td>

+      <td style="vertical-align: top;">The Figure UI

+allows user to create a hierarchical UI structure.

+This UI structure can be controlled by the GEF layout

+mechanism.</td>

+      <td style="vertical-align: top;">The Image UI can only use one single Image for presentation.</td>

+    </tr>

+    <tr>

+      <td style="vertical-align: top;"><span style="font-weight: bold;">Interactivity</span></td>

+      <td style="vertical-align: top;">The Figure UI allows user to add listener for certain UI events. This allows custom handling for specific UI events. </td>

+      <td style="vertical-align: top;">No interactivity support</td>

+    </tr>

+  </tbody>

+</table>

+

+<p>Image or Figure? That's a question. However though Figure UI provides

+more capability than Image UI, it also requires more comprehensive understanding about the GEF framework, 

+while Image UI needs nearly nothing new. So the decision still depends on your real needs.</p>

+

+<p>Here we'll give a simple example for Figure UI practice.

+In this example, we show how to handle the mouse middle-button click event. For

+each middle-button click, we will change the rotation angle by adding

+45 degrees to the original value.</p>

+

+<p>Like using the Image UI provider, we first remove any existing

+Label UI or Image UI provider, adding the new Figure UI

+provider extension,

+and specifying the implementor class which must implement

+<code>org.eclipse.birt.report.designer.ui.extensions.IReportItemFigureProvider</code>.</p>

+

+<p><img style="width: 1002px; height: 227px;" alt="" src="images/figure-ui-extension.png"></p>

+

+<p>we specify the class as <code>org.eclipse.birt.sample.reportitem.rotatedtext.RotatedTextFigureUI</code>, the code is like this:</p>

+

+<pre>public class RotatedTextFigureUI extends ReportItemFigureProvider

+{

+	public IFigure createFigure( ExtendedItemHandle handle )

+	{

+		try

+		{

+			IReportItem item = handle.getReportItem( );

+			

+			if ( item instanceof RotatedTextItem )

+			{

+				return new RotatedTextFigure( (RotatedTextItem) item );

+			}

+		}

+		catch ( ExtendedElementException e )

+		{

+			e.printStackTrace( );

+		}

+		return null;

+	}

+	

+	public void updateFigure( ExtendedItemHandle handle, IFigure figure )

+	{

+		try

+		{

+			IReportItem item = handle.getReportItem( );

+			

+			if ( item instanceof RotatedTextItem )

+			{

+				RotatedTextFigure fig = (RotatedTextFigure) figure;

+				

+				fig.setRotatedTextItem( (RotatedTextItem) item );

+			}

+		}

+		catch ( ExtendedElementException e )

+		{

+			e.printStackTrace( );

+		}

+	}

+	

+	public void disposeFigure( ExtendedItemHandle handle, IFigure figure )

+	{

+		( (RotatedTextFigure) figure ).dispose( );

+	}

+}</pre>

+

+<p>In the Figure UI provider, we create an RotatedTextFigure

+instance and delegate all the real logic to this class:</p>

+

+<pre>public class RotatedTextFigure extends Figure

+{

+	private String lastText;

+	private int lastAngle;

+	private Image cachedImage;

+	private RotatedTextItem textItem;

+	

+	RotatedTextFigure( RotatedTextItem textItem )

+	{

+		super( );

+		

+		this.textItem = textItem;

+		

+		addMouseListener( new MouseListener.Stub( ) {

+		

+			public void mousePressed( MouseEvent me )

+			{

+				if ( me.button == 2 )

+				{

+					try

+					{

+						RotatedTextFigure.this.textItem.setRotationAngle( normalize( RotatedTextFigure.this.textItem.getRotationAngle( ) + 45 ) );

+					}

+					catch ( SemanticException e )

+					{

+						e.printStackTrace( );

+					}

+				}

+			}

+		} );

+	}

+	

+	private int normalize( int angle )

+	{

+		angle = angle % 360;

+		

+		if ( angle &lt; 0 )

+		{

+			angle += 360;

+		}

+		

+		return angle;

+	}

+	

+	public Dimension getMinimumSize( int hint, int hint2 )

+	{

+		return getPreferredSize( hint, hint2 );

+	}

+	

+	public Dimension getPreferredSize( int hint, int hint2 )

+	{

+		Display display = Display.getCurrent( );

+		

+		GC gc = null;

+		

+		try

+		{

+			String text = textItem.getText( );

+			int angle = textItem.getRotationAngle( );

+			

+			gc = new GC( display );

+			

+			Point pt = gc.textExtent( text == null ? "" : text ); //$NON-NLS-1$

+			

+			double[] info = SwtGraphicsUtil.computedRotatedInfo( pt.x, pt.y, angle );

+			

+			if ( getBorder( ) != null )

+			{

+				Insets bdInsets = getBorder( ).getInsets( this );

+				

+				return new Dimension( (int) info[0] + bdInsets.getWidth( ), (int) info[1] + bdInsets.getHeight( ) );

+			}

+			return new Dimension( (int) info[0], (int) info[1] );

+		}

+		finally

+		{

+			if ( gc != null &amp;&amp; !gc.isDisposed( ) )

+			{

+				gc.dispose( );

+			}

+		}

+	}

+	

+	protected void paintClientArea( Graphics graphics )

+	{

+		final Rectangle r = getClientArea( ).getCopy( );

+		

+		String text = textItem.getText( );

+		int angle = textItem.getRotationAngle( );

+		

+		if ( text == null )

+		{

+			text = ""; //$NON-NLS-1$

+		}

+		

+		if ( !text.equals( lastText ) || angle != lastAngle || cachedImage == null || cachedImage.isDisposed( ) )

+		{

+			lastText = text;

+			lastAngle = angle;

+			

+			if ( cachedImage != null &amp;&amp; !cachedImage.isDisposed( ) )

+			{

+				cachedImage.dispose( );

+			}

+			

+			cachedImage = SwtGraphicsUtil.createRotatedTextImage( text, angle, null );

+		}

+		

+		if ( cachedImage != null &amp;&amp; !cachedImage.isDisposed( ) )

+		{

+			graphics.drawImage( cachedImage, r.x, r.y );

+		}

+	}

+	

+	void setRotatedTextItem( RotatedTextItem item )

+	{

+		this.textItem = item;

+	}

+	

+	void dispose( )

+	{

+		if ( cachedImage != null &amp;&amp; !cachedImage.isDisposed( ) )

+		{

+			cachedImage.dispose( );

+		}

+	}

+}</pre>

+

+<p>You can see the core rendering logic is the same as the Image UI. We still

+call the SwtGraphicsUtil to create an image and then paint it to figure. The

+only additional logic is the part about the mouse listener, where we handle the

+middle-button click event, adding 45 degrees to the original value per click.</p>

+

+<p>Now run and test. In layout editor, each time you middle-click the

+RotatedTextItem, it will automatically change the rotation angle.</p>

+

+<p>Note this is just an example to show the simplest possibility for the Figure UI.

+Basically you can create very complex UI

+structures based on the GEF framework. As it is mostly related to GEF, we

+will not introduce more here. You can refer to <a href="http://www.eclipse.org/gef/">GEF</a> official site for more details.</p>

+

+<h3>3) The Builder</h3>

+

+<p>One common approach for property editing is the <em>Builder</em>. Most built-in

+report items in BIRT have the builder support. Each time when you create or

+double-click the report item in layout editor, the associated builder for this

+report item will pop up and allow you to edit in

+a more friendly way. In following sections, we will introduce how to add the builder support

+for custom extended report items.</p>

+

+<p>To support the builder, we need add the builder UI extension and specify

+the implementor class which must implement

+<code>org.eclipse.birt.report.designer.ui.extensions.IReportItemBuilderUI</code>:</p>

+

+<img style="width: 1000px; height: 244px;" alt="" src="images/builder-extension.png">

+

+<p>Here we specify the class as <code>org.eclipse.birt.sample.reportitem.rotatedtext.RotatedTextBuilder</code>, the code is like this:</p>

+

+<pre>public class RotatedTextBuilder extends ReportItemBuilderUI

+{

+	public int open( ExtendedItemHandle handle )

+	{

+		try

+		{

+			IReportItem item = handle.getReportItem( );

+			

+			if ( item instanceof RotatedTextItem )

+			{

+				RotatedTextEditor editor = new RotatedTextEditor( Display.getCurrent( ).getActiveShell( ), (RotatedTextItem) item );

+				return editor.open( );

+			}

+		}

+		catch ( Exception e )

+		{

+			e.printStackTrace( );

+		}

+		return Window.CANCEL;

+	}

+}</pre>

+

+<p>The code is very simple, we just create another RotatedTextEditor class to populate the actual UI:</p>

+

+<pre>class RotatedTextEditor extends TrayDialog

+{

+	protected RotatedTextItem textItem;

+	protected Text txtText;

+	protected Scale sclAngle;

+	protected Label lbAngle;

+	

+	protected RotatedTextEditor( Shell shell, RotatedTextItem textItem )

+	{

+		super( shell );

+		this.textItem = textItem;

+	}

+	

+	protected void configureShell( Shell newShell )

+	{

+		super.configureShell( newShell );

+		newShell.setText( "Rotated Text Builder" ); //$NON-NLS-1$

+	}

+	

+	protected void createTextArea( Composite parent )

+	{

+		Label lb = new Label( parent, SWT.None );

+		lb.setText( "Text Content:" ); //$NON-NLS-1$

+		

+		txtText = new Text( parent, SWT.BORDER );

+		GridData gd = new GridData( GridData.FILL_HORIZONTAL );

+		gd.horizontalSpan = 2;

+		txtText.setLayoutData( gd );

+	}

+	

+	protected Control createDialogArea( Composite parent )

+	{

+		Composite composite = new Composite( parent, SWT.NONE );

+		GridLayout layout = new GridLayout( 3, false );

+		layout.marginHeight = convertVerticalDLUsToPixels( IDialogConstants.VERTICAL_MARGIN );

+		layout.marginWidth = convertHorizontalDLUsToPixels( IDialogConstants.HORIZONTAL_MARGIN );

+		layout.verticalSpacing = convertVerticalDLUsToPixels( IDialogConstants.VERTICAL_SPACING );

+		layout.horizontalSpacing = convertHorizontalDLUsToPixels( IDialogConstants.HORIZONTAL_SPACING );

+		composite.setLayout( layout );

+		composite.setLayoutData( new GridData( GridData.FILL_BOTH ) );

+		

+		createTextArea( composite );

+		

+		Label lb = new Label( composite, SWT.None );

+		lb.setText( "Rotation Angle:" ); //$NON-NLS-1$

+		

+		sclAngle = new Scale( composite, SWT.None );

+		sclAngle.setLayoutData( new GridData( GridData.FILL_HORIZONTAL ) );

+		sclAngle.setMinimum( 0 );

+		sclAngle.setMaximum( 360 );

+		sclAngle.setIncrement( 10 );

+		

+		lbAngle = new Label( composite, SWT.None );

+		GridData gd = new GridData( );

+		gd.widthHint = 20;

+		lbAngle.setLayoutData( gd );

+		

+		sclAngle.addSelectionListener( new SelectionListener( ) {

+		

+			public void widgetDefaultSelected( SelectionEvent e )

+			{

+				lbAngle.setText( String.valueOf( sclAngle.getSelection( ) ) );

+			}

+			

+			public void widgetSelected( SelectionEvent e )

+			{

+				lbAngle.setText( String.valueOf( sclAngle.getSelection( ) ) );

+			}

+		} );

+		

+		applyDialogFont( composite );

+		

+		initValues( );

+		

+		return composite;

+	}

+	

+	private void initValues( )

+	{

+		txtText.setText( textItem.getText( ) );

+		sclAngle.setSelection( textItem.getRotationAngle( ) );

+		lbAngle.setText( String.valueOf( textItem.getRotationAngle( ) ) );

+	}

+	

+	protected void okPressed( )

+	{

+		try

+		{

+			textItem.setText( txtText.getText( ) );

+			textItem.setRotationAngle( sclAngle.getSelection( ) );

+		}

+		catch ( Exception ex )

+		{

+			ex.printStackTrace( );

+		}

+		

+		super.okPressed( );

+	}

+}</pre>

+

+<p>In the RotatedText builder UI, we provide one <code>Text</code> control and one <code>Scale</code>

+control, so user can easily change the "text content" and "rotation

+angle" properties.</p>

+

+<p>Now let's run the designer. </p>

+

+<p>Create one RotatedText item from palette first. Good, right after the

+creation, the builder pops up. Now It's very convenient to edit the

+properties. And when you double-click the RotatedText item in layout, it 

+also brings up the builder.</p>

+

+<p><img style="width: 497px; height: 380px;" alt="" src="images/builder.png"></p>

+

+<p>By this way, user can create very complicated builder UI for editing. One good example is the built-in Chart builder,

+basically it's the most complicated builder in BIRT so far.</p>

+

+<p>Regarding the builder, there are another two extensions:

+<code>org.eclipse.birt.core.ui.tasks</code> and

+<code>org.eclipse.birt.core.ui.taskWizards</code>.

+These two extensions are not directly related to the builder UI

+extension, but providing a comprehensive way to create a wizard-like

+dialog UI. This dialog UI then can be further integrated to the builder UI

+extension. Chart builder is based on this technology. Since we are not going to explore the 

+details about these two extensions in this article, you may refer to the 

+Chart builder implementation for more details.</p>

+

+<h3>4) Context Menu</h3>

+

+<p>Another well-known UI feature may be the context menu. Through the context menu,

+user can get the quick entry points to perform specific actions. In

+BIRT, you can also provides a customized context menu for

+your extended report item by extensions.</p>

+

+<p>To support custom context menu, you need implement the

+extension <code>org.eclipse.birt.report.designer.ui.menuBuilders</code>, the

+implementor class must implement the

+<code>org.eclipse.birt.report.designer.ui.extensions.IMenuBuilder</code> interface.</p>

+

+<img style="width: 998px; height: 278px;" alt="" src="images/menu-extension.png">

+

+<p>As usual, we specify the element name as "RotatedText" to bind it

+to the model extension and give the implementor class name as

+<code>org.eclipse.birt.sample.reportitem.rotatedtext.RotatedTextMenuBuilder</code>.</p>

+

+<p>In the menu builder extension for RotatedText item, we add four

+custom actions to perform quick rotations, respectively,

+set the angle value to -90, 90, 0 and 180 degrees. The code is like this:</p>

+

+<pre>public class RotatedTextMenuBuilder implements IMenuBuilder

+{

+	public void buildMenu( IMenuManager menu, List selectedList )

+	{

+		if ( selectedList != null &amp;&amp; selectedList.size( ) == 1 &amp;&amp; selectedList.get( 0 ) instanceof ExtendedItemHandle )

+		{

+			ExtendedItemHandle handle = (ExtendedItemHandle) selectedList.get( 0 );

+			

+			if ( !RotatedTextItem.EXTENSION_NAME.equals( handle.getExtensionName( ) ) )

+			{

+				return;

+			}

+			

+			RotatedTextItem item = null;

+			try

+			{

+				item = (RotatedTextItem) handle.getReportItem( );

+			}

+			catch ( ExtendedElementException e )

+			{

+				e.printStackTrace( );

+			}

+			

+			if ( item == null )

+			{

+				return;

+			}

+			

+			Separator separator = new Separator( "group.rotatedtext" ); //$NON-NLS-1$

+			if ( menu.getItems( ).length &gt; 0 )

+			{

+				menu.insertBefore( menu.getItems( )[0].getId( ), separator );

+			}

+			else

+			{

+				menu.add( separator );

+			}

+			

+			menu.appendToGroup( separator.getId( ),	new RotateAction( item, -90 ) );

+			menu.appendToGroup( separator.getId( ), new RotateAction( item, 90 ) );

+			menu.appendToGroup( separator.getId( ), new RotateAction( item, 0 ) );

+			menu.appendToGroup( separator.getId( ),	new RotateAction( item, 180 ) );

+		}

+	}

+	

+	static class RotateAction extends Action

+	{

+		private RotatedTextItem item;

+		private int angle;

+		

+		RotateAction( RotatedTextItem item, int angle )

+		{

+			this.item = item;

+			this.angle = angle;

+			

+			setText( "Rotate as " + angle + "\u00BA" ); //$NON-NLS-1$ //$NON-NLS-2$

+		}

+		

+		public void run( )

+		{

+			try

+			{

+				item.setRotationAngle( angle );

+			}

+			catch ( SemanticException e )

+			{

+				e.printStackTrace( );

+			}

+		}

+	}

+}</pre>

+

+<p>Each custom action sets the rotation angle to a most commonly

+used value, which is just like a shortcut for setting the values.</p>

+

+<p>Run the designer and check again. Right-click on any RotatedText

+extended report item in layout editor, now you can see the custom actions in the context menu.</p>

+

+<img style="width: 491px; height: 462px;" alt="" src="images/contextmenu.png">

+

+<p>Through menu builder extension, actually you are not only able to add

+actions, it's also possible to remove or overwrite actions. Anyway, this offers you another

+chance to provide a smoother UI to the end user.</p>

+

+<h3>5) Property Pages</h3>

+

+<div style="text-align: left;">

+<p>An important UI concept in BIRT designer is the <em>Property Editor</em>. The

+<em>Property Editor</em> view provides the UI for miscellaneous property editting. It

+includes both specific settings for each report item and generic settings for all report items.</p>

+

+<img style="width: 811px; height: 284px;" alt="" src="images/label-general.png"><img style="width: 811px; height: 284px;" alt="" src="images/label-border.png">

+

+<p>So can we also create a similar property UI for our

+custom extended report items? And can we reuse the 

+generic property UI for custom extended report items?</p>

+

+<p>Both answers are YES. In following sections, we will introduce how to

+implement custom property pages as well as how to reuse built-in property pages for

+custom extended report items.</p>

+

+<p>Before we start, let's have a brief look at the layout structure of the <em>Property Editor</em> view.</p>

+

+<img style="width: 749px; height: 284px;" alt="" src="images/tab-structure.png">

+

+<p>Normally the Property Editor UI is in a "Tabbed" style. There are

+two kinds of tabs: the "Property Tab" and the "Category Tab".

+The "Property Tab" divides the entire Property Editor UI into several

+pages, while the "Category Tab" divides a single page into several

+categories. By default, the first "Properties" tab page is designed to

+support categories and other pages not. But this is only the default behavior, user can always 

+alter the UI style and overwrite the logic, although the

+default behavior in most cases already looks good. </p>

+

+<p>Now let's go on. To support custom property pages, we need implement following extensions:</p>

+

+<img style="width: 1001px; height: 253px;" alt="" src="images/property-page-extension-1.png">

+

+<p>As you see, unlike the previous extension points, this time the

+<code>org.eclipse.birt.report.designer.ui.elementAdpaters</code> extension point

+looks a little bit different. Actually, this is a very generic extension point

+used by BIRT designer. It is not only used to support property page

+extension, but also a lot of other UI extensions in various places.</p>

+

+<p>The first thing is to specify the <code>adaptable</code> class. In this case,

+we always specify it as

+<code>org.eclipse.birt.report.model.api.ExtendedItemHandle</code>, as custom

+extended report items are always represented by this class in BIRT model.</p>

+

+<p>The next thing is to specify the <code>adapter</code> settings:</p>

+

+<img style="width: 1001px; height: 251px;" alt="" src="images/property-page-extension-2.png">

+

+<p>Here is a brief explanation for the adapter settings:</p>

+

+<ul>

+  <li><em>id</em></li>

+  <ul>

+    <li>the unique identifier for this adapter. Here we specify it as <code>ReportDesign.AttributeView.RotatedTextPageGenerator</code>.</li>

+  </ul>

+  <li><em>type</em></li>

+  <ul>

+    <li>the Java class type this adapter will adapt to. For property

+pages, we must specify it as

+<code>org.eclipse.birt.report.designer.ui.views.IPageGenerator</code>.</li>

+  </ul>

+  <li><em>class</em></li>

+  <ul>

+    <li>The Java class type for the adapter. The class must have a

+constructor without any argument. As we are using the factory mode

+here, just leave it as blank.</li>

+  </ul>

+  <li><em>factory</em></li>

+  <ul>

+    <li>The Java class type for the adapter factory. The class must

+implement <code>org.eclipse.core.runtime.IAdapterFactory</code> interface. Here we specify

+it as

+<code>org.eclipse.birt.sample.reportitem.rotatedtext.views.RotatedTextPageGeneratorFactory</code>.</li>

+  </ul>

+  <li><em>singleton</em></li>

+  <ul>

+    <li>Specifies if the adapter object is a singleton or not. For singleton, the adapter object will be cached and reused for all matching

+adaptable. Here since we are using the factory mode, just set it to

+false.</li>

+  </ul>

+  <li><em>priority</em></li>

+  <ul>

+    <li>Specifies the priority for the adapter. This will be used for sorting

+when multiple adapters are defined for same adaptable. Just use the

+default value by leaving it as blank.</li>

+  </ul>

+  <li><em>overwrite</em></li>

+  <ul>

+    <li>Specifies a semicolon separated id list that this adapter want to overwrite. Here we just leave it as blank.</li>

+  </ul>

+  <li><em>comments</em></li>

+  <ul>

+    <li>An additional field to put some description text for the adapter.</li>

+  </ul>

+</ul>

+

+<p>Regarding the <em>class</em> and <em>factory</em> setting, there are already plenty of 

+documents talking about them, so we are not repeating any more here. Normally the <em>factory</em> 

+mode means more flexibility and extensibility. If <em>class</em> and

+<em>factory</em> are both specified, <em>class</em> will take higher priority.</p>

+

+<p>This is not the end yet. As we only want to add custom property pages for

+the RotatedText item, we need set some additional constraints 

+for the adaptable.</p>

+

+<img style="width: 1002px; height: 252px;" alt="" src="images/property-page-extension-3.png">

+

+<p>To achieve this, we simply create an <em>enablement</em> element with type

+<em>test</em> under the <em>adapter</em> node. In the settings, we specify the test property as

+<code>ExtendItemHandle.extensionName</code> and the value as <code>RotatedText</code>. As you

+can conceive, this effectively restricts the adaptable object to be only

+our RotatedText extended report items.</p>

+

+<p>Now let's look at the code of the factory class:</p>

+

+<pre>public class RotatedTextPageGeneratorFactory implements IAdapterFactory

+{

+	public Object getAdapter( Object adaptableObject, Class adapterType )

+	{

+		return new RotatedTextPageGenerator( );

+	}

+	

+	public Class[] getAdapterList( )

+	{

+		return new Class[]{

+			IPageGenerator.class

+		};

+	}

+}</pre>

+

+<p>The factory class simply creates a generator instance per call. Each

+generator must implement the 

+<code>org.eclipse.birt.report.designer.ui.views.IPageGenerator</code> interface,

+but usually we just extend it from

+<code>org.eclipse.birt.report.designer.ui.views.attributes.AbstractPageGenerator</code>

+class, which provides some basic support for the categorized styles.</p>

+

+<p>In this example, we want to overwrite the <code>General</code>

+category page in <code>Properties</code> tab, and reuse some built-in categories

+like <code>Border</code>, <code>Margin</code>, <code>Page Break</code>, etc. Also we want to add a

+<code>Custom</code> property tab to the property editor. Here is the code:</p>

+

+<pre>public class RotatedTextPageGenerator extends AbstractPageGenerator

+{

+	<span style="font-weight: bold;">private static final String CUSTOM_PAGE_TITLE = "Custom"; //$NON-NLS-1$</span>

+	private IPropertyTabUI generalPage;

+	

+	protected void buildItemContent( CTabItem item )

+	{

+		if ( itemMap.containsKey( item ) &amp;&amp; itemMap.get( item ) == null )

+		{

+			String title = tabFolder.getSelection( ).getText( );

+			

+			<span style="font-weight: bold;">if ( CUSTOM_PAGE_TITLE.equals( title ) )</span>

+			<span style="font-weight: bold;">{</span>

+			<span style="font-weight: bold;">	TabPage page = new RotatedTextCustomPage( ).getPage( );</span>

+			<span style="font-weight: bold;">	if ( page != null )</span>

+			<span style="font-weight: bold;">	{</span>

+			<span style="font-weight: bold;">		setPageInput( page );</span>

+			<span style="font-weight: bold;">		refresh( tabFolder, page, true );</span>

+			<span style="font-weight: bold;">		item.setControl( page.getControl( ) );</span>

+			<span style="font-weight: bold;">		itemMap.put( item, page );</span>

+			<span style="font-weight: bold;">	}</span>

+			<span style="font-weight: bold;">}</span>

+		}

+		else if ( itemMap.get( item ) != null )

+		{

+			setPageInput( itemMap.get( item ) );

+			refresh( tabFolder, itemMap.get( item ), false );

+		}

+	}

+	

+	public void refresh( )

+	{

+		createTabItems( input );

+		

+		generalPage.setInput( input );

+		addSelectionListener( this );

+		( (TabPage) generalPage ).refresh( );

+	}

+	

+	public void createTabItems( List input )

+	{

+		if ( generalPage == null || generalPage.getControl( ).isDisposed( ) )

+		{

+			tabFolder.setLayout( new FillLayout( ) );

+			<span style="font-weight: bold;">generalPage = AttributesUtil.buildGeneralPage( tabFolder,</span>

+			<span style="font-weight: bold;">	new String[]{</span>

+			<span style="font-weight: bold;">		null,</span>

+			<span style="font-weight: bold;">		AttributesUtil.BORDER,</span>

+			<span style="font-weight: bold;">		AttributesUtil.MARGIN,</span>

+			<span style="font-weight: bold;">		AttributesUtil.SECTION,</span>

+			<span style="font-weight: bold;">		AttributesUtil.VISIBILITY,</span>

+			<span style="font-weight: bold;">		AttributesUtil.TOC,</span>

+			<span style="font-weight: bold;">		AttributesUtil.BOOKMARK,</span>

+			<span style="font-weight: bold;">		AttributesUtil.USERPROPERTIES,</span>

+			<span style="font-weight: bold;">		AttributesUtil.NAMEDEXPRESSIONS,</span>

+			<span style="font-weight: bold;">		AttributesUtil.ADVANCEPROPERTY</span>

+			<span style="font-weight: bold;">	},</span>

+			<span style="font-weight: bold;">	new String[]{ "General" }, //$NON-NLS-1$</span>

+			<span style="font-weight: bold;">	new String[]{ "General" }, //$NON-NLS-1$</span>

+			<span style="font-weight: bold;">	new AttributesUtil.PageWrapper[]{ new RotatedTextGeneralPage( )	},</span>

+			<span style="font-weight: bold;">	input );</span>

+			

+			CTabItem tabItem = new CTabItem( tabFolder, SWT.NONE );

+			tabItem.setText( ATTRIBUTESTITLE );

+			tabItem.setControl( generalPage.getControl( ) );

+		}

+		

+		this.input = input;

+		generalPage.setInput( input );

+		addSelectionListener( this );

+		( (TabPage) generalPage ).refresh( );

+		

+		<span style="font-weight: bold;">createTabItem( CUSTOM_PAGE_TITLE, ATTRIBUTESTITLE );</span>

+		

+		if ( tabFolder.getSelection( ) != null )

+		{

+			buildItemContent( tabFolder.getSelection( ) );

+		}

+	}

+}</pre>

+

+<p>Let's explain a little more here:</p>

+

+<p>In the generator class, we extend it from the <code>AbstractPageGenerator</code> and

+overwrite three methods. Among them, the <code>createTabItems</code> and

+<code>buildItemContent</code> methods

+are the central places that containing the custom logic. In

+<code>createTabItem</code> method, we overwrite the creation logic for <code>General</code>

+category page, adding interested built-in category pages as well as the 

+<code>Custom</code> property page. In <code>buildItemContent</code> method, we insert the

+creation logic for the <code>Custom</code> property page. You can focus on the 

+text in bold to see how these are achieved. The remaining code are trivial,

+normally you can directly use it as a template for other page generator

+extensions.</p>

+

+<p>Note one thing you need take care of is when using the built-in pages,

+you must ensure the relevant model property definitions are applicable. As for some extended 

+report items, they will overwrite

+or remove some of the built-in property definitions, in this case,

+those built-in page may not work properly.</p>

+

+<p>The last thing is the property page implementation. For

+<code>General</code> category page, we simply create two text controls for

+property editing. The code is like this:</p>

+

+<pre>public class RotatedTextGeneralPage extends AttributesUtil.PageWrapper

+{

+	protected FormToolkit toolkit;

+	protected Object input;

+	protected Composite contentpane;

+	private Text txtText, txtAngle;

+	

+	public void buildUI( Composite parent )

+	{

+		if ( toolkit == null )

+		{

+			toolkit = new FormToolkit( Display.getCurrent( ) );

+			toolkit.setBorderStyle( SWT.NULL );

+		}

+		

+		Control[] children = parent.getChildren( );

+		

+		if ( children != null &amp;&amp; children.length &gt; 0 )

+		{

+			contentpane = (Composite) children[children.length - 1];

+			

+			GridLayout layout = new GridLayout( 2, false );

+			layout.marginLeft = 8;

+			layout.verticalSpacing = 12;

+			contentpane.setLayout( layout );

+			

+			toolkit.createLabel( contentpane, "Text Content:" ); //$NON-NLS-1$

+			txtText = toolkit.createText( contentpane, "" ); //$NON-NLS-1$

+			GridData gd = new GridData( );

+			gd.widthHint = 200;

+			

+			txtText.setLayoutData( gd );

+			txtText.addFocusListener( new FocusAdapter( ) {

+			

+				public void focusLost( org.eclipse.swt.events.FocusEvent e )

+				{

+					updateModel( RotatedTextItem.TEXT_PROP );

+				};

+			} );

+			

+			toolkit.createLabel( contentpane, "Rotation Angle:" ); //$NON-NLS-1$

+			txtAngle = toolkit.createText( contentpane, "" ); //$NON-NLS-1$

+			gd = new GridData( );

+			gd.widthHint = 200;

+			

+			txtAngle.setLayoutData( gd );

+			txtAngle.addFocusListener( new FocusAdapter( ) {

+			

+				public void focusLost( org.eclipse.swt.events.FocusEvent e )

+				{

+					updateModel( RotatedTextItem.ROTATION_ANGLE_PROP );

+				};

+			} );

+		}

+	}

+	

+	public void setInput( Object input )

+	{

+		this.input = input;

+	}

+	

+	public void dispose( )

+	{

+		if ( toolkit != null )

+		{

+			toolkit.dispose( );

+		}

+	}

+	

+	private void adaptFormStyle( Composite comp )

+	{

+		Control[] children = comp.getChildren( );

+		for ( int i = 0; i &lt; children.length; i++ )

+		{

+			if ( children[i] instanceof Composite )

+			{

+				adaptFormStyle( (Composite) children[i] );

+			}

+		}

+		

+		toolkit.paintBordersFor( comp );

+		toolkit.adapt( comp );

+	}

+	

+	protected RotatedTextItem getItem( )

+	{

+		Object element = input;

+		

+		if ( input instanceof List &amp;&amp; ( (List) input ).size( ) &gt; 0 )

+		{

+			element = ( (List) input ).get( 0 );

+		}

+		

+		if ( element instanceof ExtendedItemHandle )

+		{

+			try

+			{

+				return (RotatedTextItem) ( (ExtendedItemHandle) element ).getReportItem( );

+			}

+			catch ( Exception e )

+			{

+				e.printStackTrace( );

+			}

+		}

+		

+		return null;

+	}

+	

+	public void refresh( )

+	{

+		if ( contentpane != null &amp;&amp; !contentpane.isDisposed( ) )

+		{

+			if ( toolkit == null )

+			{

+				toolkit = new FormToolkit( Display.getCurrent( ) );

+				toolkit.setBorderStyle( SWT.NULL );

+			}

+			

+			adaptFormStyle( contentpane );

+			

+			updateUI( );

+		}

+	}

+	

+	public void postElementEvent( )

+	{

+		if ( contentpane != null &amp;&amp; !contentpane.isDisposed( ) )

+		{

+			updateUI( );

+		}

+	}

+	

+	private void updateModel( String prop )

+	{

+		RotatedTextItem item = getItem( );

+		

+		if ( item != null )

+		{

+			try

+			{

+				if ( RotatedTextItem.ROTATION_ANGLE_PROP.equals( prop ) )

+				{

+					item.setRotationAngle( Integer.parseInt( txtAngle.getText( ) ) );

+				}

+				else if ( RotatedTextItem.TEXT_PROP.equals( prop ) )

+				{

+					item.setText( txtText.getText( ) );

+				}

+			}

+			catch ( Exception e )

+			{

+				e.printStackTrace( );

+			}

+		}

+	}

+	

+	protected void updateUI( )

+	{

+		RotatedTextItem item = getItem( );

+		

+		if ( item != null )

+		{

+			String text = item.getText( );

+			txtText.setText( text == null ? "" : text ); //$NON-NLS-1$

+			

+			txtAngle.setText( String.valueOf( item.getRotationAngle( ) ) );

+		}

+	}

+}</pre>

+

+<p>Note the using of <code>FormToolkit</code> is just to achieve the same look and feel as

+the built-in pages. You can always design your own UI styles if no need for

+this consistency.</p>

+

+<p>For the <code>Custom</code> property page, as we only want to show the

+extension mechanism and the possibility, to simplify, we only give a very trivial example

+by implementing a read-only version of the <code>General</code>

+category page.</p>

+

+<pre>public class RotatedTextCustomPage extends RotatedTextGeneralPage

+{

+	private Label lbText, lbAngle;

+	

+	public void buildUI( Composite parent )

+	{

+		if ( toolkit == null )

+		{

+			toolkit = new FormToolkit( Display.getCurrent( ) );

+			toolkit.setBorderStyle( SWT.NULL );

+		}

+		

+		Control[] children = parent.getChildren( );

+		

+		if ( children != null &amp;&amp; children.length &gt; 0 )

+		{

+			contentpane = (Composite) children[children.length - 1];

+			

+			GridLayout layout = new GridLayout( 2, false );

+			layout.marginTop = 8;

+			layout.marginLeft = 8;

+			layout.verticalSpacing = 12;

+			contentpane.setLayout( layout );

+			

+			toolkit.createLabel( contentpane, "Text Content:" ); //$NON-NLS-1$

+			lbText = toolkit.createLabel( contentpane, "" ); //$NON-NLS-1$

+			GridData gd = new GridData( );

+			gd.widthHint = 200;

+			lbText.setLayoutData( gd );

+			

+			toolkit.createLabel( contentpane, "Rotation Angle:" ); //$NON-NLS-1$

+			lbAngle = toolkit.createLabel( contentpane, "" ); //$NON-NLS-1$

+			gd = new GridData( );

+			gd.widthHint = 200;

+			lbAngle.setLayoutData( gd );

+		}

+	}

+	

+	protected void updateUI( )

+	{

+		RotatedTextItem item = getItem( );

+		

+		if ( item != null )

+		{

+			String text = item.getText( );

+			lbText.setText( text == null ? "" : text ); //$NON-NLS-1$

+			lbAngle.setText( String.valueOf( item.getRotationAngle( ) ) );

+		}

+	}

+}</pre>

+

+<p>OK, now let's wrap all things up and take a look.</p>

+

+<p>Run the designer, ensure the <em>Property Editor</em> view is visible and

+select one RotatedText extended report item in layout editor. </p>

+

+<p>Cool, the <code>General</code> category page shows exactly what we designed. You can view 

+and edit property values in this page

+directly now.</p>

+

+<img style="width: 818px; height: 284px;" alt="" src="images/rotatedtext-general.png">

+

+<p>Click the <code>Border</code> category, it brings up the built-in border

+property page as well. You can try change some border settings.

+Immediately you can see it works perfectly in layout editor. </p>

+

+<img style="width: 799px; height: 284px;" alt="" src="images/rotatedtext-border.png"><br>

+

+<br>

+

+<img style="width: 340px; height: 265px;" alt="" src="images/border-effect.png">

+

+<p>Now try switching to the <code>Custom</code> page, it brings up our read-only

+property page. Since this is only an example, in real practice,

+you can implement any custom pages.</p>

+

+<img style="width: 818px; height: 284px;" alt="" src="images/rotatedtext-custom.png">

+

+<p>OK, now we get a nearly complete picture for how to create an

+extended report item through model, engine and designer extensions in

+BIRT, and the ways to polish the extension UI and improve the usablity. However 

+there's another important topic that we'd like to talk about a little

+more, that is the <em>Data Driven</em>.</p>

+

+<h2>Data Driven</h2>

+

+<p>An essential topic in BIRT is about the data integration. In the world of 

+reporting, data is always the first class citizen. Normally data

+integration can be divided into two parts: <em>Data retrieving</em> and <em>Data 

+representation</em>. In this chapter, we will focus on the <em>Data representation</em> part,

+exploring how to make our RotatedText extended report item data-aware

+and capable of leveraging the common data feature in BIRT.</p>

+

+<p>Before the start, we'd like to introduce a little more about the data

+infrastructure in BIRT. One important concept in BIRT is <em>Binding</em>. All

+report data in BIRT are actually populated through bindings. Binding is an

+abstract layer that connects the <em>Data retrieving</em> layer and <em>Data

+representation</em> layer. In other words, all report items in BIRT are

+actually consuming bindings instead of raw data from data source directly.</p>

+

+<p>There are two kinds of bindings in BIRT: <em>Regular binding</em> and

+<em>Aggregation binding</em>. For <em>Regular binding</em>, it normally contains a

+JavaScript expression and a data type. For <em>Aggregation binding</em>, it

+further contains an aggregation function, aggregation filter and aggregation

+target information. By using JavaScript in expression, bindings are

+very flexible to organize the data representation logic. You can either simply write 

+the expression like:</p>

+

+<pre>row["columnA"]<br></pre>

+

+<p>or like:</p>

+

+<pre>if ( someConditionIsOK )

+{

+	if ( otherConditionIsOK )

+	{

+		functionA();

+	}

+	else

+	{

+		resultB;

+	}

+}

+else

+{

+	resultC;

+}</pre>

+

+<p>Bindings are associated with report items. Every

+report item can consume its own bindings as well as its parent-level

+bindings in different scopes. Since 2.3, BIRT also support a new feature called <em>Shared

+ResultSet</em>, which allows one report item to share the bindings from another

+report item that is not necessarily in its parent tree.</p>

+

+<p>Now let's continue to explorer how we can support bindings for our

+Rotated extended report item. The basic idea is to change the <code>text</code>

+property to be binding-aware, so the text value can be controlled by

+bindings dynamically.

+</p>

+

+<h3>1) Model Change</h3>

+

+<p>To support binding for <code>text</code> property, the first thing we need  

+change is the model extension definition:</p>

+

+<img style="width: 999px; height: 273px;" alt="" src="images/text-expression-type.png"><br>

+

+<p>Actually the only thing we need is changing the property type of

+<code>text</code> from <code>string</code> to <code>expression</code> and adding the quotation mark

+around the

+original default value. The change means now this property supports

+expressions, and as the original default value <code>Rotated Text</code> is

+not a valid JavaScript expression, we changed it so it follows the

+literal string syntax in JavaScript. </p>

+

+<p>That's all we need for model change, now let's look at the engine part.</p>

+

+<h3>2) Engine Change</h3>

+

+<p>To support expressions, we just need change a piece of code in the <code>onRowSets()</code> method 

+of <code>RotatedTextPresentationImpl</code> like this:</p>

+

+<pre>public Object onRowSets( IBaseResultSet[] results ) throws BirtException

+{

+	if ( textItem == null )

+	{

+		return null;

+	}

+	

+	int angle = textItem.getRotationAngle( );

+	String text = textItem.getText( );

+	

+	<span style="font-weight: bold;">// XXX added to support expression</span>

+	<span style="font-weight: bold;">if ( results != null &amp;&amp; results.length &gt; 0 )</span>

+	<span style="font-weight: bold;">{</span>

+	<span style="font-weight: bold;">	if ( results[0] instanceof IQueryResultSet &amp;&amp; ( (IQueryResultSet) results[0] ).isBeforeFirst( ) )</span>

+	<span style="font-weight: bold;">	{</span>

+	<span style="font-weight: bold;">		( (IQueryResultSet) results[0] ).next( );</span>

+	<span style="font-weight: bold;">	}</span>

+	<span style="font-weight: bold;">	text = String.valueOf( results[0].evaluate( text ) );</span>

+	<span style="font-weight: bold;">}</span>

+	<span style="font-weight: bold;">else</span>

+	<span style="font-weight: bold;">{</span>

+	<span style="font-weight: bold;">	text = String.valueOf( context.evaluate( text ) );</span>

+	<span style="font-weight: bold;">}</span>

+	<span style="font-weight: bold;">// end new code</span>

+	

+	BufferedImage rotatedImage = SwingGraphicsUtil.createRotatedTextImage( text, angle, new Font( "Default", 0, 12 ) ); //$NON-NLS-1$

+	

+	ByteArrayInputStream bis = null;

+	

+	try

+	{

+		ImageIO.setUseCache( false );

+		ByteArrayOutputStream baos = new ByteArrayOutputStream( );

+		ImageOutputStream ios = ImageIO.createImageOutputStream( baos );

+		

+		ImageIO.write( rotatedImage, "png", ios ); //$NON-NLS-1$

+		ios.flush( );

+		ios.close( );

+		bis = new ByteArrayInputStream( baos.toByteArray( ) );

+	}

+	catch ( IOException e )

+	{

+		e.printStackTrace( );

+	}

+	return bis;

+}</pre>

+

+<p>The new code adds the extra logic to handle the <code>text</code> property as an

+expression, and evaluating it under current engine context to get the

+final string result.</p>

+

+<h3>3) UI Change</h3>

+

+<p>Now the UI's turn. Two places need the change. The first applies to the builder, the 

+second applies to the property page.</p>

+

+<p>For the builder, we want to add a button besides the text control,

+so user can invoke the standard expression builder to edit the

+expression. The standard expression builder provides the 

+common JavaScript support in BIRT. </p>

+

+<img style="width: 424px; height: 380px;" alt="" src="images/builder-expression-support.png">

+<p><img style="width: 702px; height: 594px;" alt="" src="images/expression-builder.png"></p>

+

+<p>To achieve this, we change the code in <code>RotatedTextBuilder</code> class as following:</p>

+

+<pre>public class RotatedTextBuilder extends ReportItemBuilderUI

+{

+	public int open( ExtendedItemHandle handle )

+	{

+		try

+		{

+			IReportItem item = handle.getReportItem( );

+			

+			if ( item instanceof RotatedTextItem )

+			{

+				RotatedTextEditor editor = new <span style="font-weight: bold;">RotatedTextEditor2</span>( Display.getCurrent( ).getActiveShell( ), (RotatedTextItem) item );

+				return editor.open( );

+			}

+		}

+		catch ( Exception e )

+		{

+			e.printStackTrace( );

+		}

+		return Window.CANCEL;

+	}

+}

+

+class RotatedTextEditor2 extends RotatedTextEditor

+{

+	protected RotatedTextEditor2( Shell shell, RotatedTextItem textItem )

+	{

+		super( shell, textItem );

+	}

+	

+	protected void createTextArea( Composite parent )

+	{

+		Label lb = new Label( parent, SWT.None );

+		lb.setText( "Text Content:" ); //$NON-NLS-1$

+		

+		txtText = new Text( parent, SWT.BORDER );

+		GridData gd = new GridData( GridData.FILL_HORIZONTAL );

+		txtText.setLayoutData( gd );

+		

+		Button btnExp = new Button( parent, SWT.PUSH );

+		btnExp.setText( "..." ); //$NON-NLS-1$

+		btnExp.setToolTipText( "Invoke Expression Builder" ); //$NON-NLS-1$

+		

+		btnExp.addSelectionListener( new SelectionAdapter( ) {

+		

+			public void widgetSelected( SelectionEvent event )

+			{

+				openExpression( txtText );

+			}

+		} );

+	}

+	

+	private void openExpression( Text textControl )

+	{

+		String oldValue = textControl.getText( );

+		

+		ExpressionBuilder eb = new ExpressionBuilder( textControl.getShell( ), oldValue );

+		eb.setExpressionProvier( new ExpressionProvider( textItem.getModelHandle( ) ) );

+		

+		String result = oldValue;

+		

+		if ( eb.open( ) == Window.OK )

+		{

+			result = eb.getResult( );

+		}

+		

+		if ( !oldValue.equals( result ) )

+		{

+			textControl.setText( result );

+		}

+	}

+}</pre>

+

+<p>In the above code, we create another <code>RotatedTextEditor2</code> class to overwrite the UI creation 

+logic for the new expression button.</p>

+

+<p>Now let's look at the property page. For <code>General</code> property page, we

+also want to add an expression button besides the text control. Here is

+the change for the <code>RotatedTextGeneralPage</code> class:</p>

+

+<pre>public class RotatedTextGeneralPage extends AttributesUtil.PageWrapper

+{

+	//.........

+	

+	public void buildUI( Composite parent )

+	{

+		if ( toolkit == null )

+		{

+			toolkit = new FormToolkit( Display.getCurrent( ) );

+			toolkit.setBorderStyle( SWT.NULL );

+		}

+		

+		Control[] children = parent.getChildren( );

+		

+		if ( children != null &amp;&amp; children.length &gt; 0 )

+		{

+			contentpane = (Composite) children[children.length - 1];

+			

+			<span style="font-weight: bold;">GridLayout layout = new GridLayout( 3, false );</span>

+			layout.marginLeft = 8;

+			layout.verticalSpacing = 12;

+			contentpane.setLayout( layout );

+			

+			toolkit.createLabel( contentpane, "Text Content:" ); //$NON-NLS-1$

+			txtText = toolkit.createText( contentpane, "" ); //$NON-NLS-1$

+			GridData gd = new GridData( );

+			gd.widthHint = 200;

+			

+			txtText.setLayoutData( gd );

+			txtText.addFocusListener( new FocusAdapter( ) {

+			

+				public void focusLost( org.eclipse.swt.events.FocusEvent e )

+				{

+					updateModel( RotatedTextItem.TEXT_PROP );

+				};

+			} );

+			

+			<span style="font-weight: bold;">Button btnExp = toolkit.createButton( contentpane, "...", SWT.PUSH ); //$NON-NLS-1$</span>

+			<span style="font-weight: bold;">btnExp.setToolTipText( "Invoke Expression Builder" ); //$NON-NLS-1$</span>

+			<span style="font-weight: bold;">btnExp.addSelectionListener( new SelectionAdapter( ) {</span>

+			

+			<span style="font-weight: bold;">	public void widgetSelected( SelectionEvent e )</span>

+			<span style="font-weight: bold;">	{</span>

+			<span style="font-weight: bold;">		openExpression( txtText );</span>

+			<span style="font-weight: bold;">	}</span>

+			<span style="font-weight: bold;">} );</span>

+			

+			toolkit.createLabel( contentpane, "Rotation Angle:" ); //$NON-NLS-1$

+			txtAngle = toolkit.createText( contentpane, "" ); //$NON-NLS-1$

+			gd = new GridData( );

+			gd.widthHint = 200;

+			<span style="font-weight: bold;">gd.horizontalSpan = 2;</span>

+			txtAngle.setLayoutData( gd );

+			txtAngle.addFocusListener( new FocusAdapter( ) {

+			

+				public void focusLost( org.eclipse.swt.events.FocusEvent e )

+				{

+					updateModel( RotatedTextItem.ROTATION_ANGLE_PROP );

+				};

+			} );

+		}

+	}

+	

+	private void openExpression( Text textControl )

+	{

+		RotatedTextItem item = getItem( );

+		

+		if ( item != null )

+		{

+			String oldValue = textControl.getText( );

+			ExpressionBuilder eb = new ExpressionBuilder( textControl.getShell( ), oldValue );

+			eb.setExpressionProvier( new ExpressionProvider( item.getModelHandle( ) ) );

+			

+			String result = oldValue;

+			

+			if ( eb.open( ) == Window.OK )

+			{

+				result = eb.getResult( );

+			}

+			

+			if ( !oldValue.equals( result ) )

+			{

+				textControl.setText( result );

+				updateModel( RotatedTextItem.TEXT_PROP );

+			}

+		}

+	}

+	

+	//..........

+}</pre>

+

+<p>You can focus on the code in bold and the newly added methods only. Now the <code>General</code> property page 

+looks like this:</p>

+

+<img style="width: 713px; height: 284px;" alt="" src="images/property-page-expression-support.png">

+

+<p>OK, time for an integrated test.</p>

+

+<p>Create one Table report item first, bind it with a dataset, insert

+one RotatedText extended report item in the table detail row as well as

+several other Data items.</p>

+

+<img style="width: 628px; height: 309px;" alt="" src="images/test-expression-1.png"><br>

+

+<p>Specify the expression of the RotatedText item as:</p>

+

+<img style="width: 702px; height: 594px;" alt="" src="images/test-expression-2.png">

+

+<p>Now preview it in designer. </p>

+

+<img style="width: 688px; height: 545px;" alt="" src="images/test-expression-3.png">

+

+<p>Voila! The text data now comes from the table bindings and automatically changes per row.</p>

+

+<p>Now change the expression as:</p>

+

+<img style="width: 702px; height: 594px;" alt="" src="images/property-page-extension-4.png"><br>

+

+<p>Preview again:</p>

+

+<img style="width: 727px; height: 366px;" alt="" src="images/property-page-extension-5.png">

+

+<p>Perfect! Now we have successfully created a data-driven custom report item.</p>

+

+</div>

+

+<h2>Summary</h2>

+

+<p>BIRT provides very good extension mechanism, which

+allows user

+to create custom reporting features. This article introduced the techniques that used to

+enhance the feature of custom extended report items for BIRT.</p>

+

+<h2>Resources</h2>

+

+<p>[1] <a href="extension-tutorial-2.zip">The

+complete source code</a>: you can extract and import it into

+Eclipse workspace as a plugin project directly</p>

+

+<p>[2] BIRT Official Site: <a href="http://eclipse.org/birt">http://eclipse.org/birt</a></p>

+

+<p>[3] How to access Eclipse CVS: <a href="http://wiki.eclipse.org/CVS_Howto">http://wiki.eclipse.org/CVS_Howto</a></p>

+

+<p><br>

+

+<br>

+

+</p>

+

+<br>

+

+</div>

+

+</body>

+</html>