The GtkEv Widget

GtkEv is a widget that reports events on a subwindow, similar to the xev client that comes with X.

gtkev.h


#ifndef INC_GTK_EV_H
#define INC_GTK_EV_H

#include <gtk/gtkwidget.h>

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

#define GTK_EV(obj)          GTK_CHECK_CAST (obj, gtk_ev_get_type (), GtkEv)
#define GTK_EV_CLASS(klass)  GTK_CHECK_CLASS_CAST (klass, gtk_ev_get_type (), GtkEvClass)
#define GTK_IS_EV(obj)       GTK_CHECK_TYPE (obj, gtk_ev_get_type ())

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;


};


guint           gtk_ev_get_type           (void);
GtkWidget*      gtk_ev_new                (void);


#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif /* __GTK_EV_H__ */



gtkev.c


#include "gtkev.h"

static void   gtk_ev_class_init    (GtkEvClass  *klass);
static void   gtk_ev_init          (GtkEv       *ev);


/* GtkObject functions */
static void   gtk_ev_destroy       (GtkObject   *object);


/* GtkWidget functions */

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

static void gtk_ev_realize        (GtkWidget        *widget);
static void gtk_ev_unrealize      (GtkWidget        *widget);
static void gtk_ev_size_request   (GtkWidget        *widget,
                                   GtkRequisition   *requisition);
static void gtk_ev_size_allocate  (GtkWidget        *widget,
                                   GtkAllocation    *allocation);

static void gtk_ev_draw           (GtkWidget        *widget,
                                   GdkRectangle     *area);
static void gtk_ev_draw_focus     (GtkWidget        *widget);

static gint gtk_ev_expose         (GtkWidget        *widget,
                                   GdkEventExpose   *event);
static gint gtk_ev_focus_in       (GtkWidget        *widget,
                                   GdkEventFocus    *event);
static gint gtk_ev_focus_out      (GtkWidget        *widget,
                                   GdkEventFocus    *event);

/* GtkEv-specific functions */

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

static void gtk_ev_paint_event_window  (GtkEv            *ev,
                                        GdkRectangle     *area);

static void gtk_ev_push_text       (GtkEv            *ev,
                                    const gchar*     text);

/* Utility Functions */

static gchar* event_to_text (GdkEvent* event);

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

/* GtkObject functions */
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);
}

/* GtkWidget functions */

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

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]);
}

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

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

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

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

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

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

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

/* GtkEv-specific functions */

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

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

static void 
gtk_ev_push_text (GtkEv            *ev,
                  const gchar*     text)
{  
  if (text)
    {
      gchar** event; 
      event = g_strsplit(text, "\n", 10);
      ev->buffer = g_list_prepend(ev->buffer, event);
      ev->buffer_size += 1;

      /* See if this was the first one we added */
      if (ev->buffer_end == NULL)
        {
          ev->buffer_end = ev->buffer;
        }
      
      /* See if we have too many to possibly fit on-screen */
      if (ev->buffer_size > 100)
        {
          GList* prev = ev->buffer_end->prev;
          
          prev->next = NULL;
          
          g_strfreev(ev->buffer_end->data);
          g_list_free_1(ev->buffer_end);

          ev->buffer_end = prev;

          ev->buffer_size -= 1;
        }
    }

  if (GTK_WIDGET_DRAWABLE (ev))
    gtk_widget_queue_draw_area(GTK_WIDGET(ev), 
                               ev->description_rect.x,
                               ev->description_rect.y,
                               ev->description_rect.width,
                               ev->description_rect.height);
}

