GTK+ Forums

Discussion forum for GTK+ and Programming. Ask questions, troubleshoot problems, view and post example code, or express your opinions.
It is currently Fri Oct 31, 2014 12:49 pm

All times are UTC




Post new topic Reply to topic  [ 3 posts ] 
Author Message
 Post subject: GtkTreeModelFilter tutorial
PostPosted: Thu Jan 15, 2009 7:20 pm 
Offline
Never Seen the Sunlight

Joined: Wed Jul 23, 2008 10:31 am
Posts: 2406
Location: Slovenia
Hello everybody.

In my current project I did quite a lot of GtkTreeView manipulation with filters and since this part of GtkTreeView is quite complex and not described in GtkTreeView tutorial, I decided to write down this article.

I've contacted the original author of GtkTreeView tutorial, but since I haven't received any response from him yet, I'm posting my tutorial here.

Filtering

Filtering is another powerful tool which can help us reuse our data and display it in number of different ways. To add filtering abilities to your treeview, you need to add another layer in between your GtkTreeView and it's GtkTreeModel - GtkTreeModelFilter.

GtkTreeModelFilter wraps around yound your underlying GtkTreeModel and provides two groups of functionalities:
* limiting the amount of data that is being displayed
* modifying data being displayed


Limiting the amount of data being displayed

We can limit the amount of data being displayed on two levels. First level would be specifying a "virtual root" when creating new filter with gtk_tree_model_filter_new. This way we can display only one branch of our whole tree.

The second level is filtering data on a row-by-row basis. We can do that by setting "visible column" or "visible function" of the GtkTreeModelFilter. Visible column is a column in filters child model, containing boolean values, and is set with gtk_tree_model_filter_set_visible_column function. The values in this column determine if the row is getting filtered or not (if the value in this column is set to TRUE, the row is visible, otherwise is hidden). Visible function on the other hand is a GtkTreeModelFilterVisibleFunc function returning boolean value and is set with gtk_tree_model_filter_set_visible_func. Visible function gets called with model, iter and user_data as parameters for each row in child model. From that data we determine if the particular row should be filtered or not and according to this return FALSE or TRUE respectively.

Below is an example that demonstrates the usage of GtkTreeModelFilter for limiting the amount of data being displayed. All the methods described above are used, so the code is quite long, but the interesting parts are all included inside create_*_tree family of functions.

Code:
#include <gtk/gtk.h>
#include <string.h>

enum
{
   COL_TEXT,   /* Model column which holds strings to display. */
   COL_FILT,   /* Column with boolean values to determine visibility */
   N_COLS
};

/* Function declarations */
static GtkWidget *create_non_filtered_tree( GtkTreeModel *model );
static GtkWidget *create_visual_root_tree( GtkTreeModel *model );
static GtkWidget *create_visible_column_tree( GtkTreeModel *model );
static GtkWidget *create_visible_function_tree( GtkTreeModel *model,
                                    GtkEntry     *entry );

static gboolean filter_func( GtkTreeModel *model,
                      GtkTreeIter  *iter,
                      GtkEntry     *entry );
static GtkTreeModel *create_model_contents( void );
static void create_treeview_display( GtkTreeView *treeview );
static gboolean cb_entry_changed( GtkEditable *entry,
                          GtkTreeView *treeview );


