Creating a Composite widget

GTK+ 2.0 Tutorial
<<< Previous | Writing Your Own Widgets | Next >>>

# Creating a Composite widget

## Introduction

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>

    G_BEGIN_DECLS
    .
    .
    .
    G_END_DECLS

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.

    GType
    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,
                                             "Tictactoe",
                                             &ttt_info,
                                             0);
        }

      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 {
      TICTACTOE_SIGNAL,
      LAST_SIGNAL
    };

    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_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
                      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_TYPE_INVALID
    G_TYPE_NONE
    G_TYPE_INTERFACE
    G_TYPE_CHAR
    G_TYPE_UCHAR
    G_TYPE_BOOLEAN
    G_TYPE_INT
    G_TYPE_UINT
    G_TYPE_LONG
    G_TYPE_ULONG
    G_TYPE_INT64
    G_TYPE_UINT64
    G_TYPE_ENUM
    G_TYPE_FLAGS
    G_TYPE_FLOAT
    G_TYPE_DOUBLE
    G_TYPE_STRING
    G_TYPE_POINTER
    G_TYPE_BOXED
    G_TYPE_PARAM
    G_TYPE_OBJECT

`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.

    GtkWidget*
    tictactoe_new (void)
    {
      return GTK_WIDGET ( g_object_new (TICTACTOE_TYPE, NULL));
    }

    void
    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]),
                                             G_SIGNAL_MATCH_DATA,
                                             0, 0, NULL, NULL, ttt);
    	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ttt->buttons[i][j]),
    				     FALSE);
    	g_signal_handlers_unblock_matched (G_OBJECT (ttt->buttons[i][j]),
                                               G_SIGNAL_MATCH_DATA,
                                               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 &&
    	    GTK_TOGGLE_BUTTON(ttt->buttons[rwins[k][i]][cwins[k][i]])->active;
    	  found = found ||
    	    ttt->buttons[rwins[k][i]][cwins[k][i]] == widget;
    	}

          if (success && found)
    	{
    	  g_signal_emit (ttt,
    			 tictactoe_signals[TICTACTOE_SIGNAL], 0);
    	  break;
    	}
        }
    }

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 */
    void
    win (GtkWidget *widget, gpointer data)
    {
      g_print ("Yay!\n");
      tictactoe_clear (TICTACTOE (widget));
    }

    int
    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;
    }

<<< Previous | Home | Next >>>
The Anatomy Of A Widget | Up | Creating a widget from scratch