GTK+ Forums Forum Index GTK+ Forums
Discussion forum for GTK+ and Programming. Ask questions, troubleshoot problems, view and post example code, or express your opinions.
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

GtkTreeModelFilter tutorial

 
Post new topic   Reply to topic    GTK+ Forums Forum Index -> GTK+ Example Code
Author Message
tadeboro
Never Seen the Sunlight


Joined: 23 Jul 2008
Posts: 2114
Location: Slovenia

PostPosted: Thu Jan 15, 2009 7:20 pm    Post subject: GtkTreeModelFilter tutorial Reply with quote

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: (C)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
#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: (C)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#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.
Back to top
Display posts from previous:   
Post new topic   Reply to topic    GTK+ Forums Forum Index -> GTK+ Example Code All times are GMT
Page 1 of 1

 


Powered by phpBB © 2001, 2005 phpBB Group
CodeBB 1.0 Beta 2
Protected by Anti-Spam ACP