int
main( int    argc,
     char **argv )
{
   GtkWidget    *window;
   GtkWidget    *table;
   GtkWidget    *label;
   GtkWidget    *entry;
   GtkWidget    *treeview;
   GtkTreeModel *model;
   
   gtk_init( &argc, &argv );
   
   /* Main window */
   window = gtk_window_new( GTK_WINDOW_TOPLEVEL );
   gtk_container_set_border_width( GTK_CONTAINER( window ), 5 );
   gtk_window_set_default_size( GTK_WINDOW( window ), 700, 400 );
   g_signal_connect( G_OBJECT( window ), "destroy",
                 G_CALLBACK( gtk_main_quit ), NULL );
   
   /* Content table */
   table = gtk_table_new( 3, 4, FALSE );
   gtk_table_set_row_spacings( GTK_TABLE( table ), 5 );
   gtk_table_set_col_spacings( GTK_TABLE( table ), 5 );
   gtk_container_add( GTK_CONTAINER( window ), table );
   
   /* Labels */
   label = gtk_label_new( "Non-filtered content" );
   gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 0, 1,
                 GTK_FILL, GTK_FILL, 0, 0 );
   label = gtk_label_new( "Virtual root node" );
   gtk_table_attach( GTK_TABLE( table ), label, 1, 2, 0, 1,
                 GTK_FILL, GTK_FILL, 0, 0 );
   label = gtk_label_new( "Visible column" );
   gtk_table_attach( GTK_TABLE( table ), label, 2, 3, 0, 1,
                 GTK_FILL, GTK_FILL, 0, 0 );
   label = gtk_label_new( "Visible function" );
   gtk_table_attach( GTK_TABLE( table ), label, 3, 4, 0, 1,
                 GTK_FILL, GTK_FILL, 0, 0 );
   
   /* Entry for last treeview's filter */
   entry = gtk_entry_new();
   gtk_table_attach( GTK_TABLE( table ), entry, 3, 4, 1, 2,
                 GTK_FILL, GTK_FILL, 0, 0 );
   
   /* Create and populate model */
   model = create_model_contents();
   
   /* Create treeviews */
   treeview = create_non_filtered_tree( model );
   gtk_table_attach( GTK_TABLE( table ), treeview, 0, 1, 1, 3,
                 GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0 );
   
   treeview = create_visual_root_tree( model );
   gtk_table_attach( GTK_TABLE( table ), treeview, 1, 2, 1, 3,
                 GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0 );
   
   treeview = create_visible_column_tree( model );
   gtk_table_attach( GTK_TABLE( table ), treeview, 2, 3, 1, 3,
                 GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0 );
   
   treeview = create_visible_function_tree( model, GTK_ENTRY( entry ) );
   gtk_table_attach( GTK_TABLE( table ), treeview, 3, 4, 2, 3,
                 GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0 );
   
   /* We need to monitor our entry for changes and refilter the model
    * when new text is entered
    */
   g_signal_connect( G_OBJECT( entry ), "changed",
                 G_CALLBACK( cb_entry_changed ),
                 GTK_TREE_VIEW( treeview ) );
   
   g_object_unref( G_OBJECT( model ) );
   
   gtk_widget_show_all( window );
   
   gtk_main();
   
   return( 0 );
}


/* ********* *
* FUNCTIONS *
* ********* */
static GtkWidget *
create_non_filtered_tree( GtkTreeModel *model )
{
   GtkWidget *treeview;
   
   /* Create treeview with model */
   treeview = gtk_tree_view_new_with_model( model );
   
   /* Create display components of tree view */
   create_treeview_display( GTK_TREE_VIEW( treeview ) );
   gtk_tree_view_expand_all( GTK_TREE_VIEW( treeview ) );
   
   return( treeview );
}

static GtkWidget *
create_visual_root_tree( GtkTreeModel *model )
{
   GtkWidget    *treeview;
   GtkTreeModel *filter;
   GtkTreePath  *path;
   
   /* Create path to set as virtual root */
   path = gtk_tree_path_new_from_string( "0" );
   
   /* Create filter and set virtual root */
   filter = gtk_tree_model_filter_new( model, path );
   gtk_tree_path_free( path );
   
   /* Create treeview with model */
   treeview = gtk_tree_view_new_with_model( filter );
   
   /* Create display components of tree view */
   create_treeview_display( GTK_TREE_VIEW( treeview ) );
   
   return( treeview );
}

static GtkWidget *
create_visible_column_tree( GtkTreeModel *model )
{
   GtkWidget    *treeview;
   GtkTreeModel *filter;
   
   /* Create filter and set visible column */
   filter = gtk_tree_model_filter_new( model, NULL );
   gtk_tree_model_filter_set_visible_column( GTK_TREE_MODEL_FILTER( filter ),
                                   COL_FILT );
   
   /* Create treeview with model */
   treeview = gtk_tree_view_new_with_model( filter );
   
   /* Create display components of tree view */
   create_treeview_display( GTK_TREE_VIEW( treeview ) );
   gtk_tree_view_expand_all( GTK_TREE_VIEW( treeview ) );
   
   return( treeview );
}

static GtkWidget *
create_visible_function_tree( GtkTreeModel *model,
                       GtkEntry     *entry )
{
   GtkWidget    *treeview;
   GtkTreeModel *filter;
   GtkTreePath  *path;
   
   /* Create path to set as virtual root */
   path = gtk_tree_path_new_from_string( "1" );
   
   /* Create filter and set visible function */
   filter = gtk_tree_model_filter_new( model, path );
   gtk_tree_model_filter_set_visible_func(
            GTK_TREE_MODEL_FILTER( filter ),
            (GtkTreeModelFilterVisibleFunc)filter_func, entry, NULL );
   
   /* Create treeview with model */
   treeview = gtk_tree_view_new_with_model( filter );
   gtk_tree_path_free( path );
   
   /* Create display components of tree view */
   create_treeview_display( GTK_TREE_VIEW( treeview ) );
   
   return( treeview );
}

