Eclipse BIRT : Create your own SWT Emitter
As you probably know several emitters are available to render BIRT reports into PDF, HTML, EXCEL, WORD, etc… But why can’t we integrate them natively into a RCP Application ? I will show you how to do that by creating a SWT Emitter.
1. Requirements
First of all, you need to download and install the BIRT Report Designer All-In-One 2.3.2.1.
To avoid wasting time, I provide you a Report Design example that can be used (SwtEmitter.zip) Obviously, you can create your own report (just add a table that contains some data).
2. Create a simple RCP Application
Before we create the SWT Emitter, we need to build a RCP Application to “display” the report. Let’s be lazy for this part and use the default RCP Client wizard :
- Create a “Plug-in Project”
- Answer “Yes” to the question “Would you like to create a rich client application?” in the “Plug-in Content” settings
- Select the “RCP Application with a View” in the “Template” settings
Once you get your RCP Application generated, you can notice several classes. Try to launch the application by clicking on “Launch an Eclipse Application” in the “Overview” part of the plugin.xml.
If you plan to use the “Classic Models Inc, Sample DataBase” in your BIRT report, you must add the corresponding plug-in in your launch configuration. To do so, open your “Run Configuration”, add the plug-in “org.eclipse.birt.report.data.oda.sampledb” and click on “Add Required Plug-in”.
3. Render the Design Report
Now that the application is built, we can customize it to render the BIRT report. First, copy the provided report directly into the project. Then clean up the code of createPartControl() and setFocus() methods in the View class because we don’t need any TreeViewer. Now create the view content :
- create a ReportEngine which will create a task to render the report
- set up the URL according to the report path
- set the output format of the task in order to render the report in SWT
- submit the parent composite in which we would like to display the report
- render the report in the ViewPart with the given composite
-
public View extends ViewPart {
-
public static final String ID = “com.zenika.emitter.view”;
-
public static final String OUTPUT_FORMAT_SWT = “swt”;
-
public static final String RENDER_OPT_SWT_VIEWER = “swtViewer”;
-
private static final String reportName = “/products.rptdesign”;
-
private IReportEngine engine = null;
-
private EngineConfig config = null;
-
private IReportRunnable design = null;
-
private IRunAndRenderTask task;
-
private IRenderOption options;
-
public void createPartControl(Composite parent) {
-
// We initialize the engine configuration
-
config = new EngineConfig();
-
IReportEngineFactory factory = (IReportEngineFactory) Platform
-
.createFactoryObject(IReportEngineFactory.EXTENSION_REPORT_ENGINE_FACTORY);
-
// We create the report engine
-
engine = factory.createReportEngine(config);
-
// We get the right URL for our report design
-
URL reportUrl = FileLocator.find(Activator.getDefault().getBundle(),
-
new Path(reportName), null);
-
// We open the Report Design from the url
-
try {
-
design = engine.openReportDesign(reportUrl.openStream());
-
} catch (EngineException e) {
-
e.printStackTrace();
-
} catch (IOException e) {
-
// TODO Auto-generated catch block
-
e.printStackTrace();
-
}
-
// We create a task to run and render the report design
-
task = engine.createRunAndRenderTask(design);
-
// We set the options
-
options = new RenderOption();
-
/*
-
* It’s very important to set the output format declared in the
-
* extension org.eclipse.birt.report.engine.emitters in the plugin.xml.
-
*/
-
options.setOutputFormat(OUTPUT_FORMAT_SWT);
-
/*
-
* We need to give a composite in order to retrieve it in our swt
-
* emitter.< /div>
-
*/
-
options.setOption(View.RENDER_OPT_SWT_VIEWER, parent);
-
task.setRenderOption(options);
-
// We run and render the task
-
try {
-
task.run();
-
} catch (EngineException e) {
-
e.printStackTrace();
-
}
-
// We close the task and destroy the engine once we have finished
-
task.close();
-
engine.destroy();
-
}
-
public void setFocus() {
-
}
-
}
4. Declare a BIRT Emitter
It’s very easy to create/declare a new emitter in BIRT, we just need to add an extension in the plugin.xml file.
- Open your plugin.xml file and in the “Dependencies” tab, add the “org.eclipse.birt.report.engine” plug-in in the “Required Plug-ins” section.
- Create a class named “SwtEmitter” which extends the class ContentEmitterAdapter : this class will be in charge of rendering the report.
- In the “Extensions” tab, contribute to the extension point “org.eclipse.birt.report.engine.emitters” and create an “emitter” element.
- Specify the fully qualified name of your SWT Emitter in the “class” field.
- In the “format” field, type “swt”. The value of this field must match the argument of
options.setOutputFormat("swt")
in the View class. So when we set the output format of the task in the View class, it will use that new SWT Emitter class to render the report (instead of HTML or PDF, …). - Finally in the “mimeType” field, type “text/html”.
5. Creation of the SWT Emitter
Let’s now focus on the SWT Emitter class, which will render our report as a SWT component. To understand how an emitter works, we need to think that we are parsing an XML file (do not forget that BIRT Report Design and Document are XML files). By extending the ContentEmitterAdapter class, our class automatically benefits from many methods. These methods are triggered during the report rendering phase (like a SAX Parsing). That’s why we get methods such as :
startTable(ITableContent table)
andendRow(IRowContent row)
startRow(IRowContent row)
andendTable(ITableContent table)
startCell(ICellContent cell)
andendCell(ICellContent cell)
If the report contains a table, the rendering phase will trigger a startTable(ITableContent table)
and a endRow(IRowContent row)
events. So, to render a report containing a table, we will overwrite these methods (those concerning the table, rows, cells and the text element contained in a cell).
These first level features are provided by overwritting the following methods :
initialize(IEmitterServices)
: retrieve the parent composite given by the View classstartTable(ITableContent table)
: create a new SWT Table from parent compositestartRow(IRowContent row)
: create a new TableItem for each parsed row and assess if it is the header rowstartCell(ICellContent cell)
: create a new TableColumn for each cell, only if we are parsing a header row and we got that information from the previous method and we increment a cell countstartText(ITextContent)
: set the text contained in the cell into the current TableItem or current TableColumn in the right cell according to the cell count.
-
/*
-
* The main composite submitted by the view
-
*/
-
private Composite mainComposite;
-
/*
-
* The current Table we are processing
-
*/
-
private Table currentTable;
-
/*
-
* The current TableItem we are processing
-
*/
-
private TableItem currentTableItem;
-
/*
-
* The current TableColumn we are processing
-
*/
-
private TableColumn currentColumn;
-
/*
-
* The index of the cell we are processing
-
*/
-
private int cellIndex = 0;
-
/*
-
* Boolean to know if we are building the header of the Table
-
*/
-
private boolean header = false;
-
@Override
-
public void initialize(IEmitterServices service) {
-
super.initialize(service);
-
// Important : we retrieve the composite from the View class
-
mainComposite = (Composite) service.getRenderOption().getOption(
-
View.RENDER_OPT_SWT_VIEWER);
-
}
-
@Override
-
public void startTable(ITableContent table) {
-
// We create the new Table from the current element
-
currentTable = new Table(mainComposite, SWT.BORDER);
-
// We want to see lines and headers
-
currentTable.setLinesVisible(true);
-
currentTable.setHeaderVisible(true);
-
}
-
@Override
-
public void endTable(ITableContent table) {
-
// We resize the table inside the view
-
resizeTable();
-
}
-
@Override
-
public void startCell(ICellContent cell) {
-
if (header) {
-
// If we have a header, we create a new TableColumn
< /li>
-
currentColumn = new TableColumn(currentTable, SWT.BORDER);
-
}
-
}
-
@Override
-
public void endCell(ICellContent cell) {
-
// Once we have set the cell content we increment the index
-
cellIndex++;
-
}
-
@Override
-
public void startRow(IRowContent row) {
-
// Everytime we begin to parse a row, we reset the cellIndex
-
cellIndex = 0;
-
// We retrieve the Band Type of the row to know what type of row we are
-
// processing
-
int bandType = row.getBand().getBandType();
-
// Header of the table
-
if (bandType == IBandContent.BAND_HEADER) {
-
// We set the header attribute to true, we will use it to create the
-
// TableColumn in the startRow method
-
header = true;
-
}
-
// Detail Row of the table or Footer of the table
-
else if (bandType == IBandContent.BAND_DETAIL
-
|| bandType == IBandContent.BAND_FOOTER) {
-
// We create a new TableItem every time we start to parse a row
-
currentT ableItem = new TableItem(currentTable, SWT.NONE);
-
// We retrieve the background color of the row
-
String birtColor = row.getStyle().getBackgroundColor();
-
// We get the RGB color from the BIRT Color
-
RGB rgb = computeColor(birtColor);
-
// We set it onto the background of the TableItem
-
if (rgb != null) {
-
currentTableItem.setBackground(new Color(currentTable
-
.getDisplay(), rgb));
-
}
-
}
-
}
-
@Override
-
public void endRow(IRowContent row) {
-
// Once we have finished the parse of the header, we reset the header
-
// attribute
-
if (header) {
-
header = false;
-
}
-
}
-
@Override
-
public void startText(ITextContent text) {
-
if (header) {
-
// If we are on header row, we set the text of the current
-
// TableColumn
-
currentColumn.setText(text.getText());
-
} else {
-
// If we are a detail or footer row, we set the text on the current
-
// TableItem
-
currentTableItem.setText(cellIndex, text.getText());
-
}
-
}
-
/*
-
* Sets the size of the table to see the columns.
-
*/
-
private void resizeTable() {
-
for (TableColumn column : currentTable.getColumns()) {
-
column.setWidth(200);
-
}
-
currentTable.pack();
-
}
6. Example of processing a style : color of a row
Let’s make our table a little more “appealing” by changing the rows’ background color. To do so, we need to add some more code in the startRow(IRowContent row)
method. The style of a row can be obtained by calling row.getStyle()
; from that style, we can then retrieve several properties such as the background color, the font or the size of the row for example.
Once we have retrieved the row color, we only have to set the background color of the corresponding TableItem. However, when we retrieve a color property from BIRT, we get it in a String format, so we need to create the correct SWT object corresponding to that property. For example, we can have “rgb(233, 233, 233)” or “Blue” for a color, we have to create a SWT Color representing that color.
-
/*
-
* Returns a RGB object from a BIRT color.
-
*
-
* There are two type of formats in BIRT :
-
* – “rgb(233, 233, 233)”
-
* – “Blue”,”Red”,”Yellow”,etc …
-
*
-
* We just process the first format in that method.
-
*
-
*/
-
private RGB computeColor(String birtColor) {
-
int[] rgb = new int[3];
-
if (birtColor != null && birtColor.startsWith(“rgb(“)) {
-
String numbers = birtColor.substring(4, birtColor.length() – 1);
-
String[] split = numbers.split(“,”);
-
for (int i = 0; i < 3; i++) {
-
rgb[i] = Integer.parseInt(split[i].trim());
-
}
-
return new RGB(rgb[0], rgb[1], rgb[2]);
-
}
-
return null;
-
}
Now run your RCP Application again and enjoy the result : you have created a basic working SWT Emitter !
The original Birt Report Design :
In PDF :
In SWT :
Obvioulsy, it’s easy to imagine how powerful that solution can be, especially once embedded into a RAP Application …
You can find all the sources to realise this SWT Emitter in the archive file SwtEmitter.zip.
Quand on a un format bien défini, il est plus sympa d’utiliser des regex que de s’amuser à découper des chaînes :
private RGB computeColorRegex(String birtColor) {
I am trying to run your code, it compiles ok, but when it is running I get the following error:
20/06/2012 12:07:41 PM org.eclipse.birt.report.engine.api.impl.EngineTask handleFatalExceptions
GRAVE: An error happened while running the report. Cause:
org.eclipse.birt.report.engine.api.EngineException: Cant create data engine.
at org.eclipse.birt.report.engine.executor.ExecutionContext.openDataEngine(ExecutionContext.java:888)
at org.eclipse.birt.report.engine.executor.ExecutionContext.getDataEngine(ExecutionContext.java:900)
…
Do you have any ideas to solve this issue?
Thanks
Manuel