GTK+ By Example/Tree View/Custom Cell Renderers

Writing Custom Cell RenderersEdit

The cell renderers that come with Gtk+ should be sufficient for most purposes, but there might be occasions where you want to display something in a tree view that you cannot display with the provided cell renderers, or where you want to derive from one of the provided cell renderers to extend its functionality.

You can do this by writing a new object that derives from GtkCellRenderer (or even one of the other cell renderers if you just want to extend an existing one).

Three things you need to do in the course of that:

  • Register some new properties that your renderer needs with the type system and write your own set_property and get_property functions to set and get your new renderer's properties.
  • Write your own cell_renderer_get_size function and override the parent object's function (usually the parent is of type GtkCellRenderer. Note that you should honour the standard properties for padding and cell alignment of the parent object here.
  • Write your own cell_renderer_render function and override the parent object's function. This function does the actual rendering.

The GObject type system stuff of writing a new cell renderer is similar to what we have done above when writing a custom tree model, and is relatively straight forward in this case. Copy and paste and modify according to your own needs.

Good examples of cell renderer code to look at or even modify are GtkCellRendererPixbuf and GtkCellRendererToggle in the Gtk+ source code tree. Both cases are less than five hundred lines of code to look at and thus should be fairly easy to digest. 12.1. Working Example: a Progress Bar Cell Renderer

