Creating a Composite widget


One type of widget that you may be interested in creating is a widget that is merely an aggregate of other GTK widgets. This type of widget does nothing that couldn't be done without creating new widgets, but provides a convenient way of packaging user interface elements for reuse. The FileSelection and ColorSelection widgets in the standard distribution are examples of this type of widget.

The example widget that we'll create in this section is the Tictactoe widget, a 3x3 array of toggle buttons which triggers a signal when all three buttons in a row, column, or on one of the diagonals are depressed.

Note: the full source code for the Tictactoe example described below is in the Code Examples Appendix

Choosing a parent class

The parent class for a composite widget is typically the container class that holds all of the elements of the composite widget. For example, the parent class of the FileSelection widget is the Dialog class. Since our buttons will be arranged in a table, it is natural to make our parent class the Table class.

The header file

Each GObject class has a header file which declares the object and class structures for that object, along with public functions. A couple of features are worth pointing out. To prevent duplicate definitions, we wrap the entire header file in:

#ifndef __TICTACTOE_H__
#define __TICTACTOE_H__
#endif /* __TICTACTOE_H__ */

And to keep C++ programs that include the header file happy, in:

#include <glib.h>


Along with the functions and structures, we declare five standard macros in our header file, TICTACTOE_TYPE, TICTACTOE(obj), TICTACTOE_CLASS(klass), IS_TICTACTOE(obj), and IS_TICTACTOE_CLASS(klass), which cast a pointer into a pointer to the object or class structure, and check if an object is a Tictactoe widget respectively.

The _get_type() function

We now continue on to the implementation of our widget. A core function for every object is the function WIDGETNAME_get_type(). This function, when first called, tells Glib about the new class, and gets an ID that uniquely identifies the class. Upon subsequent calls, it just returns the ID.

