[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2. Getting Started


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.1 Installation

The first thing to do is to download the cl-cffi-gtk source and to install it. The latest version is available from the repository at http://github.com/crategus/cl-cffi-gtk. The cl-cffi-gtk library is ASDF installable and can be loaded with the command (asdf:load-system :cl-cffi-gtk) from the Lisp prompt. The library is developed with the Lisp SBCL 1.2.14 on a Linux system and GTK+ 3.16. Furthermore, the library runs successfully with Clozure Common Lisp and CLISP on Linux. The library compiles and the demos run with Windows 7.

The minimum version requirements are GTK+ 3.4 and GLIB 2.32.

GTK+ depends on the libraries GLib, GObject, GDK, GDK-Pixbuf, GIO, Pango, and Cairo. These libraries can be loaded separately with the following commands:

   (asdf:load-system 'cl-cffi-gtk-glib)
   (asdf:load-system 'cl-cffi-gtk-gobject)
   (asdf:load-system 'cl-cffi-gtk-gdk)
   (asdf:load-system 'cl-cffi-gtk-gdk-pixbuf)
   (asdf:load-system 'cl-cffi-gtk-gio)
   (asdf:load-system 'cl-cffi-gtk-pango)
   (asdf:load-system 'cl-cffi-gtk-cairo)

Please consult the ASDF documentation which is available at http://common-lisp.net/project/asdf/ for configuring ASDF to find your systems.

The cl-cffi-gtk library depends further on the following libraries:

CFFI

the Common Foreign Function Interface, purports to be a portable foreign function interface for Common Lisp. See http://common-lisp.net/project/cffi/.

Warning: Yout must use the version 0.11.2 or newer of the CFFI library. Older versions of CFFI are no longer compatible with the implementation of cl-cffi-gtk.

Trivial-Garbage

provides a portable API to finalizers, weak hash-tables and weak pointers on all major CL implementations. See http://common-lisp.net/project/trivial-garbage/.

Iterate

is a lispy and extensible replacement for the LOOP macro. See http://common-lisp.net/project/iterate/.

Bordeaux-Threads

lets you write multi-threaded applications in a portable way. See http://common-lisp.net/project/bordeaux-threads/.

Closer-MOP

Closer to MOP is a compatibility layer that rectifies many of the absent or incorrect MOP features as detected by MOP Feature Tests. See http://common-lisp.net/project/closer/closer-mop.html.

Information about the installation can be obtained with the function cl-cffi-gtk-build-info. This is an example for the output, when calling the function from the Lisp prompt after loading the library:

* (cl-cffi-gtk-build-info)

cl-cffi-gtk version: 1.0.0
cl-cffi-gtk build date: 20:55 1/5/2016
GTK+ version: 3.16.7
GLIB version: 2.46.1
GDK-Pixbuf version: 2.32.1
Pango version: 1.36.8
Cairo version: 1.14.2
Machine type: X86-64
Machine version: Intel(R) Core(TM) i5-4210U CPU  1.70GHz
Software type: Linux
Software version: 4.2.0-22-generic
Lisp implementation type: SBCL
Lisp implementation version: 1.2.14.debian

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.2 A Simple Window

figures/simple-window202x229

Figure 2.1: A Simple Window

The cl-cffi-gtk source distribution contains the complete source to all of the examples used in this tutorial. To begin the introduction to GTK+, the output of the simplest program possible is shown in figure-simple-window.

The program creates a 200 x 200 pixel window. In this case the window has the default title "sbcl". The window can be sized and moved. First in example-simple-window-C the C program of the GTK+ 3 Reference Manual is presented to show the close connection between the C library and the implementation of the Lisp binding. The code of the Lisp program is shown in example-simple-window.

Example 2.1: A simple window in the programming language C

#include <gtk/gtk.h>

int main (int argc, char *argv[])
{
  GtkWidget *window;

  gtk_init (&argc, &argv);

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

  g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);

  gtk_widget_show (window);

  gtk_main ();

  return 0;
}

Example 2.2: A Simple Window in the programming language Lisp

(asdf:load-system :cl-cffi-gtk)

(defpackage :gtk-tutorial
  (:use :gtk :gdk :gdk-pixbuf :gobject
   :glib :gio :pango :cairo :common-lisp))

(in-package :gtk-tutorial)

(defun example-simple-window ()
  (within-main-loop
    (let (;; Create a toplevel window.
          (window (gtk-window-new :toplevel)))
      ;; Signal handler for the window to handle the signal "destroy".
      (g-signal-connect window "destroy"
                        (lambda (widget)
                          (declare (ignore widget))
                          (leave-gtk-main)))
      ;; Show the window.
      (gtk-widget-show-all window))))

The first four lines of code load the cl-cffi-gtk library and define an own package :gtk-tutorial for the example program. The package :gtk-tutorial includes the symbols from the packages :gtk for GTK, :gdk for GDK, :gdk-pixbuf for GDK-Pixbuf, :gobject for GObject, :glib for GLib, :gio for GIO, :pango for Pango, and :cairo for Cairo. Most of the symbols of the included packages are not needed for the first simple examples, but we include all packages so later no symbol is missing. In further examples of this tutorial these first lines of code are omitted.

The macro within-main-loop is a wrapper around a GTK+ program. The functionality of the macro corresponds to the C functions gtk_init() and gtk_main() which initialize and start a GTK+ program. Both functions have corresponding Lisp functions. The function gtk_main() is exported as the Lisp function gtk-main. The corresponding Lisp function to gtk_init() is called internally when loading the library, but is not exported.

Already in this simple example we have connected a signal to the created window. More about signals and callback functions follows in Introduction to Signals and Callbacks. The signal "destroy" is emitted when the user quits the window. The function g-signal-connect connects a Lisp lambda function to this signal, which calls the function leave-gtk-main to destroy the GTK+ main loop. Like the macro within-main-loop the function leave-gtk-main is special for the Lisp binding. It calls internally the C function gtk_main_quit(), but does some extra work to finish the Lisp program. The C function gtk_main_quit() is available in the Lisp binding as gtk-main-quit, but do not call this function in your code.

Only two further functions are needed in this simple example. The window is created with the function gtk-window-new. The keyword :toplevel tells GTK+ to create a toplevel window. The second function gtk-widget-show-all displays the new window.

In addition to the function gtk-widget-show-all the function gtk-widget-show is available. It only displays the widget, which is the argument to the function. The function gtk-widget-show-all displays the window and all including child widgets. For the first simple window this makes no difference, because the window has not child widgets.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.3 More about the Lisp binding to GTK+

figures/getting-started252x229

Figure 2.2: Getting started

figure-getting-started and example-getting-started show a second implementation of the simple program discussed in A Simple Window. The second implementation uses the fact, that all GTK+ widgets are internally represented in the Lisp binding through a Lisp class. The Lisp class gtk-window represents the required window, which corresponds to the C class GtkWindow. An instance of the Lisp class gtk-window can be created with the function make-instance. Furthermore, the slots of the window class can be given new values to overwrite the default values. These slots represent the properties of the C classes. In addition an instance has all properties of the inherited classes. The object hierarchy in the cl-cffi-gtk API documentation shows, that the class gtk-window inherits all properties of the classes gtk-widget, gtk-container, and gtk-bin.

In example-getting-started the property "type" with the keyword :toplevel creates again a toplevel window. In addition a title is set assigning the string "Getting started" to the property "title" and the width of the window is a little enlarged assigning the value 250 to the property "default-width". The result of the example program is shown in figure-getting-started.

The keyword :toplevel is one of the values of the enumeration type GtkWindowType in C. In the Lisp binding this enumeration is implemented as gtk-window-type with the two possible keywords :toplevel for GTK_WINDOW_TOPLEVEL and :popup for GTK_WINDOW_POPUP. Most windows are of the type :toplevel. Windows with this type are managed by the window manager and have a frame by default. Windows with type :popup are ignored by the window manager and are used to implement widgets such as menus or tooltips.

Example 2.3: Getting Started

(defun example-getting-started ()
  (within-main-loop
    (let (;; Create a toplevel window with a title and a default width.
          (window (make-instance 'gtk-window
                                 :type :toplevel
                                 :title "Getting started"
                                 :default-width 250)))
      ;; Signal handler for the window to handle the signal "destroy".
      (g-signal-connect window "destroy"
                        (lambda (widget)
                          (declare (ignore widget))
                          (leave-gtk-main)))
      ;; Show the window.
      (gtk-widget-show-all window))))

The example-getting-started shows, that the Lisp function gtk-window-new is not really needed. The function gtk-window-new is internally implemented in the Lisp binding simply as:

(defun gtk-window-new (type)
  (make-instance 'gtk-window :type type))

To set the title of the window or to change the default width of a window the C library knows accessor functions to set the corresponding values. In C the title of the window is set with the function gtk_window_set_title(). The corresponding Lisp function is gtk-window-title. Accordingly, the default width of the window can be set in C with the function gtk_window_set_default_size(), which sets both the default width and the default height. In Lisp this function is named gtk-window-default-size.

At last, in Lisp it is possible to use the accessors of the slots to get or set the value of a widget property. The properties "default-width" and "default-height" of the Lisp class gtk-window have the Lisp accessor functions gtk-window-default-width and gtk-window-default-height. With these accessor functions the C function gtk_window_set_default_size() is implemented the following way in the Lisp library as the generic function (setf gtk-window-default-size):

(defgeneric (setf gtk-window-default-size) (size window)
  (:method (size (window gtk-window))
    (destructuring-bind (width height) size
      (values (setf (gtk-window-default-width window) width)
              (setf (gtk-window-default-height window) height)))))

As a second example the Lisp implementation of the C function gtk_window_get_default_size() is shown:

(defgeneric gtk-window-default-size (window)
  (:method ((window gtk-window))
    (values (gtk-window-default-width window)
            (gtk-window-default-height window))))

In distinction to the C function gtk_window_get_default_size(), which is implemented as

void gtk_window_get_default_size (GtkWindow *window,
                                  gint      *width,
                                  gint      *height)

the Lisp implementation does not modify the arguments width and height, but returns the values.

Note the naming conventions for the translation of C accessor functions to Lisp generic functions. C reader functions with the name gtk_<class>_get_<property> get the Lisp name gtk-<class>-<property> and the C writer functions gtk_<class>_set_<property> are replaced by the corresponding (setf gtk-<class>-<property>) functions. That is, for example, to get the property "title" of a gtk-window use the generic function gtk-window-title and to set the title use the generic function (setf gtk-window-title).


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.4 Hello World in GTK+

figures/hello-world252x80

Figure 2.3: Hello World

Now a program with a button is presented. The output is shown in figure-hello-world. Again the C program from the GTK+ 3 Reference Manual is shown first in example-hello-world-C to learn more about the differences between a C and a Lisp implementation.

Example 2.4: Hello World in the programming language C

#include <gtk/gtk.h>

/* This is a callback function. The data arguments are ignored
 * in this example. More on callbacks below. */
static void hello( GtkWidget *widget, gpointer data )
{
    g_print ("Hello World\n");
}

static gboolean delete_event( GtkWidget *widget,
                              GdkEvent  *event,
                              gpointer   data )
{
    /* If you return FALSE in the "delete-event" signal handler,
     * GTK will emit the "destroy" signal. Returning TRUE means
     * you don't want the window to be destroyed.
     * This is useful for popping up 'are you sure you want to quit?'
     * type dialogs. */

    g_print ("delete event occurred\n");

    /* Change TRUE to FALSE and the main window will be destroyed with
     * a "delete-event". */

    return TRUE;
}

/* Another callback */
static void destroy( GtkWidget *widget, gpointer data )
{
    gtk_main_quit ();
}

int main( int argc, char *argv[] )
{
    /* GtkWidget is the storage type for widgets */
    GtkWidget *window;
    GtkWidget *button;

    /* This is called in all GTK applications. Arguments are parsed
     * from the command line and are returned to the application. */
    gtk_init (&argc, &argv);

    /* create a new window */
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

    /* When the window is given the "delete-event" signal (this is given
     * by the window manager, usually by the "close" option, or on the
     * titlebar), we ask it to call the delete_event () function
     * as defined above. The data passed to the callback
     * function is NULL and is ignored in the callback function. */
    g_signal_connect (window, "delete-event",
		      G_CALLBACK (delete_event), NULL);

    /* Here we connect the "destroy" event to a signal handler.
     * This event occurs when we call gtk_widget_destroy() on the window,
     * or if we return FALSE in the "delete-event" callback. */
    g_signal_connect (window, "destroy",
		      G_CALLBACK (destroy), NULL);

    /* Sets the border width of the window. */
    gtk_container_set_border_width (GTK_CONTAINER (window), 10);

    /* Creates a new button with the label "Hello World". */
    button = gtk_button_new_with_label ("Hello World");

    /* When the button receives the "clicked" signal, it will call the
     * function hello() passing it NULL as its argument.  The hello()
     * function is defined above. */
    g_signal_connect (button, "clicked",
		      G_CALLBACK (hello), NULL);

    /* This will cause the window to be destroyed by calling
     * gtk_widget_destroy(window) when "clicked".  Again, the destroy
     * signal could come from here, or the window manager. */
    g_signal_connect_swapped (button, "clicked",
			      G_CALLBACK (gtk_widget_destroy),
                              window);

    /* This packs the button into the window (a gtk container). */
    gtk_container_add (GTK_CONTAINER (window), button);

    /* The final step is to display this newly created widget. */
    gtk_widget_show (button);

    /* and the window */
    gtk_widget_show (window);

    /* All GTK applications must have a gtk_main(). Control ends here
     * and waits for an event to occur (like a key press or
     * mouse event). */
    gtk_main ();

    return 0;
}

Now, the Lisp implementation is presented in example-hello-world. One difference is, that the function make-instance is used to create the window and the button. Another point is, that the definition of separate callback functions is avoided. The callback functions are short, implemented through Lisp lambda functions and are passed as the third argument to the function g-signal-connect. More about signals and callback functions follows in Introduction to Signals and Callbacks.

In example-hello-world a border with a width of 12 is added to the window setting the property "border-width" when creating the window with the function make-instance. The C implementation uses the function gtk_container_set_border_width() which is available in Lisp as the accessor gtk-container-border-width. The property "border-width" is inherited from the C class GtkContainer, which in the Lisp library is represented through the Lisp class gtk-container. Therefore, the accessor function has the prefix gtk_container in C and gtk-container in Lisp. A full list of properties of GtkContainer is available at gtk-container.

Example 2.5: Hello World in the programming language Lisp

(defun example-hello-world ()
  (within-main-loop
    (let (;; Create a toplevel window, set a border width.
          (window (make-instance 'gtk-window
                                 :type :toplevel
                                 :title "Hello World"
                                 :default-width 250
                                 :border-width 12))
          ;; Create a button with a label.
          (button (make-instance 'gtk-button :label "Hello World")))
      ;; Signal handler for the button to handle the signal "clicked".
      (g-signal-connect button "clicked"
                        (lambda (widget)
                          (declare (ignore widget))
                          (format t "Hello world.~%")
                          (gtk-widget-destroy window)))
      ;; Signal handler for the window to handle the signal "destroy".
      (g-signal-connect window "destroy"
                        (lambda (widget)
                          (declare (ignore widget))
                          (leave-gtk-main)))
      ;; Signal handler for the window to handle the signal "delete-event".
      (g-signal-connect window "delete-event"
                        (lambda (widget event)
                          (declare (ignore widget event))
                          (format t "Delete Event Occured.~%")
                          +gdk-event-stop+))
      ;; Put the button into the window.
      (gtk-container-add window button)
      ;; Show the window and the button.
      (gtk-widget-show-all window))))

An attentive reader notes that in distinction to the C implementation the function gtk-widget-show is not called for every single widget, which are in example-hello-world the window and the button. Instead the function gtk-widget-show-all is used to display the window with all including widgets.

Three more functions are introduced in example-hello-world. The function gtk-widget-destroy takes as an argument any widget and destroys it. In the above example this function is called by the signal handler of the button. When the button is clicked by the user, the signal "clicked" is catched by the signal handler, which causes a call of the function gtk-widget-destroy for the toplevel window. Now the toplevel window receives the signal "destroy", which is handled by a signal handler of the toplevel window. This signal handler calls the function leave-gtk-main, which stops the event loop and finishes the application.

A second signal handler is connected to the toplevel window to catch the signal "delete-event". The signal "delete-event" occurs, when the user or the window manager tries to close the window. In this case, the signal handler prints a message on the console. Because the value of the constant +gdk-event-stop+ is true the signal handler stops the handling of the signal and the window is not closed, but the execution of the application is continued. To close the window, the user has to press the button in this example. There is a second constant +gdk-event-prograte+ which is used to make sure that the propagation of the event is continued.

At last, the function gtk-container-add is used to put the button into the toplevel window. Packing Widgets shows how it is possible to put more than one widget into a window.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.5 Introduction to Signals and Callbacks

GTK+ is an event driven toolkit, which means GTK+ will sleep until an event occurs and control is passed to the appropriate function. This passing of control is done using the idea of "signals". (Note that these signals are not the same as the Unix system signals, and are not implemented using them, although the terminology is almost identical.) When an event occurs, such as the press of a mouse button, the appropriate signal will be "emitted" by the widget that was pressed. This is how GTK+ does most of its useful work. There are signals that all widgets inherit, such as "destroy", and there are signals that are widget specific, such as "toggled" on a toggle button.

To make a button perform an action, a signal handler is set up to catch these signals and to call the appropriate function. This is done in the C GTK+ library by using a function such as

gulong g_signal_connect( gpointer      *object,
                         const gchar   *name,
                         GCallback     func,
                         gpointer      func_data );

where the first argument is the widget which will be emitting the signal, and the second the name of the signal to catch. The third is the function to be called when the signal is caught, and the fourth, the data to have passed to this function.

The function specified in the third argument is called a "callback function", and is for a C program of the form

void callback_func( GtkWidget *widget,
                    ... /* other signal arguments */
                    gpointer   callback_data );

where the first argument will be a pointer to the widget that emitted the signal, and the last a pointer to the data given as the last argument to the C function g_signal_connect() as shown above. Note that the above form for a signal callback function declaration is only a general guide, as some widget specific signals generate different calling parameters.

This mechanism is realized in Lisp with a similar function g-signal-connect which has the arguments widget, name, and func. In distinction from C the Lisp function g-signal-connect has not the argument func_data. The functionality of passing data to a callback function can be realized with the help of a lambda function in Lisp.

As an example the following code shows a typical C implementation which is used in the Hello World program.

g_signal_connect (window, "destroy", G_CALLBACK (destroy), NULL);

This is the corresponding callback function which is called when the event "destroy" occurs.

static void destroy (GtkWidget *widget, gpointer data)
{
    gtk_main_quit ();
}

In the corresponding Lisp implementation we simply declare a lambda function as a callback function which is passed as the third argument.

(g-signal-connect window "destroy"
                  (lambda (widget)
                    (declare (ignore widget))
                    (leave-gtk-main)))

If it is necessary to have a separate function which needs user data, the following implementation is possible

(defun separate-event-handler (widget arg1 arg2 arg3)
  [ here is the code of the event handler ] )
(g-signal-connect window "destroy"
                  (lambda (widget)
                    (separate-event-handler widget arg1 arg2 arg3)))

If no extra data is needed, but the callback function should be separated out than it is also possible to implement something like

(g-signal-connect window "destroy" #'separate-event-handler)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.6 An Upgraded Hello World

figures/upgraded-hello-world252x104

Figure 2.4: Upgraded Hello World

figure-upgraded-hello-world and example-upgraded-hello-world show a slightly improved Hello World with better examples of callbacks. This will also introduce the next topic, packing widgets. First, the C program is shown in example-upgraded-hello-world-C.

Example 2.6: An upgraded Hello World in the programming language C

#include <gtk/gtk.h>

/* Our new improved callback.  The data passed to this function
 * is printed to stdout. */
static void callback( GtkWidget *widget, gpointer data )
{
    g_print ("Hello again - %s was pressed\n", (gchar *) data);
}

/* another callback */
static gboolean delete_event( GtkWidget *widget,
                              GdkEvent  *event,
                              gpointer   data )
{
    gtk_main_quit ();
    return FALSE;
}

int main( int argc, char *argv[] )
{
    /* GtkWidget is the storage type for widgets */
    GtkWidget *window;
    GtkWidget *button;
    GtkWidget *box1;

    /* This is called in all GTK applications. Arguments are parsed
     * from the command line and are returned to the application. */
    gtk_init (&argc, &argv);

    /* Create a new window */
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

    /* This is a new call, which just sets the title of our
     * new window to "Hello Buttons!" */
    gtk_window_set_title (GTK_WINDOW (window), "Hello Buttons!");

    /* Here we just set a handler for delete_event that immediately
     * exits GTK. */
    g_signal_connect (window, "delete-event",
		      G_CALLBACK (delete_event), NULL);

    /* Sets the border width of the window. */
    gtk_container_set_border_width (GTK_CONTAINER (window), 10);

    /* We create a box to pack widgets into.  This is described in detail
     * in the "packing" section. The box is not really visible, it
     * is just used as a tool to arrange widgets. */
    box1 = gtk_hbox_new (FALSE, 0);

    /* Put the box into the main window. */
    gtk_container_add (GTK_CONTAINER (window), box1);

    /* Creates a new button with the label "Button 1". */
    button = gtk_button_new_with_label ("Button 1");

    /* Now when the button is clicked, we call the "callback" function
     * with a pointer to "button 1" as its argument */
    g_signal_connect (button, "clicked",
		      G_CALLBACK (callback), (gpointer) "button 1");

    /* Instead of gtk_container_add, we pack this button into the invisible
     * box, which has been packed into the window. */
    gtk_box_pack_start (GTK_BOX(box1), button, TRUE, TRUE, 0);

    /* Always remember this step, this tells GTK that our preparation for
     * this button is complete, and it can now be displayed. */
    gtk_widget_show (button);

    /* Do these same steps again to create a second button */
    button = gtk_button_new_with_label ("Button 2");

    /* Call the same callback function with a different argument,
     * passing a pointer to "button 2" instead. */
    g_signal_connect (button, "clicked",
		      G_CALLBACK (callback), (gpointer) "button 2");

    gtk_box_pack_start(GTK_BOX (box1), button, TRUE, TRUE, 0);

    /* The order in which we show the buttons is not really important, but I
     * recommend showing the window last, so it all pops up at once. */
    gtk_widget_show (button);

    gtk_widget_show (box1);

    gtk_widget_show (window);

    /* Rest in gtk_main and wait for the fun to begin! */
    gtk_main ();

    return 0;
}

The Lisp implementation in example-upgraded-hello-world tries to be close to the C program. Therefore, the window and the box are created with the functions gtk-window-new and gtk-box-new. Various properties like the title of the window, the default size or the border width are set with the functions gtk-window-title, gtk-window-default-size and gtk-container-border-width. As described for example-hello-world the function gtk-widget-show-all is used to display the window including all child widgets.

One main difference of the Lisp implementation is the use of the function gtk-box-new with an argument :horizontal to create a horizontal box. The GtkHBox widget which is used in the GTK+ 3 Reference Manual is deprecated and is replaced by GtkBox with the property "orientation". More about boxes and their usages follows in Packing Widgets.

Example 2.7: Upgraded Hello world

(defun example-upgraded-hello-world ()
  (within-main-loop
    (let ((window (gtk-window-new :toplevel))
          (box (gtk-box-new :horizontal 6))
          (button  nil))
      (g-signal-connect window "destroy"
                        (lambda (widget)
                          (declare (ignore widget))
                          (leave-gtk-main)))
      (setf (gtk-window-title window) "Hello Buttons")
      (setf (gtk-window-default-size window) '(250 75))
      (setf (gtk-container-border-width window) 12)
      (setf button (gtk-button-new-with-label "Button 1"))
      (g-signal-connect button "clicked"
                        (lambda (widget)
                          (declare (ignore widget))
                          (format t "Button 1 was pressed.~%")))
      (gtk-box-pack-start box button :expand t :fill t :padding 0)
      (setf button (gtk-button-new-with-label "Button 2"))
      (g-signal-connect button "clicked"
                        (lambda (widget)
                          (declare (ignore widget))
                          (format t "Button 2 was pressed.~%")))
      (gtk-box-pack-start box button :expand t :fill t :padding 0)
      (gtk-container-add window box)
      (gtk-widget-show-all window))))

The second implementation in example-upgraded-hello-world-2 makes more use of a Lisp style. The window is created with the Lisp function make-instance. All desired properties of the window are initialized by assigning values to the slots of the classes gtk-window and gtk-box. The Lisp implementation uses a lot keywords arguments with default values for long lists of arguments. In example-upgraded-hello-world-2 the keyword arguments expand, fill, and padding of the function gtk-box-pack-start take their default values. In future examples of this tutorial the style shown in example-upgraded-hello-world-2 is preferred. Furthermore, the C code is no longer presented for comparison.

Example 2.8: Second implementation of an Upgraded Hello World

(defun example-upgraded-hello-world-2 ()
  (within-main-loop
    (let ((window (make-instance 'gtk-window
                                 :type :toplevel
                                 :title "Hello Buttons"
                                 :default-width 250
                                 :default-height 75
                                 :border-width 12))
          (box (make-instance 'gtk-box
                              :orientation :horizontal
                              :spacing 6)))
      (g-signal-connect window "destroy"
                        (lambda (widget)
                          (declare (ignore widget))
                          (leave-gtk-main)))
      (let ((button (gtk-button-new-with-label "Button 1")))
        (g-signal-connect button "clicked"
                          (lambda (widget)
                            (declare (ignore widget))
                            (format t "Button 1 was pressed.~%")))
        (gtk-box-pack-start box button))
      (let ((button (gtk-button-new-with-label "Button 2")))
        (g-signal-connect button "clicked"
                          (lambda (widget)
                            (declare (ignore widget))
                            (format t "Button 2 was pressed.~%")))
        (gtk-box-pack-start box button))
      (gtk-container-add window box)
      (gtk-widget-show-all window))))

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

2.7 Drawing

figures/drawing278x255

Figure 2.5: Drawing in response to input

Many widgets, like buttons, do all their drawing themselves. You just tell them the label you want to see, and they figure out what font to use, draw the button outline and focus rectangle, etc. Sometimes, it is necessary to do some custom drawing. In that case, a gtk-drawing-area might be the right widget to use. It offers a canvas on which you can draw by connecting to the "draw" signal.

The contents of a widget often need to be partially or fully redrawn, e. g. when another window is moved and uncovers part of the widget, or when the window containing it is resized. It is also possible to explicitly cause part or all of the widget to be redrawn, by calling the function gtk-widget-queue-draw or its variants. GTK+ takes care of most of the details by providing a ready-to-use cairo context to the "draw" signal handler.

The following example shows a "draw" signal handler. It is a more complicated than the previous examples, since it also demonstrates input event handling by means of "button-press" and "motion-notify" handlers.

Example 2.9: Drawing in response to input

(let ((surface nil))
  (defun example-drawing ()
    (within-main-loop
      (let ((window (make-instance 'gtk-window
                                   :type :toplevel
                                   :title "Example Drawing"))
            (frame (make-instance 'gtk-frame
                                  :shadow-type :in))
            (area (make-instance 'gtk-drawing-area
                                 :width-request 250
                                 :height-request 200)))
        (g-signal-connect window "destroy"
                          (lambda (widget)
                            (declare (ignore widget))
                            (leave-gtk-main)))
        ;; Signals used to handle the backing surface
        (g-signal-connect area "draw"
           (lambda (widget cr)
             (declare (ignore widget))
             (let ((cr (pointer cr)))
               (cairo-set-source-surface cr surface 0.0 0.0)
               (cairo-paint cr)
               (cairo-destroy cr)
               +gdk-event-propagate+)))
        (g-signal-connect area "configure-event"
           (lambda (widget event)
             (declare (ignore event))
             (when surface
               (cairo-surface-destroy surface))
             (setf surface
                   (gdk-window-create-similar-surface
                                   (gtk-widget-window widget)
                                   :color
                                   (gtk-widget-get-allocated-width widget)
                                   (gtk-widget-get-allocated-height widget)))
             ;; Clear surface
             (let ((cr (cairo-create surface)))
               (cairo-set-source-rgb cr 1.0 1.0 1.0)
               (cairo-paint cr)
               (cairo-destroy cr))
             (format t "leave event 'configure-event'~%")
             +gdk-event-stop+))
        ;; Event signals
        (g-signal-connect area "motion-notify-event"
           (lambda (widget event)
             (format t "MOTION-NOTIFY-EVENT ~A~%" event)
             (when (member :button1-mask (gdk-event-motion-state event))
               (let ((cr (cairo-create surface))
                     (x (gdk-event-motion-x event))
                     (y (gdk-event-motion-y event)))
                 (cairo-rectangle cr (- x 3.0) (- y 3.0) 6.0 6.0)
                 (cairo-fill cr)
                 (cairo-destroy cr)
                 (gtk-widget-queue-draw-area widget
                                             (truncate (- x 3.0))
                                             (truncate (- y 3.0))
                                             6
                                             6)))
             ;; We have handled the event, stop processing
             +gdk-event-stop+))
        (g-signal-connect area "button-press-event"
           (lambda (widget event)
             (format t "BUTTON-PRESS-EVENT ~A~%" event)
             (if (eql 1 (gdk-event-button-button event))
                 (let ((cr (cairo-create surface))
                       (x (gdk-event-button-x event))
                       (y (gdk-event-button-y event)))
                   (cairo-rectangle cr (- x 3.0) (- y 3.0) 6.0 6.0)
                   (cairo-fill cr)
                   (cairo-destroy cr)
                   (gtk-widget-queue-draw-area widget
                                               (truncate (- x 3.0))
                                               (truncate (- y 3.0))
                                               6
                                               6))
                 ;; Clear surface
                 (let ((cr (cairo-create surface)))
                   (cairo-set-source-rgb cr 1.0 1.0 1.0)
                   (cairo-paint cr)
                   (cairo-destroy cr)
                   (gtk-widget-queue-draw widget)))))
        (gtk-widget-add-events area
                               '(:button-press-mask
                                 :pointer-motion-mask))
        (gtk-container-add frame area)
        (gtk-container-add window frame)
        (gtk-widget-show-all window)))))

[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by Crategus on January, 10 2016 using texi2html 1.76.