CPSC 427a: Object-Oriented Programming

Michael J. Fischer

Lecture 22
November 30, 2010

Graphical User Interfaces

User Interfaces Modern computer systems support two primary general-purpose user interaces: Command line: User input is via a command line typed at the keyboard. Output is character-based and goes to a physical or simulated typewriter-like terminal. Graphical User Interface (GUI): User input is via a pointing device (mouse), button clicks, and keyboard. Output is graphical and goes to a window on the screen.

Interfaces for C++

The C++ standard specifies a command line interface: iostream and associated packages. No similar standard exists for GUIs.

De facto GUI standards in the Linux world are GTK+ (used by the Gnome desktop) and Qt (used by the KDE desktop).

GTK+ is based on C; Qt is based on an extension of C++ and requires a special preprocessor.

gtkmm is a C++ wrapper on top of GTK+. Advantages: Provides type safety, polymorphism, and subclassing.
Provides a native interface to C++ code.
Disadvantages: Components not so well integrated.
Documentation spread between gtkmm, gtk+, and other components but improving.

Overall Structure of a GUI

A GUI manages one or more windows.

Each window displays one or more widgets.

These are objects that provide graphical and textual input and output to the program.

A GUI package such as gtkmm maintains a widget tree.

A widget controls a particular kind of user input or output.

Examples: label, text box, drawing area, button, scroll bar, etc.

Concurrency and Events

The central problem in building a GUI is handling concurrency.

Data arrives from multiple concurrent sources – mouse and keyboard, network, disk, other threads, etc.

We call the arrival of a piece of data an event.

For example, to have a good interactive feel, the GUI should respond to a mouse click event within milliseconds.

Event Loop

An event loop allows a single thread to manage a set of events.

At some level, the hardware or software polls for events.

When an event is detected, it is dispatched to an event handler.

The event handler either processes the event itself, queues a task for later processing, or spawns a thread to process it.

While the event thread is processing one event, no other events can be processed, so event handlers should be short.

Problem is to prevent a long-running low-priority event handler from delaying the handling of a high-priority event.

A GUI event structure

A GUI typically translates raw hardware events into semantically-meaningful software events.

For example, a mouse click at particular screen coordinates might turn into a button-pressed event for some widget in the GUI tree.

Several system layers may be involved in this translation, from the kernel processing of hardware interrupts at the bottom level, up through device drivers, windowing systems such as X, and finally a GUI frameworks such as GTK+.

Interface between user and system code

A major software challenge is how to design the interface between the GUI and the user code that ultimately deals with the events.

In a command-line interface, the user code is at the top level.

It connects to the lower layers through familiar function calls.

With a GUI, things are turned upside down.

Binding system calls to user functions

How can one write the GUI to call user functions that did not even exist when the GUI system itself was written?

The basic idea is that of interface.

Polymorphic binding

C++ virtual functions provide an elegant way to bind user code to an interface.

Of course, the actual binding occurs at run time through the use of type tags and the vtable as we have seen before.

Binding through callback registration

The user explicitly registers an event handler with the GUI by calling a special registration function.

This is sometimes called a callback mechanism since the user asks to be called back when an event occurs.

Callback using function pointers: GUI side

Callbacks can be done directly in C. Here’s the GUI code:

  1. Define the signature of the handler function:
    typedef void (*handler_t)(int, int);
  2. Declare a function pointer in which to save the handler:
    handler_t buttonPressHandlerPtr;
  3. Define a registration function:
    void systemRegister(int slot, handler_t f) {
      button_press_handler_ptr = f;
  4. Perform the callback:
    buttonPressHandlerPtr(23, 45);

Callback using function pointer: User side

Here’s how the user attaches a handler to the GUI:

  1. Create an event handler:
    void myHandler(int x, int y) {
      printf("My handler (%i, %i) called\n", x, y);
  2. Register the handler for event 17:
    systemRegister(17, myHandler);

Type safety

The above scheme does not generalize well to multiple events with different signatures.

The alternative is for systemRegister() to take a void* for its second argument and to cast function pointers before call them.

This is not type safe and can lead to subtle bugs if the wrong type of function is attached to a callback slot.

Signals and slots

Signals and slots is a more abstract way of linking events to handlers and can be implemented in a type safe way.

Several signals can be connected to the same slot, and several slots can be conencted to the same signal.

The gtkmm Framework

Structure of gtkmm gtkmm relies on several libraries and packages:

Compiling a gtkmm program

Many include files and libraries are needed to compile and build a gtkmm program.

A utility pkg-config is used to generate the necessary command line for the compiler.

> pkg-config gtkmm-2.4 --cflags  
-I/usr/include/gtkmm-2.4 -I/usr/lib64/gtkmm-2.4/include  
-I/usr/include/giomm-2.4 -I/usr/lib64/giomm-2.4/include  
-I/usr/include/pangomm-1.4 -I/usr/lib64/pangomm-1.4/include  
-I/usr/include/gtk-2.0 -I/usr/include/gtk-unix-print-2.0  
-I/usr/include/atkmm-1.6 -I/usr/include/gdkmm-2.4  
-I/usr/lib64/gdkmm-2.4/include -I/usr/include/glibmm-2.4  
-I/usr/lib64/glibmm-2.4/include -I/usr/include/glib-2.0  
-I/usr/lib64/glib-2.0/include -I/usr/include/sigc++-2.0  
-I/usr/lib64/sigc++-2.0/include -I/usr/include/cairomm-1.0  
-I/usr/lib64/cairomm-1.0/include -I/usr/include/pango-1.0  
-I/usr/include/cairo -I/usr/include/pixman-1  
-I/usr/include/freetype2 -I/usr/include/libpng12  
-I/usr/lib64/gtk-2.0/include -I/usr/include/atk-1.0

Linking a gtkmm program

pkg-config also generates the necessary linker flags.

> pkg-config gtkmm-2.4 --libs  
-lgtkmm-2.4 -latkmm-1.6 -lgdkmm-2.4 -lgiomm-2.4 -lpangomm-1.4  
-lgtk-x11-2.0 -lglibmm-2.4 -lcairomm-1.0 -lsigc-2.0 -lgdk-x11-2.0  
-latk-1.0 -lgio-2.0 -lpangoft2-1.0 -lgdk_pixbuf-2.0 -lpangocairo-1.0  
-lcairo -lpango-1.0 -lfreetype -lfontconfig -lgobject-2.0  
-lgmodule-2.0 -lgthread-2.0 -lrt -lglib-2.0

To use package config, use the backquote operator on the g++ command line:

Compiling: g++ -c pkg-config gtkmm-2.4 --cflags ...

Linking: g++ pkg-config gtkmm-2.4 --libs ...

Both: g++ pkg-config gtkmm-2.4 --cflags --libs ...

Using a GUI

The following steps are involved in creating a GUI using gtkmm:

  1. Initialize gtkmm.
  2. Create a window.
  3. Create and lay out widgets within the window.
  4. Connect user code to events.
  5. Show the widgets.
  6. Enter the main event loop.

The GUI then displays the window and waits for events.

When an event occurs, the corresponding user code is run.

When the event handler returns, the GUI waits for the next event.

Example: clock

The code example 22-clock is a significant extension of the clock example in the gtkmm tutorial book.

It illustrates many of the features of gtkmm.

Main program

#include <gtkmm/main.h>  
#include "clockwin.h"  
int main(int argc, char** argv)  
   Gtk::Main kit(argc, argv);  
   ClockWin win;        // custom window with several widgets  
   Gtk::Main::run(win); // start main event loop  
   return 0;