2: Classes and Methods

Yes: I, too, share your frustration at the fact that everything has to be object-oriented. I, too, use lists and at most a struct and a hash table. But that’s just how life is and you’ll just have to deal with it. Classes and methods in Common Lisp aren’t particularly hard. The translation is fairly straightforward but very mechanical – In another tutorial I’ll show you how to translate C++ Qt variable definitions, slot and signal names to something Common Lisp can understand. But before that I have to show you how to do it by hand.

For this I’ll use Robert Synnott’s currency converter example, with just a slight modification.

The full code is here:

(ql:quickload 'qt)

(defpackage :qt-conv
  (:use :cl :qt)
  (:export :main))

(in-package :qt-conv)
(named-readtables:in-readtable :qt)

(defvar *qapp*)

(defclass my-window()
  ((dollarAmount :accessor dollarAmount)
   (conversionRate :accessor conversionRate)
   (result :accessor result))
  (:metaclass qt-class)
  (:qt-superclass "QWidget")
  (:slots ("convert()" convert)))

(defmethod convert ((instance my-window) &aux (res 0))
  ; Just ignore if input isn't numeric...
  (handler-case
      (setf res (* (read-from-string (#_text (dollarAmount instance)))
     (read-from-string (#_text (conversionRate instance)))))
    (t (e) nil))
  (#_setText (result instance) (format nil "~A" res)))

(defmethod initialize-instance :after ((instance my-window) &rest rest)
  (new instance)

  (#_setFixedSize instance 360 170)
  (let ((convert (#_new QPushButton "Convert" instance)))

    (setf (conversionRate instance) (#_new QLineEdit "1.00" instance)
   (dollarAmount instance) (#_new QLineEdit "0.00" instance)
   (result instance) (#_new QLineEdit "0.00" instance))
    (#_move (#_new QLabel "Exchange Rate per $1:" instance) 20 20)
    (#_move (#_new QLabel "Dollars to Covert:" instance) 20 60)
    (#_move (#_new QLabel "Amount in other Currency:" instance) 20 100)
    (#_move (dollarAmount instance) 200 60)
    (#_move (conversionRate instance) 200 20)
    (#_move (result instance) 200 100)
    (#_move convert 220 130)
    (#_connect "QObject"
        convert (QSIGNAL "clicked()")
        instance (QSLOT "convert()"))))

(defun main (&optional style)
  (when style
    (#_setStyle "QApplication"
  (#_create "QStyleFactory" (case style
         (:cde "CDE")
         (:macintosh "Macintosh")
         (:windows "Windows")
         (:motif "Motif")
         (t "CDE")))))
  (setf *qapp* (make-qapplication))
  (let ((window (make-instance 'my-window)))
    (#_setWindowTitle window "Currency Convertor")
    (#_show window)
    (unwind-protect
  (#_exec *qapp*)
      (#_hide window))))

The change has been highlighted. His screenshot showed this:

But when you actually run the code it just shows garbage on the title bar. So the change I made was add a call to #_setWindowTitle, with the arguments window and “Currency Convertor”.

Section by section:

(ql:quickload 'qt)

(defpackage :qt-conv
  (:use :cl :qt)
  (:export :main))

(in-package :qt-conv)
(named-readtables:in-readtable :qt)

Default header. If your editor supports template files, this should be it. The only thing that has to be changed is the package name, used twice. Oh, and, I added the call to Quicklisp up there.

(defvar *qapp*)

This is the alternative way to define the APP variable, instead of putting it in a LET/LET* binding, by defining it globally. Generally, though, you’d use DEFPARAMETER and set the value to (make-qapplication) and get over it in one line.

(defclass my-window()
  ((dollarAmount :accessor dollarAmount)
   (conversionRate :accessor conversionRate)
   (result :accessor result))
  (:metaclass qt-class)
  (:qt-superclass "QWidget")
  (:slots ("convert()" convert)))

The class definition, just ordinary CLOS functions and macros. The name here is MY-WINDOW, the first argument (Here and empty list) should be the superclass (The parent). With CommonQt, though, you only have to specify a metaclass and a Qt-specific superclass/parent.

The second argument is the slot specifier, a list of lists in the following form:

(slot-name :initform initial-value :accessor name-to-use)

The accessor I always found horribly redundant, and was one of the reasons I wrote the wrapper I’ll show you to convert classes from C++ to CommonQt. :initform is actually optional, you can just leave it nil, but I prefer to initialize everything here. Generally, you use these slots to put widgets that can be accessed by other methods with the (instance slot-name) syntax, but you can alternatively create the widgets inside a LET binding in the initialize-instance method (The Lisp equivalent of a constructor, for all the C++ programmers, or Python’s __init__).

Here the slots will be set to widgets later on, and as usual the accessors are copies of their names.

The third argument is completely static, (:metaclass qt-class) remains the same for every Qt class that you make.

The fourth argument is the Qt widget parent. For example, if you’re developing a class that handles a text editor, the parent might be “QTextEdit”. And the class that holds the entire classes of the application may be a subclass of “QMainWindow”, inheriting all its slots, signals and functions.

Then you have room to put your slots and signals, for example, like this:

(:signals ("findNext(const QString &str, Qt::CaseSensitivity cs)")
          ("findPrevious(const QString &str, Qt::CaseSensitivity cs)"))
(:slots ("findClicked()" find-clicked)
        ("enableFindButton(const QString)" (lambda (this text) (enable-find-button this text)))))

In this example, there are no signals, and the only slot is a function called convert. Again, this has to be in C++ syntax. The function has no arguments so you simply type the function name.

There is one thing, though. Look at the example above, and note a few things: First is that signals are just like that. They are a list containing a string, nothing more. Second is that, slots that take no arguments are simply followed by the name of a corresponding function/method (Which, however, takes one argument but it is implicit). I’m using function and method and slot sort of interchangeably here.

When they actually take arguments, you must use an anonymous function to put them through. This can also be very tiresome, but my wrapper will get around that. Just let me tell you how to do it by hand, if you ever need to.

(lambda (this argument1 argument2 ...) (method-name this argument1 argument2 ...))

That should be it. THIS represents the sort-of-implicit argument I mentioned earlier. METHOD-NAME is the name of whatever method you’re connecting the slot to.

And that’s essentially the whole class definition. There is also something else I didn’t discuss, :o verride is exactly like :slots but allows you to define slots that have the same name (And arguments) as the original slots of a Qt class and :o verride its behaviour.

The method INITIALIZE-INSTANCE creates the class. In Robert’s code it’s defined as follows:

(defmethod initialize-instance :after ((instance my-class) &rest rest)
  (new instance)

The fourteenth tutorial in the official gitorious page defines it:

(defmethod initialize-instance :after ((instance my-class) &key parent)
    (if parent
        (new instance parent)
        (new instance))

The latter is the version we’ll use.

The (instance my-class) part is very important. It defines what class this method applies to, but also defines the value INSTANCE, which is important. Say that you want to access the value of a particular slot. For example, say that the slot EDITOR of your class MY-EDITOR has been set to be a QTextEdit widget. You want to set its contents in the initialization phase so it will not show up empty:

(defmethod initialize-instance :after ((instance my-editor) &key parent)
    (if parent
        (new instance parent)
        (new instance))
    (#_clear (editor instance)))

So when this method is called with an instance of the class MY-EDITOR, the value of INSTANCE is essentially a pointer to that instance of the class. To access a slot, you use (slot-name instance).

In this example, one of the widgets is created inside a LET binding, and the others are SETF’d from slots:

(let ((convert (#_new QPushButton "Convert" instance)))

A line prior, there is a call to a Qt function. Note that the first argument is always the class or whatever you want to apply it to, in this case, the class referred to by INSTANCE.

(setf (conversionRate instance) (#_new QLineEdit "1.00" instance)
   (dollarAmount instance) (#_new QLineEdit "0.00" instance)
   (result instance) (#_new QLineEdit "0.00" instance))

The last argument the #_new macro here is the class you want to put these in — Here, the class referred, again, by INSTANCE. The other arguments are the ones you can supply at first, for example, the stringified numbers here are the initial contents of these widgets.

(#_move (#_new QLabel "Exchange Rate per $1:" instance) 20 20)
    (#_move (#_new QLabel "Dollars to Covert:" instance) 20 60)
    (#_move (#_new QLabel "Amount in other Currency:" instance) 20 100)

Here he automatically creates new widgets (Labels) and moves them to positions described by X,Y coordinates. I would recommend using a QLayout, but, whatever floats your boat. These labels you could also create inside a LET binding, but adding them as slots to the class is probably a waste of space. They are just labels.

(#_move (dollarAmount instance) 200 60)
    (#_move (conversionRate instance) 200 20)
    (#_move (result instance) 200 100)
    (#_move convert 220 130)

And this, well, moves the first argument to the positions marked by the second and third arguments.

(#_connect "QObject"
        convert (QSIGNAL "clicked()")
        instance (QSLOT "convert()"))))

This final call ties the CONVERT widget so when it is clicked, it will call the convert() slot in INSTANCE.

And, finally, we have the main function:

(defun main (&optional style)
  (when style
    (#_setStyle "QApplication" 
  (#_create "QStyleFactory" (case style
         (:cde "CDE")
         (:macintosh "Macintosh")
         (:windows "Windows")
         (:motif "Motif")
         (t "CDE")))))
  (setf *qapp* (make-qapplication))
  (let ((window (make-instance 'my-window)))
    (#_setWindowTitle window "Currency Convertor")
    (#_show window)
    (unwind-protect
  (#_exec *qapp*)
      (#_hide window))))

You’ll notice a difference, mainly the calls related to styles. There’s an optional argument you can pass to MAIN, which is a string: “Macintosh”, “Windows”, “Motif” or “CDE”, the default being the latter. QStyleFactory::create() is called with the corresponding argument, and the style of QApplication is set. Here are examples of each option. There doesn’t seem to be much of a change, though, so maybe I’m doing it wrong.

The rest is ordinary: Setting *qapp* to be a QApplication, creating a variable WINDOW which is an instance of the class MY-WINDOW, my change (Setting the title), then the other boilerplate.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s