Capítulo 7. Sorting

Lists and trees are meant to be sorted. This is done using the GtkTreeSortable interface that can be implemented by tree models. 'Interface' means that you can just cast a GtkTreeModel into a GtkTreeSortable with GTK_TREE_SORTABLE(model) and use the documented tree sortable functions on it, just like we did before when we cast a list store to a tree model and used the gtk_tree_model_foo family of functions. Both GtkListStore and GtkTreeStore implement the tree sortable interface.

The most straight forward way to sort a list store or tree store is to directly use the tree sortable interface on them. This will sort the store in place, meaning that rows will actually be reordered in the store if required. This has the advantage that the position of a row in the tree view will always be the same as the position of a row in the model, in other words: a tree path refering to a row in the view will always refer to the same row in the model, so you can get a row's iter easily with gtk_tree_model_get_iter using a tree path supplied by the tree view. This is not only convenient, but also sufficient for most scenarios.

However, there are cases when sorting a model in place is not desirable, for example when several tree views display the same model with different sortings, or when the unsorted state of the model has some special meaning and needs to be restored at some point. This is where GtkTreeModelSort comes in, which is a special model that maps the unsorted rows of a child model (e.g. a list store or tree store) into a sorted state without changing the child model.

7.1. GtkTreeSortable

The tree sortable interface is fairly simple and should be easy to use. Basically you define a 'sort column ID' integer for every criterion you might want to sort by and tell the tree sortable which function should be called to compare two rows (represented by two tree iters) for every sort ID with gtk_tree_sortable_set_sort_func. Then you sort the model by setting the sort column ID and sort order with gtk_tree_sortable_set_sort_column_id, and the model will be re-sorted using the compare function you have set up. Your sort column IDs can correspond to your model columns, but they do not have to (you might want to sort according to a criterion that is not directly represented by the data in one single model column, for example). Some code to illustrate this:


  enum
  {
    COL_NAME = 0,
    COL_YEAR_BORN
  };


  enum
  {
    SORTID_NAME = 0,
    SORTID_YEAR
  };


  GtkTreeModel  *liststore = NULL;


  void
  toolbar_onSortByYear (void)
  {
    GtkTreeSortable *sortable;
    GtkSortType      order;
    gint             sortid;

    sortable = GTK_TREE_SORTABLE(liststore);

    /* If we are already sorting by year, reverse sort order,
     *  otherwise set it to year in ascending order */

    if (gtk_tree_sortable_get_sort_column_id(sortable, &sortid, &order) == TRUE
          &&  sortid == SORTID_YEAR)
    {
      GtkSortType neworder;

      neworder = (order == GTK_SORT_ASCENDING) ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;

      gtk_tree_sortable_set_sort_column_id(sortable, SORTID_YEAR, neworder);
    }
    else
    {
      gtk_tree_sortable_set_sort_column_id(sortable, SORTID_YEAR, GTK_SORT_ASCENDING);
    }
  }


  /* This is not pretty. Of course you can also use a
   *  separate compare function for each sort ID value */

  gint
  sort_iter_compare_func (GtkTreeModel *model,
                          GtkTreeIter  *a,
                          GtkTreeIter  *b,
                          gpointer      userdata)
  {
    gint sortcol = GPOINTER_TO_INT(userdata);
    gint ret = 0;

    switch (sortcol)
    {
      case SORTID_NAME:
      {
        gchar *name1, *name2;

        gtk_tree_model_get(model, a, COL_NAME, &name1, -1);
        gtk_tree_model_get(model, b, COL_NAME, &name2, -1);

        if (name1 == NULL || name2 == NULL)
        {
          if (name1 == NULL && name2 == NULL)
            break; /* both equal => ret = 0 */

          ret = (name1 == NULL) ? -1 : 1;
        }
        else
        {
          ret = g_utf8_collate(name1,name2);
        }

        g_free(name1);
        g_free(name2);
      }
      break;

      case SORTID_YEAR:
      {
        guint year1, year2;

        gtk_tree_model_get(model, a, COL_YEAR_BORN, &year1, -1);
        gtk_tree_model_get(model, b, COL_YEAR_BORN, &year2, -1);

        if (year1 != year2)
        {
          ret = (year1 > year2) ? 1 : -1;
        }
        /* else both equal => ret = 0 */
      }
      break;

      default:
        g_return_val_if_reached(0);
    }

    return ret;
  }


  void
  create_list_and_view (void)
  {
    GtkTreeSortable *sortable;

    ...

    liststore = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_UINT);

    sortable = GTK_TREE_SORTABLE(liststore);

    gtk_tree_sortable_set_sort_func(sortable, SORTID_NAME, sort_iter_compare_func,
                                    GINT_TO_POINTER(SORTID_NAME), NULL);

    gtk_tree_sortable_set_sort_func(sortable, SORTID_YEAR, sort_iter_compare_func,
                                    GINT_TO_POINTER(SORTID_YEAR), NULL);

    /* set initial sort order */
    gtk_tree_sortable_set_sort_column_id(sortable, SORTID_NAME, GTK_SORT_ASCENDING);

    ...

    view = gtk_tree_view_new_with_model(liststore);

    ...

  }

Usually things are a bit easier if you make use of the tree view column headers for sorting, in which case you only need to assign sort column IDs and your compare functions, but do not need to set the current sort column ID or order yourself (see below).

Your tree iter compare function should return a negative value if the row specified by iter a comes before the row specified by iter b, and a positive value if row b comes before row a. It should return 0 if both rows are equal according to your sorting criterion (you might want to use a second sort criterion though to avoid 'jumping' of equal rows when the store gets resorted). Your tree iter compare function should not take the sort order into account, but assume an ascending sort order (otherwise bad things will happen).