In the following we will write a custom cell renderer to render progress bars into a tree view (the code was "heavily inspired" by Sean Egan's progress bar cell renderer implementation in GAIM; note this is merely for demonstrational purposes, Gtk+ has had a progress bar cell renderer for quite a while now: see the GtkCellRendererProgress API documentation for more details).

  • custom-cell-renderer-progressbar.h
  • custom-cell-renderer-progressbar.c
  • main.c

custom-cell-renderer-progressbar.hEdit

The header file consists of the usual GObject type cast and type check defines and our CustomCellRendererProgress structure. As the type of the parent indicates, we derive from GtkCellRenderer. The parent object must always be the first item in the structure (note also that it is not a pointer to an object, but the parent object structure itself embedded in our structure).

Our CustomCellRendererProgress structure is fairly uneventful and contains only a double precision float variable in which we store our new "percentage" property (which will determine how long the progressbar is going to be).

  #ifndef _custom_cell_renderer_progressbar_included_
  #define _custom_cell_renderer_progressbar_included_
 
  #include <gtk/gtk.h>
 
  /* Some boilerplate GObject type check and type cast macros.
   *  'klass' is used here instead of 'class', because 'class'
   *  is a c++ keyword */
 
  #define CUSTOM_TYPE_CELL_RENDERER_PROGRESS             (custom_cell_renderer_progress_get_type())
  #define CUSTOM_CELL_RENDERER_PROGRESS(obj)             (G_TYPE_CHECK_INSTANCE_CAST((obj),  CUSTOM_TYPE_CELL_RENDERER_PROGRESS, CustomCellRendererProgress))
  #define CUSTOM_CELL_RENDERER_PROGRESS_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass),  CUSTOM_TYPE_CELL_RENDERER_PROGRESS, CustomCellRendererProgressClass))
  #define CUSTOM_IS_CELL_PROGRESS_PROGRESS(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CUSTOM_TYPE_CELL_RENDERER_PROGRESS))
  #define CUSTOM_IS_CELL_PROGRESS_PROGRESS_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass),  CUSTOM_TYPE_CELL_RENDERER_PROGRESS))
  #define CUSTOM_CELL_RENDERER_PROGRESS_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj),  CUSTOM_TYPE_CELL_RENDERER_PROGRESS, CustomCellRendererProgressClass))
 
  typedef struct _CustomCellRendererProgress CustomCellRendererProgress;
  typedef struct _CustomCellRendererProgressClass CustomCellRendererProgressClass;
 
  /* CustomCellRendererProgress: Our custom cell renderer
   *   structure. Extend according to need */
 
  struct _CustomCellRendererProgress
  {
    GtkCellRenderer   parent;
 
    gdouble           progress;
  };
 
 
  struct _CustomCellRendererProgressClass
  {
    GtkCellRendererClass  parent_class;
  };
 
 
  GType                custom_cell_renderer_progress_get_type (void);
 
  GtkCellRenderer     *custom_cell_renderer_progress_new (void);
 
 
  #endif /* _custom_cell_renderer_progressbar_included_ */



custom-cell-renderer-progressbar.cEdit

The code contains everything as described above, so let's jump right into it:

#include "custom-cell-renderer-progressbar.h"
 
/* This is based mainly on GtkCellRendererProgress
 *  in GAIM, written and (c) 2002 by Sean Egan
 *  (Licensed under the GPL), which in turn is
 *  based on Gtk's GtkCellRenderer[Text|Toggle|Pixbuf]
 *  implementation by Jonathan Blandford */
 
/* Some boring function declarations: GObject type system stuff */
 
static void     custom_cell_renderer_progress_init       (CustomCellRendererProgress      *cellprogress);
 
static void     custom_cell_renderer_progress_class_init (CustomCellRendererProgressClass *klass);
 
static void     custom_cell_renderer_progress_get_property  (GObject                    *object,
                                                             guint                       param_id,
                                                             GValue                     *value,
                                                             GParamSpec                 *pspec);
 
static void     custom_cell_renderer_progress_set_property  (GObject                    *object,
                                                             guint                       param_id,
                                                             const GValue               *value,
                                                             GParamSpec                 *pspec);
 
static void     custom_cell_renderer_progress_finalize (GObject *gobject);
 
 
/* These functions are the heart of our custom cell renderer: */
 
static void     custom_cell_renderer_progress_get_size   (GtkCellRenderer            *cell,
                                                          GtkWidget                  *widget,
                                                          GdkRectangle               *cell_area,
                                                          gint                       *x_offset,
                                                          gint                       *y_offset,
                                                          gint                       *width,
                                                          gint                       *height);
 
static void     custom_cell_renderer_progress_render     (GtkCellRenderer            *cell,
                                                          GdkWindow                  *window,
                                                          GtkWidget                  *widget,
                                                          GdkRectangle               *background_area,
                                                          GdkRectangle               *cell_area,
                                                          GdkRectangle               *expose_area,
                                                          guint                       flags);
 
 
enum
{
  PROP_PERCENTAGE = 1,
};
 
static   gpointer parent_class;
 
 
/***************************************************************************
 *
 *  custom_cell_renderer_progress_get_type: here we register our type with
 *                                          the GObject type system if we
 *                                          haven't done so yet. Everything
 *                                          else is done in the callbacks.
 *
 ***************************************************************************/
 
GType
custom_cell_renderer_progress_get_type (void)
{
  static GType cell_progress_type = 0;
 
  if (cell_progress_type == 0)
  {
    static const GTypeInfo cell_progress_info =
    {
      sizeof (CustomCellRendererProgressClass),
      NULL,                                                     /* base_init */
      NULL,                                                     /* base_finalize */
      (GClassInitFunc) custom_cell_renderer_progress_class_init,
      NULL,                                                     /* class_finalize */
      NULL,                                                     /* class_data */
      sizeof (CustomCellRendererProgress),
      0,                                                        /* n_preallocs */
      (GInstanceInitFunc) custom_cell_renderer_progress_init,
    };
 
    /* Derive from GtkCellRenderer */
    cell_progress_type = g_type_register_static (GTK_TYPE_CELL_RENDERER,
                                                 "CustomCellRendererProgress",
                                                  &cell_progress_info,
                                                  0);
  }
 
  return cell_progress_type;
}
 
 
/***************************************************************************
 *
 *  custom_cell_renderer_progress_init: set some default properties of the
 *                                      parent (GtkCellRenderer).
 *
 ***************************************************************************/
 
static void
custom_cell_renderer_progress_init (CustomCellRendererProgress *cellrendererprogress)
{
  GTK_CELL_RENDERER(cellrendererprogress)->mode = GTK_CELL_RENDERER_MODE_INERT;
  GTK_CELL_RENDERER(cellrendererprogress)->xpad = 2;
  GTK_CELL_RENDERER(cellrendererprogress)->ypad = 2;
}
 
 
/***************************************************************************
 *
 *  custom_cell_renderer_progress_class_init:
 *
 *  set up our own get_property and set_property functions, and
 *  override the parent's functions that we need to implement.
 *  And make our new "percentage" property known to the type system.
 *  If you want cells that can be activated on their own (ie. not
 *  just the whole row selected) or cells that are editable, you
 *  will need to override 'activate' and 'start_editing' as well.
 *
 ***************************************************************************/
 
static void
custom_cell_renderer_progress_class_init (CustomCellRendererProgressClass *klass)
{
  GtkCellRendererClass *cell_class   = GTK_CELL_RENDERER_CLASS(klass);
  GObjectClass         *object_class = G_OBJECT_CLASS(klass);
 
  parent_class           = g_type_class_peek_parent (klass);
  object_class->finalize = custom_cell_renderer_progress_finalize;
 
  /* Hook up functions to set and get our
   *   custom cell renderer properties */
  object_class->get_property = custom_cell_renderer_progress_get_property;
  object_class->set_property = custom_cell_renderer_progress_set_property;
 
  /* Override the two crucial functions that are the heart
   *   of a cell renderer in the parent class */
  cell_class->get_size = custom_cell_renderer_progress_get_size;
  cell_class->render   = custom_cell_renderer_progress_render;
 
  /* Install our very own properties */
  g_object_class_install_property (object_class,
                                   PROP_PERCENTAGE,
                                   g_param_spec_double ("percentage",
                                                        "Percentage",
                                                         "The fractional progress to display",
                                                         0, 1, 0,
                                                         G_PARAM_READWRITE));
}
 
 
/***************************************************************************
 *
 *  custom_cell_renderer_progress_finalize: free any resources here
 *
 ***************************************************************************/
 
static void
custom_cell_renderer_progress_finalize (GObject *object)
{
/*
  CustomCellRendererProgress *cellrendererprogress = CUSTOM_CELL_RENDERER_PROGRESS(object);
*/
 
  /* Free any dynamically allocated resources here */
 
  (* G_OBJECT_CLASS (parent_class)->finalize) (object);
}
 
 
/***************************************************************************
 *
 *  custom_cell_renderer_progress_get_property: as it says
 *
 ***************************************************************************/
 
static void
custom_cell_renderer_progress_get_property (GObject    *object,
                                            guint       param_id,
                                            GValue     *value,
                                            GParamSpec *psec)
{
  CustomCellRendererProgress  *cellprogress = CUSTOM_CELL_RENDERER_PROGRESS(object);
 
  switch (param_id)
  {
    case PROP_PERCENTAGE:
      g_value_set_double(value, cellprogress->progress);
      break;
 
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, psec);
      break;
  }
}
 
 
/***************************************************************************
 *
 *  custom_cell_renderer_progress_set_property: as it says
 *
 ***************************************************************************/
 
