CommonQt is a Common Lisp binding to the smoke library for Qt.

Download

Get it from git:

git clone git://repo.or.cz/commonqt.git

(gitweb)

Dependencies (C++)

CommonQt needs the smoke library for Qt. GNU make and gcc are also required.

It was developed using the LGPLed smoke 2 from kdebindings svn and Qt 4.5 and is available under a BSD-style license. Qt >= 4.3 is required for proper memory management.

You can also use its branch for smoke 1 support with the binaries described below. (In that case, make certain that you understand the license of smoke 1.)

Debian users:

aptitude install libsmokeqt4-dev libqt4-dev

Gentoo users (might need use flags for Qt4):

emerge kde-base/smoke

You can check out the master branch from CommonQt git to try it with current smoke 2 from kdebindings svn.

Dependencies (Lisp)

CommonQt needs

and their dependencies.

Tested with SBCL and CCL on Linux.

Installation

ASDF is used for compilation. Register the .asd file, e.g. by symlinking it, then compile the QT system using asdf:operate.

$ ln -sf `pwd`/qt.asd /path/to/your/registry/
* (asdf:operate 'asdf:load-op :qt)

And to follow the tutorial:

$ ln -sf `pwd`/qt-tutorial.asd /path/to/your/registry/
* (asdf:operate 'asdf:load-op :qt-tutorial)

The ASDF system runs make for you automatically to build the C++ wrapper library libcommonqt.so.

Run make yourself for troubleshooting.

Qt tutorial

Lisp translations of the Qt tutorial:

  1. C++ version , Lisp version
  2. C++ version , Lisp version
  3. C++ version , Lisp version
  4. C++ version , Lisp version
  5. C++ version , Lisp version
  6. C++ version , Lisp version
  7. C++ version , Lisp version
  8. C++ version , Lisp version
  9. C++ version , Lisp version
  10. C++ version , Lisp version
  11. C++ version , Lisp version
  12. C++ version , Lisp version
  13. C++ version , Lisp version
  14. C++ version , Lisp version

Calling instance methods

Use the #_ reader macro to invoke Qt methods easily.

Example:

(#_setGeometry window 100 100 500 355)
(#_show window)

The first argument is the instance to call the method on, the following arguments are passed on to the method.

Method names are case sensitive.

The reader macro expands into a use of the qt:call function.

To enable the reader macro, use (enable-syntax) at the top of your file, while is short for (named-readtables:in-readtable :qt).

Calling static methods

For static methods, pass the class name as a string in place of an instance:

Example:

(#_blue "Qt")

Instantiating Qt classes

The reader macro for #_ has a special case: If it is followed by new, it keeps reading to find a class name, then returns a call to a constructor instead.

Example:

(#_new QPushButton "Quit")

Class names are case-sensitive.

In this case, the macro expands into a use of the qt:new function.

Connecting signals and slots

When connecting signals and slots, C++ programmers would use the macros SLOT and SIGNAL to disambiguate string and signal names from each other. We call these QSLOT and QSIGNAL.

Note that QObject::connect() is a static method on QObject.

(#_connect "QObject"
           quit-button (QSIGNAL "clicked()")
           application (QSLOT "quit()"))

QApplication

The constructor for QApplication requires more FFI magic than you would probably want to write yourself, because it is designed to take the (int* argc, void** argc) arguments from a main function in C.

Use the make-qapplication function provided by Qt instead, which takes string arguments and converts them.

Examples:

(setf qt-user:*application* (qt:make-qapplication))
(setf qt-user:*application* (qt:make-qapplication "-display" ":0"))

Subclassing C++ classes

You can make "subclasses" using smoke like this:

(defclass button ()
    ()
  (:metaclass qt-class)
  (:qt-superclass "QPushButton"))

(defmethod initialize-instance :after ((instance button))

  ;; Must call the C++ constructor here first:
  (new instance "label")

  ;; can call C++ methods on this lisp object afterwards:
  (#_connect "QObject"
             instance (QSIGNAL "clicked()")
             *application* (QSLOT "quit()")))

Always specify the metaclass. Specify the qt-superclass only when not subclassing another such Lisp class that already has it. Above we didn't specify a superclass, so it was defaulted to qt:dynamic-object, a subclass of qt:qobject.

Always call the C++ constructor from initialize-instance. Note the use of qt:new with an existing instance that already knows its class (but doesn't have a pointer slot yet).

Overriding C++ methods

In a subclass (see the example above), you can override C++ methods like this:

(defclass canvas ()
    ()
  (:metaclass qt-class)
  (:qt-superclass "QWidget")
  (:override ("paintEvent" paint-event)))

(defmethod paint-event ((this canvas) paint-event)
  (declare (ignore paint-event))
  (let ((painter (#_new QPainter this)))
    ... paint something ...
    (#_end painter)))
  

In overriding methods, you can call qt:call-next-qmethod to call the C++ method that is being intercepted.

Note that we always intercept all methods of the specified name, ignoring their argument type signature. In this case, there's only one method called paintEvent, so that is safe to do. If there was a second paintEvent method without arguments, you'd have to use &optional or &rest to avoid errors.

Defining signals and slots

In a subclass (see the example above), you can add signals and slots. Here is a real example from tutorial 13:

(defclass cannon-field ()
    (...)
  (:metaclass qt-class)
  (:qt-superclass "QWidget")
  (:slots ("setAngle(int)" (lambda (this newval)
                             (setf (current-angle this)
                                   (min (max 5 newval) 70))))
          ("setForce(int)" (lambda (this newval)
                             (setf (current-force this)
                                   (max 0 newval))))
          ("void moveShot()" move-shot)
          ("void shoot()" shoot)
          ("void setGameOver()" set-game-over)
          ("void restartGame()" restart-game))
  (:signals ("angleChanged(int)")
            ("forceChanged(int)")
            ("void hit()")
            ("void missed()")
            ("void canShoot(bool)")))

Note the use of lambda to pre-process values and swap arguments in slots that are meant to just call a CLOS accessor eventually, and the use of lisp function names elsewhere.

Signals don't have a corresponding Lisp function. Emit them like this: (emit-signal object signal-name arguments)

QAPROPOS

There a function qapropos similar in spirit to apropos which looks for Qt classes and methods.

Example:

QT-USER> (qapropos "sliderposition")
Method QAbstractSlider.setSliderPosition [660]
Method QAbstractSlider.sliderPosition [661]
Method QStyle.sliderPositionFromValue [14984]
Method QStyle.sliderPositionFromValue [14985]
Method QStyleOptionSlider.sliderPosition [15351]
Method QStyleOptionSlider.setSliderPosition [15364]
  

QDESCRIBE for classes

There a function qdescribe similar in spirit to describe.

Example for a class name:

QT-USER> (qdescribe "QPushButton")
#<QCLASS QPushButton> is a smoke class

    name: QPushButton
    flags: VIRTUAL, CONSTRUCTOR

Superclasses:
    QAbstractButton
        QWidget
            QObject
            QPaintDevice

Methods:
    paintEvent#               QPushButton.paintEvent [12423]
    minimumSizeHint           QPushButton.minimumSizeHint [12409]
    metaObject                QPushButton.metaObject [12399]
    menu                      QPushButton.menu [12415]

    [long list trimmed here]

    QPushButton               QPushButton.QPushButton [12402]

Use (QDESCRIBE "QPushButton" T) to see inherited methods.

Properties:
    bool autoDefault                    
    bool default                        
    bool flat                           

Use (QDESCRIBE "QPushButton" T) to see inherited properties.
  

QDESCRIBE for methods

qdescribe can also show methods, if used with a string of the form classname.methodname:

QT-USER> (qdescribe "QPushButton.paintEvent")
#<QMETHOD QPushButton.paintEvent> is a smoke method
    class: #<QCLASS QPushButton>
    name: paintEvent
    return type: NIL
    flags: PROTECTED
  argument types:
    #<QTYPE (QPaintEvent*) kind: POINTER, stack: CLASS, class: #<QCLASS QPaintEvent>>
  

If there are several methods of that name, it shows each method with its arguments.

QDESCRIBE for objects

qdescribe on Qt objects can show their property values at run time:

QT-USER> (enable-syntax)

QT-USER> (qdescribe (#_new QPushButton "test"))
#<QPushButton 0x27A654D0> is a smoke object.

Use (QDESCRIBE "QPushButton") to see details about its C++ class.

Properties:
    QString objectName                  ""
    bool modal                          NIL
    Qt::WindowModality windowModality   #<QVariant 0x27A65100>
    bool enabled                        T
    QRect geometry                      #<QRect 0x27443860>
    QRect frameGeometry                 #<QRect 0x27A66E20>
    QRect normalGeometry                #<QRect 0x27A63540>
    int x                               0
    int y                               0
    QString text                        "test"

    [long list trimmed here]
  

If there are several methods of that name, it shows each method with its arguments.

ABORT restart

During debugging, when an error happens in a Qt callback, never unwind to an outer restart, because that crashes the application. Instead use the ABORT restart provided by CommonQt for this purpose, which aborts only the callback.

Reference arguments

References to primitive types are handled automatically using multiple return values.

For example: (#_new QApplication argc argv) returns the QApplication and an updated argc values as multiple return values instead of modifying the argc argument. (In the case of QApplication this doesn't do much good, because it actually stuffs away the references for the lifetime of the program somewhere, but other better will hopefully be better behaved and access references only for their dynamic extent.)

We deviate from the Ruby bindings in this, which have an int class that is mutable.

Enum values

Like the ruby binding we have ENUM instances that record the enum type's name in addition to the value for type sately. Use PRIMITIVE-VALUE to get the enum as an integer.

Type disambiguation

Types like int, uint, float, double are auto-boxed from and to Lisp integers and rationals.

If you need to pass, say, a uint explicitly, you can make an qt:uint wrapper object.

Automatic deletion

Objects created by Lisp code are deleted automatically in a finalizer.

This implies that application programmers sometimes need to keep references on the lisp side to any object that is still used from C++ code.

As an exception, CommonQt keeps track of child widgets added to their parent, and ensures that there is a strong reference to the child keeping it alive while it is on the screen.

(fixme: There are one or two other special cases in addition to child widgets in QtRuby. Need to port them over.)

How about CLOS bindings?

Why a reader macro instead of Lisp functions?

CommonQt is currently a purely dynamic binding that does not autogenerate any Lisp code at all.

The advantages of this approach are that it compiles and loads quickly, consumes less memory and is easily extensible to additional smoke libraries.

But a higher-level bindings with more CLOS integration would be possible.

In fact, I've already tried it. (Ask me for the code if you want to look at it.)

SBCL needs 13 minutes to compile and load that trivial auto-generated code. And that's just for Qt itself, not including the rest of KDE.

That's not pretty, and personally I've given up on it for now. The dynamic bindings work well enough for me.

Bugs

CommonQt probably still has many bugs.

In all likelihood we still leak memory in some places.

Fixme: Type mappings

The most common missing feature you'll encounter when using CommonQt are mappings for the various C++ types used by Qt.

Smoke knows hundreds of non-QObject types that need to be marshalled and unmarshalled from Lisp to C++ and back. For each type, CommonQt needs to pick and Lisp type to map to and provide CFFI code to do so.

See commonqt/marshall.lisp and commonqt/unmarshall.lisp for details.

Additional marshalling issues exist for signal and slot arguments and values as well as property values, but these involve fewer types.

To do: External classes

The main branch in CommonQt uses smoke2, which can also interface to various other Qt-related and KDE-related sets of classes, which would be nice to support. We currently ignore classes with the external flag and link only to qt_Smoke.