11.5. Additional interfaces, here: the GtkTreeSortable interface

A custom model can implement additional interfaces to extend its functionality. Additional interfaces are:

Here, we will show how to implement additional interfaces at the example of the GtkTreeSortable interface, which we will implement only partially (enough to make it functional and useful though).

Three things are necessary to add another interface: we will need to register the interface with our model in custom_list_get_type, provide an interface init function where we set the interface to our own implementation of the interface functions, and then provide the implementation of those functions.

Firstly, we need to provide the function prototypes for our functions at the beginning of the file:


  /* custom-list.c */

  ...

  /* -- GtkTreeSortable interface functions -- */

  static gboolean     custom_list_sortable_get_sort_column_id (GtkTreeSortable *sortable,
                                                               gint            *sort_col_id,
                                                               GtkSortType     *order);

  static void         custom_list_sortable_set_sort_column_id (GtkTreeSortable *sortable,
                                                               gint             sort_col_id,
                                                               GtkSortType      order);

  static void         custom_list_sortable_set_sort_func (GtkTreeSortable        *sortable,
                                                          gint                    sort_col_id,
                                                          GtkTreeIterCompareFunc  sort_func,
                                                          gpointer                user_data,
                                                          GtkDestroyNotify        destroy_func);

  static void         custom_list_sortable_set_default_sort_func (GtkTreeSortable        *sortable,
                                                                  GtkTreeIterCompareFunc  sort_func,
                                                                  gpointer                user_data,
                                                                  GtkDestroyNotify        destroy_func);

  static gboolean     custom_list_sortable_has_default_sort_func (GtkTreeSortable *sortable);

  static void         custom_list_resort (CustomList *custom_list);

  ...

Next, let's extend our CustomList structure with a field for the currently active sort column ID and one for the sort order, and add an enum for the sort column IDs:


  /* custom-list.h */

  enum
  {
    SORT_ID_NONE = 0,
    SORT_ID_NAME,
    SORT_ID_YEAR_BORN,
  };

  ...

  struct _CustomList
  {
    GObject         parent;

    guint           num_rows;    /* number of rows that we have */
    CustomRecord  **rows;        /* a dynamically allocated array of pointers to the
                                  *  CustomRecord structure for each row */

    gint            n_columns;
    GType           column_types[CUSTOM_LIST_N_COLUMNS];

    gint            sort_id;
    GtkSortType     sort_order;

    gint            stamp;       /* Random integer to check whether an iter belongs to our model */
  };

  ...

Now, we make sure we initialise the new fields in custom_list_new, and add our new interface:


  ...

  static void    custom_list_sortable_init (GtkTreeSortableIface *iface);

  ...

  void
  custom_list_init (CustomList *custom_list)
  {
    ...
    custom_list->sort_id    = SORT_ID_NONE;
    custom_list->sort_order = GTK_SORT_ASCENDING;
    ...
  }


  GType
  custom_list_get_type (void)
  {
    ...
    /* Add GtkTreeSortable interface */
    {
      static const GInterfaceInfo tree_sortable_info =
      {
        (GInterfaceInitFunc) custom_list_sortable_init,
        NULL,
        NULL
      };

      g_type_add_interface_static (custom_list_type, GTK_TYPE_TREE_SORTABLE, &tree_sortable_info);
    }
    ...
  }


  static void
  custom_list_sortable_init (GtkTreeSortableIface *iface)
  {
    iface->get_sort_column_id    = custom_list_sortable_get_sort_column_id;
    iface->set_sort_column_id    = custom_list_sortable_set_sort_column_id;
    iface->set_sort_func         = custom_list_sortable_set_sort_func;          /* NOT SUPPORTED */
    iface->set_default_sort_func = custom_list_sortable_set_default_sort_func;  /* NOT SUPPORTED */
    iface->has_default_sort_func = custom_list_sortable_has_default_sort_func;  /* NOT SUPPORTED */
  }

Now that we have finally taken care of the administrativa, we implement the tree sortable interface functions:


  static gboolean
  custom_list_sortable_get_sort_column_id (GtkTreeSortable *sortable,
                                           gint            *sort_col_id,
                                           GtkSortType     *order)
  {
    CustomList *custom_list;

    g_return_val_if_fail ( sortable != NULL        , FALSE );
    g_return_val_if_fail ( CUSTOM_IS_LIST(sortable), FALSE );

    custom_list = CUSTOM_LIST(sortable);

    if (sort_col_id)
      *sort_col_id = custom_list->sort_id;

    if (order)
      *order =  custom_list->sort_order;

    return TRUE;
  }


  static void
  custom_list_sortable_set_sort_column_id (GtkTreeSortable *sortable,
                                           gint             sort_col_id,
                                           GtkSortType      order)
  {
    CustomList *custom_list;

    g_return_if_fail ( sortable != NULL         );
    g_return_if_fail ( CUSTOM_IS_LIST(sortable) );

    custom_list = CUSTOM_LIST(sortable);

    if (custom_list->sort_id == sort_col_id && custom_list->sort_order == order)
      return;

    custom_list->sort_id = sort_col_id;
    custom_list->sort_order  = order;

    custom_list_resort(custom_list);

    /* emit "sort-column-changed" signal to tell any tree views
     *  that the sort column has changed (so the little arrow
     *  in the column header of the sort column is drawn
     *  in the right column)                                     */

    gtk_tree_sortable_sort_column_changed(sortable);
  }


  static void
  custom_list_sortable_set_sort_func (GtkTreeSortable        *sortable,
                                      gint                    sort_col_id,
                                      GtkTreeIterCompareFunc  sort_func,
                                      gpointer                user_data,
                                      GtkDestroyNotify        destroy_func)
  {
    g_warning ("%s is not supported by the CustomList model.\n", __FUNCTION__);
  }


  static void
  custom_list_sortable_set_default_sort_func (GtkTreeSortable        *sortable,
                                              GtkTreeIterCompareFunc  sort_func,
                                              gpointer                user_data,
                                              GtkDestroyNotify        destroy_func)
  {
    g_warning ("%s is not supported by the CustomList model.\n", __FUNCTION__);
  }


  static gboolean
  custom_list_sortable_has_default_sort_func (GtkTreeSortable *sortable)
  {
    return FALSE;
  }