static void
custom_cell_renderer_progress_set_property (GObject      *object,
                                            guint         param_id,
                                            const GValue *value,
                                            GParamSpec   *pspec)
{
  CustomCellRendererProgress *cellprogress = CUSTOM_CELL_RENDERER_PROGRESS (object);
 
  switch (param_id)
  {
    case PROP_PERCENTAGE:
      cellprogress->progress = g_value_get_double(value);
      break;
 
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
      break;
  }
}
 
/***************************************************************************
 *
 *  custom_cell_renderer_progress_new: return a new cell renderer instance
 *
 ***************************************************************************/
 
GtkCellRenderer *
custom_cell_renderer_progress_new (void)
{
  return g_object_new(CUSTOM_TYPE_CELL_RENDERER_PROGRESS, NULL);
}
 
 
/***************************************************************************
 *
 *  custom_cell_renderer_progress_get_size: crucial - calculate the size
 *                                          of our cell, taking into account
 *                                          padding and alignment properties
 *                                          of parent.
 *
 ***************************************************************************/
 
#define FIXED_WIDTH   100
#define FIXED_HEIGHT  10
 
static void
custom_cell_renderer_progress_get_size (GtkCellRenderer *cell,
                                        GtkWidget       *widget,
                                        GdkRectangle    *cell_area,
                                        gint            *x_offset,
                                        gint            *y_offset,
                                        gint            *width,
                                        gint            *height)
{
  gint calc_width;
  gint calc_height;
 
  calc_width  = (gint) cell->xpad * 2 + FIXED_WIDTH;
  calc_height = (gint) cell->ypad * 2 + FIXED_HEIGHT;
 
  if (width)
    *width = calc_width;
 
  if (height)
    *height = calc_height;
 
  if (cell_area)
  {
    if (x_offset)
    {
      *x_offset = cell->xalign * (cell_area->width - calc_width);
      *x_offset = MAX (*x_offset, 0);
    }
 
    if (y_offset)
    {
      *y_offset = cell->yalign * (cell_area->height - calc_height);
      *y_offset = MAX (*y_offset, 0);
    }
  }
}99
 
 
/***************************************************************************
 *
 *  custom_cell_renderer_progress_render: crucial - do the rendering.
 *
 ***************************************************************************/
 
