An Example: The GtkEv Widget

This section describes a very simple widget called GtkEv, inspired by the xev client that comes with X. GtkEv has two components: a small sub-window that receives events, and a larger window where information about each event is reported. Figure 1 shows GtkEv in action. The complete GtkEv source code is in Appendix E. GtkEv would be a lovely way to implement an xev-style application for Gnome; it packages the core application functionality in a nice module.

Figure 1. The GtkEv widget. Events are reported for the white subwindow.

Overview

GtkEv uses two GdkWindows; the larger one, GtkEv's widget->window, has a gray background and is used to render text describing each event. The smaller one is a child of the primary window, and is the window the widget reports events for.

Here are the class and instance structs for GtkEv:


typedef struct _GtkEv       GtkEv;
typedef struct _GtkEvClass  GtkEvClass;

struct _GtkEv
{
  GtkWidget widget;
  
  GdkWindow*     event_window;

  GdkRectangle   event_window_rect;

  GdkRectangle   description_rect;

  GList*         buffer;
  GList*         buffer_end;
  gint           buffer_size;
};

struct _GtkEvClass
{
  GtkWidgetClass parent_class;


};
      

As you can see, GtkEv has no class functions or signals of its own. Each instance stores a pointer to the small event window in event_window. Two rectangles cache the area covered by the event window, and the area covered by the event description text. The widget's allocation is divided between these two areas. Finally, GtkEv stores a list of string vectors describing events; it caches the end of the list and the length of the list. As events are received, text describing them is pushed on to the front of the buffer. When the list becomes too long to fit on the screen, GtkEv removes an event from the back of the buffer each time it adds a new event to the front, keeping the buffer size constant.

GtkObject Features

Like all GtkObjects, GtkEv provides for its creation and destruction.

Creation

GtkEv's init, class init, and constructor functions are pure boilerplate and should require no explanation; here they are, to help you get oriented:


static GtkWidgetClass *parent_class = NULL;

guint
gtk_ev_get_type (void)
{
  static guint ev_type = 0;

  if (!ev_type)
    {
      static const GtkTypeInfo ev_info =
      {
        "GtkEv",
        sizeof (GtkEv),
        sizeof (GtkEvClass),
        (GtkClassInitFunc) gtk_ev_class_init,
        (GtkObjectInitFunc) gtk_ev_init,
        /* reserved_1 */ NULL,
        /* reserved_2 */ NULL,
        (GtkClassInitFunc) NULL,
      };

      ev_type = gtk_type_unique (gtk_widget_get_type (), &ev_info);
    }

  return ev_type;
}

static void
gtk_ev_class_init (GtkEvClass *klass)
{
  GtkObjectClass *object_class;
  GtkWidgetClass *widget_class;

  object_class = (GtkObjectClass*) klass;
  widget_class = (GtkWidgetClass*) klass;

  parent_class = gtk_type_class (gtk_widget_get_type ());

  object_class->destroy = gtk_ev_destroy;

  widget_class->realize = gtk_ev_realize;
  widget_class->unrealize = gtk_ev_unrealize;

  widget_class->size_request = gtk_ev_size_request;
  
  widget_class->size_allocate = gtk_ev_size_allocate;
  
  widget_class->draw = gtk_ev_draw;         

  widget_class->event = gtk_ev_event;
  
  widget_class->draw_focus = gtk_ev_draw_focus;

  widget_class->expose_event = gtk_ev_expose;
    
  widget_class->focus_in_event = gtk_ev_focus_in;
  widget_class->focus_out_event = gtk_ev_focus_out;
}

static void
gtk_ev_init (GtkEv *ev)
{
  GTK_WIDGET_SET_FLAGS (GTK_WIDGET(ev), GTK_CAN_FOCUS);

  ev->event_window = NULL;
  ev->buffer       = NULL;
  ev->buffer_end   = NULL;
  ev->buffer_size  = 0;
  
  ev->event_window_rect.x = ev->event_window_rect.y = 0;
  ev->event_window_rect.width = ev->event_window_rect.height = 0;

  ev->description_rect.x = ev->description_rect.y = 0;
  ev->description_rect.width = ev->description_rect.height = 0;
}

GtkWidget*
gtk_ev_new (void)
{
  GtkEv *ev;

  ev = gtk_type_new (gtk_ev_get_type ());

  return GTK_WIDGET (ev);
}
    

Destruction