static gchar*
event_name_line(GdkEvent* event)
{
  switch (event->type)
    {
    case GDK_NOTHING:
      return g_strdup(_("Invalid event!\n"));
      break;
      
    case GDK_DELETE:
      return g_strdup(_("Delete\n"));
      break;
               
    case GDK_DESTROY:
      return g_strdup(_("Destroy\n"));
      break;
               
    case GDK_EXPOSE:
      return g_strdup(_("Expose\n"));
      break;
               
    case GDK_MOTION_NOTIFY:
      return g_strdup(_("Motion Notify\n"));
      break;
    
    case GDK_BUTTON_PRESS:
      return g_strdup(_("Button Press\n"));
      break;

    case GDK_2BUTTON_PRESS:
      return g_strdup(_("2 Button Press\n"));
      break;
    
    case GDK_3BUTTON_PRESS:
      return g_strdup(_("3 Button Press\n"));
      break;
    
    case GDK_BUTTON_RELEASE:
      return g_strdup(_("Button Release\n"));
      break;
   
    case GDK_KEY_PRESS:
      return g_strdup(_("Key Press\n"));
      break;
               
    case GDK_KEY_RELEASE:
      return g_strdup(_("Key Release\n"));
      break;
      
    case GDK_ENTER_NOTIFY:
      return g_strdup(_("Enter Notify\n"));
      break;
     
    case GDK_LEAVE_NOTIFY:
      return g_strdup(_("Leave Notify\n"));
      break;
     
    case GDK_FOCUS_CHANGE:
      return g_strdup(_("Focus Change\n"));
      break;
     
    case GDK_CONFIGURE:
      return g_strdup(_("Configure\n"));
      break;
               
    case GDK_MAP:
      return g_strdup(_("Map\n"));
      break;
               
    case GDK_UNMAP:
      return g_strdup(_("Unmap\n"));
      break;
               
    case GDK_PROPERTY_NOTIFY:
      return g_strdup(_("Property Notify\n"));
      break;
  
    case GDK_SELECTION_CLEAR:
      return g_strdup(_("Selection Clear\n"));
      break;
  
    case GDK_SELECTION_REQUEST:
      return g_strdup(_("Selection Request\n"));
      break;

    case GDK_SELECTION_NOTIFY:
      return g_strdup(_("Selection Notify\n"));
      break;
 
    case GDK_PROXIMITY_IN:
      return g_strdup(_("Proximity In\n"));
      break;
     
    case GDK_PROXIMITY_OUT:
      return g_strdup(_("Proximity Out\n"));
      break;
    
    case GDK_DRAG_ENTER:
      return g_strdup(_("Drag Enter\n"));
      break;
       
    case GDK_DRAG_LEAVE:
      return g_strdup(_("Drag Leave\n"));
      break;
       
    case GDK_DRAG_MOTION:
      return g_strdup(_("Drag Motion\n"));
      break;
      
    case GDK_DRAG_STATUS:
      return g_strdup(_("Drag Status\n"));
      break;
      
    case GDK_DROP_START:
      return g_strdup(_("Drop Start\n"));
      break;
       
    case GDK_DROP_FINISHED: 
      return g_strdup(_("Drop Finished\n"));
      break;
    
    case GDK_CLIENT_EVENT:
      return g_strdup(_("Client Event\n"));
      break;
     
    case GDK_VISIBILITY_NOTIFY:
      return g_strdup(_("Visibility Notify\n"));
      break;

    case GDK_NO_EXPOSE:
      return g_strdup(_("No Expose\n"));
      break;
               
    default:
      g_assert_not_reached();
      return NULL;
      break;
    }
}

static gchar* 
any_event_line(GdkEvent* event)
{
  guint32 event_time;

  event_time = gdk_event_get_time(event);

  if (event_time != GDK_CURRENT_TIME)
    return g_strdup_printf(_("Window: %p Time: %u send_event: %s\n"),
                           event->any.window, 
                           event_time,
                           event->any.send_event ? _("True") : _("False"));
  else
    return g_strdup_printf(_("Window: %p send_event: %s\n"),
                           event->any.window, 
                           event->any.send_event ? _("True") : _("False"));
}

#define MAX_STATES 30

static gchar* 
event_state_line(GdkModifierType state)
{
  gchar** states;
  gint n_active;

  states = g_new(gchar*, MAX_STATES);

  n_active = 0;
  
  if (state & GDK_SHIFT_MASK)
    {
      states[n_active] = _("Shift");
      ++n_active;
    }   
  if (state & GDK_LOCK_MASK)
    {
      states[n_active] = _("Lock");
      ++n_active;
    }      
  if (state & GDK_CONTROL_MASK)
    {
      states[n_active] = _("Ctrl");
      ++n_active;
    } 
  if (state & GDK_MOD1_MASK)
    {
      states[n_active] = _("Mod1");
      ++n_active;
    }      
  if (state & GDK_MOD2_MASK)
    {
      states[n_active] = _("Mod2");
      ++n_active;
    }      
  if (state & GDK_MOD3_MASK)
    {
      states[n_active] = _("Mod3");
      ++n_active;
    }      
  if (state & GDK_MOD4_MASK)
    {
      states[n_active] = _("Mod4");
      ++n_active;
    }      
  if (state & GDK_MOD5_MASK)
    {
      states[n_active] = _("Mod5");
      ++n_active;
    }      
  if (state & GDK_BUTTON1_MASK)
    {
      states[n_active] = _("Button1");
      ++n_active;
    } 
  if (state & GDK_BUTTON2_MASK)
    {
      states[n_active] = _("Button2");
      ++n_active;
    } 
  if (state & GDK_BUTTON3_MASK)
    {
      states[n_active] = _("Button3");
      ++n_active;
    } 
  if (state & GDK_BUTTON4_MASK)
    {
      states[n_active] = _("Button4");
      ++n_active;
    } 
  if (state & GDK_BUTTON5_MASK)
    {
      states[n_active] = _("Button4");
      ++n_active;
    } 
  if (state & GDK_RELEASE_MASK)
    {
      states[n_active] = _("Release");
      ++n_active;
    }   

  if (n_active == 0)
    return NULL;
  else
    {
      /* Not efficient, but not important */

      gchar* str = NULL;
      gchar* tmp = NULL;

      guint i = 0;
      while (i < n_active)
        {

          if (str)
            {
              tmp = str;          
              str = g_strconcat(str, " | ", states[i], NULL);
              g_free(tmp);
            }
          else
            {
              str = g_strdup(states[i]);
            }

          ++i;
        }

      tmp = str;
      str = g_strconcat(str, "\n", NULL);
      g_free(tmp);

      return str;
    }
}