Now, last but not least, the only thing missing is the function that does the actual sorting. We do not implement set_sort_func, set_default_sort_func and set_has_default_sort_func because we use our own internal sort function here.

The actual sorting is done using GLib's g_qsort_with_data function, which sorts an array using the QuickSort algorithm. Note how we notify the tree view and other objects of the new row order by emitting the "rows-reordered" signal on the tree model.


  static gint
  custom_list_compare_records (gint sort_id, CustomRecord *a, CustomRecord *b)
  {
    switch(sort_id)
    {
      case SORT_ID_NONE:
        return 0;

      case SORT_ID_NAME:
      {
        if ((a->name) && (b->name))
          return g_utf8_collate(a->name, b->name);

        if (a->name == b->name)
          return 0; /* both are NULL */
        else
          return (a->name == NULL) ? -1 : 1;
      }

      case SORT_ID_YEAR_BORN:
      {
        if (a->year_born == b->year_born)
          return 0;

        return (a->year_born > b->year_born) ? 1 : -1;
      }
    }

    g_return_val_if_reached(0);
  }


  static gint
  custom_list_qsort_compare_func (CustomRecord **a, CustomRecord **b, CustomList *custom_list)
  {
    gint ret;

    g_assert ((a) && (b) && (custom_list));

    ret = custom_list_compare_records(custom_list->sort_id, *a, *b);

    /* Swap -1 and 1 if sort order is reverse */
    if (ret != 0  &&  custom_list->sort_order == GTK_SORT_DESCENDING)
      ret = (ret < 0) ? 1 : -1;

    return ret;
  }


  static void
  custom_list_resort (CustomList *custom_list)
  {
    GtkTreePath *path;
    gint        *neworder, i;

    g_return_if_fail ( custom_list != NULL );
    g_return_if_fail ( CUSTOM_IS_LIST(custom_list) );

    if (custom_list->sort_id == SORT_ID_NONE)
      return;

    if (custom_list->num_rows == 0)
      return;

    /* resort */
    g_qsort_with_data(custom_list->rows,
                      custom_list->num_rows,
                      sizeof(CustomRecord*),
                      (GCompareDataFunc) custom_list_qsort_compare_func,
                      custom_list);

    /* let other objects know about the new order */
    neworder = g_new0(gint, custom_list->num_rows);

    for (i = 0; i < custom_list->num_rows; ++i)
    {
      /* Note that the API reference might be wrong about
       * this, see bug number 124790 on bugs.gnome.org.
       * Both will work, but one will give you 'jumpy'
       * selections after row reordering. */
      /* neworder[(custom_list->rows[i])->pos] = i; */
      neworder[i] = (custom_list->rows[i])->pos;
      (custom_list->rows[i])->pos = i;
    }

    path = gtk_tree_path_new();

    gtk_tree_model_rows_reordered(GTK_TREE_MODEL(custom_list), path, NULL, neworder);

    gtk_tree_path_free(path);
    g_free(neworder);
  }

Finally, we should make sure that the model is resorted after we have inserted a new row by adding a call to custom_list_resort to the end of custom_list_append:


  ...
  void
  custom_list_append_record (CustomList *custom_list, const gchar *name, guint year_born)
  {
    ...

    custom_list_resort(custom_list);
  }

And that is it. Adding two calls to gtk_tree_view_column_set_sort_column_id in main.c is left as yet another exercise for the reader.

If you are interested in seeing string sorting speed issues in action, you should modify main.c like this:


  GtkWidget *
  create_view_and_model (void)
  {
    gint i;
    ...
    for (i=0; i < 1000; ++i)
    {
      fill_model(customlist);
    }
    ...
  }

Most likely, sorting 24000 rows by name will take up to several seconds now. Now, if you go back to custom_list_compare_records and replace the call to g_utf8_collate with:


  static gint
  custom_list_compare_records (gint sort_id, CustomRecord *a, CustomRecord *b)
  {
    ...

    if ((a->name) && (b->name))
      return strcmp(a->name_collate_key,b->name_collate_key);

    ...
  }

... then you should hopefully register a dramatic speed increase when sorting by name.