GtkEv overrides only the destroy method from GtkObject, to clean up the event description buffer. The widget's windows will be destroyed in GtkWidget's shutdown method, which unrealizes the widget. GtkWidget's finalize method cleans up some GtkWidget resources and then chains to the GtkObject method which frees the instance struct. (Refer to the section called Object Finalization in the chapter called The GTK+ Object and Type System for more details on these methods.)

Because GtkEv has no object arguments, it does not need to implement get_arg or set_arg methods.

Here is its destroy method implementation:


static void   
gtk_ev_destroy       (GtkObject   *object)
{
  GtkEv* ev;
  GList* tmp;

  g_return_if_fail(object != NULL);
  g_return_if_fail(GTK_IS_EV(object));

  ev = GTK_EV(object);

  tmp = ev->buffer;
  while (tmp != NULL)
    {
      g_strfreev((gchar**)tmp->data);
      
      tmp = g_list_next(tmp);
    }

  g_list_free(ev->buffer);

  ev->buffer = NULL;
  ev->buffer_end = NULL;
  ev->buffer_size = 0;

  /* Chain up */
  if (GTK_OBJECT_CLASS(parent_class)->destroy)
    (* GTK_OBJECT_CLASS(parent_class)->destroy) (object);
}
    

The only detail worthy of note is that freed pointers are set to NULL, because a destroyed object should remain "sane," unlike a finalized object. The GtkEv code depends on the fact that destroyed widgets are always unrealized; otherwise, text could be re-added to the buffer after destruction but before finalization, and a finalize method would be required.

Realization and Mapping

If you aren't familiar with the concept of realizing and mapping a widget, go back and read the section called Realizing, Mapping, and Showing in the chapter called GTK+ Basics before reading this section.

GtkEv does not override the map or unmap method; the default GtkWidget methods suffice. The defaults set and unset the GTK_WIDGET_MAPPED flag, and show or hide widget->window.

Any widget with a GdkWindow that has GtkWidget as its immediate parent will need to override the realize method; the default is only suitable for windowless widgets. GtkEv is no exception. GtkEv also overrides the unrealize method, in order to destroy the event window.

Here is GtkEv's realize method:


static void 
gtk_ev_realize        (GtkWidget        *widget)
{
  GdkWindowAttr attributes;
  gint attributes_mask;
  GtkEv* ev;
  GdkCursor* cursor;

  g_return_if_fail(widget != NULL);
  g_return_if_fail(GTK_IS_EV(widget));

  ev = GTK_EV(widget);

  /* Set realized flag */

  GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);

  /* Main widget window */

  attributes.window_type = GDK_WINDOW_CHILD;
  attributes.x = widget->allocation.x;
  attributes.y = widget->allocation.y;
  attributes.width = widget->allocation.width;
  attributes.height = widget->allocation.height;
  attributes.wclass = GDK_INPUT_OUTPUT;
  attributes.visual = gtk_widget_get_visual (widget);
  attributes.colormap = gtk_widget_get_colormap (widget);
  attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK;

  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;

  widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
                                   &attributes, attributes_mask);
  gdk_window_set_user_data (widget->window, widget);

  /* Event window */

  cursor = gdk_cursor_new(GDK_CROSSHAIR);

  attributes.window_type = GDK_WINDOW_CHILD;
  attributes.x = ev->event_window_rect.x;
  attributes.y = ev->event_window_rect.y;
  attributes.width = ev->event_window_rect.width;
  attributes.height = ev->event_window_rect.height;
  attributes.wclass = GDK_INPUT_OUTPUT;
  attributes.visual = gtk_widget_get_visual (widget);
  attributes.colormap = gtk_widget_get_colormap (widget);
  attributes.event_mask = GDK_ALL_EVENTS_MASK;
  attributes.cursor = cursor;

  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | 
    GDK_WA_COLORMAP | GDK_WA_CURSOR;

  ev->event_window = gdk_window_new (widget->window,
                                     &attributes, attributes_mask);
  gdk_window_set_user_data (ev->event_window, widget);

  gdk_window_show(ev->event_window);

  gdk_cursor_destroy(cursor);

  /* Style */

  widget->style = gtk_style_attach (widget->style, widget->window);

  gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);

  gdk_window_set_background (ev->event_window, 
                             &widget->style->base[GTK_STATE_NORMAL]);
}
      

The first step in any realize method is to set the GTK_REALIZED flag; this is a small but important detail. After that, most of the realize method is concerned with creating the two GdkWindows, as described in the section called GdkWindow in the chapter called GDK Basics. widget->window should be created as a subwindow of the widget's parent's GdkWindow; the parent window is obtained with gtk_widget_get_parent_window().

