GTK+ 2.0 Tree View Tutorial | ||
---|---|---|
<<< Previous | Chapter 11. Writing Custom Models | Next >>> |
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.
<<< Previous | Home | Next >>> |
From a List to a Tree | Up | Working Example: Custom List Model Source Code |