Qt4 in Lisp?!

NOTE: Updated 2008-12-12 to reflect Clojure's new syntax.

Imagine Qt4 bindings for Lisp that are:

  • Officially supported
  • Thoroughly documented, with tons of examples
  • Cross-platform
  • Consistently up-to-date

Keep dreaming, right? Your first thought might be to check CLiki for some Common Lisp bindings. There is indeed a Qt project there. Except at a glance, it's listed as working only for CMUCL. And the last update was 2003. And the download link is 404'd. Oops!

But there's a link to a some QT4 bindings, CFFI style. Much more promising. Except... last update: March 2007. "Project status: dead." Never mind. There's also this and maybe a few others you could Google up, but I haven't tried them and I'm not going to.

Kudos to the people who wrote the above; providing bindings for all of Qt4 is a daunting task and it's no wonder it's not done for Common Lisp. Doesn't change the reality of the fact that Qt4 (like many other libraries) is a no-go in Common Lisp. There's nothing stopping Common Lisp from having great libraries except lack of manpower; but lack of manpower and lack of mature, tested, up-to-date libraries is still a real problem when you want to write an application today.

Enter Clojure

Luckily, it turns out there ARE Qt4 bindings that you can use from Lisp. That Lisp is Clojure, and the bindings are Qt Jambi. Many people are excited about Clojure nowadays, and this is one big reason (of many). It's hard to beat Java when it comes to libraries, and Clojure gets them all for free.

Let's try to write one of the official Qt4 examples / tutorials in Clojure and see how it goes. Keep in mind that I'm still learning Clojure, so this may be far from ideal, but it should give you a bit of a taste of Clojure and Qt Jambi if you've never seen it before.

To spoil the ending a bit: It works! In Vista and Linux (I don't have an OS X box to try, but it'll probably work there too):

Clojure Qt4 demo Clojure Qt4 demo

Setup

To set up Clojure, follow the directions on the Clojure wiki. Be sure to get the SVN version of Clojure, because it's being developed very rapidly and is often updated many times per week. You can also check out some recent posts on Bill Clementson's blog, and this movie on LispCast which walks through setting up Clojure and Emacs/SLIME.

To make Qt Jambi available to Clojure, you may have some options. If you use Linux, your distro may make this available via your package manager. Otherwise go to the Qt download site and download "Qt for Java" for your OS. This is a freaking huge download, but it includes all the documentation and source code and plugins; all you need out of it are a couple of JAR files. There are two JARs you need, one cross-OS and one specific to your OS.

You have to put these JARs on your CLASSPATH so Clojure can find them. There are lots of ways to do this; one way is on the commandline:

java -cp qt-jambi-4.4.3_01.jar:qtjambi-linux32-gcc-4.4.3_01.jar:clojure-lang-1.0-SNAPSHOT.jar clojure.lang.Repl

There's also add-classpath in Clojure, which lets you edit the classpath while Clojure is running. I was unable to get this to work with Qt Jambi, but I didn't try very hard. In reality I just use a shell script so I can dump a bunch of JARs into a directory, and they will all be added to CLASSPATH automatically when I start Clojure.

That's it. Installing libraries in Clojure couldn't be much easier.

The Code

Here's the complete example code for download. Let's walk through it a bit. First we have to import the Qt Jambi apps into Clojure. Qt Jambi is on our CLASSPATH and available to Clojure, but we still have to explicitly import the bits of it we want:

(ns clojure-qt4-demo
  (:import (com.trolltech.qt.gui QApplication QPushButton QFont QFont$Weight)
           (com.trolltech.qt.core QCoreApplication)))

ns creates a new namespace, and provides convenience syntax to import Java classes at the same time. It can also import other Clojure files or Clojure libraries, among other things.

Note immediately how this is nicer than Java. In Java you would be writing something like

import com.trolltech.qt.gui.QApplication;
import com.trolltech.qt.gui.QPushButton;
import com.trolltech.qt.gui.QFont;
...

And so on, over and over. If you're lucky you have a bloated IDE to type those things for you. In Clojure, macros let us factor out the repetition; we name the big long path once, and pluck out the things we want. The other option in Java is to import com.trolltech.qt.gui.*, which pollutes your namespace unnecessarily; Clojure lets us succinctly take just what we want.

One thing I couldn't find documentation for is the way to import a static inner subclass of another class. To do this you have to use syntax like SomeClass$SomeSubClass. My first thought was QFont/Weight or QFont.Weight, but those don't work.

If you're typing all of this at a REPL, you now have to do:

(in-ns 'clojure-qt4-demo)

to switch to the namespace we just created. Next we set up some convenience functions:

(defn init []
  (QApplication/initialize (make-array String 0)))

In Qt4, the first thing you always do is initialize QApplication. QApplication is essentially a singleton class, and it has to be initialized via QApplication.initialize(), a static method call, before you do anything else. In Clojure this becomes (QApplication/initialize), which says to call the function or static method called "initialize" in the namespace called "QApplication". Static methods in Java become functions in a class-namespace in Clojure.

This function expects an array of Strings, which in a normal app would be commandline parameters. In Clojure we can make a native Java Array of Strings via make-array. I just pass in an empty one because I'm running this from a REPL. Note, a native Java Array of Strings is different from a Clojure persistent array, [ ]. This is a bit nasty, but necessary for Java interop. Most of the time you will be able to get by with [ ]. Next:

(defn exec []
  (QApplication/exec))

This function (again a static method call) is generally the LAST thing you do in a Qt application. It starts displaying widgets and fires up the event loop.

A gotcha here is that certain things must happen in a certain order. A Qt app looks something like this:

  • QApplication.initialize()
  • Initialize and set up all of your widgets etc.
  • QApplication.exec()
  • Either the program exits, or go to step 1 to start over.

For example if you try to exec() before you initialize(), it does nothing at all. If you call initialize() more than once, it dies with a RuntimeException. When I was playing around with this at the REPL, it was very common that I called initialize multiple times by mistake. It's often safe to swallow the exception though.

All of that bookkeeping really wants to be made into a macro. Here's a simple macro:

(defmacro qt4 [& rest]
  `(do
     (try (init) (catch RuntimeException e# (println e#)))
     ~@rest
     (exec)))

This kind of thing lets you avoid the insanity of Java, which forces you to re-type exception-handling code and other boilerplate code over and over and over. You can abstract that all away in Clojure.

Macros in Clojure are much like Common Lisp macros. ~@ is a splicing-unquote, like ,@ in Common Lisp. Another interesting thing here is e#, which is a shorthand way to create a gensym. It creates a unique symbol; this prevents our macro from stepping on the toes of any other symbol called "e".

Using this macro we can finish our simple example:

(defn hello-world []
  (qt4
   (let [app (QCoreApplication/instance)
         button (new QPushButton "Go Clojure Go")]
     (.. button clicked (connect app "quit()"))
     (doto button
       (.setFont (new QFont "Deja Vu Sans" 18 (.. QFont$Weight Bold value)))
       (.setWindowTitle "Go Clojure Go")
       (.resize 250 100)
       (.show)))))

EDIT: Updated doto's to reflect Clojure's latest syntax.

The hello-world function is pretty straightforward. It makes a button, sets up an event handler that closes our app when the button is clicked, then sets its title and font, resizes it and shows it. Note how seamless this is. Aside from all the new's and .'s, you could barely tell this was Java. It's all tasty Lisp.

Clojure has a bunch of simple convenience macros that make writing Java less painful, two of which I show here. One of the biggest problems with Java is its extremely verbose and punctuation-heavy C-like syntax. Compare:

button.resize(250, 100);
button.setFont(new QFont("Deja Vu Sans", 18, QFont.Weight.Bold.value()));
button.setWindowTitle("Go Clojure Go");
button.show();

Look how much repetition there is in the code. button.blah, button.blah, button.blah. Repetition is bad. Clojure has the doto macro which says "take this thing, and do a bunch of stuff to it" without having to retype it every time:

(doto button
  (.resize 250 100)
  (.setFont (new QFont "Deja Vu Sans" 18 (.. QFont$Weight Bold value)))
  (.setWindowTitle "Go Clojure Go")
  (.show))

EDIT: Updated doto's to reflect Clojure's latest syntax.

Another macro is .. which says "take these things and put dots between everything", chaining method calls. So instead of QFont.Weight.Bold.value(), you can say (.. QFont$Weight Bold value). Handy.

To run this from a REPL, type

(hello-world)

Verdict

This app of course is a silly toy and just scratches the surface. But in my opinion, Clojure already beats all other Lisps by providing access libraries like this one. This is one of its killer features for me. The fact that you can seamlessly and easily use any Java library from Clojure is pretty amazing.

Why would you want to run Qt4 apps from Lisp anyways? Java (and thus Clojure) already have other GUI frameworks, like Swing and SWT and AWT, so why bother with this? Well, a few reasons...

  • Because I can.
  • Qt4 has a lot of impressive features nowadays. Run this Qt Jambi example to see some of those features.
  • Qt's design may appeal more to you than Swing's, which can be clunky at times.
  • Maybe you want to integrate well into KDE. I first started exploring GUI programming in Clojure when trying to make an icon sit in the system tray, and while Swing can do this, it's a bit painful to get it to look just right. It's trivial to do in Qt4.
  • If I'm forced to write a Qt4 app, I'd much rather write it in a highly interactive and iterative way using Clojure than struggle with a write/compile/debug/recompile/wait-for-hours cycle. At the very least Clojure would be ideal for prototyping.
  • Because I can!

Verdict: Clojure rules. But Java interop is just one reason it rules. Clojure has native Perl/Ruby-like reader support for hashes/arrays/sets/regexes/etc., support for easy and safe concurrency, cross-platform-ness galore, and a much more modern feel than Common Lisp. It's being worked on by some very smart people and the community is vibrant, enthusiastic, and welcoming. It has all the strengths of Java and Common Lisp, and very few of their weaknesses.

October 31, 2008 @ 9:31 AM PDT
Cateogory: Programming
Tags: Lisp, KDE, Clojure

17 Comments

SOme guy
Quoth SOme guy on October 31, 2008 @ 10:14 PM PDT

Sadly clojure has yet to meet other lisps for performance. Having to drop back to java when things get hot is really annoying.

Brett Morgan
Quoth Brett Morgan on October 31, 2008 @ 10:31 PM PDT

@SOme guy: what are you doing that is more expensive in clojure than java? Have you flipped clojure's display reflection flag?

Sohail
Quoth Sohail on November 01, 2008 @ 1:54 AM PDT

The one thing I don't like about Clojure is that Rich has decided that reader macros are only good for the compiler writer, not the user. This increases verbosity, albeit in a tiny amount of code.

Besides that, this was inevitable.

Eric Normand
Quoth Eric Normand on November 01, 2008 @ 2:24 AM PDT

I think using $ is supposed to work for referring to inner classes.

Stephan Schmidt
Quoth Stephan Schmidt on November 01, 2008 @ 3:07 AM PDT

"If you're lucky you have a bloated IDE to type those things for you."

Or if you're lucky you have a intelligent IDE to hide those things for you.

Peace -stephan

http://stephan.reposita.org

jni
Quoth jni on November 01, 2008 @ 5:09 AM PDT

was this a troll?! it said lisp, then i read it's clojure junk. go

Bill King
Quoth Bill King on November 01, 2008 @ 8:22 AM PDT

As a trolltechie/nokian, I must say, this is very cool to see.

mosi
Quoth mosi on December 12, 2008 @ 6:22 AM PST

Just one change I noticed from 11/2008: (doto button (resize 250 100) (setFont ... ...

probably does not work with the current clojure (12/2008): (doto button (. resize 250 100) (. setFont ... ...

Thank you for this helpful starting point.

Brian
Quoth Brian on December 12, 2008 @ 1:20 PM PST

Thanks, I updated the code to reflect the new syntax.

CL Community on Clojure - Page 3 | keyongtech
Quoth CL Community on Clojure - Page 3 | keyongtech on January 18, 2009 @ 12:41 AM PST

[...] Community on Clojure J Kenneth King ha scritto: > Clojure An interesting related article: http://briancarper.net/2008/10/31/qt4-in-lisp/ [...]

felzix
Quoth felzix on January 26, 2009 @ 9:43 AM PST

Thanks for posting this! I wanted to use qt for my app's gui but had trouble getting it to work with CL so I went with ltk, which I don't like. I switched to clojure for the libraries, parallelism, and community and now, with this post, I finally have qt with lisp :).

Michel S.
Quoth Michel S. on February 15, 2009 @ 9:15 PM PST

jni: Lisp is not just Common Lisp, you know.

Anonymous Cow
Quoth Anonymous Cow on March 29, 2009 @ 10:22 PM PDT

This is very cool. However it looks like nokia is dropping Qt Jambi.

http://www.qtsoftware.com/products/programming-language-support

Qt Jambi ... has been discontinued in order to focus resources on the Qt cross platform application and UI framework. Qt Jambi will be maintained for one year after the March 2009 release of Qt Jambi 4.5.0_01, and will be made available upon release under the LGPL license.

Brian
Quoth Brian on March 30, 2009 @ 6:58 AM PDT

So I read. :( There's still a chance that it will be community-supported, given that it's open-source. But it was nice having an officially-maintained and documented Qt for Lisp.

Felix
Quoth Felix on May 14, 2009 @ 5:12 AM PDT

Thanks for the informative post. Works for me, yet crashes on exit:

A fatal error has been detected by the Java Runtime Environment:

#

SIGSEGV (0xb) at pc=0x77f0a981, pid=14014, tid=3074214800

Anyone has a clue why this could be?

Brian
Quoth Brian on May 15, 2009 @ 6:20 AM PDT

I've had lots of random segfaults when using SLIME and QtJambi. I never figured them out. It was very random. I don't think Jambi was ever meant to be used in an interactive way like is possible from Clojure, so I'm amazed it even works at all.

angel
Quoth angel on February 15, 2012 @ 6:28 AM PST

I think would be interesting see a example clojure and javachromiumembedded...which let embedded chrome (html5 + javascript) with java (and clojure)...sounds nice no?.....

Speak your Mind

You can use Markdown in your comment.
Email/URL are optional. Email is only used for Gravatar.

Preview