/* This function is called on every model row. We retrieve the text from
* model and compare it to the one entered in entry. If model row contains
* entry's text as a substring, we return TRUE (showing the row), else FALSE
*/
static gboolean
filter_func( GtkTreeModel *model,
          GtkTreeIter  *iter,
          GtkEntry     *entry )
{
   const gchar *needle;
   gchar       *haystack;
   
   gtk_tree_model_get( model, iter, COL_TEXT, &haystack, -1 );
   needle = gtk_entry_get_text( entry );
   
   if( strstr( haystack, needle ) != NULL )
      return( TRUE );
   else
      return( FALSE );
}

static GtkTreeModel *
create_model_contents()
{
   GtkTreeStore *store;
   GtkTreeIter   iter, p_iter;
   gint          i;
   
   /* Create list store to hold our data */
   store = gtk_tree_store_new( N_COLS, G_TYPE_STRING,
                              G_TYPE_BOOLEAN );
   
   /* Populate store with sample data */
   gtk_tree_store_append( store, &p_iter, NULL );
   gtk_tree_store_set( store, &p_iter, COL_TEXT, "Visible root",
                              COL_FILT, TRUE,
                              -1 );
   for( i = 1; i < 7; i +=2 )
   {
      gchar *string;
      
      string = g_strdup_printf( "Leaf %d987", i );
      gtk_tree_store_append( store, &iter, &p_iter );
      gtk_tree_store_set( store, &iter, COL_TEXT, string,
                                COL_FILT, TRUE,
                                -1 );
      g_free( string );
      
      string = g_strdup_printf( "Leaf %d789", i + 1 );
      gtk_tree_store_append( store, &iter, &p_iter );
      gtk_tree_store_set( store, &iter, COL_TEXT, string,
                                COL_FILT, FALSE,
                                -1 );
      g_free( string );
   }
   
   gtk_tree_store_append( store, &p_iter, NULL );
   gtk_tree_store_set( store, &p_iter, COL_TEXT, "Hidden root",
                              COL_FILT, TRUE,
                              -1 );
   for( i = 1; i < 7; i +=2 )
   {
      gchar *string;
      
      string = g_strdup_printf( "Leaf %d097", i );
      gtk_tree_store_append( store, &iter, &p_iter );
      gtk_tree_store_set( store, &iter, COL_TEXT, string,
                                COL_FILT, TRUE,
                                -1 );
      g_free( string );
      
      string = g_strdup_printf( "Leaf %d089", i + 1 );
      gtk_tree_store_append( store, &iter, &p_iter );
      gtk_tree_store_set( store, &iter, COL_TEXT, string,
                                COL_FILT, FALSE,
                                -1 );
      g_free( string );
   }
   
   return( GTK_TREE_MODEL( store ) );
}

static void
create_treeview_display( GtkTreeView *treeview )
{
   GtkTreeViewColumn *col;
   GtkCellRenderer   *renderer;
   
   /* Create column for displaying text */
   col = gtk_tree_view_column_new();
   renderer = gtk_cell_renderer_text_new();
   gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( col ), renderer, TRUE );
   gtk_tree_view_column_set_attributes( col, renderer, "text", COL_TEXT, NULL );
   gtk_tree_view_append_column( treeview, col );
   
   /* Remove column header */
   gtk_tree_view_set_headers_visible( treeview, FALSE );
}

/* This function gets called whenever the text inside entry changes.
* All that we need to do is to inform our filter that our filtering
* conditions have changed and the display should be recalculated.
*/
static gboolean
cb_entry_changed( GtkEditable *entry,
              GtkTreeView *treeview )
{
   GtkTreeModelFilter *filter;
   
   filter = GTK_TREE_MODEL_FILTER( gtk_tree_view_get_model( treeview ) );
   gtk_tree_model_filter_refilter( filter );
   
   return( FALSE );
}



Modifying data being displayed

This is probably the most powerful capability of the GtkTreeModelFilter. You can transform original data to meet almost any need. All you need to do is write a function that does the transformation. But this flexibility comes at a price: since transformation function is called whenever the data needs to be displayed, it may slow your application down a bit (or a lot;). Modify function is set with gtk_tree_model_filter_set_modify_func.

