Here’s the second part of our exploration of GUI toolkits for Common Lisp.

The first part and introduction is accessible here:

This blog post series was initially written for the Common Lisp Cookbook, you can (and should) read it there:

Qt4 (Qtools)

Do we need to present Qt and Qt4? Qt is huge and contains everything and the kitchen sink. Qt not only provides UI widgets, but numerous other layers (networking, D-BUS…).

Qt is free for open-source software, however you’ll want to check the conditions to ship proprietary ones.

The Qtools bindings target Qt4. The Qt5 Lisp bindings are yet to be created.

  • Framework written in: C++
  • Framework Portability: multi-platform, Android, embedded systems, WASM.
  • Bindings Portability: Qtools runs on x86 desktop platforms on Windows, macOS and GNU/Linux.

  • Widgets choice: large.

  • Graphical builder: yes.

  • Other features: Web browser, a lot more.

  • Bindings documentation: lengthy explanations, a few examples. Prior Qt knowledge is required.

  • Bindings stability: stable

  • Bindings activity: active

  • Qt Licence: both commercial and open source licences.

  • Example applications:

Getting started

(ql:quickload '(:qtools :qtcore :qtgui))
(defpackage #:qtools-test
  (:use #:cl+qt)
  (:export #:main))
(in-package :qtools-test)
(in-readtable :qtools)

We create our main widget that will contain the rest:

(define-widget main-window (QWidget)

We create an input field and a button inside this main widget:

(define-subwidget (main-window name) (q+:make-qlineedit main-window)
  (setf (q+:placeholder-text name) "Your name please."))
(define-subwidget (main-window go-button) (q+:make-qpushbutton "Go!" main-window))

We stack them horizontally:

(define-subwidget (main-window layout) (q+:make-qhboxlayout main-window)
  (q+:add-widget layout name)
  (q+:add-widget layout go-button))

and we show them:

  (window 'main-window))

That’s cool, but we don’t react to the click event yet.

Reacting to events

Reacting to events in Qt happens through signals and slots. Slots are functions that receive or “connect to” signals, and signals are event carriers.

Widgets already send their own signals: for example, a button sends a “pressed” event. So, most of the time, we only need to connect to them.

However, had we extra needs, we can create our own set of signals.

Built-in events

We want to connect our go-button to the pressed and return-pressed events and display a message box.

  • we need to do this inside a define-slot function,
  • where we establish the connection to those events,
  • and where we create the message box. We grab the text of the name input field with (q+:text name).
(define-slot (main-window go-button) ()
  (declare (connected go-button (pressed)))
  (declare (connected name (return-pressed)))
  (q+:qmessagebox-information main-window
                              "Greetings"  ;; title
                              (format NIL "Good day to you, ~a!" (q+:text name))))

And voilà. Run it with

(with-main-window (window 'main-window))

Custom events

We’ll implement the same functionality as above, but for demonstration purposes we’ll create our own signal named name-set to throw when the button is clicked.

We start by defining the signal, which happens inside the main-window, and which is of type string:

(define-signal (main-window name-set) (string))

We create a first slot to make our button react to the pressed and return-pressed events. But instead of creating the message box here, as above, we send the name-set signal, with the value of our input field..

(define-slot (main-window go-button) ()
  (declare (connected go-button (pressed)))
  (declare (connected name (return-pressed)))
  (signal! main-window (name-set string) (q+:text name)))

So far, nobody reacts to name-set. We create a second slot that connects to it, and displays our message. Here again, we precise the parameter type.

(define-slot (main-window name-set) ((new-name string))
  (declare (connected main-window (name-set string)))
  (q+:qmessagebox-information main-window "Greetings" (format NIL "Good day to you, ~a!" new-name)))

and run it:

(with-main-window (window 'main-window))

Building and deployment

It is possible to build a binary and bundle it together with all the necessary shared libraries.

Please read

You might also like this Travis CI script to build a self-contained binary for the three OSes.

Next, we’ll have a look at Gtk+3.