tictactoe_get_type (void)
  static GType ttt_type = 0;

  if (!ttt_type)
      const GTypeInfo ttt_info =
	sizeof (TictactoeClass),
	NULL, /* base_init */
	NULL, /* base_finalize */
	(GClassInitFunc) tictactoe_class_init,
	NULL, /* class_finalize */
	NULL, /* class_data */
	sizeof (Tictactoe),
	0,    /* n_preallocs */
	(GInstanceInitFunc) tictactoe_init,

      ttt_type = g_type_register_static (GTK_TYPE_TABLE,

  return ttt_type;

The GTypeInfo structure has the following definition:

struct _GTypeInfo
  /* interface types, classed types, instantiated types */
  guint16                class_size;
  GBaseInitFunc          base_init;
  GBaseFinalizeFunc      base_finalize;
  /* classed types, instantiated types */
  GClassInitFunc         class_init;
  GClassFinalizeFunc     class_finalize;
  gconstpointer          class_data;
  /* instantiated types */
  guint16                instance_size;
  guint16                n_preallocs;
  GInstanceInitFunc      instance_init;
  /* value handling */
  const GTypeValueTable *value_table;

The important fields of this structure are pretty self-explanatory. We'll ignore the base_init and base_finalize as well as the value_table fields here. Once Glib has a correctly filled in copy of this structure, it knows how to create objects of a particular type.

The _class_init() function

The WIDGETNAME_class_init() function initializes the fields of the widget's class structure, and sets up any signals for the class. For our Tictactoe widget it looks like:

enum {

static guint tictactoe_signals[LAST_SIGNAL] = { 0 };

static void
tictactoe_class_init (TictactoeClass *klass)
  tictactoe_signals[TICTACTOE_SIGNAL] =
    g_signal_new ("tictactoe",
                  G_TYPE_FROM_CLASS (klass),
                  G_STRUCT_OFFSET (TictactoeClass, tictactoe),
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);

Our widget has just one signal, the tictactoe signal that is invoked when a row, column, or diagonal is completely filled in. Not every composite widget needs signals, so if you are reading this for the first time, you may want to skip to the next section now, as things are going to get a bit complicated.

The function:

guint g_signal_new( const gchar         *signal_name,
                    GType                itype,
                    GSignalFlags         signal_flags,
                    guint                class_offset,
                    GSignalAccumulator  *accumulator,
                    gpointer             accu_data,
                    GSignalCMarshaller  *c_marshaller,
                    GType                return_type,
                    guint                n_params,

Creates a new signal. The parameters are:

  • signal_name: The name of the signal.

  • itype: The ID of the object that this signal applies to. (It will also apply to that objects descendants.)

  • signal_flags: Whether the default handler runs before or after user handlers and other flags. Usually this will be one of G_SIGNAL_RUN_FIRST or G_SIGNAL_RUN_LAST, although there are other possibilities. The flag G_SIGNAL_ACTION specifies that no extra code needs to run that performs special pre or post emission adjustments. This means that the signal can also be emitted from object external code.

  • class_offset: The offset within the class structure of a pointer to the default handler.

  • accumulator: For most classes this can be set to NULL.

  • accu_data: User data that will be handed to the accumulator function.

  • c_marshaller: A function that is used to invoke the signal handler. For signal handlers that have no arguments other than the object that emitted the signal and user data, we can use the pre-supplied marshaller function g_cclosure_marshal_VOID__VOID.

  • return_type: The type of the return value.

  • n_params: The number of parameters of the signal handler (other than the two default ones mentioned above)

  • ...: The types of the parameters.

When specifying types, the following standard types can be used:


g_signal_new() returns a unique integer identifier for the signal, that we store in the tictactoe_signals array, which we index using an enumeration. (Conventionally, the enumeration elements are the signal name, uppercased, but here there would be a conflict with the TICTACTOE() macro, so we called it TICTACTOE_SIGNAL instead.

The _init() function

Each class also needs a function to initialize the object structure. Usually, this function has the fairly limited role of setting the fields of the structure to default values. For composite widgets, however, this function also creates the component widgets.

static void
tictactoe_init (Tictactoe *ttt)
  gint i,j;

  gtk_table_resize (GTK_TABLE (ttt), 3, 3);
  gtk_table_set_homogeneous (GTK_TABLE (ttt), TRUE);

  for (i=0;i<3; i++)
    for (j=0;j<3; j++)
	ttt->buttons[i][j] = gtk_toggle_button_new ();
	gtk_table_attach_defaults (GTK_TABLE (ttt), ttt->buttons[i][j], 
				   i, i+1, j, j+1);
	g_signal_connect (ttt->buttons[i][j], "toggled",
			  G_CALLBACK (tictactoe_toggle), ttt);
	gtk_widget_set_size_request (ttt->buttons[i][j], 20, 20);
	gtk_widget_show (ttt->buttons[i][j]);

And the rest...

There is one more function that every object (except for abstract classes like Bin that cannot be instantiated) needs to have - the function that the user calls to create an object of that type. This is conventionally called OBJECTNAME_new(). In some widgets, though not for the Tictactoe widgets, this function takes arguments, and does some setup based on the arguments. The other two functions are specific to the Tictactoe widget.

tictactoe_clear() is a public function that resets all the buttons in the widget to the up position. Note the use of g_signal_handlers_block_matched() to keep our signal handler for button toggles from being triggered unnecessarily.

tictactoe_toggle() is the signal handler that is invoked when the user clicks on a button. It checks to see if there are any winning combinations that involve the toggled button, and if so, emits the "tictactoe" signal.

tictactoe_new (void)
  return GTK_WIDGET ( g_object_new (TICTACTOE_TYPE, NULL));

tictactoe_clear (Tictactoe *ttt)
  int i,j;

  for (i=0;i<3;i++)
    for (j=0;j<3;j++)
	g_signal_handlers_block_matched (G_OBJECT (ttt->buttons[i][j]),
                                         0, 0, NULL, NULL, ttt);
	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ttt->buttons[i][j]),
	g_signal_handlers_unblock_matched (G_OBJECT (ttt->buttons[i][j]),
                                           0, 0, NULL, NULL, ttt);

static void
tictactoe_toggle (GtkWidget *widget, Tictactoe *ttt)
  int i,k;

  static int rwins[8][3] = { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
			     { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
			     { 0, 1, 2 }, { 0, 1, 2 } };
  static int cwins[8][3] = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
			     { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
			     { 0, 1, 2 }, { 2, 1, 0 } };

  int success, found;

  for (k=0; k<8; k++)
      success = TRUE;
      found = FALSE;

      for (i=0;i<3;i++)
	  success = success && 
	  found = found ||
	    ttt->buttons[rwins[k][i]][cwins[k][i]] == widget;
      if (success && found)
	  g_signal_emit (ttt, 
			 tictactoe_signals[TICTACTOE_SIGNAL], 0);

And finally, an example program using our Tictactoe widget:

#include <gtk/gtk.h>
#include "tictactoe.h"

/* Invoked when a row, column or diagonal is completed */
win (GtkWidget *widget, gpointer data)
  g_print ("Yay!\n");
  tictactoe_clear (TICTACTOE (widget));

main (int argc, char *argv[])
  GtkWidget *window;
  GtkWidget *ttt;
  gtk_init (&argc, &argv);

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title (GTK_WINDOW (window), "Aspect Frame");
  g_signal_connect (window, "destroy",
                    G_CALLBACK (exit), NULL);
  gtk_container_set_border_width (GTK_CONTAINER (window), 10);

  /* Create a new Tictactoe widget */
  ttt = tictactoe_new ();
  gtk_container_add (GTK_CONTAINER (window), ttt);
  gtk_widget_show (ttt);

  /* And attach to its "tictactoe" signal */
  g_signal_connect (ttt, "tictactoe",
                    G_CALLBACK (win), NULL);

  gtk_widget_show (window);
  gtk_main ();
  return 0;