Tuesday 16 January 2007

Groovy in System Tray

As of Java 1.6 there is a cool feature available - System Tray. Actually, System Trasy plus nice notifications which look like a balloon on Windows. Now you can put Duke next to your favorite icons, if you like!

An example source code is available on the java docs page. Indeed, it is an example and even not a fully working one. I am not going to rewrite it - I believe Sun it works. Instead, I will try making the code more ... groovy ;-)

Here it is, System Tray example in Groovy:

import java.awt.Image
import java.awt.AWTException
import java.awt.MenuItem
import java.awt.PopupMenu
import java.awt.SystemTray
import java.awt.Toolkit
import java.awt.TrayIcon

def trayIcon = null

def exit = { e ->
println "Exiting ..."
System.exit(0)
}

def showMessage = { e ->
trayIcon.displayMessage("Action Event",
"An Action Event Has Been Peformed!",
TrayIcon.MessageType.INFO)
}

if (SystemTray.isSupported()) {
def tray = SystemTray.getSystemTray()

def image = Toolkit.getDefaultToolkit().
getImage("duke16.gif")

def popup = new PopupMenu()
popup.add(new MenuItem(label:"Exit", actionPerformed:exit))

trayIcon = new TrayIcon(image:image, tooltip:"Tray Demo",
popup:popup, imageAutoSize:true,
actionPerformed:showMessage)
try {
tray.add(trayIcon)
} catch (AWTException e) {
println e
}
} else {
prinltn "System Tray not supported"
}


As you can see it is possible to make Duke shout: Groovy did it! However, the code looks more like java. And what is worse, I am afraid we can't make it more groovy, using Swing Builder (the popup menu could be 'built'). The Tray functionality sits under java.awt package, which the Swing Builder does not support I think (well, it did not work for me). Luckily, we can still use Groovy's closures instead of Action Listeners.

Nevertheless, it works fine and - what is worth stressing here - it is platform independent! I have run the example successfully on GNOME, KDE and Win Xp desktops. Here are my screenshots:


GNOME

KDE

Windows Xp

And two Dukes, if you would like to run the example on your desktop



Oh, and one more thing. If you are curious what other dynamic languages support System Tray, my friend, Andrzej, shows how to do this in Iron Python. See his article here.

Thursday 11 January 2007

Groovy at work - developing a GUI build tool

Suppose you have a complicated Ant build which builds your latest code from SVN. It is very complicated and running it requires a set of parameters to be passed into it. This involves creating and maintaining a set of properties files and probably also batch files which start the build as you don't want to manually type in all props every time. Also, source files need to be updated before running the build. Does it not sound like a tough manual and repeatable work?

How about a simple GUI where you could type in (or even choose from drop downs, if possible) required properties and run the build by clicking one button? Yes, why not, but we don't want to waste two weeks or more writing such a tool, in Swing for example. Ok, how about Groovy and its brilliant features, like SwingBuilder, AntBuilder and closures? Hmm, that sounds much better!

Recently I have developed such a GUI build tool using Groovy. It took me 2 days to code it. Now guess what features it has:
  • full checkout from SVN to a local directory
  • project update before each build
  • running the old Ant build, written in XML
  • intercepting build output for error messages
  • reading tool properties from an external XML config
All in about 350 lines of groovy code! Most of the code are SwingBuilder closures where I am building the GUI and AntBuilder doing the build job. The most interesting parts however are:

Defining external svn ant task
To get that to work some additional jars are needed. Assuming they are under local lib directory, the svn ant task can be defined like that:

def PATH = "task.path"
ant.path(id:PATH) {
ant.pathelement(location:"lib/ganymed.jar")
ant.pathelement(location:"lib/svnkit.jar")
ant.pathelement(location:"lib/svnant.jar")
ant.pathelement(location:"lib/svnClientAdapter.jar")
ant.pathelement(location:"lib/svnkit-javahl.jar")
}
ant.taskdef(
resource:"org/tigris/subversion/svnant/svnantlib.xml",
classpathref:PATH)

Accessing SVN
Now, having the svn ant task defined, accessing svn (checkout, update etc.) is as easy as:

def svnCall = { task ->
ant.svn(username:USER, password:PASS) {
task()
}
}
where the task can be svn checkout:

svnCall {
ant.checkout(url:url, destpath:dest)
}
or svn update:

svnCall {
ant.update(dir:dest)
}

As you can see there is no code duplication. The ant.svn call is common for all svn tasks. I am passing a closure as a parameter into it, which then can call ant.checkout or ant.update. Closures - it's brilliant, isn't it?

Running an external Ant build and reading output info.
The XML ant build needs to be started from command line (no, we are not going to rewrite it using Gant, not yet ;-) ). How do we do this in Grooovy?

def build = "cmd /c ant -f build.xml
-propertyfile build.properties target".execute()
The build variable is actually a Process instance, so we can now intercept the in and err streams:

build.in.eachLine { line ->
//consume each line
}

build.err.eachLine { line ->
//consume each line
}

Why intercepting the err stream? Well, if the build fails Ant writes BUILD FAILED + the error to that stream. Then I can show an info message using JOptionPane as MsgBOX:

import javax.swing.JOptionPane as MsgBOX
...
MsgBOX.showMessageDialog(gui, message, title,
MsgBOX.INFORMATION_MESSAGE)

The build tool
To give you an idea what the build tool looks like, here is a screenshot (I have removed real labels/data) :

Title borders, drop downs, text fields, labels and box layout - all in 2 days? Yep, plus event handling for all the combo boxes, which make up some sort of hierarchy, actually. This makes the tool a bit complicated, but not the code! Event handling is as easy as clicking a button (actionPerformed as a closure):

def comboBox = swing.comboBox(items:items,
actionPerformed:action)
and the hierarchy is read from an external XML file, using Groovy's XML parser:

def XML = new File(xmlFile).getText()
root = new XmlParser().parseText(XML)
Nice and simple, isn't it?

Summary
It was such a pleasure writing the tool, in no time actually. I have to admit that it was my first bigger piece of Groovy code, which now really works fine in my company! What else can I say - thank you, Groovy! You saved me at least a week of swing coding in Java!