GTK+ By Example/Tree View/Custom Cell Renderers

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Writing Custom Cell Renderers[edit | edit source]

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.h[edit | edit source]

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. */

#define CUSTOM_TYPE_CELL_RENDERER_PROGRESS             (custom_cell_renderer_progress_get_type())
G_DECLARE_DERIVABLE_TYPE(CustomCellRendererProgress, custom_cell_renderer_progress, CUSTOM, CELL_RENDERER_PROGRESS, GtkCellRenderer)


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.c[edit | edit source]

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 */


typedef struct _CustomCellRendererProgressPrivate CustomCellRendererProgressPrivate;
struct _CustomCellRendererProgressPrivate
{
    gdouble progress;
};
G_DEFINE_TYPE_WITH_PRIVATE(CustomCellRendererProgress, custom_cell_renderer_progress,  GTK_TYPE_CELL_RENDERER)


/* 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_preferred_width (
              GtkCellRenderer       *cell_renderer,
              GtkWidget             *parent_widget,
              int                   *minimal_size,
              int                   *natural_size);

static void custom_cell_renderer_progress_get_preferred_height (
              GtkCellRenderer       *cell_renderer,
              GtkWidget             *parent_widget,
              int                   *minimal_size,
              int                   *natural_size);

static void custom_cell_renderer_progress_render (
              GtkCellRenderer      *cell_renderer,
              cairo_t              *cr,
              GtkWidget            *widget,
              const GdkRectangle   *background_area,
              const GdkRectangle   *cell_area,
              GtkCellRendererState  flags);

enum
{
  PROP_PERCENTAGE = 1,
};

static   gpointer parent_class;

/******************************************************************
 *
 * CustomCellRendererProgressPrivate: shortcut function to access
 *                                    private data.
 *
 ******************************************************************/
static inline CustomCellRendererProgressPrivate *
private (CustomCellRendererProgress *self)
{
   return custom_cell_renderer_progress_get_instance_private(self);
}

/***************************************************************************
 *
 *  custom_cell_renderer_progress_init: set some default properties of the
 *                                      parent (GtkCellRenderer).
 *
 ***************************************************************************/

static void
custom_cell_renderer_progress_init (CustomCellRendererProgress *self)
{
  /* init parent */
  {
    GtkCellRenderer *parent = GTK_CELL_RENDERER(self);

    g_object_set (parent, "mode", GTK_CELL_RENDERER_MODE_INERT, NULL);
    g_object_set (parent, "xpad", 2, NULL);
    g_object_set (parent, "ypad", 2, NULL);
  }

  /* init private variables */
  {
   private (self)->progress = 0.0;
  }
}


/***************************************************************************
 *
 *  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 crucial functions that are the heart
   *   of a cell renderer in the parent class */
  cell_class->get_preferred_width = custom_cell_renderer_progress_get_preferred_width;
  cell_class->get_preferred_height = custom_cell_renderer_progress_get_preferred_height;
  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  *self = CUSTOM_CELL_RENDERER_PROGRESS(object);

  switch (param_id)
  {
    case PROP_PERCENTAGE:
      g_value_set_double(value, private (self)->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 *self = CUSTOM_CELL_RENDERER_PROGRESS (object);

  switch (param_id)
  {
    case PROP_PERCENTAGE:
      private (self)->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_preferred_width:
 *            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_preferred_width (GtkCellRenderer *cell_renderer,
                                                   GtkWidget       *parent_widget,
                                                   int             *minimal_size,
                                                   int             *natural_size)
{
  gint calc_width;
  gint xpad;

  g_object_get( cell_renderer, "xpad", &xpad, NULL);

  calc_width  = (gint) xpad * 2 + FIXED_WIDTH;

  if (minimal_size)
    *minimal_size = calc_width;

  if (natural_size)
    *natural_size = calc_width;
}


/***************************************************************************
 *
 *  custom_cell_renderer_progress_get_preferred_height:
 *            crucial - calculate the size of our cell, taking into account
 *             padding and alignment properties of parent.
 *
 ***************************************************************************/

static void
custom_cell_renderer_progress_get_preferred_height (GtkCellRenderer *cell_renderer,
                                                    GtkWidget       *parent_widget,
                                                    int             *minimal_size,
                                                    int             *natural_size)
{
  gint calc_height;
  gint ypad;

  g_object_get( cell_renderer, "ypad", &ypad, NULL);

  calc_height = (gint) ypad * 2 + FIXED_HEIGHT;

  if (minimal_size)
    *minimal_size = calc_height;

  if (natural_size)
    *natural_size = calc_height;
}

/***************************************************************************
 *
 *  custom_cell_renderer_progress_render: crucial - do the rendering.
 *
 ***************************************************************************/

static void
custom_cell_renderer_progress_render(GtkCellRenderer      *cell_renderer,
                                     cairo_t              *cr,
                                     GtkWidget            *widget,
                                     const GdkRectangle   *background_area,
                                     const GdkRectangle   *cell_area,
                                     GtkCellRendererState  flags)
{
  CustomCellRendererProgress *self = CUSTOM_CELL_RENDERER_PROGRESS (cell_renderer);
  GtkStateType                state;
  gint                        width, height, x_pad, y_pad;
  gint                        x_offset, y_offset;

  custom_cell_renderer_progress_get_preferred_height (cell_renderer, widget, &height, &height);
  custom_cell_renderer_progress_get_preferred_width (cell_renderer, widget, &width, &width);

  if (gtk_widget_is_focus (widget))
    state = GTK_STATE_ACTIVE;
  else
    state = GTK_STATE_NORMAL;

  g_object_get (cell_renderer, "xpad", &x_pad, NULL);
  width -= x_pad*2;
  g_object_get (cell_renderer, "ypad", &y_pad, NULL);
  height -= y_pad*2;

  {
    GtkStyleContext *style = gtk_widget_get_style_context(widget);
    gtk_style_context_save(style);
    gtk_style_context_set_state(style, state);

    gint draw_width = cell_area->x + x_pad;
    gint draw_height = cell_area->y + y_pad;

    /* draw border */
    {
      gtk_render_frame (style, cr,
                        draw_width, draw_height,
                        width - 1, height - 1);
      /* fallback, if gtk_render_frame is now shown (as on my system) */
      gtk_render_background (style, cr,
                             draw_width, draw_height,
                             width - 1, height - 1);
    }

    /* draw progress indicator */
    {
      gint progress_width = (private (self)->progress) * (width - 1);
      gtk_style_context_set_state(style, GTK_STATE_INSENSITIVE);
      gtk_render_background (style, cr,
                             draw_width + 1, draw_height + 1,
                             progress_width - 2, height - 1 - 2);
    }

    gtk_style_context_restore(style);
  }
}


main.c[edit | edit source]

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 Written[edit | edit source]

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?!