The next discrete API layer is the GTK+ Drawing Kit (GDK), a portable abstraction of the X Window System. GDK makes use of GLib's containers and portability wrappers to act as the foundation upon which GTK+ rests.
The GNOME GUI is built on the GIMP Toolkit (GTK+). The GIMP (GNU Image Manipulation Program) is an open-source graphics manipulation application along the lines of Adobe Photoshop. GTK+ was created as a widget library for GIMP. Many other people started using it for their own projects, and over time it grew in popularity to become one of the most beloved widget kits around.
GTK+ is divided into two major components: GDK and the widget set. GDK provides the basic services and resources that the widgets rely on, such as event propagation, graphics rendering, window creation, mouse cursors, keyboard shortcuts, and drag-and-drop. GDK is a wrapper around the X Window System and is (in theory) the sole access point between GTK+ and the X server. GDK is the sandbox that GTK+ plays in. This abstraction makes it possible to port GTK+ to another windowing system: If you can port GDK, then GTK+ will follow with very little effort. This is a proven theory. A version of GTK+ has already been ported to Microsoft Windows!
Figure 2.4 lays all of this out, plus gnome-libs and its core dependencies, in a hierarchical chart.
If you're already familiar with X11 programming, you will notice that many of the abstractions in GDK look very similar to their X counterparts. Each server-side resource in X typically has a similarly named structure in GDK. For example, the Pixmap structure in Xlib has a matching GdkPixmap structure with the same properties, plus a few extra GDK-specific fields.
The function names are also very similar in most cases. For example, the gdk_window_set_background( ) function is a wrapper around the Xlib function XSetWindowBackground( ). Often GDK does little more than provide defaults for rarely used parameters in Xlib functions. GDK mimics the Xlib function names where it can, and it tries to lend consistency and ease of use in places where the Xlib function names don't match the concepts behind GDK, such as the gdk_window_set_title( ) function, which is a wrapper around the Xlib function XmbSetWMProperties( ).
GDK also provides wrappers around other facets of the X Window System, such as keyboard and mouse interaction, drag-and-drop, and the many graphics functions of Xlib. Other aspects of GDK aren't so thin, such as the double-buffered drawing API GdkRGB and the event system. We'll explore GDK graphics in Chapter 10 and the event system in the next section.
The GDK event system is, as we've learned, a wrapper around the X protocol's event queue. When something happens to a window on the X server (each of which is encapsulated on the client side by the GdkWindow structure), the X server sends an appropriate X event to the client. This event travels along a socket connection from the X server to the client, across the Internet, across the network, or just "across" your workstation if everything is running locally.
As part of its initialization routine, GDK installs a hook into GLib's main loop that will monitor the X socket for new X events. When something shows up-perhaps the user has just resized your GNOME application's main window or typed something on the keyboard-the main loop triggers GDK's event-processing callback function. This callback pulls the queued events off the socket one by one and translates them into GdkEvent structures. GDK stores the GdkEvent instances inside a queue of its own, in memory, ready to be pulled out and processed at the widget level by GTK+. Some of these events will end up triggering GTK+ signals (see Section 2.3.5).
Figure 2.5 illustrates this process. The gdk_event_get( ) function pulls X events from the (possibly remote) X server into its own queue of GdkEvent elements. As the main loop cycles along, the gtk_main_do_event( ) function pulls single events off the queue and sends them to the appropriate handler(s), implied in Figure 2.5 by the theoretical widget_event_handler( ) function. All of this is transparent to you, the developer, except for the handler function at the end of the line.
GDK's event system is polymorphic to some extent, just like X's native event system. A generic event is expressed as a GdkEventAny structure; all events can be cast to this data type. A GdkEvent is actually a union of every possible type of GDK events, including GdkEventKey, GdkEventButton, GdkEventMotion, GdkEventExpose, and GdkEventFocus. When you set up each event callback in your application, you have the choice to express the event as a generic GdkEvent if the handler function expects more than one type of event, or as a specific event type for a specialized event handler. For example, if you wanted to write a handler for only keyboard events, you could specify a GdkEventKey parameter directly in the function prototype for that handler.
The GDK event system is a fairly elegant abstraction between the raw Xlib interface and the platform-independent GTK+ layer. We'll learn more about events later, as the issue comes up, but it certainly wouldn't hurt for you to do some investigation on your own. In particular, you should explore glib/gmain.c (the code that drives the main loop), gtk+/gdk/gdkevents.c (managing the GDK event queue and translating X events into GdkEvent instances), and gtk+/gtk/gtkmain.c (processing and dispatching the GDK event queue into GTK+ proper) in the GLib and GTK+ source code.
To move some of the drawing logic from the client to the remote X server, the X Window System defines various server-side resources, as we discussed briefly in Section 1.3.5. Xlib supplies numerous functions to manipulate these remote resources. As it must with all other useful parts of the Xlib API, GDK wraps these functions, thus completely insulating you from Xlib. For the most part, these GDK wrappers are fairly thin; most of the original X concepts are carried through intact to GDK.
Some of these resources are basic geometric objects, called drawing primitives. GDK supports five point-based drawing primitives: points, lines, rectangles, polygons, and arcs. Each of these primitives consists of one or more coordinate points. To render them, you send the critical points-for example, the four corners of the rectangle-and the X server connects the dots, so to speak. This saves you the trouble and bandwidth of calculating and sending every pixel in a line. In addition, certain video cards may have access to optimized hardware line-drawing routines, which would be unusable if you had to send each pixel separately.
Unlike a window resource, these five drawing primitives are not persistent objects. You don't create them on the server and move them around. They are more like special drawing commands that the X server is guaranteed to know, more like lines on a chalkboard that you can erase and redraw, and less like pieces of paper on your desk that you can slide around and manipulate.
GDK also has two other types of (non-point-based) drawing primitives: text and pixmaps. The text primitives allow you to specify a text string, a font, and a coordinate location at which to render the text. The pixmap primitives let you copy all or parts of a graphical image from one drawing surface to another. This makes double buffering much easier: You render your drawings to an off-screen pixmap and then copy the finished product to the screen.
To supplement the drawing primitives, the X Window System introduces the concept of a graphics context resource on the X server. The graphics context is sort of a catchall container for keeping track of various common drawing properties, such as foreground and background colors, fonts, stipple patterns for area fills, line-drawing attributes, clipping masks, and more. Rather than sending all this information each time you draw a line or render text, you can specify the drawing parameters in the graphics context, and the X server will use those values by default.
Another thing GDK adds over the raw Xlib functions is reference counting. Rather than explicitly creating and destroying graphical objects and having to worry about cleaning up shared instances of those objects, you can use GDK's reference-counting mechanism. Each time a part of the application needs to use a particular graphical object or resource, you can reference it. Later, when that section of code is done with the object, you unreference it. GDK promises that unreferencing an object with multiple reference counts will not destroy it until the reference count reaches zero (although in some cases, such as window resources, these objects may be destroyed explicitly, forcibly, thus overriding the reference count). In essence, when you reference an object, you ensure that it will stay around for as long as you need it to, and that no one else will destroy it from under you. The last owner to let go of the object will trigger its destruction.
A good example of the need for reference counting is GdkPixmap, GDK's wrapper around the X pixmap. A GdkPixmap can hold only one graphical image, but that image may be used in multiple places in the application, especially in the case of a graphical icon. The icon may turn up in the application's dialog boxes, its About box, and even its main window. Rather than allocating separate memory for each instance, you can allocate a single GdkPixmap and reference it from each window that's using it.
The main application would create it with gdk_pixmap_new( ) (which implicitly references it). When a second window comes up that also uses the pixmap, it calls gdk_pixmap_ref( ), increasing the reference count to 2. When the second window closes, it calls gdk_pixmap_unref( ), dropping the reference count back to 1. Later, when the application closes down, it unreferences the GdkPixmap again, dropping the reference count to 0, at which point the GdkPixmap implicitly destroys itself. If the application had destroyed its main window while the second window was still open, the second window would have been left with the only active reference, and the GdkPixmap would not have destroyed itself until that window had closed too.
GDK also uses reference counting for windows, graphics contexts, color maps, visuals, and even fonts. Anything that refers to a remote graphical resource that needs to be cleaned up is managed with references in GDK. Reference counting is a simple, powerful tool for resource sharing, and its use is not limited to GDK. GTK+, GNOME, and the gdk-pixbuf image-loading library also make extensive use of it.