Using this functionality can be quite hard at first, but if you follow three simple steps, you'll be able to create something useful without driving yourself crazy. The first step is to determine what do you have (starting point), second step is to determine what would you like to have and the third step is to create a transformation from first to second step.

In example below, the steps would look something like this:
1. Model with one G_TYPE_STRING column, containing strings of length 3.
2. Model with three G_TYPE_INT columns, containing numeric representations of characters.
3. Split string into characters and determine their numeric values.

Now we are ready to set the modify function of filter with gtk_tree_model_filter_set_modify_func. The first parameter in this function call is filter. The second parameter is number of columns in filter (in our case this would be 3, since we want to have 3 integer columns). Third parameter is array of GTypes that we want to see in our filter (in our example we need array of 3 G_TYPE_INTs). The fourth parameter is function that will do the actual transformation. The fifth parameter is user data and sixth parameter is destroy notifier (you can safely ignore the last two parameters).

All that is left to do now is to write a modify function. The important thing to understand is that all of the parameters that you get are related to filter and not to underlying model. You must convert them to access underlying model.

That's it. After you get through this piece of code, you should be armed to tackle almost any GtkTreeView transformation task.

Code:
#include <gtk/gtk.h>
#include <string.h>

enum
{
   COL_TEXT,   /* Model column which holds strings to display. */
   N_COLS
};

/* Function declarations */
static GtkWidget *create_non_transformed_tree( GtkTreeModel *model );
static GtkWidget *create_transformed_tree( GtkTreeModel *model );

static void modify_func( GtkTreeModel *model,
                   GtkTreeIter  *iter,
                   GValue       *value,
                   gint          column,
                   gpointer      data );
static GtkTreeModel *create_model_contents( void );
static void create_treeview_display( GtkTreeView *treeview,
                            gint         text_col );


int
main( int    argc,
     char **argv )
{
   GtkWidget    *window;
   GtkWidget    *table;
   GtkWidget    *label;
   GtkWidget    *treeview;
   GtkTreeModel *model;
   
   gtk_init( &argc, &argv );
   
   /* Main window */
   window = gtk_window_new( GTK_WINDOW_TOPLEVEL );
   gtk_container_set_border_width( GTK_CONTAINER( window ), 5 );
   gtk_window_set_default_size( GTK_WINDOW( window ), 400, 300 );
   g_signal_connect( G_OBJECT( window ), "destroy",
                 G_CALLBACK( gtk_main_quit ), NULL );
   
   /* Content table */
   table = gtk_table_new( 2, 2, FALSE );
   gtk_table_set_row_spacings( GTK_TABLE( table ), 5 );
   gtk_table_set_col_spacings( GTK_TABLE( table ), 5 );
   gtk_container_add( GTK_CONTAINER( window ), table );
   
   /* Labels */
   label = gtk_label_new( "Non-transformed content" );
   gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 0, 1,
                 GTK_FILL, GTK_FILL, 0, 0 );
   label = gtk_label_new( "Transformed contend" );
   gtk_table_attach( GTK_TABLE( table ), label, 1, 2, 0, 1,
                 GTK_FILL, GTK_FILL, 0, 0 );
   
   /* Create and populate model */
   model = create_model_contents();
   
   /* Create treeviews */
   treeview = create_non_transformed_tree( model );
   gtk_table_attach( GTK_TABLE( table ), treeview, 0, 1, 1, 2,
                 GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0 );
   
   treeview = create_transformed_tree( model );
   gtk_table_attach( GTK_TABLE( table ), treeview, 1, 2, 1, 2,
                 GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0 );
   
   g_object_unref( G_OBJECT( model ) );
   
   gtk_widget_show_all( window );
   
   gtk_main();
   
   return( 0 );
}


/* ********* *
* FUNCTIONS *
* ********* */
static GtkWidget *
create_non_transformed_tree( GtkTreeModel *model )
{
   GtkWidget *treeview;
   
   /* Create treeview with model */
   treeview = gtk_tree_view_new_with_model( model );
   
   /* Create display components of tree view */
   create_treeview_display( GTK_TREE_VIEW( treeview ), COL_TEXT );
   
   return( treeview );
}