static gchar* 
event_to_text (GdkEvent* event)
{
  gchar* any_line;
  gchar* name_line;
  gchar* entire_line;
  gchar* detail;
  gchar* state;

  name_line = event_name_line(event);
  any_line = any_event_line(event);

  entire_line = NULL;
  detail = NULL;
  state = NULL;

  switch (event->type)
    {
    case GDK_NOTHING:
      break;
      
    case GDK_DELETE:
      break;
               
    case GDK_DESTROY:
      break;
               
    case GDK_EXPOSE:
      detail = g_strdup_printf(_("Area: %d,%d  %dx%d Count: %d\n"),
                               event->expose.area.x, 
                               event->expose.area.y,
                               event->expose.area.width,
                               event->expose.area.height,
                               event->expose.count);
      break;
               
    case GDK_MOTION_NOTIFY:
      detail = g_strdup_printf(_("x: %g y: %g\n"),
                               event->motion.x, 
                               event->motion.y);
      state = event_state_line(event->motion.state);
      break;
    
    case GDK_BUTTON_PRESS:
    case GDK_2BUTTON_PRESS:
    case GDK_3BUTTON_PRESS:
    case GDK_BUTTON_RELEASE:
      detail = g_strdup_printf(_("Button: %d\n"),
                               event->button.button);
      state = event_state_line(event->button.state);
      break;
   
    case GDK_KEY_PRESS:
    case GDK_KEY_RELEASE:
      detail = g_strdup_printf(_("Keyval: GDK_%s Text: %s\n"),
                               gdk_keyval_name(event->key.keyval),
                               event->key.string);
      state = event_state_line(event->key.state);
      break;
      
    case GDK_ENTER_NOTIFY:
      break;
     
    case GDK_LEAVE_NOTIFY:
      break;
     
    case GDK_FOCUS_CHANGE:
      break;
     
    case GDK_CONFIGURE:
      break;
               
    case GDK_MAP:
      break;
               
    case GDK_UNMAP:
      break;
               
    case GDK_PROPERTY_NOTIFY:
      break;
  
    case GDK_SELECTION_CLEAR:
      break;
  
    case GDK_SELECTION_REQUEST:
      break;

    case GDK_SELECTION_NOTIFY:
      break;
 
    case GDK_PROXIMITY_IN:
      break;
     
    case GDK_PROXIMITY_OUT:
      break;
    
    case GDK_DRAG_ENTER:
      break;
       
    case GDK_DRAG_LEAVE:
      break;
       
    case GDK_DRAG_MOTION:
      break;
      
    case GDK_DRAG_STATUS:
      break;
      
    case GDK_DROP_START:
      break;
       
    case GDK_DROP_FINISHED:
      break;
    
    case GDK_CLIENT_EVENT:
      break;
     
    case GDK_VISIBILITY_NOTIFY:
      break;

    case GDK_NO_EXPOSE:
      break;
               
    default:
      g_assert_not_reached();
      break;
    }

  if (entire_line == NULL)
    {
      /* Assumes we always have a detail if we have a state */

      entire_line = g_strconcat(name_line, 
                                "  ", any_line, 
                                detail ? "  " : NULL, detail, 
                                state ? "  " : NULL, state,
                                NULL);
    }

  g_free(name_line);
  g_free(any_line);
  g_free(detail);
  g_free(state);

  return entire_line;
}