Hello world     Function list     Interactive development example

Introduction

Please consider the whole project in an incomplete state and not very well tested yet!

This project is mostly based on the following dynamic features of the Qt (C++) toolkit:

(The above is possible without even knowing the type of a given object, because every Qt object has its own meta information allowing some nice dynamic features.)


A hello world example, explained

Variant 1


 (defun hello-world ()
   (with-qt ()
     (object lb 'label)
     (setpr lb :text "<h1>Hello world!")))

To create the GUI you use the with-qt macro. If there is only one widget you don't need to pass arguments.

The object function creates a Qt widget. The name of the widget is 'lispified', so:

The object function expects at least two arguments: the variable to store the object and the name of the object.
You don't need to create local variables for your widgets, since this is done automatically.

To set the properties of the created object, you use the setpr (short for "set property") function. Here we set the text to "<h1>Hello world!". As you note, you can use (a subset of) HTML here.

The with-qt macro then starts the Qt main event loop and shows the main widget. The event loop exits automatically when the last window of the GUI is closed.


Variant 2


 (defun hello-world ()
   (with-qt ()
     (object lb '(label :text "<h1>Hello world!"))))

Instead of the object name you can pass a list with the object name and any number of property / value pairs.


The main functions


 (with-qt (&optional main &key show splash ev reset) &body body)
 (ev &optional seconds)
 (with-objects (&optional object-to-return) &body body)
 (object var name/properties &optional parent) *
 (object* name/properties &optional parent)
 (destroy object)
 
 (getpr object name)
 (setpr object name value) *
 (set-color object role name) *
 (set-style-sheet-file file)
 
 (box object type var) *
 (grid object type var row column &optional row-span column-span) *
 
 (call object name &optional arg1 arg2 arg3 arg4)
 (sender)
 (connect from-object signal function) *
 (connect-qt from-object signal to-object slot) *
 (disconnect-qt from-object signal to-object slot) *
 
 (message-box type text &optional button0 button1 button2 default)
 (input-dialog type label &key title value cancel min max decimals)
 (file-dialog type &key dir title filter)
 (font-dialog &optional initial)
 (color-dialog &optional initial)
 (error-message text)
 (with-message (text) &body body)
 (with-progress-dialog (label max) &body body)
 
 (event-filter object event function eat) *
 (with-wait-cursor () &body body)
 (save-screenshot object file)
 
 Please see the qt.lisp source file for some additional convenience functions.
 
 * ((% ...) args)


Main functions, explained

(with-qt (&optional main &key show splash ev reset) &body body)

Standard way of creating the GUI. The first argument is the widget that will be shown when the event loop is entered. It can be omitted if there is only one widget.

The show argument can be one of:

If this argument is omitted, the widget will be shown in its initial size.

In order to display a splash screen, you can pass a file name as the splash argument.

The ev keyword changes the event loop behaviour (please see the example at the bottom). In the final program version it should not be set (for efficiency reasons).

If you want the created objects to be destroyed after leaving with-qt, you can change reset to t.

This macro contains a with-objects macro (see below).

(ev &optional seconds)

Allows restarting the Qt event loop after breaking e.g. the Slime loop (please see the example at the bottom).

(with-objects (&optional var) &body body)

This macro is only needed when you create objects outside the with-qt macro.

It looks in the enclosed body for unbound Qt object variables (from the object function) and creates local variables for them (with let).

The variable that optionally can be passed will be the return value of the whole expression. This is convenient if you want to divide the GUI creation in some smaller parts.

For an example see 3-dialogs-and-objects.lisp.

(object var name/properties &optional parent) *

Creates a Qt widget and stores the (foreign) object pointer in var. The name can be given as keyword or as quoted symbol or it must evaluate to a string.

Instead of the object name you can pass a list with the object name and any number of property / value pairs.

Another option is to provide a function returning an object. An example for this can be found in 2-calculator.lisp.

You don't normally need to create local variables for your objects, since this is done automatically for all unbound variables (see the with-objects macro).

(Optionally you can use the object* function.)

For a list of all possible widget names (and an example of the above), see example 3-dialogs-and-objects.lisp.

The parent argument is not necessary for widgets (the parent is set automatically if you add a widget to a layout).

The parent is only important for layout objects: it sets the layout for the given parent widget. In case you want to add a layout to another layout, the parent must be NIL.

A top level widget will be created if the parent is NIL and the widget is not added to a layout.

(object* name/properties &optional parent)

This function creates a (single) object and returns its (foreign) object pointer.

Instead of the object name you can pass a list with the object name and any number of property / value pairs.


Example:

 (object* '(label :text "Hello!" :size #(200 100)))
(destroy object)

Deletes the given object. Normally you don't need this function, since the with-qt macro automatically cleanes up all the objects created.

An example for this function would be a local dialog, which is created only when needed and destroyed afterwards.

Another example are the items of a graphics-view. To remove items you can simply use this function.

Note that if you destroy a widget, all its child widgets will be deleted too.

(getpr object name)

Short for "get property". For a list of all available poperties, see the example 3-dialogs-and-objects.lisp.

In the example mentioned above the enumerator properties are listed separately. So if the variable type of a property is :enum, you can find the possible values for it in the appropriate list.


Examples:

 (getpr w :checked)
 (getpr w :count)
 (getpr w :text)
(setpr object name value) *

Short for "set property". For a list of all available poperties, see the example 3-dialogs-and-objects.lisp. Note that some properties are read only.

In the example mentioned above the enumerator properties are listed separately. So if the variable type of a property is :enum, you can find the possible values for it in the appropriate list.


Examples:

 (setpr w :checked t)
 (setpr w :max-length 100)
 (setpr w :alignment :align-center)
 (setpr w :size #(100 50))
(set-color object role name) *

A convenience function to set the widget colors. The name can be either in the common hexadecimal string format (e.g. "#ff0000") or you can give a name from the w3.org color keywords.

The role can be one of:


Examples:

 (set-color w :window "#87ceeb")
 (set-color w :window "skyblue")
(set-style-sheet-file file)

Sets the style sheet file for the whole application. For an exhaustive description see the Qt Style Sheets section in Qt Assistant.

Note that the widget names must be in Qt style (e.g. QPushButton).

(box object type var) *

The object argument must be one of h-box-layout (horizontal) or v-box-layout (vertical).

If you create a layout object the parent argument is important: if given, the layout will become the layout for the widget. In case you want to add the layout to another layout, the parent must be NIL.

The type to add can be one of:

For an exhaustive description of the Qt layout possibilities see the Qt Assistant.


Examples:

 ...
 (object vbox 'v-box-layout main)
 (box ((vbox :widget %)
       one two three))
 ...
 ...
 (object vbox 'v-box-layout main
         hbox 'h-box-layout nil)
 (box ((vbox % %)
       :widget edit
       :layout hbox)
      ((hbox % %)
       :stretch nil
       :widget ok
       :widget cancel))
 ...


(grid object type var row column &optional row-span column-span) *

The object argument must be a grid-layout.

If you create a layout object the parent argument is important: if given, the layout will become the layout for the widget. In case you want to add the layout to another layout, the parent must be NIL.

The type to add can be one of:

This layout function is similar to the above one, it adds the given object at the row and column coordinate of the layout.

Optionally you can pass the row-span and column-span.

For an exhaustive description of the Qt layout possibilities see the Qt Assistant.


Example:

 ...
 (object grid 'grid-layout main)
 (grid ((grid :widget % % %)
        up    0 1
        left  1 0
        right 1 2
        down  2 1))
 ...


(call object name &optional arg1 arg2 arg3 arg4)

In order to call a Qt slot directly from Lisp, you can use this function.

For a list of all available Qt slots to call, see example 3-dialogs-and-objects.lisp.


Examples:

 (call box 'clear)
 (call box 'add-items lst)

the same using the with- macro:

 (with- (call box)
   'clear
   ('add-items lst))
(sender)

This function should only be used within a function that previously has been called by a Qt signal. The returned value is the object that emitted the Qt signal.

It can be convenient in case you want to access the properties of the calling object, or the cases where you connect multiple Qt signals to a single slot.

Examples for both possibilities can be found in 2-calculator.lisp.

(connect from-object signal function) *

Creates a dynamic connection from a Qt signal (emitted on certain events) to a Lisp function or a lambda expression.

Note that the number of arguments in the Lisp function must correspond exactly to the respective number of signal arguments.
When using a lambda expression instead, you can omit some arguments if you don't need them.

For a lambda expression see the examples below.
Note that it is possible to use the (sender) function inside this lambda expression, so this way you have access to all the properties of the calling object.

The function argument can be omitted - it will then be built automatically by simply concatenating the variable name and the signal name, separated by a hyphen (e.g. 'ok-clicked).

For a list of all available Qt signals, see example 3-dialogs-and-objects.lisp.


Examples:

 (connect btn 'clicked 'show-message)
 (connect btn 'clicked '(lambda () (message-box :info "Hello!")))
 (connect edit 'text-changed '(lambda (str) (print (length str))))
(connect-qt from-object signal to-object slot) *

Creates a dynamic connection from a Qt signal (emitted on certain events) to a Qt slot function.

Note that the argument types of the slot must correspond exactly to the ones of the signal (except that you can omit some arguments in the slot).

Any connection can be disconnected at any time with the disconnect-qt function.

For a list of all available Qt signals and Qt slots, see example 3-dialogs-and-objects.lisp.


Examples:

 (connect-qt btn 'clicked box 'clear)
 (connect-qt edit 'text-changed label 'set-text)
(disconnect-qt from-object signal to-object slot) *

To dynamically disconnect an existing connection.

(message-box type text &optional button0 button1 button2 default)

The type of the message box can be one of:

The optional arguments are only for the :question type. The button0 defaults to "Yes", button1 defaults to "No". The default argument is the index of the default button.

The return value of :question is the index of the clicked button (0, 1, 2).

(input-dialog type label &key title value cancel min max decimals)

The type of the input dialog can be one of:

The title is the window title, and the label is the description of the value asked for.

The &key argument value is for the initial value, the cancel argument is the value that should be returned if the dialog has been canceled, the min and max arguments are the respective minimum and maximum values for the :int and :float types, and the decimals argument is for the number of decimals that should be displayed (for type :float).

(file-dialog type &key dir title filter)

The type of the file dialog can be one of:

The title is the window title, and dir specifies the initial directory.

You can specify a filter like this: "Images (*.png *.xpm *.jpg)"

(font-dialog &optional initial)

Returns the selected font as a list of properties and values, or NIL when the dialog has been canceled.

Optionally you can pass an initial font.

(color-dialog &optional initial)

Returns a hexadecimal string representation of the selected color, or NIL when the dialog has been canceled.

Optionally you can pass an initial color.

(error-message text)

Shows the given text and a check box to let the user control whether the message will be displayed again.

Qt will remember every message that has been shown with this dialog, so you don't have to keep track of the different messages, as for every message the check box state will be remembered independently.

(with-message (text) &body body)

Shows a message during some short operation (too short for a progress dialog).

Inside the body you can hide the message at any time with (hide-message).

(with-progress-dialog (label max) &body body)

A convenient dialog to show the progress of some operation. The arguments are the description of the operation (label), and the maximum value of the progress (max) corresponding to 100%.

In the body you can set the progress with (set-progress value).

When this dialog has been canceled, the global parameter *progress-dialog-canceled* will be set to T. This allows to check for a canceled operation even after the body of this macro has been left.


Example:

 ...
 (with-progress-dialog ("Calculating..." max)
   (dotimes (n max)
     (calculate-secret-number n)
     (set-progress (1+ n))))
 (when *progress-dialog-canceled*
   (message-box :warning "Calculation canceled!"))
 ...
(event-filter object event function eat) *

If you want to be notified explicitely on certain events (like :key-press or :mouse-move) you can use this function for a specific widget. The event data (if present) will be passed to the given function.

Certain events will only be filtered if the given widget has input focus. You may also need to change the :focus-policy for widgets which generally don't get input focus.

Notice that you may need to set the :mouse-tracking property in order to receive mouse move events.

In order to install the event filter on the whole application, pass nil instead of the object argument.

If you set the eat argument to T, the event will be stopped ("eaten").


Example:

 ...
 (object lb '(label :mouse-tracking t))
 (event-filter lb 'mouse-move '(lambda (data) (print (getf data :x))))
 ...
(with-wait-cursor () &body body)

Shows a wait cursor during an operation that might take some time.

(save-screenshot object file)

To save the currently shown state of a widget directly to an image file in the PNG format (the file ending ".png" will be added automatically).

This function calls the Qt function grabWidget(), which creates a pixmap and paints the given widget (including all its child widgets) in it.

* ((% ...) args)

This template function can save you some typing in all the functions supporting it (marked with *).

If your code looks similar to this:

 (setpr w1 :text "one"
        w2 :text "two"
        w3 :text "three")

you can write instead:

 (setpr ((% :text %)
         w1 "one"
         w2 "two"
         w3 "three"))

(you get the concept).


Interactive development example

This is a small practical example of interactive development.

It shows how to interrupt the Qt event loop, making some changes, and continuing the event loop.

The example uses Emacs+Slime. To follow it, please open the example 2-calculator.lisp.

First we must ensure to pass :ev t to the with-qt macro:

 (with-qt (main :ev t) ...

Now we load the example in Slime, change the package and start event handling:

 (load "2-calculator.lisp")
 (in-package :calculator)
 (ev)

If you enter some number an click the [blah] button, the text shows up in a message-box:





Now let's change the function words-clicked so that the text will be shown in an error-message dialog instead.

In order to interrupt the Qt event loop, simply break the Slime loop (keys Ctrl C C).

Select [ABORT REQUEST] (pay attention not to select [ABORT]):





Next let's change the function words-clicked, replacing 'message-box' with 'error-message' and removing the following if statement, as shown below.

Don't forget to evaluate the changed function afterwards (keys Ctrl Alt X).

To restart the Qt event loop, simply do (ev).

In order to look if the changes really happened, click on [blah] again.
If all went well, you see:





Now you may break and continue on the REPL until some C/C++ crash...







Qt is a registered trademark of Trolltech AS