Using SWT with JRuby
JRuby is a Java implementation of the Ruby programming language. One of the strengths of a Java implementation is that Java libraries can be used within Ruby code, the Ruby way (and that is cool, as we have seen!); the weaknesses are that this means that the code is a lot slower to execute, and similar to what happens with Jython (the Java implementation of Python) the Java implementation lags behind the “official” version. This is obviously to be expected, and in fairness to the JRuby guys, they are pretty reactive!
This post will describe how to use SWT with JRuby. It is written for Ubuntu, but more than likely very similar on other platforms.
Setup
First, get JRuby: I’ll be using 1.4.0 (which was released last month). Install it in a location that I’ll be calling $JRUBY_HOME
. Add $JRUBY_HOME/bin
into your $PATH
so that you can run the jruby
command:
sebastien@kilkenny:~$ jruby --version jruby 1.4.0 (ruby 1.8.7 patchlevel 174) (2009-11-02 69fbfa3) (Java HotSpot(TM) Client VM 1.6.0_12) [i386-java]
Get SWT from here (this article will be using SWT 3.5.1 for Linux), and extract swt.jar
into $JRUBY_HOME/lib
: that’s a quick way of making the jar available to JRuby.
Once everything is set up, we can begin with a very simple example.
First Quick Example
(I have posted the following example as a snippet on dreamincode at the time of writing, it is still awaiting approval)
require 'java' display = org.eclipse.swt.widgets.Display.new shell = org.eclipse.swt.widgets.Shell.new(display) shell.setSize(800, 600) shell.setText("First Example") shell.setLayout(org.eclipse.swt.layout.RowLayout.new) org.eclipse.swt.widgets.Button.new(shell, \ org.eclipse.swt.SWT::PUSH).setText("Click me!") shell.open # Classic SWT stuff while (!shell.isDisposed) do display.sleep unless display.readAndDispatch end display.dispose
This opens a nice and simple window:
Typical to JRuby, the first thing we do here is require 'java'
to be able to call Java classes from our ruby code. And then, from then on, it is all SWT code: create a Display
class, and with this Display
, create a new Shell
(window), whose size is 800×600. Then set the layout as RowLayout
, and add a Button
. Make the window visible with open
, and the rest of the code is pretty much common SWT code to keep the window open until the application is shut down, at which point, the display is cleared up.
As you can see from above, the only tricky things are:
- the fully-qualified names of the Java classes have to be provided (we’ll see a way of avoiding this in the next example);
- static variables have to be accessed with
::
And Now, For a More Advanced Example
The more advanced example will read the RSS feed from weblogism, and fill in some labels, and put items in a table. Ruby provides a neat rss package, and good example of how to use it can be found here.
One of the things you’ll want to look at is the repetition of the Java packages: this is a bit tedious, so we have to find a way of avoiding this. One way is to call include_class
to include the Java classes by their name. One trick is to list all the classes of a given package in an array, and to include them with an iterator:
%w(Display Shell Label Table TableColumn TableItem).each do |c| include_class "org.eclipse.swt.widgets." + c end
With this knowledge, I can now give you the example:
require 'java' require 'open-uri' require 'rss' include_class "org.eclipse.swt.SWT" # Neat little trick to include several classes from the same package. %w(Display Shell Label Table TableColumn TableItem).each do |c| include_class "org.eclipse.swt.widgets." + c end %w(GridLayout GridData).each do |c| include_class "org.eclipse.swt.layout." + c end # uppercase variable is constant FEED_URL = 'http://www.weblogism.com/rss/' class RssViewer attr_reader :shell, :display def initialize rss = get_messages @display = Display.new @shell = Shell.new(display) @shell.setSize(800, 600) @shell.setText("Second Example") gridlayout = GridLayout.new gridlayout.numColumns = 2 @shell.setLayout(gridlayout) name = Label.new(shell, SWT::NONE) name.setText("Name:") name.setLayoutData(GridData.new(GridData::VERTICAL_ALIGN_END)) Label.new(shell, SWT::NONE).setText(rss.channel.title) Label.new(shell, SWT::NONE).setText("URL:") Label.new(shell, SWT::NONE).setText(rss.channel.link) table = Table.new(shell, SWT::MULTI | SWT::BORDER | SWT::FULL_SELECTION) table.setLinesVisible(true) table.setHeaderVisible(true) gridData = GridData.new(GridData::FILL_BOTH) gridData.horizontalSpan = 2 table.setLayoutData(gridData) # Set the header of columns. columns = %w(Title Date Author) columns.each{ |h| TableColumn.new(table, SWT::NONE).setText(h) } rss.channel.items.each do |i| item = TableItem.new(table, SWT::NONE) item.setText(0, i.title) item.setText(1, i.dc_creator) item.setText(2, i.date.to_s) puts i end # Each column then needs to be packed to display properly for i in 0...columns.size table.getColumn(i).pack() end end def show @shell.open while (!@shell.isDisposed) do @display.sleep unless @display.readAndDispatch end @display.dispose end def get_messages rss_content = '' # Read URL open(FEED_URL) { |f| rss_content = f.read } # and parse. "false" means "no validation" RSS::Parser.parse(rss_content, false) end end RssViewer.new.show
This simple snippet gives the following window:
Conclusion
The examples above are not that far away from the Java snippets proposed on the SWT web site. This shows how JRuby can be easy to learn if you are already familiar with Java – and you get to play with a few syntactic niceties like the iterators that ruby brings in.
However, it is true that performance can still be an issue on some big apps, but hopefully this will improve in the coming months.