static void
custom_cell_renderer_progress_render (GtkCellRenderer *cell,
                                      GdkWindow       *window,
                                      GtkWidget       *widget,
                                      GdkRectangle    *background_area,
                                      GdkRectangle    *cell_area,
                                      GdkRectangle    *expose_area,
                                      guint            flags)
{
  CustomCellRendererProgress *cellprogress = CUSTOM_CELL_RENDERER_PROGRESS (cell);
  GtkStateType                state;
  gint                        width, height;
  gint                        x_offset, y_offset;
 
  custom_cell_renderer_progress_get_size (cell, widget, cell_area,
                                          &x_offset, &y_offset,
                                          &width, &height);
 
  if (GTK_WIDGET_HAS_FOCUS (widget))
    state = GTK_STATE_ACTIVE;
  else
    state = GTK_STATE_NORMAL;
 
  width  -= cell->xpad*2;
  height -= cell->ypad*2;
 
  gtk_paint_box (widget->style,
                 window,
                 GTK_STATE_NORMAL, GTK_SHADOW_IN,
                 NULL, widget, "trough",
                 cell_area->x + x_offset + cell->xpad,
                 cell_area->y + y_offset + cell->ypad,
                 width - 1, height - 1);
 
  gtk_paint_box (widget->style,
                 window,
                 state, GTK_SHADOW_OUT,
                 NULL, widget, "bar",
                 cell_area->x + x_offset + cell->xpad,
                 cell_area->y + y_offset + cell->ypad,
                 width * cellprogress->progress,
                 height - 1);
}


main.cEdit

And here is a little test that makes use of our new CustomCellRendererProgress:

#include "custom-cell-renderer-progressbar.h"
 
static GtkListStore        *liststore;
 
static gboolean             increasing = TRUE;   /* direction of progress bar change */
 
enum
{
  COL_PERCENTAGE = 0,
  COL_TEXT,
  NUM_COLS
};
 
#define STEP  0.01
 
gboolean
increase_progress_timeout (GtkCellRenderer *renderer)
{
  GtkTreeIter  iter;
  gfloat       perc = 0.0;
  gchar        buf[20];
 
  gtk_tree_model_get_iter_first(GTK_TREE_MODEL(liststore), &iter); /* first and only row */
 
  gtk_tree_model_get (GTK_TREE_MODEL(liststore), &iter, COL_PERCENTAGE, &perc, -1);
 
  if ( perc > (1.0-STEP)  ||  (perc < STEP && perc > 0.0) )
  {
    increasing = (!increasing);
  }
 
  if (increasing)
    perc = perc + STEP;
  else
    perc = perc - STEP;
 
  g_snprintf(buf, sizeof(buf), "%u %%", (guint)(perc*100));
 
  gtk_list_store_set (liststore, &iter, COL_PERCENTAGE, perc, COL_TEXT, buf, -1);
 
  return TRUE; /* Call again */
}
 
 
GtkWidget *
create_view_and_model (void)
{
  GtkTreeViewColumn   *col;
  GtkCellRenderer     *renderer;
  GtkTreeIter          iter;
  GtkWidget           *view;
 
  liststore = gtk_list_store_new(NUM_COLS, G_TYPE_FLOAT, G_TYPE_STRING);
  gtk_list_store_append(liststore, &iter);
  gtk_list_store_set (liststore, &iter, COL_PERCENTAGE, 0.5, -1); /* start at 50% */
 
  view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(liststore));
 
  g_object_unref(liststore); /* destroy store automatically with view */
 
  renderer = gtk_cell_renderer_text_new();
  col = gtk_tree_view_column_new();
  gtk_tree_view_column_pack_start (col, renderer, TRUE);
  gtk_tree_view_column_add_attribute (col, renderer, "text", COL_TEXT);
  gtk_tree_view_column_set_title (col, "Progress");
  gtk_tree_view_append_column(GTK_TREE_VIEW(view),col);
 
  renderer = custom_cell_renderer_progress_new();
  col = gtk_tree_view_column_new();
  gtk_tree_view_column_pack_start (col, renderer, TRUE);
  gtk_tree_view_column_add_attribute (col, renderer, "percentage", COL_PERCENTAGE);
  gtk_tree_view_column_set_title (col, "Progress");
  gtk_tree_view_append_column(GTK_TREE_VIEW(view),col);
 
  g_timeout_add(50, (GSourceFunc) increase_progress_timeout, NULL);
 
  return view;
}
 
 
int
main (int argc, char **argv)
{
  GtkWidget *window, *view;
 
  gtk_init(&argc,&argv);
 
  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_default_size (GTK_WINDOW(window), 150, 100);
  g_signal_connect(window, "delete_event", gtk_main_quit, NULL);
 
  view = create_view_and_model();
 
  gtk_container_add(GTK_CONTAINER(window), view);
 
  gtk_widget_show_all(window);
 
  gtk_main();
 
  return 0;
}

Cell Renderers Others Have WrittenEdit

Just in case you are one of those people who do not like to re-invent the wheel, here is a list of custom cell renderers other people have written:

  • Date cell renderer (Planner) (is this one easy to re-use?)
  • List/combo cell renderer (Planner) (is this one easy to re-use?) (FIXME: obsoleted by GtkCellRendererCombo)
  • Pop-up cell renderer (Planner) (what does this do?)
  • Your custom cell renderer here?!
Last modified on 8 July 2009, at 21:10