static GtkWidget *
create_transformed_tree( GtkTreeModel *model )
{
   GtkWidget    *treeview;
   GtkTreeModel *filter;
   GType         types[3];
   
   /* Create column types that will be exposed to treeview */
   types[0] = G_TYPE_INT;
   types[1] = G_TYPE_INT;
   types[2] = G_TYPE_INT;
   
   /* Create filter and set virtual root */
   filter = gtk_tree_model_filter_new( model, NULL );
   gtk_tree_model_filter_set_modify_func(
         GTK_TREE_MODEL_FILTER( filter ), 3, types,
         (GtkTreeModelFilterModifyFunc)modify_func, NULL, NULL );
   
   /* Create treeview with model */
   treeview = gtk_tree_view_new_with_model( filter );
   
   /* Create display components of tree view */
   create_treeview_display( GTK_TREE_VIEW( treeview ), 0 );
   create_treeview_display( GTK_TREE_VIEW( treeview ), 1 );
   create_treeview_display( GTK_TREE_VIEW( treeview ), 2 );
   
   return( treeview );
}

/* This function is called for every value in model. We retrieve that value
* from model and transform it to our liking. (In this sample code, we populate
* right tree view with sum of numeric representation of chars on the left.)
*/
static void
modify_func( GtkTreeModel *filter,
          GtkTreeIter  *iter,
          GValue       *value,
          gint          column,
          gpointer      data )
{
   GtkTreeModel *model;
   GtkTreeIter   citer;
   gchar        *string;
   
   /* We need to obtain filter's child model and convert filter's iter
    * to child model iter.
    */
   g_object_get( G_OBJECT( filter ), "child-model", &model, NULL );
   gtk_tree_model_filter_convert_iter_to_child_iter(
         GTK_TREE_MODEL_FILTER( filter ), &citer, iter );
   
   /* We need to handle all columns in our destinetion model */
   gtk_tree_model_get( model, &citer, COL_TEXT, &string, -1 );
   g_value_set_int( value, string[column] );
   g_free( string );
   
   g_object_unref( G_OBJECT( model ) );
}

static GtkTreeModel *
create_model_contents()
{
   GtkListStore *store;
   GtkTreeIter   iter;
   gint          i;
   gchar         ch = 'a';
   
   /* Create list store to hold our data */
   store = gtk_list_store_new( N_COLS, G_TYPE_STRING );
   
   /* Populate store with sample data */
   for( i = 0; i < 8; i++ )
   {
      gchar *string;
      
      string = g_strdup_printf( "%c%c%c", ch, ch + 1, ch + 2 );
      gtk_list_store_append( store, &iter );
      gtk_list_store_set( store, &iter, COL_TEXT, string, -1 );
      g_free( string );
      
      ch += 3;
   }
   
   return( GTK_TREE_MODEL( store ) );
}

static void
create_treeview_display( GtkTreeView *treeview,
                   gint         text_col )
{
   GtkTreeViewColumn *col;
   GtkCellRenderer   *renderer;
   
   /* Create column for displaying text */
   col = gtk_tree_view_column_new();
   renderer = gtk_cell_renderer_text_new();
   gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( col ), renderer, TRUE );
   gtk_tree_view_column_set_attributes( col, renderer, "text", text_col, NULL );
   gtk_tree_view_append_column( treeview, col );
}


Any comments, corrections and constructive critics are welcome.


Top
 Profile  
 
 Post subject: Re: GtkTreeModelFilter tutorial
PostPosted: Fri Oct 28, 2011 2:33 pm 
Offline

Joined: Fri Oct 28, 2011 2:27 pm
Posts: 1
Very very useful!!!
I have however a question:

I don't want to use the Filter modify callback function by performance reasons, as it get called for EVERY render of EVERY field of EVERY line in the tree view.

I figure then to perform the in-the-flight modification during the callback to the filter visible callback.

The problem is that if I modify the base model during such callback, the program enters in a loop, as the visible callback gets called for the same line forever, once it is modified.

Any solution? Thanks in advance.


Top
 Profile  
 
 Post subject: Re: GtkTreeModelFilter tutorial
PostPosted: Mon Oct 31, 2011 8:39 pm 
Offline
Never Seen the Sunlight

Joined: Wed Jul 23, 2008 10:31 am
Posts: 2406
Location: Slovenia
Hello and welcome to the GTK+ forums!

I think you'll need to find another way of solving your problem, since visibility function should only inspect data, not modify it. Exact solution will depend heavily on the main purpose of your app, so I cannot give you any more detailed help here.

Cheers,
Tadej


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 3 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 3 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group