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.

 
---

Comment

your_ip_is_blacklisted_by sbl.spamhaus.org

---