Notice that all events are requested on the event window, for obvious reasons. Also, the event window has a special cursor, to give the user visual feedback when the pointer moves into it. The client-side cursor handle is destroyed immediately after attaching the cursor to the window; the X server will keep it around as long as it's in use.

After creating each GdkWindow, a pointer to the GtkEv is stored in the GdkWindow's "user data" field. GTK+ uses the contents of this field to determine which widget should receive events that occur on the window. Recall that GTK+ receives a stream of events from GDK, and that each GdkEvent has a window field indicating the GdkWindow that received it. GTK+ forwards each event to the widget owning the event's GdkWindow. (the section called Events in the chapter called GDK Basics details this process if you don't remember.)

The code calls gdk_window_show() on the event window but not widget->window; widget->window should not be shown until the widget is mapped. Because the event window is a child of widget->window, it will remain offscreen until its parent is shown. Alternatively, GtkEv could implement a map method to show the child, but this way seems simpler.

All widgets must take create their associated GtkStyle in their realize method, because a style contains X resources. (See the section called GtkStyle and Themes in the chapter called GDK Basics for more information about GtkStyle.) Recall from the section called Realizing, Mapping, and Showing in the chapter called GTK+ Basics that widgets allocate all X resources in their realize method. GTK+ provides a simple function to create a widget's style:


  widget->style = gtk_style_attach (widget->style, widget->window);

      

After filling in widget->style, GtkEv uses colors from the style to set window backgrounds. It sets the main window's background using gtk_style_set_background(), which could do almost anything (it might invoke a routine from a dynamically loaded theme module). If the default theme is running, it simply sets the window's background to an appropriate color or pixmap tile. There is no special style function to set the background of the event window, so we set it to the "base" color (the base color is white by default; it's the background color for lists and text entries). Selecting a color from the style means that users will be able to customize the widget's color. It's also convenient to avoid allocating and deallocating a custom color.

Notice that the realize method does not chain up to the default realize method, because the default isn't appropriate for GtkEv.

Unrealizing GtkEv is relatively simple:


static void 
gtk_ev_unrealize (GtkWidget        *widget)
{
  GtkEv* ev;

  g_return_if_fail(widget != NULL);
  g_return_if_fail(GTK_IS_EV(widget));

  ev = GTK_EV(widget);

  /* Hide all windows */

  if (GTK_WIDGET_MAPPED (widget))
    gtk_widget_unmap (widget);
  
  GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED);

  /* Destroy our child window */

  if (ev->event_window)
    {
      gdk_window_set_user_data(ev->event_window, NULL);
      gdk_window_destroy(ev->event_window);
      ev->event_window = NULL;
    }

  /* This destroys widget->window and unsets the realized flag
   */
  if (GTK_WIDGET_CLASS(parent_class)->unrealize)
    (* GTK_WIDGET_CLASS(parent_class)->unrealize) (widget);
}
      

First, the unrealize method ensures that the widget is unmapped. This is essential: GTK+ maintains the invariant that mapped widgets are also realized. Next, the unrealize method destroys the event window. It sets the window's user data to NULL before destroying it; otherwise GtkEv would receive a useless destroy event. Finally, GtkEv chains up to the default unrealize method, which unsets the GTK_WIDGET_REALIZED flag and destroys widget->window. Unrealize implemenations are required to chain up to their base class's implementation.

When writing your realize and unrealize methods, keep in mind that they can be called multiple times, but they are always paired. That is, a widget can be unrealized and re-realized over and over, but it will never be realized twice without an intervening unrealize. The pairing is guaranteed; that is, if a widget is realized it will definitely be unrealized sooner or later, unless the program exits.

Size Negotiation

the section called Size Allocation in the chapter called GTK+ Basics describes the size negotiation process; be sure you're familiar with it before reading this section.

There's no obvious "right" size for GtkEv, so the size request method requests an arbitrary size that looks nice:


static void 
gtk_ev_size_request   (GtkWidget        *widget,
                       GtkRequisition   *requisition)
{
  g_return_if_fail(widget != NULL);
  g_return_if_fail(GTK_IS_EV(widget));

  /* 
   * GtkEv always wants to be the same fixed size.
   */
  
  requisition->width  = 450;
  requisition->height = 300;
}
      

GTK+ takes care of storing a widget's last size request in widget->requisition.

If GtkEv were a real-life widget rather than an illustrative example, it would be unnecessary to implement a size request method. The default GtkWidget method simply returns the current value of widget->requisition, so GtkEv could initialize widget->requisition in gtk_ev_init() and use the default method.

Alternatively, the size request method could be implemented more elaborately; GtkEv could attempt to predict the maximum width of the text to be displayed, for example.

Once a widget's parent container decides how much space is actually available, the widget receives a size allocation. The size allocation method should do the following:

Here is the GtkEv size allocation method; it should be self-explanatory:


static void 
gtk_ev_size_allocate  (GtkWidget        *widget,
                       GtkAllocation    *allocation)
{
  static const gint spacing = 10; 
  GtkEv* ev;

  g_return_if_fail(widget != NULL);
  g_return_if_fail(GTK_IS_EV(widget));
  
  ev = GTK_EV(widget);

  widget->allocation = *allocation;

  ev->event_window_rect.width = 
    MAX(allocation->width - spacing*2, 0);
  ev->event_window_rect.height = 
    MAX(allocation->height / 5 - spacing / 2, 0);

  ev->event_window_rect.x = 
    (allocation->width - ev->event_window_rect.width)/2;
  ev->event_window_rect.y = 
    MIN(spacing, allocation->height);

  ev->description_rect.x = ev->event_window_rect.x;
  ev->description_rect.y = 
    ev->event_window_rect.y + ev->event_window_rect.height + spacing;
  ev->description_rect.width = 
    ev->event_window_rect.width;
  ev->description_rect.height = 
    MAX((allocation->height - ev->event_window_rect.height - spacing*3), 0);

  if (GTK_WIDGET_REALIZED (widget))
    {
      gdk_window_move_resize (widget->window,
                              allocation->x, 
                              allocation->y,
                              allocation->width, 
                              allocation->height);

      gdk_window_move_resize (ev->event_window,
                              ev->event_window_rect.x, 
                              ev->event_window_rect.y,
                              ev->event_window_rect.width,
                              ev->event_window_rect.height);      
    }
}
      

Drawing

There are three common situations that require a widget to redraw all or part of itself:

  1. Expose events signal that all or part of a widget's GdkWindow has just become visible on the screen and needs repainting (see the section called Expose Events in the chapter called GDK Basics). A widget's expose_event method performs these redraws.

  2. GTK+ sometimes determines that a widget should be redrawn. This might happen when a widget receives a new size allocation different from its previous size allocation, or when a new theme is loaded. A widget's draw method, usually invoked via gtk_widget_queue_draw() or gtk_widget_queue_clear(), handles this case.

  3. Widgets sometimes decide to redraw themselves. For example, if you change the text of a GtkLabel, the label will redraw itself to reflect the new text. Widget implementations are free to handle this case however they like, but most will use the draw method.

There are two special cases of the second situation. The first occurs when a widget receives or loses the keyboard focus; the second occurs when the widget becomes (or unbecomes) the "default" widget. Widgets should indicate these states visually, but they can often do so without a complete redraw. Thus, there are special draw_focus and draw_default signals to handle them. These signals only have to be implemented if a widget can meaningfully receive the focus or default.

Because there is typically little difference between a widget's draw and expose methods, a common convention is to write a static function to handle both of them. This function is standardly called gtk_whatever_paint(). It's also possible to avoid implementing the draw method, because the default draw method synthesizes an expose event covering the widget's entire allocation and invokes the expose method. (Remember that a synthetic expose event will have its send_event flag set to TRUE; you can use this to distinguish synthetic events.)

The primary reason for distinguishing expose events from other draws is that expose events are marked with the window they occurred on; for widgets with multiple windows such as GtkEv, this can increase efficiency. GtkEv implements two private functions, gtk_ev_paint() and gtk_ev_paint_event_window(), which it uses to implement the expose and draw methods.

Here is the draw method:


static void 
gtk_ev_draw           (GtkWidget        *widget,
                       GdkRectangle     *area)
{
  GdkRectangle event_window_area;
  GdkRectangle intersection;
  GtkEv* ev;

  g_return_if_fail(widget != NULL);
  g_return_if_fail(GTK_IS_EV(widget));

  ev = GTK_EV(widget);

  gtk_ev_paint(ev, area);

  event_window_area = *area;

  if (gdk_rectangle_intersect(area, &ev->event_window_rect, &intersection))
    {
      /* Make the intersection relative to the event window */
      intersection.x -= ev->event_window_rect.x;
      intersection.y -= ev->event_window_rect.y;
      
      gtk_ev_paint_event_window(ev, &intersection);
    }
}
      

And the expose method:


static gint 
gtk_ev_expose         (GtkWidget        *widget,
                       GdkEventExpose   *event)
{  
  if (event->window == widget->window)
    gtk_ev_paint(GTK_EV(widget), &event->area);
  else if (event->window == GTK_EV(widget)->event_window)
    gtk_ev_paint_event_window(GTK_EV(widget), &event->area);
  else
    g_assert_not_reached();

  return TRUE;
}
      

Both the draw and expose methods should be self-explanatory. All the work is done in the two paint functions. Here is gtk_ev_paint(), which renders the main widget window:


static void 
gtk_ev_paint          (GtkEv            *ev,
                       GdkRectangle     *area)
{
  GtkWidget* widget;

  g_return_if_fail(ev != NULL);
  g_return_if_fail(GTK_IS_EV(ev));

  widget = GTK_WIDGET(ev);

  if (!GTK_WIDGET_DRAWABLE (widget))
    return;

  gdk_window_clear_area (widget->window,
                         area->x, 
                         area->y,
                         area->width, 
                         area->height);

  gdk_gc_set_clip_rectangle(widget->style->black_gc, area);

  /* Draw a black rectangle around the event window */

  gdk_draw_rectangle(widget->window,
                     widget->style->black_gc,
                     FALSE,
                     ev->event_window_rect.x - 1, 
                     ev->event_window_rect.y - 1,
                     ev->event_window_rect.width + 2,
                     ev->event_window_rect.height + 2);

  gdk_gc_set_clip_rectangle(widget->style->black_gc, NULL);

  /* Draw text in the description area, if applicable */

  if (ev->buffer)
    {
      GdkRectangle intersection;

      if (gdk_rectangle_intersect(area,
                                  &ev->description_rect,
                                  &intersection))
        {
          static const gint space = 2;
          gint line;
          gint step;
          gint first_baseline;          
          GList* tmp;
      
          step  = widget->style->font->ascent + 
            widget->style->font->descent + space;
      
          first_baseline = ev->description_rect.y + 
            widget->style->font->ascent + space;
      
          line = 0;
      
          tmp = ev->buffer;
      
          while (tmp != NULL)
            {
              gchar** this_event = tmp->data;
              gint i = 0;
              while (this_event[i])
                {
                  gtk_paint_string (widget->style, 
                                    widget->window, 
                                    widget->state,
                                    &intersection, widget, "ev", 
                                    ev->description_rect.x,
                                    first_baseline + line*step,
                                    this_event[i]);
                  ++i;
                  ++line;
                }
          
              /* Bail out if we're off the bottom; the "- 2*step" is
               *  needed because the next baseline may be outside the
               *  redraw area but we are interested in the whole row of 
               *  text, not the baseline. The 2* is because line is one 
               *  larger than we've actually drawn.
               */
              if ((first_baseline + line*step - 2*step) > 
                  (intersection.y + intersection.height))
                break;
          
              tmp = g_list_next(tmp);
            }
        }
    }

  if (GTK_WIDGET_HAS_FOCUS (widget))
    {
      gtk_paint_focus (widget->style, widget->window,
                       area, widget, "ev",
                       widget->allocation.x, widget->allocation.y, 
                       widget->allocation.width-1, widget->allocation.height-1);
    }
}
      

Most of gtk_ev_paint() is GtkEv-specific; it simply draws the contents of the window. Notice that it checks GTK_WIDGET_DRAWABLE() at the beginning; this is required because the draw method may invoke the function. Unsynthesized expose events guarantee that a widget's X window is on-screen and thus this check is not really necessary when responding to expose events.

gtk_ev_paint_event_window() paints the small subwindow; it's a very simple function:


static void 
gtk_ev_paint_event_window  (GtkEv            *ev,
                            GdkRectangle     *area)
{
  GtkWidget* widget;
  gint width;
  gint x, y;
  const char* title;

  g_return_if_fail(ev != NULL);
  g_return_if_fail(GTK_IS_EV(ev));

  widget = GTK_WIDGET(ev);

  if (!GTK_WIDGET_DRAWABLE (widget))
    return;

  title = _("Event Window");

  gdk_window_clear_area (ev->event_window,
                         area->x, 
                         area->y,
                         area->width, 
                         area->height);

  gdk_gc_set_clip_rectangle(widget->style->black_gc, area);

  /* Clearly it would be better to cache this */

  width = gdk_string_width(widget->style->font,
                           title);

  x = (ev->event_window_rect.width - width)/2;
  y = widget->style->font->ascent + 2;

  gdk_draw_string(ev->event_window,
                  widget->style->font,
                  widget->style->black_gc,
                  x, y, 
                  title);

  gdk_gc_set_clip_rectangle(widget->style->black_gc, NULL);
}
      

Handling Focus

GtkEv wants to receive key press events so it can report information about them. As discussed in the section called Focus in the chapter called GTK+ Basics and the section called Keyboard Focus in the chapter called GDK Basics, only toplevel windows receive key events from GDK. GtkWindow keeps track of a current focus widget and forwards key events to it.

If a widget wants to receive key events, it must:

GtkEv set the GTK_CAN_FOCUS flag in gtk_ev_init(); it implements focus in and focus out methods as follows:


static gint 
gtk_ev_focus_in       (GtkWidget        *widget,
                       GdkEventFocus    *event)
{
  g_return_val_if_fail(widget != NULL, FALSE);
  g_return_val_if_fail(GTK_IS_EV(widget), FALSE);

  GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS);
  gtk_widget_draw_focus (widget);

  return FALSE;
}

