Wednesday, July 15, 2009

Eclipse RCP : Single instance of RCP Application

There are methods of maintaining a single instance of an application. Most often the application creates a lock file and holds on to it during the lifecycle of the application.

A new instance of the application will try to find this file and if it exists, the application will try to show the existing instance to the user, preventing from creating whole new instance of the application. One drawback of this approach will be if the application crashes leaving the lock file. The user has to manually delete the file (or application can try deleting this file )
The same can be achieved via opening a server socket when the application is opened. Now how do we do it for a simple RCP Application.



/*
* (non-Javadoc)
* @see org.eclipse.equinox.app.IApplication#start(org.eclipse.equinox.app.IApplicationContext)
*/
public Object start(final IApplicationContext context)
{
if (!checkServerPort())
{
try
{
final ServerSocket server = new ServerSocket(5000);

serverThread = new Thread(new Runnable()
{
@Override
public void run()
{
boolean socketClosed = false;
while (!socketClosed)
{
if (server.isClosed())
{
socketClosed = true;
}
else
{
try
{
Socket client = server.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(client
.getInputStream()));
new UIJob("Reopening the app...")
{
@Override
public IStatus runInUIThread(IProgressMonitor monitor)
{
advisor.getWindowAdvisor().handleEvent(null);
return null;
}
}.schedule();

/**
* if (SINGLE_INSTANCE_SHARED_KEY.trim().equals(message.trim())) {
* System.out.println("Receiving message"); }
*/
in.close();
client.close();
}
catch (IOException ex)
{
socketClosed = false;
}
}
}
}
});
serverThread.start();
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
else
{
try
{
Socket clientSocket = new Socket(InetAddress.getLocalHost(), 5000);
OutputStream out = clientSocket.getOutputStream();
out.write(SINGLE_INSTANCE_SHARED_KEY.getBytes());
out.close();
clientSocket.close();
}
catch (UnknownHostException ex)
{
ex.printStackTrace();
}
catch (IOException ex)
{
ex.printStackTrace();
}
return IApplication.EXIT_OK;
}
final Display display = PlatformUI.createDisplay();
try
{

final int returnCode = PlatformUI.createAndRunWorkbench(display, advisor);
if (returnCode == PlatformUI.RETURN_RESTART)
{
return IApplication.EXIT_RESTART;
}
return IApplication.EXIT_OK;
}
finally
{
display.dispose();
}
}

private boolean checkServerPort()
{
try
{
new Socket("localhost", 5000);
}
catch (IOException ex)
{
return false;
}
return true;
}

/*
* (non-Javadoc)
* @see org.eclipse.equinox.app.IApplication#stop()
*/
public void stop()
{
final IWorkbench workbench = PlatformUI.getWorkbench();
if (workbench == null)
{
return;
}
final Display display = workbench.getDisplay();
display.syncExec(new Runnable()
{
public void run()
{
if (!display.isDisposed())
{
workbench.close();
}
}
});
}



Now when the application is launched for the first time, it will open a server socket 5000 and will start listening on it. The next instance of the application will check the server port and if it is used already, it will create a client for that server socket and send a message to the server.

When the server receives any message from the client, it will reopen the instance which created the server socket.

The handleEvent method on the WorkbenchWindowAdvisor will have to have the following code..


/*
* (non-Javadoc)
* @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event)
*/
@Override
public void handleEvent(Event event)
{
Shell workbenchWindowShell = getWindowConfigurer().getWindow().getShell();
workbenchWindowShell.setVisible(true);
workbenchWindowShell.setActive();
workbenchWindowShell.setFocus();
workbenchWindowShell.setMinimized(false);
}


Now enjoy the single instance of the RCP application. This approach can be used for any java application.

Friday, July 10, 2009

Eclipse RCP : Redirecting eclipse errors to log4j

Its sometimes tedious to find the real exception occured when using an eclipse based application. Since it goes under the workspace/.metadata/.log file. It takes a while to figure this out. But most of the desktop application do write to a log file of its own. So when using eclipse using two log files to know about the application is kind of not nice.

But still eclipse provides a way to listen to the log messages of its own. So if the application uses its own logging , it can listen to the eclipse log and add it to its own logger. Here we will look at connecting eclipse log messages to the log4j logger.

Eclipse Platform provides a method to add listeners Platform.addLogListener(ILogListener);

So how do we add the listener to the Platform. In the Activator of the application plugin's start method, add the following.


public class Activator extends AbstractUIPlugin {

private ILogListener listener;


@Override
public void start(final BundleContext context) throws Exception {
super.start(context);
plugin = this;
listener = new Listener();
Platform.addLogListener(listener);
}

@Override
public void stop(final BundleContext context) throws Exception {
Platform.removeLogListener(listener);
listener = null;
plugin = null;
super.stop(context);
}

}


Now every error/warning eclipse logs to its .log file under .metadata directory will be sent to the log listener. How does the log listener handle the events from the eclipse?


package com.logging;
public class Listener implements ILogListener {
private static final Logger LOGGER = Logger.getLogger(Listener.class.getName());

@Override
public void logging(final IStatus status, final String plugin) {
if (status.getSeverity() == IStatus.WARNING) {
if (status.getException() == null) {
LOGGER.warn(status.getMessage());
} else {
LOGGER.warn(status.getMessage() + status.getException());
}
} else if (status.getSeverity() == IStatus.ERROR) {
if (status.getException() == null) {
LOGGER.error(status.getMessage());
} else {
LOGGER.error(status.getMessage()+status.getException());
}
}
}
}


Now all the eclipse errors and warnings will go to the logger which is configured for Listener.class.getName().

To get all the log messages from the eclipse. the log4j.properties

log4j.category.com.logging.Listener=all

This will log all the errors and warnings from the eclipse application to the log4j's log file or console as configured.

Monday, July 6, 2009

Eclipse RAP : Custom Widget development

It took a while to understand and create a custom widget but finally i could do it. I wanted to create a custom widget to draw sequence of svg graphics. I followed the Custom widget development at the RAP help site. It helped me to get a good start but since i was trying to do SVG it didnt really work at my first attempt..(i know... nothing works at the first attempt).

The SVG support i wanted to create was something like below.



Here is what i did to create a custom widget.

1. Create a new plugin project and name it like com.xyz.project.mywidget
This is not mandatory but its good to call the plugin as the widget name.

2. Create a package com.xyz.project.mywidget
This package will contain the server side, client side and API resource classes. Just follow as explained in the RAP help.

3. Create the server side Widget class , i wanted a widget to send events to the server and also behave as a selection provider when a image is clicked. So i extended SWT Composite and implemented ISelectionProvider.


public class MyWidget extends Composite implements ISelectionProvider {

/**
* for showing multiple images on the widget
*/
private String[] images;

/**
* To get the selection back from the client.
*/
int selectionIndex;

/**
* Listeners
*/
private ListenerList listeners = new ListenerList();

/**
* Selection.
*/
private ISelection selection;

private String selectedImage;

public MyWidget(Composite parent) {
super(parent, SWT.NONE);
}

public String[] getImages() {
return images;
}

public void setImages(String[] parts) {
this.images = parts;
}

@Override
public void setLayout(Layout layout) {
super.setLayout(new FillLayout());
}

public String getSelectedImage() {
return selectedImage;
}

public void setSelectedImage(String selectedPart) {
this.selectedImage = selectedPart;
if (selectedPart != null) {
selectionIndex = Integer.parseInt(selectedPart);
setSelection(new StructuredSelection(images[selectionIndex / 2]));
}
}

public void addSelectionChangedListener(ISelectionChangedListener listener) {
listeners.add(listener);
}

public ISelection getSelection() {
return selection;
}

public void removeSelectionChangedListener(
ISelectionChangedListener listener) {
listeners.remove(listener);
}

public void setSelection(ISelection selection) {
this.selection = selection;
Object[] list = listeners.getListeners();
for (int i = 0; i < list.length; i++) {
((ISelectionChangedListener) list[i])
.selectionChanged(new SelectionChangedEvent(this, selection));
}
}
}



Follow this post for how to implement ISelectionProvider - http://random-eclipse-tips.blogspot.com/2009/02/eclipse-how-to-implement.html

Now that the server side, widget is ready, we need the client side widget code, the qooxdoo javascript code. Read the qooxdoo explanation in RAP custom widget tutorial and follow it, it gives you a basic idea of what to do.


    qx.Class.define( "com.xyz.project.MyWidget", {
extend: qx.ui.layout.CanvasLayout,

construct: function( id ) {
this.base( arguments );
this.setHtmlAttribute("id",id);
this._id = id;
},

properties : {
images : {
init : "",
apply : "load"
},

members : {
load : function() {
var current = this.getParts()[0];
if( current != null && current != "" ) {
qx.ui.core.Widget.flushGlobalQueues();
var id = document.getElementById( this._id );
var wm = org.eclipse.swt.WidgetManager.getInstance();
var designWidgetId = wm.findIdByWidget( this);
var newParts = this.getParts();
var current = null;
var image = 1;
if(this.__paper == null ) {
this.__paper = Raphael(id, newParts.length*100, 480);
startx = 10;
starty = 10;
width = 100;
height=25;
curve=10;
var part=0, colorhue = .6 || Math.random(),
color = "hsb(" + [colorhue, 1, .75] + ")";
var selectionColor = "#d54";
var currentSelection = null;
var detail = this.__paper.rect(startx, starty+height+5, 200, 100, 5).attr({fill: "#d54" , stroke: "#474", "stroke-width": 2}).hide();
label0 = this.__paper.text(startx+20,starty+height+10,"ID : ").hide();
label1 = this.__paper.text(startx+20,starty+height+25,"Other : ").hide();
for (part=0;part<newParts.length;part++)
{
(function(paper,part,type){
var c = "#ccc";
if(image==0) {
if(type == "image1") {
paper.image("./Part_icon_image1.png", startx, starty, width, height);
}else if(type == "image2") {
image("./Part_icon_image2.png", startx, starty, width, height);
}
}
var label = paper.text(startx+25,starty+10,part).hide();
paper.rect(startx,starty,width,height,curve).attr({stroke: c, fill: c, "fill-opacity": .4}).
mouseover(
function(){
this.animate({"fill-opacity": .75}, 500);
detail.show().animate({x: 10+(width*part), y: starty+height+5}, 200 );
label0.attr({text: "ID : "+newParts[part]}).show().animate({x: 40+(width*part), y: starty+height+10}, 200);
label1.attr({text: "Other : "}).show().animate({x: 40+(width*part), y: starty+height+25}, 200);
paper.safari();
}).
mouseout(
function(){
this.animate({"fill-opacity": .25}, 500);
detail.hide();
label0.hide();
label1.hide();
paper.safari();
}).
click(
function(){
if(currentSelection != null ) {
currentSelection.attr({fill:c});
}
currentSelection = this;
currentSelection.attr({fill:selectionColor});
var req = org.eclipse.swt.Request.getInstance();
req.addParameter( designWidgetId +".selectedImage", part );
req.send();
});
})(this.__paper,part,newParts[part]);
startx+=width;
}
}}

},
}
});


Now that, whenever the setImages method is called on the server side widget, the client has to update the browser. But how does it interact, the server side widget and the client side widget get connected through two main classes, the API resource class and the LCA class. There are different ways of defining the LCA class but here i would just follow the simple approach as explained in the RAP help.

First to create the API resource, we need to create a IResource implementation , In our case, MyWidgetResource as follows,


public class DesignWidgetResource implements IResource {
public String getCharset() {
return HTML.CHARSET_NAME_ISO_8859_1;
}

public ClassLoader getLoader() {
return this.getClass().getClassLoader();
}

public RegisterOptions getOptions() {
return RegisterOptions.VERSION_AND_COMPRESS;
}

public String getLocation() {
return "com/xyz/project/mywidget/MyWidget.js";
}

public boolean isJSLibrary() {
return true;
}

public boolean isExternal() {
return false;
}
}

you can just copy and paste the above code and replace the getLocation method to your .js file. Now the LCA, this can either be written in a pre defined package or can be done using getAdapter method in the widget class. I will follow the pre defined package which com.xyz.project.mywidget.internal.mywidgetkit and create MyWidgetLCA.java




public class MyWidgetLCA extends AbstractWidgetLCA {

private static final String PARAM_SELECTED = "selectedImage";

private static final String PROP_IMAGES = "images";

private static final String JS_PROP_IMAGE = "images";

public void preserveValues(final Widget widget) {
ControlLCAUtil.preserveValues((Control) widget);
IWidgetAdapter adapter = WidgetUtil.getAdapter(widget);
adapter.preserve(PROP_IMAGES, ((MyWidget) widget).getImages());
// adapter.preserve(PROP_MOVE_RIGHT, ((DesignWidget) widget)
// .getMoveRight());
// only needed for custom variants (theming)
WidgetLCAUtil.preserveCustomVariant(widget);
}

/*
* Read the parameters transfered from the client
*/
public void readData(final Widget widget) {
MyWidget myWidget = (MyWidget) widget;
String location = WidgetLCAUtil.readPropertyValue(myWidget,
PARAM_SELECTED);
myWidget.setSelectedImage(location);
}

/*
* Initial creation procedure of the widget
*/
public void renderInitialization(final Widget widget) throws IOException {
JSWriter writer = JSWriter.getWriterFor(widget);
String id = WidgetUtil.getId(widget);
writer.newWidget("com.xyz.project.mywidget.MyWidget",
new Object[] { id });
writer.set("appearance", "composite");
writer.set("overflow", "hidden");
ControlLCAUtil.writeStyleFlags((MyWidget) widget);
}

public void renderChanges(final Widget widget) throws IOException {
MyWidget gmap = (MyWidget) widget;
ControlLCAUtil.writeChanges(gmap);
JSWriter writer = JSWriter.getWriterFor(widget);
writer.set(PROP_IMAGES, JS_PROP_IMAGE, gmap.getImages());
// only needed for custom variants (theming)
WidgetLCAUtil.writeCustomVariant(widget);
}

public void renderDispose(final Widget widget) throws IOException {
JSWriter writer = JSWriter.getWriterFor(widget);
writer.dispose();
}

public void createResetHandlerCalls(String typePoolId) throws IOException {
}

public String getTypePoolId(Widget widget) {
return null;
}

}


Here, the LCA class is the important class which communicates between the server code and client script. The selection on the client sends an event to the server through the LCA in the readData method. The client code will use org.eclipse.swt.Request.getInstance() to send the request to the server when an image is clicked on the Widget.

Now we got the both the resource and LCA ready. we need to create an resources extension point and add the MyWidgetResource classes.
   <extension
point="org.eclipse.rap.ui.resources">
<resource
class="com.xyz.project.mywidget.MyWidgetResource">
</resource>
</extension>


ok..we have a basic setup ready, what is the Raphael stuff on the Qooxdoo javascript. Raphael is a Javascript based SVG library which helps creating different SVG based graphics. For more details on that visit http://raphaeljs.com/. The demos explain a lot about the library. How do we add an external javascript resource into the myWidget implementation.

1. Create a RaphaelAPIResource similar to the one we created for MyWidget as MyWidgetResource.

public class RaphaelAPIResource implements IResource {

private String location;

public String getCharset() {
return HTML.CHARSET_NAME_ISO_8859_1;
}

public ClassLoader getLoader() {
return this.getClass().getClassLoader();
}

public String getLocation() {
location = "http://raphaeljs.com/raphael.js";
return location;
}

public RegisterOptions getOptions() {
return RegisterOptions.VERSION;
}

public boolean isExternal() {
return true;
}

public boolean isJSLibrary() {
return true;
}

}


and add the resources extension to the existing extension point we used as following.
   <extension
point="org.eclipse.rap.ui.resources">
<resource
class="com.biologistics.gd.design.RaphaelAPIResource">
</resource>
<resource
class="com.biologistics.gd.design.DesignWidgetResource">
</resource>
</extension>


Since we are displaying images on the client side , we need the images also as resources sent to the client. we have to create http.registryresources to use aliases for the images. In this case, we are referring to Part_icon_image1.png and Part_icon_image2.png as alias in the javascript.

 <extension
point="org.eclipse.equinox.http.registry.resources">
<resource
alias="/Part_icon_image1.png"
base-name="branding/Part_icon_image1.png">
</resource>
<resource
alias="/Part_icon_image2.png"
base-name="original/Part_icon_image2.png">
</resource>
</extension>


Now we can create a view with MyWidget as a child and setImages on that.

 public void createPartControl(final Composite parent) {
widget = new MyWidget(parent);
widget.setParts(new String[]{"image1","image2"});
getSite().setSelectionProvider(widget);
}


Will be glad to help !

Thursday, March 5, 2009

Quick Tips: Storing preferences and settings per user in a RCP application

Eclipse RCP applications store the prefrences and settings in the workspace. When a RCP application is exported, the workspace is opened within the application folder unless the workspace location is overridden. This will make every user who uses the application to use the same preferences. To enable the preferences and workbench state to be user specific, the default location of the workspace can be configured in the config.ini file as follows.


osgi.instance.area.default=@user.home/eclipseworkspace


Now the preferences and other dialog settings will be written in the user's own workspaces.

Quick Tip: Save and Restore the perspective layout

To restore the perspctive to the last modified state, eclipse stores the workbench state to a file called workbench.xml. To enable the save and restore layout, the IWorkbenchConfigurer must be set in the ApplicationWorkbenchAdvisor.


public void initialize(IWindowConfigurer configurer)
{
configurer.setSaveAndRestore(true);
}

Monday, March 2, 2009

Eclipse : Writing to the console in a RCP Application

When you want to add console message to the eclipse console view, there is no easiest way to do this. The sysout/syserr messages will go on the console where you have the application if you run it with -consoleLog option.

How do we show the messages on the console view eclipse provides. First of all we have to add the console view to the perspective. The console view is part of the org.eclipse.ui.console plugin, so add it to the dependencies if you dont have it already.

Now add the ConsoleView to the perspective in the createInitialLayout method ( the following snippet is written with the RCP Mail example )


public void createInitialLayout(IPageLayout layout) {
String editorArea = layout.getEditorArea();
layout.setEditorAreaVisible(false);

layout.addStandaloneView(NavigationView.ID, false, IPageLayout.LEFT,
0.25f, editorArea);
IFolderLayout folder = layout.createFolder("messages", IPageLayout.TOP,
0.5f, editorArea);
folder.addPlaceholder(View.ID + ":*");
folder.addView(View.ID);

IFolderLayout consoleFolder = layout.createFolder("console",
IPageLayout.BOTTOM, 0.65f, "messages");
consoleFolder.addView(IConsoleConstants.ID_CONSOLE_VIEW);
layout.getViewLayout(NavigationView.ID).setCloseable(false);
}


Now if you run the application you can see the console view at the bottom of the Messages view of the RCP Mail application. But now there are no open consoles on the console view. Now let us take an example, whenever a Messages view is opened we want to write something to the console.


public class OpenViewAction extends Action {

private final IWorkbenchWindow window;
private int instanceNum = 0;
private final String viewId;
MessageConsole messageConsole;

public OpenViewAction(IWorkbenchWindow window, String label, String viewId) {
this.window = window;
this.viewId = viewId;
setText(label);
// The id is used to refer to the action in a menu or toolbar
setId(ICommandIds.CMD_OPEN);
// Associate the action with a pre-defined command, to allow key
// bindings.
setActionDefinitionId(ICommandIds.CMD_OPEN);
setImageDescriptor(com.blog.sample.Activator
.getImageDescriptor("/icons/sample2.gif"));

}

public void run() {
if (window != null) {
try {
int instance = instanceNum++;
window.getActivePage().showView(viewId,
Integer.toString(instance),
IWorkbenchPage.VIEW_ACTIVATE);

messageConsole = getMessageConsole();
MessageConsoleStream msgConsoleStream = messageConsole
.newMessageStream();

ConsolePlugin.getDefault().getConsoleManager().addConsoles(
new IConsole[] { messageConsole });

msgConsoleStream.println(viewId + Integer.toString(instance));

} catch (PartInitException e) {
MessageDialog.openError(window.getShell(), "Error",
"Error opening view:" + e.getMessage());
}
}
}

private MessageConsole getMessageConsole() {
if (messageConsole == null) {
messageConsole = new MessageConsole("RCPMail", null);
ConsolePlugin.getDefault().getConsoleManager().addConsoles(
new IConsole[] { messageConsole });
}

return messageConsole;
}

}


Now every new view opened will write the ViewID to the RCPMail console we created.

Have fun!

Thursday, February 26, 2009

Quick Tip : Adding SWT controls to the Trim Area

Sometimes RCP applications needs to show a label or some swt control on the trim area (outer boundary of the workbench window ) for example in the status area, after the perspective switch bar area and so on.

org.eclipse.ui.menus extension point allows to places command or control on the toolbar , menubar or on the trim area. For this post, if you want to add a control or command to the status area or the perspective switch area, use the following locationURI.

For placing it on the status area - toolbar:org.eclipse.ui.trim.status this will place the control or command on the status area. ( You have to make sure the status area is made visible in the workbenchwindowadvisors preWindowOpen() method as


public void preWindowOpen() {
IWorkbenchWindowConfigurer configurer = getWindowConfigurer();
configurer.setInitialSize(new Point(600, 400));
configurer.setShowCoolBar(true);
configurer.setShowPerspectiveBar(true);
configurer.setShowStatusLine(true);
}


For placing it on the perspective switch area - toolbar:org.eclipse.ui.trim.command2, this will add the control next to the perspective switch area.

The extension point should create a menuContribution with the correct locationURI, and add the controls/commands to a toolbar.


<extension
point="org.eclipse.ui.menus">
<menuContribution
locationURI="toolbar:org.eclipse.ui.trim.command2">
<toolbar
id="com.blog.sample.toolbar1">
<control
class="com.blog.sample.WorkbenchWindowControlContribution1">
</control>
</toolbar>
</menuContribution>
</extension>


Have fun !