static gint 
gtk_ev_focus_out      (GtkWidget        *widget,
                       GdkEventFocus    *event)
{
  g_return_val_if_fail(widget != NULL, FALSE);
  g_return_val_if_fail(GTK_IS_EV(widget), FALSE);

  GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS);
  gtk_widget_draw_focus (widget);

  return FALSE;
}
      

These implementations are the minimal ones; all focusable widgets must set or unset the GTK_HAS_FOCUS flag when they gain or lose the focus, and they must emit the "draw_focus" signal.

GtkEv has a lazy implementation of the "draw_focus" signal; it just calls the same gtk_ev_paint() used to respond to expose events and redraw requests. Recall that gtk_ev_paint() checks whether the GtkEv has the focus and draws a focus frame if so. Here is the code:


static void 
gtk_ev_draw_focus     (GtkWidget        *widget)
{
  GdkRectangle rect;
  GtkEv* ev;

  g_return_if_fail(widget != NULL);
  g_return_if_fail(GTK_IS_EV(widget));

  ev = GTK_EV(widget);

  rect.x = 0;
  rect.y = 0;
  rect.width = widget->allocation.width;
  rect.height = widget->allocation.height;

  if (GTK_WIDGET_DRAWABLE (ev))
    gtk_ev_paint(ev, &rect);
}
      

Notice that widget implementations are responsible for emitting the "draw_focus" signal themselves; GTK+ does not emit it as the focus moves. Contrast this with the "draw_default" signal, which GTK+ automatically emits whenever a widget gains or loses the default. GtkEv cannot be the default widget, so it does not implement this signal.

GtkEv's Functionality

All the code presented so far implements the GtkWidget and GtkObject interfaces. GtkEv does have some unique functionality; namely, it responds to events on its event window by adding text describing the event to its buffer, and queueing a redraw. To do this, it overrides the default "event" signal handler.

Here is GtkEv's event method:


static gint   
gtk_ev_event (GtkWidget        *widget,
              GdkEvent         *event)
{
  GtkEv* ev;

  g_return_val_if_fail(widget != NULL, FALSE);
  g_return_val_if_fail(GTK_IS_EV(widget), FALSE);

  ev = GTK_EV(widget);

  if (event->any.window == widget->window)
    {
      if (GTK_WIDGET_CLASS(parent_class)->event)
        return (* GTK_WIDGET_CLASS(parent_class)->event) (widget, event);
      else
        return FALSE;
    }
  else
    {
      gchar* text;

      /* The event is either on ev->event_window, or it is a key event 
       * passed down to us from the toplevel GtkWindow
       */

      text = event_to_text(event);
      
      gtk_ev_push_text(ev, text);

      g_free(text);

      /* If it was a motion event, make sure we get more */
      if (event->type == GDK_MOTION_NOTIFY)
        {
          gdk_window_get_pointer(ev->event_window, NULL, NULL, NULL);
        }

      /* We didn't "handle" the event, just listened in on it. */
      return FALSE;
    }
}
      

Notice that the window method of the event is used to distinguish events that occur on widget->window from events that occur on the event subwindow. Some events will be received from a different window entirely; for example, key events actually occur on a toplevel window, and are passed to GtkEv if GtkEv has the focus.

event_to_text() is a lengthy but trivial function that creates a string describing the event; gtk_ev_push_text() pushes the text onto the front of the buffer and queues a redraw. The implementation of these functions is part of the complete GtkEv code listing, in Appendix E.