1 | /* |
---|
2 | * This file Copyright (C) 2010 Mnemosyne LLC |
---|
3 | * |
---|
4 | * This file is licensed by the GPL version 2. Works owned by the |
---|
5 | * Transmission project are granted a special exemption to clause 2(b) |
---|
6 | * so that the bulk of its code can remain under the MIT license. |
---|
7 | * This exemption does not extend to derived works not owned by |
---|
8 | * the Transmission project. |
---|
9 | * |
---|
10 | * $Id:$ |
---|
11 | */ |
---|
12 | |
---|
13 | #include <gtk/gtk.h> |
---|
14 | #include <glib/gi18n.h> |
---|
15 | |
---|
16 | #include <libtransmission/transmission.h> |
---|
17 | #include <libtransmission/utils.h> |
---|
18 | |
---|
19 | #include "filter.h" |
---|
20 | #include "hig.h" /* GUI_PAD */ |
---|
21 | #include "tr-core.h" |
---|
22 | #include "util.h" /* gtr_idle_add() */ |
---|
23 | |
---|
24 | #define DIRTY_KEY "tr-filter-dirty" |
---|
25 | #define TEXT_KEY "tr-filter-text" |
---|
26 | #define TEXT_MODE_KEY "tr-filter-text-mode" |
---|
27 | #define TORRENT_MODEL_KEY "tr-filter-torrent-model-key" |
---|
28 | |
---|
29 | #if !GTK_CHECK_VERSION( 2,16,0 ) |
---|
30 | /* FIXME: when 2.16 has been out long enough, it would be really nice to |
---|
31 | * get rid of this libsexy usage because of its makefile strangeness */ |
---|
32 | #define USE_SEXY |
---|
33 | #include "sexy-icon-entry.h" |
---|
34 | #endif |
---|
35 | |
---|
36 | /*** |
---|
37 | **** |
---|
38 | **** CATEGORIES |
---|
39 | **** |
---|
40 | ***/ |
---|
41 | |
---|
42 | enum |
---|
43 | { |
---|
44 | CAT_FILTER_TYPE_ALL, |
---|
45 | CAT_FILTER_TYPE_PRIVATE, |
---|
46 | CAT_FILTER_TYPE_PUBLIC, |
---|
47 | CAT_FILTER_TYPE_HOST, |
---|
48 | CAT_FILTER_TYPE_PARENT, |
---|
49 | CAT_FILTER_TYPE_PRI_HIGH, |
---|
50 | CAT_FILTER_TYPE_PRI_NORMAL, |
---|
51 | CAT_FILTER_TYPE_PRI_LOW, |
---|
52 | CAT_FILTER_TYPE_TAG, |
---|
53 | CAT_FILTER_TYPE_SEPARATOR, |
---|
54 | }; |
---|
55 | |
---|
56 | enum |
---|
57 | { |
---|
58 | CAT_FILTER_COL_NAME, /* human-readable name; ie, Legaltorrents */ |
---|
59 | CAT_FILTER_COL_COUNT, /* how many matches there are */ |
---|
60 | CAT_FILTER_COL_TYPE, |
---|
61 | CAT_FILTER_COL_HOST, /* pattern-matching text; ie, legaltorrents.com */ |
---|
62 | CAT_FILTER_N_COLS |
---|
63 | }; |
---|
64 | |
---|
65 | static int |
---|
66 | pstrcmp( const void * a, const void * b ) |
---|
67 | { |
---|
68 | return strcmp( *(const char**)a, *(const char**)b ); |
---|
69 | } |
---|
70 | |
---|
71 | static char* |
---|
72 | get_host_from_url( const char * url ) |
---|
73 | { |
---|
74 | char * h = NULL; |
---|
75 | char * name; |
---|
76 | const char * first_dot; |
---|
77 | const char * last_dot; |
---|
78 | |
---|
79 | tr_urlParse( url, -1, NULL, &h, NULL, NULL ); |
---|
80 | first_dot = strchr( h, '.' ); |
---|
81 | last_dot = strrchr( h, '.' ); |
---|
82 | |
---|
83 | if( ( first_dot ) && ( last_dot ) && ( first_dot != last_dot ) ) |
---|
84 | name = g_strdup( first_dot + 1 ); |
---|
85 | else |
---|
86 | name = g_strdup( h ); |
---|
87 | |
---|
88 | tr_free( h ); |
---|
89 | return name; |
---|
90 | } |
---|
91 | |
---|
92 | static char* |
---|
93 | get_name_from_host( const char * host ) |
---|
94 | { |
---|
95 | char * name; |
---|
96 | const char * dot = strrchr( host, '.' ); |
---|
97 | |
---|
98 | if( dot == NULL ) |
---|
99 | name = g_strdup( host ); |
---|
100 | else |
---|
101 | name = g_strndup( host, dot - host ); |
---|
102 | |
---|
103 | *name = g_ascii_toupper( *name ); |
---|
104 | |
---|
105 | return name; |
---|
106 | } |
---|
107 | |
---|
108 | static void |
---|
109 | category_model_update_count( GtkTreeStore * store, GtkTreeIter * iter, int n ) |
---|
110 | { |
---|
111 | int count; |
---|
112 | GtkTreeModel * model = GTK_TREE_MODEL( store ); |
---|
113 | gtk_tree_model_get( model, iter, CAT_FILTER_COL_COUNT, &count, -1 ); |
---|
114 | if( n != count ) |
---|
115 | gtk_tree_store_set( store, iter, CAT_FILTER_COL_COUNT, n, -1 ); |
---|
116 | } |
---|
117 | |
---|
118 | static gboolean |
---|
119 | category_filter_model_update( GtkTreeStore * store ) |
---|
120 | { |
---|
121 | int i, n; |
---|
122 | int low = 0; |
---|
123 | int all = 0; |
---|
124 | int high = 0; |
---|
125 | int public = 0; |
---|
126 | int normal = 0; |
---|
127 | int private = 0; |
---|
128 | int store_pos; |
---|
129 | GtkTreeIter iter; |
---|
130 | GtkTreeIter parent; |
---|
131 | GtkTreeModel * model = GTK_TREE_MODEL( store ); |
---|
132 | GPtrArray * hosts = g_ptr_array_new( ); |
---|
133 | GHashTable * hosts_hash = g_hash_table_new_full( g_str_hash, g_str_equal, g_free, g_free ); |
---|
134 | GObject * o = G_OBJECT( store ); |
---|
135 | GtkTreeModel * tmodel = GTK_TREE_MODEL( |
---|
136 | g_object_get_data( o, TORRENT_MODEL_KEY ) ); |
---|
137 | |
---|
138 | g_object_steal_data( o, DIRTY_KEY ); |
---|
139 | |
---|
140 | /* walk through all the torrents, tallying how many matches there are |
---|
141 | * for the various categories. also make a sorted list of all tracker |
---|
142 | * hosts s.t. we can merge it with the existing list */ |
---|
143 | if( gtk_tree_model_get_iter_first( tmodel, &iter )) do |
---|
144 | { |
---|
145 | tr_torrent * tor; |
---|
146 | const tr_info * inf; |
---|
147 | int keyCount; |
---|
148 | char ** keys; |
---|
149 | |
---|
150 | gtk_tree_model_get( tmodel, &iter, MC_TORRENT_RAW, &tor, -1 ); |
---|
151 | inf = tr_torrentInfo( tor ); |
---|
152 | keyCount = 0; |
---|
153 | keys = g_new( char*, inf->trackerCount ); |
---|
154 | |
---|
155 | for( i=0, n=inf->trackerCount; i<n; ++i ) |
---|
156 | { |
---|
157 | int k; |
---|
158 | char * key = get_host_from_url( inf->trackers[i].announce ); |
---|
159 | int * count = g_hash_table_lookup( hosts_hash, key ); |
---|
160 | if( count == NULL ) |
---|
161 | { |
---|
162 | char * k = g_strdup( key ); |
---|
163 | count = tr_new0( int, 1 ); |
---|
164 | g_hash_table_insert( hosts_hash, k, count ); |
---|
165 | g_ptr_array_add( hosts, k ); |
---|
166 | } |
---|
167 | |
---|
168 | for( k=0; k<keyCount; ++k ) |
---|
169 | if( !strcmp( keys[k], key ) ) |
---|
170 | break; |
---|
171 | if( k==keyCount ) |
---|
172 | keys[keyCount++] = key; |
---|
173 | else |
---|
174 | g_free( key ); |
---|
175 | } |
---|
176 | |
---|
177 | for( i=0; i<keyCount; ++i ) |
---|
178 | { |
---|
179 | int * incrementme = g_hash_table_lookup( hosts_hash, keys[i] ); |
---|
180 | ++*incrementme; |
---|
181 | g_free( keys[i] ); |
---|
182 | } |
---|
183 | g_free( keys ); |
---|
184 | |
---|
185 | ++all; |
---|
186 | |
---|
187 | if( inf->isPrivate ) |
---|
188 | ++private; |
---|
189 | else |
---|
190 | ++public; |
---|
191 | |
---|
192 | switch( tr_torrentGetPriority( tor ) ) |
---|
193 | { |
---|
194 | case TR_PRI_HIGH: ++high; break; |
---|
195 | case TR_PRI_LOW: ++low; break; |
---|
196 | default: ++normal; break; |
---|
197 | } |
---|
198 | } |
---|
199 | while( gtk_tree_model_iter_next( tmodel, &iter ) ); |
---|
200 | qsort( hosts->pdata, hosts->len, sizeof(char*), pstrcmp ); |
---|
201 | |
---|
202 | /* update the "all" count */ |
---|
203 | gtk_tree_model_iter_nth_child( model, &iter, NULL, 0 ); |
---|
204 | category_model_update_count( store, &iter, all ); |
---|
205 | |
---|
206 | /* update the "public" count */ |
---|
207 | gtk_tree_model_iter_nth_child( model, &parent, NULL, 2 ); |
---|
208 | gtk_tree_model_iter_nth_child( model, &iter, &parent, 0 ); |
---|
209 | category_model_update_count( store, &iter, public ); |
---|
210 | gtk_tree_model_iter_nth_child( model, &iter, &parent, 1 ); |
---|
211 | category_model_update_count( store, &iter, private ); |
---|
212 | |
---|
213 | /* update the "priority" subtree */ |
---|
214 | gtk_tree_model_iter_nth_child( model, &parent, NULL, 3 ); |
---|
215 | gtk_tree_model_iter_nth_child( model, &iter, &parent, 0 ); |
---|
216 | category_model_update_count( store, &iter, high ); |
---|
217 | gtk_tree_model_iter_nth_child( model, &iter, &parent, 1 ); |
---|
218 | category_model_update_count( store, &iter, normal ); |
---|
219 | gtk_tree_model_iter_nth_child( model, &iter, &parent, 2 ); |
---|
220 | category_model_update_count( store, &iter, low ); |
---|
221 | |
---|
222 | /* update the "hosts" subtree */ |
---|
223 | gtk_tree_model_iter_nth_child( model, &parent, NULL, 4 ); |
---|
224 | i = 0; |
---|
225 | n = hosts->len; |
---|
226 | store_pos = 0; |
---|
227 | for( ;; ) |
---|
228 | { |
---|
229 | const gboolean new_hosts_done = i >= n; |
---|
230 | const gboolean old_hosts_done = !gtk_tree_model_iter_nth_child( model, &iter, &parent, store_pos ); |
---|
231 | gboolean remove_row = FALSE; |
---|
232 | gboolean insert_row = FALSE; |
---|
233 | |
---|
234 | /* are we done yet? */ |
---|
235 | if( new_hosts_done && old_hosts_done ) |
---|
236 | break; |
---|
237 | |
---|
238 | /* decide what to do */ |
---|
239 | if( new_hosts_done ) { |
---|
240 | /* g_message( "new hosts done; remove row" ); */ |
---|
241 | remove_row = TRUE; |
---|
242 | } else if( old_hosts_done ) { |
---|
243 | /* g_message( "old hosts done; insert row" ); */ |
---|
244 | insert_row = TRUE; |
---|
245 | } else { |
---|
246 | int cmp; |
---|
247 | char * host; |
---|
248 | gtk_tree_model_get( model, &iter, CAT_FILTER_COL_HOST, &host, -1 ); |
---|
249 | cmp = strcmp( host, hosts->pdata[i] ); |
---|
250 | /* g_message( "cmp( %s, %s ) returns %d", host, (char*)hosts->pdata[i], cmp ); */ |
---|
251 | if( cmp < 0 ) { |
---|
252 | /* g_message( "cmp<0, so remove row" ); */ |
---|
253 | remove_row = TRUE; |
---|
254 | } else if( cmp > 0 ) { |
---|
255 | /* g_message( "cmp>0, so insert row" ); */ |
---|
256 | insert_row = TRUE; |
---|
257 | } |
---|
258 | g_free( host ); |
---|
259 | } |
---|
260 | |
---|
261 | /* do something */ |
---|
262 | if( remove_row ) { |
---|
263 | /* g_message( "removing row and incrementing i" ); */ |
---|
264 | gtk_tree_store_remove( store, &iter ); |
---|
265 | } else if( insert_row ) { |
---|
266 | const char * host = hosts->pdata[i]; |
---|
267 | char * name = get_name_from_host( host ); |
---|
268 | const int count = *(int*)g_hash_table_lookup( hosts_hash, host ); |
---|
269 | gtk_tree_store_insert_with_values( store, NULL, &parent, store_pos, |
---|
270 | CAT_FILTER_COL_HOST, host, |
---|
271 | CAT_FILTER_COL_NAME, name, |
---|
272 | CAT_FILTER_COL_COUNT, count, |
---|
273 | CAT_FILTER_COL_TYPE, CAT_FILTER_TYPE_HOST, |
---|
274 | -1 ); |
---|
275 | g_free( name ); |
---|
276 | ++store_pos; |
---|
277 | ++i; |
---|
278 | } else { /* update row */ |
---|
279 | const char * host = hosts->pdata[i]; |
---|
280 | const int count = *(int*)g_hash_table_lookup( hosts_hash, host ); |
---|
281 | category_model_update_count( store, &iter, count ); |
---|
282 | ++store_pos; |
---|
283 | ++i; |
---|
284 | } |
---|
285 | } |
---|
286 | |
---|
287 | /* cleanup */ |
---|
288 | g_ptr_array_unref( hosts ); |
---|
289 | g_hash_table_unref( hosts_hash ); |
---|
290 | g_ptr_array_foreach( hosts, (GFunc)g_free, NULL ); |
---|
291 | return FALSE; |
---|
292 | } |
---|
293 | |
---|
294 | static GtkTreeModel * |
---|
295 | category_filter_model_new( GtkTreeModel * tmodel ) |
---|
296 | { |
---|
297 | GtkTreeIter iter; |
---|
298 | GtkTreeStore * store; |
---|
299 | const int invisible_number = -1; /* doesn't get rendered */ |
---|
300 | |
---|
301 | store = gtk_tree_store_new( CAT_FILTER_N_COLS, |
---|
302 | G_TYPE_STRING, |
---|
303 | G_TYPE_INT, |
---|
304 | G_TYPE_INT, |
---|
305 | G_TYPE_STRING ); |
---|
306 | |
---|
307 | gtk_tree_store_insert_with_values( store, NULL, NULL, -1, |
---|
308 | CAT_FILTER_COL_NAME, _( "All" ), |
---|
309 | CAT_FILTER_COL_TYPE, CAT_FILTER_TYPE_ALL, |
---|
310 | -1 ); |
---|
311 | gtk_tree_store_insert_with_values( store, NULL, NULL, -1, |
---|
312 | CAT_FILTER_COL_TYPE, CAT_FILTER_TYPE_SEPARATOR, |
---|
313 | -1 ); |
---|
314 | |
---|
315 | gtk_tree_store_insert_with_values( store, &iter, NULL, -1, |
---|
316 | CAT_FILTER_COL_NAME, _( "Privacy" ), |
---|
317 | CAT_FILTER_COL_COUNT, invisible_number, |
---|
318 | CAT_FILTER_COL_TYPE, CAT_FILTER_TYPE_PARENT, |
---|
319 | -1 ); |
---|
320 | gtk_tree_store_insert_with_values( store, NULL, &iter, -1, |
---|
321 | CAT_FILTER_COL_NAME, _( "Public" ), |
---|
322 | CAT_FILTER_COL_TYPE, CAT_FILTER_TYPE_PUBLIC, |
---|
323 | -1 ); |
---|
324 | gtk_tree_store_insert_with_values( store, NULL, &iter, -1, |
---|
325 | CAT_FILTER_COL_NAME, _( "Private" ), |
---|
326 | CAT_FILTER_COL_TYPE, CAT_FILTER_TYPE_PRIVATE, |
---|
327 | -1 ); |
---|
328 | |
---|
329 | gtk_tree_store_insert_with_values( store, &iter, NULL, -1, |
---|
330 | CAT_FILTER_COL_NAME, _( "Priority" ), |
---|
331 | CAT_FILTER_COL_COUNT, invisible_number, |
---|
332 | CAT_FILTER_COL_TYPE, CAT_FILTER_TYPE_PARENT, |
---|
333 | -1 ); |
---|
334 | gtk_tree_store_insert_with_values( store, NULL, &iter, -1, |
---|
335 | CAT_FILTER_COL_NAME, _( "High" ), |
---|
336 | CAT_FILTER_COL_TYPE, CAT_FILTER_TYPE_PRI_HIGH, |
---|
337 | -1 ); |
---|
338 | gtk_tree_store_insert_with_values( store, NULL, &iter, -1, |
---|
339 | CAT_FILTER_COL_NAME, _( "Normal" ), |
---|
340 | CAT_FILTER_COL_TYPE, CAT_FILTER_TYPE_PRI_NORMAL, |
---|
341 | -1 ); |
---|
342 | gtk_tree_store_insert_with_values( store, NULL, &iter, -1, |
---|
343 | CAT_FILTER_COL_NAME, _( "Low" ), |
---|
344 | CAT_FILTER_COL_TYPE, CAT_FILTER_TYPE_PRI_LOW, |
---|
345 | -1 ); |
---|
346 | |
---|
347 | gtk_tree_store_insert_with_values( store, &iter, NULL, -1, |
---|
348 | CAT_FILTER_COL_NAME, _( "Trackers" ), |
---|
349 | CAT_FILTER_COL_COUNT, invisible_number, |
---|
350 | CAT_FILTER_COL_TYPE, CAT_FILTER_TYPE_PARENT, |
---|
351 | -1 ); |
---|
352 | |
---|
353 | g_object_set_data( G_OBJECT( store ), TORRENT_MODEL_KEY, tmodel ); |
---|
354 | category_filter_model_update( store ); |
---|
355 | return GTK_TREE_MODEL( store ); |
---|
356 | } |
---|
357 | |
---|
358 | static gboolean |
---|
359 | is_it_a_separator( GtkTreeModel * model, GtkTreeIter * iter, gpointer data UNUSED ) |
---|
360 | { |
---|
361 | int type; |
---|
362 | gtk_tree_model_get( model, iter, CAT_FILTER_COL_TYPE, &type, -1 ); |
---|
363 | return type == CAT_FILTER_TYPE_SEPARATOR; |
---|
364 | } |
---|
365 | |
---|
366 | static void |
---|
367 | category_model_update_idle( gpointer category_model ) |
---|
368 | { |
---|
369 | GObject * o = G_OBJECT( category_model ); |
---|
370 | gboolean pending = GPOINTER_TO_INT( g_object_get_data( o, DIRTY_KEY ) ); |
---|
371 | if( !pending ) |
---|
372 | { |
---|
373 | GSourceFunc func = (GSourceFunc) category_filter_model_update; |
---|
374 | g_object_set_data( o, DIRTY_KEY, GINT_TO_POINTER(1) ); |
---|
375 | gtr_idle_add( func, category_model ); |
---|
376 | } |
---|
377 | } |
---|
378 | |
---|
379 | static void |
---|
380 | torrent_model_row_changed( GtkTreeModel * tmodel UNUSED, |
---|
381 | GtkTreePath * path UNUSED, |
---|
382 | GtkTreeIter * iter UNUSED, |
---|
383 | gpointer category_model ) |
---|
384 | { |
---|
385 | category_model_update_idle( category_model ); |
---|
386 | } |
---|
387 | |
---|
388 | static void |
---|
389 | torrent_model_row_deleted_cb( GtkTreeModel * tmodel UNUSED, |
---|
390 | GtkTreePath * path UNUSED, |
---|
391 | gpointer category_model ) |
---|
392 | { |
---|
393 | category_model_update_idle( category_model ); |
---|
394 | } |
---|
395 | |
---|
396 | static void |
---|
397 | render_hit_count_func( GtkCellLayout * cell_layout UNUSED, |
---|
398 | GtkCellRenderer * cell_renderer, |
---|
399 | GtkTreeModel * tree_model, |
---|
400 | GtkTreeIter * iter, |
---|
401 | gpointer data UNUSED ) |
---|
402 | { |
---|
403 | int count; |
---|
404 | char buf[512]; |
---|
405 | |
---|
406 | gtk_tree_model_get( tree_model, iter, CAT_FILTER_COL_COUNT, &count, -1 ); |
---|
407 | |
---|
408 | if( count >= 0 ) |
---|
409 | g_snprintf( buf, sizeof( buf ), "%'d", count ); |
---|
410 | else |
---|
411 | *buf = '\0'; |
---|
412 | |
---|
413 | g_object_set( cell_renderer, "text", buf, NULL ); |
---|
414 | } |
---|
415 | |
---|
416 | static GtkCellRenderer * |
---|
417 | number_renderer_new( void ) |
---|
418 | { |
---|
419 | GtkCellRenderer * r = gtk_cell_renderer_text_new( ); |
---|
420 | |
---|
421 | g_object_set( G_OBJECT( r ), "alignment", PANGO_ALIGN_RIGHT, |
---|
422 | "weight", PANGO_WEIGHT_ULTRALIGHT, |
---|
423 | "xalign", 1.0, |
---|
424 | "xpad", GUI_PAD, |
---|
425 | NULL ); |
---|
426 | |
---|
427 | return r; |
---|
428 | } |
---|
429 | |
---|
430 | static GtkWidget * |
---|
431 | category_combo_box_new( GtkTreeModel * tmodel ) |
---|
432 | { |
---|
433 | GtkWidget * c; |
---|
434 | GtkCellRenderer * r; |
---|
435 | GtkTreeModel * category_model; |
---|
436 | |
---|
437 | /* create the category combobox */ |
---|
438 | category_model = category_filter_model_new( tmodel ); |
---|
439 | c = gtk_combo_box_new_with_model( category_model ); |
---|
440 | gtk_combo_box_set_row_separator_func( GTK_COMBO_BOX( c ), |
---|
441 | is_it_a_separator, NULL, NULL ); |
---|
442 | gtk_combo_box_set_active( GTK_COMBO_BOX( c ), 0 ); |
---|
443 | |
---|
444 | r = gtk_cell_renderer_text_new( ); |
---|
445 | gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( c ), r, FALSE ); |
---|
446 | gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT( c ), r, |
---|
447 | "text", CAT_FILTER_COL_NAME, |
---|
448 | NULL ); |
---|
449 | |
---|
450 | r = number_renderer_new( ); |
---|
451 | gtk_cell_layout_pack_end( GTK_CELL_LAYOUT( c ), r, TRUE ); |
---|
452 | gtk_cell_layout_set_cell_data_func( GTK_CELL_LAYOUT( c ), r, render_hit_count_func, NULL, NULL ); |
---|
453 | |
---|
454 | g_signal_connect( tmodel, "row-changed", |
---|
455 | G_CALLBACK( torrent_model_row_changed ), category_model ); |
---|
456 | g_signal_connect( tmodel, "row-inserted", |
---|
457 | G_CALLBACK( torrent_model_row_changed ), category_model ); |
---|
458 | g_signal_connect( tmodel, "row-deleted", |
---|
459 | G_CALLBACK( torrent_model_row_deleted_cb ), category_model ); |
---|
460 | |
---|
461 | return c; |
---|
462 | } |
---|
463 | |
---|
464 | /*** |
---|
465 | **** |
---|
466 | ***/ |
---|
467 | |
---|
468 | static gboolean |
---|
469 | testCategory( GtkWidget * category_combo, tr_torrent * tor ) |
---|
470 | { |
---|
471 | int type; |
---|
472 | const tr_info * inf; |
---|
473 | GtkTreeIter iter; |
---|
474 | GtkComboBox * combo = GTK_COMBO_BOX( category_combo ); |
---|
475 | GtkTreeModel * model = gtk_combo_box_get_model( combo ); |
---|
476 | |
---|
477 | if( !gtk_combo_box_get_active_iter( combo, &iter ) ) |
---|
478 | return TRUE; |
---|
479 | |
---|
480 | inf = tr_torrentInfo( tor ); |
---|
481 | gtk_tree_model_get( model, &iter, CAT_FILTER_COL_TYPE, &type, -1 ); |
---|
482 | switch( type ) |
---|
483 | { |
---|
484 | case CAT_FILTER_TYPE_ALL: |
---|
485 | return TRUE; |
---|
486 | |
---|
487 | case CAT_FILTER_TYPE_PRIVATE: |
---|
488 | return inf->isPrivate; |
---|
489 | |
---|
490 | case CAT_FILTER_TYPE_PUBLIC: |
---|
491 | return !inf->isPrivate; |
---|
492 | |
---|
493 | case CAT_FILTER_TYPE_PRI_HIGH: |
---|
494 | return tr_torrentGetPriority( tor ) == TR_PRI_HIGH; |
---|
495 | |
---|
496 | case CAT_FILTER_TYPE_PRI_NORMAL: |
---|
497 | return tr_torrentGetPriority( tor ) == TR_PRI_NORMAL; |
---|
498 | |
---|
499 | case CAT_FILTER_TYPE_PRI_LOW: |
---|
500 | return tr_torrentGetPriority( tor ) == TR_PRI_LOW; |
---|
501 | |
---|
502 | case CAT_FILTER_TYPE_HOST: { |
---|
503 | int i; |
---|
504 | char * host; |
---|
505 | gtk_tree_model_get( model, &iter, CAT_FILTER_COL_HOST, &host, -1 ); |
---|
506 | for( i=0; i<inf->trackerCount; ++i ) { |
---|
507 | char * tmp = get_host_from_url( inf->trackers[i].announce ); |
---|
508 | const gboolean hit = !strcmp( tmp, host ); |
---|
509 | g_free( tmp ); |
---|
510 | if( hit ) |
---|
511 | break; |
---|
512 | } |
---|
513 | g_free( host ); |
---|
514 | return i < inf->trackerCount; |
---|
515 | } |
---|
516 | |
---|
517 | case CAT_FILTER_TYPE_TAG: |
---|
518 | default: |
---|
519 | g_message( "FIXME" ); |
---|
520 | return TRUE; |
---|
521 | } |
---|
522 | } |
---|
523 | |
---|
524 | /*** |
---|
525 | **** |
---|
526 | **** STATES |
---|
527 | **** |
---|
528 | ***/ |
---|
529 | |
---|
530 | enum |
---|
531 | { |
---|
532 | STATE_FILTER_ALL, |
---|
533 | STATE_FILTER_DOWNLOADING, |
---|
534 | STATE_FILTER_SEEDING, |
---|
535 | STATE_FILTER_ACTIVE, |
---|
536 | STATE_FILTER_PAUSED, |
---|
537 | STATE_FILTER_QUEUED, |
---|
538 | STATE_FILTER_CHECKING, |
---|
539 | STATE_FILTER_ERROR, |
---|
540 | STATE_FILTER_SEPARATOR |
---|
541 | }; |
---|
542 | |
---|
543 | enum |
---|
544 | { |
---|
545 | STATE_FILTER_COL_NAME, |
---|
546 | STATE_FILTER_COL_COUNT, |
---|
547 | STATE_FILTER_COL_TYPE, |
---|
548 | STATE_FILTER_N_COLS |
---|
549 | }; |
---|
550 | |
---|
551 | static gboolean |
---|
552 | state_is_it_a_separator( GtkTreeModel * m, GtkTreeIter * i, gpointer d UNUSED ) |
---|
553 | { |
---|
554 | int type; |
---|
555 | gtk_tree_model_get( m, i, STATE_FILTER_COL_TYPE, &type, -1 ); |
---|
556 | return type == STATE_FILTER_SEPARATOR; |
---|
557 | } |
---|
558 | |
---|
559 | static gboolean |
---|
560 | test_torrent_state( tr_torrent * tor, int type ) |
---|
561 | { |
---|
562 | const tr_stat * st = tr_torrentStat( tor ); |
---|
563 | |
---|
564 | switch( type ) |
---|
565 | { |
---|
566 | case STATE_FILTER_DOWNLOADING: |
---|
567 | return st->activity == TR_STATUS_DOWNLOAD; |
---|
568 | |
---|
569 | case STATE_FILTER_SEEDING: |
---|
570 | return st->activity == TR_STATUS_SEED; |
---|
571 | |
---|
572 | case STATE_FILTER_ACTIVE: |
---|
573 | return st->peersSendingToUs > 0 || st->peersGettingFromUs > 0; |
---|
574 | |
---|
575 | case STATE_FILTER_PAUSED: |
---|
576 | return st->activity == TR_STATUS_STOPPED; |
---|
577 | |
---|
578 | case STATE_FILTER_QUEUED: |
---|
579 | return FALSE; |
---|
580 | |
---|
581 | case STATE_FILTER_CHECKING: |
---|
582 | return ( st->activity == TR_STATUS_CHECK_WAIT ) |
---|
583 | || ( st->activity == TR_STATUS_CHECK ); |
---|
584 | |
---|
585 | case STATE_FILTER_ERROR: |
---|
586 | return st->error != 0; |
---|
587 | |
---|
588 | default: /* STATE_FILTER_ALL */ |
---|
589 | return TRUE; |
---|
590 | |
---|
591 | } |
---|
592 | } |
---|
593 | |
---|
594 | static gboolean |
---|
595 | testState( GtkWidget * state_combo, tr_torrent * tor ) |
---|
596 | { |
---|
597 | int type; |
---|
598 | GtkTreeIter iter; |
---|
599 | GtkComboBox * combo = GTK_COMBO_BOX( state_combo ); |
---|
600 | GtkTreeModel * model = gtk_combo_box_get_model( combo ); |
---|
601 | |
---|
602 | if( !gtk_combo_box_get_active_iter( combo, &iter ) ) |
---|
603 | return TRUE; |
---|
604 | |
---|
605 | gtk_tree_model_get( model, &iter, STATE_FILTER_COL_TYPE, &type, -1 ); |
---|
606 | return test_torrent_state( tor, type ); |
---|
607 | } |
---|
608 | |
---|
609 | static void |
---|
610 | status_model_update_count( GtkListStore * store, GtkTreeIter * iter, int n ) |
---|
611 | { |
---|
612 | int count; |
---|
613 | gtk_tree_model_get( GTK_TREE_MODEL( store ), iter, STATE_FILTER_COL_COUNT, &count, -1 ); |
---|
614 | if( n != count ) |
---|
615 | gtk_list_store_set( store, iter, STATE_FILTER_COL_COUNT, n, -1 ); |
---|
616 | } |
---|
617 | |
---|
618 | static void |
---|
619 | state_filter_model_update( GtkListStore * store ) |
---|
620 | { |
---|
621 | GtkTreeIter iter; |
---|
622 | GtkTreeModel * model = GTK_TREE_MODEL( store ); |
---|
623 | GObject * o = G_OBJECT( store ); |
---|
624 | GtkTreeModel * tmodel = GTK_TREE_MODEL( g_object_get_data( o, TORRENT_MODEL_KEY ) ); |
---|
625 | |
---|
626 | g_object_steal_data( o, DIRTY_KEY ); |
---|
627 | |
---|
628 | if( gtk_tree_model_get_iter_first( model, &iter )) do |
---|
629 | { |
---|
630 | int hits; |
---|
631 | int type; |
---|
632 | GtkTreeIter torrent_iter; |
---|
633 | |
---|
634 | gtk_tree_model_get( model, &iter, STATE_FILTER_COL_TYPE, &type, -1 ); |
---|
635 | |
---|
636 | hits = 0; |
---|
637 | if( gtk_tree_model_get_iter_first( tmodel, &torrent_iter )) do { |
---|
638 | tr_torrent * tor; |
---|
639 | gtk_tree_model_get( tmodel, &torrent_iter, MC_TORRENT_RAW, &tor, -1 ); |
---|
640 | if( test_torrent_state( tor, type ) ) |
---|
641 | ++hits; |
---|
642 | } while( gtk_tree_model_iter_next( tmodel, &torrent_iter ) ); |
---|
643 | |
---|
644 | status_model_update_count( store, &iter, hits ); |
---|
645 | |
---|
646 | } while( gtk_tree_model_iter_next( model, &iter ) ); |
---|
647 | } |
---|
648 | |
---|
649 | static GtkTreeModel * |
---|
650 | state_filter_model_new( GtkTreeModel * tmodel ) |
---|
651 | { |
---|
652 | int i, n; |
---|
653 | struct { |
---|
654 | int type; |
---|
655 | const char * name; |
---|
656 | } types[] = { |
---|
657 | { STATE_FILTER_ALL, N_( "All" ) }, |
---|
658 | { STATE_FILTER_SEPARATOR, NULL }, |
---|
659 | { STATE_FILTER_DOWNLOADING, N_( "Downloading" ) }, |
---|
660 | { STATE_FILTER_SEEDING, N_( "Seeding" ) }, |
---|
661 | { STATE_FILTER_ACTIVE, N_( "Active" ) }, |
---|
662 | { STATE_FILTER_PAUSED, N_( "Paused" ) }, |
---|
663 | { STATE_FILTER_QUEUED, N_( "Queued" ) }, |
---|
664 | { STATE_FILTER_CHECKING, N_( "Verifying" ) }, |
---|
665 | { STATE_FILTER_ERROR, N_( "Error" ) } |
---|
666 | }; |
---|
667 | GtkListStore * store; |
---|
668 | |
---|
669 | store = gtk_list_store_new( STATE_FILTER_N_COLS, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT ); |
---|
670 | for( i=0, n=G_N_ELEMENTS(types); i<n; ++i ) |
---|
671 | gtk_list_store_insert_with_values( store, NULL, -1, |
---|
672 | STATE_FILTER_COL_NAME, _( types[i].name ), |
---|
673 | STATE_FILTER_COL_TYPE, types[i].type, |
---|
674 | -1 ); |
---|
675 | |
---|
676 | g_object_set_data( G_OBJECT( store ), TORRENT_MODEL_KEY, tmodel ); |
---|
677 | state_filter_model_update( store ); |
---|
678 | return GTK_TREE_MODEL( store ); |
---|
679 | } |
---|
680 | |
---|
681 | static void |
---|
682 | state_model_update_idle( gpointer state_model ) |
---|
683 | { |
---|
684 | GObject * o = G_OBJECT( state_model ); |
---|
685 | gboolean pending = GPOINTER_TO_INT( g_object_get_data( o, DIRTY_KEY ) ); |
---|
686 | if( !pending ) |
---|
687 | { |
---|
688 | GSourceFunc func = (GSourceFunc) state_filter_model_update; |
---|
689 | g_object_set_data( o, DIRTY_KEY, GINT_TO_POINTER(1) ); |
---|
690 | gtr_idle_add( func, state_model ); |
---|
691 | } |
---|
692 | } |
---|
693 | |
---|
694 | static void |
---|
695 | state_torrent_model_row_changed( GtkTreeModel * tmodel UNUSED, |
---|
696 | GtkTreePath * path UNUSED, |
---|
697 | GtkTreeIter * iter UNUSED, |
---|
698 | gpointer state_model ) |
---|
699 | { |
---|
700 | state_model_update_idle( state_model ); |
---|
701 | } |
---|
702 | |
---|
703 | static void |
---|
704 | state_torrent_model_row_deleted_cb( GtkTreeModel * tmodel UNUSED, |
---|
705 | GtkTreePath * path UNUSED, |
---|
706 | gpointer state_model ) |
---|
707 | { |
---|
708 | state_model_update_idle( state_model ); |
---|
709 | } |
---|
710 | |
---|
711 | static GtkWidget * |
---|
712 | state_combo_box_new( GtkTreeModel * tmodel ) |
---|
713 | { |
---|
714 | GtkWidget * c; |
---|
715 | GtkCellRenderer * r; |
---|
716 | GtkTreeModel * state_model; |
---|
717 | |
---|
718 | state_model = state_filter_model_new( tmodel ); |
---|
719 | c = gtk_combo_box_new_with_model( state_model ); |
---|
720 | gtk_combo_box_set_row_separator_func( GTK_COMBO_BOX( c ), |
---|
721 | state_is_it_a_separator, NULL, NULL ); |
---|
722 | gtk_combo_box_set_active( GTK_COMBO_BOX( c ), 0 ); |
---|
723 | |
---|
724 | r = gtk_cell_renderer_text_new( ); |
---|
725 | gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( c ), r, TRUE ); |
---|
726 | gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT( c ), r, "text", STATE_FILTER_COL_NAME, NULL ); |
---|
727 | |
---|
728 | r = number_renderer_new( ); |
---|
729 | gtk_cell_layout_pack_end( GTK_CELL_LAYOUT( c ), r, TRUE ); |
---|
730 | gtk_cell_layout_set_cell_data_func( GTK_CELL_LAYOUT( c ), r, render_hit_count_func, NULL, NULL ); |
---|
731 | |
---|
732 | g_signal_connect( tmodel, "row-changed", |
---|
733 | G_CALLBACK( state_torrent_model_row_changed ), state_model ); |
---|
734 | g_signal_connect( tmodel, "row-inserted", |
---|
735 | G_CALLBACK( state_torrent_model_row_changed ), state_model ); |
---|
736 | g_signal_connect( tmodel, "row-deleted", |
---|
737 | G_CALLBACK( state_torrent_model_row_deleted_cb ), state_model ); |
---|
738 | |
---|
739 | return c; |
---|
740 | } |
---|
741 | |
---|
742 | /**** |
---|
743 | ***** |
---|
744 | ***** ENTRY FIELD |
---|
745 | ***** |
---|
746 | ****/ |
---|
747 | |
---|
748 | enum |
---|
749 | { |
---|
750 | TEXT_MODE_NAME, |
---|
751 | TEXT_MODE_FILES, |
---|
752 | TEXT_MODE_TRACKER, |
---|
753 | TEXT_MODE_N_TYPES |
---|
754 | }; |
---|
755 | |
---|
756 | static gboolean |
---|
757 | testText( const tr_torrent * tor, const char * key, int mode ) |
---|
758 | { |
---|
759 | gboolean ret; |
---|
760 | tr_file_index_t i; |
---|
761 | const tr_info * inf = tr_torrentInfo( tor ); |
---|
762 | |
---|
763 | switch( mode ) |
---|
764 | { |
---|
765 | case TEXT_MODE_FILES: |
---|
766 | for( i=0; i<inf->fileCount && !ret; ++i ) { |
---|
767 | char * pch = g_utf8_casefold( inf->files[i].name, -1 ); |
---|
768 | ret = !key || strstr( pch, key ) != NULL; |
---|
769 | g_free( pch ); |
---|
770 | } |
---|
771 | break; |
---|
772 | |
---|
773 | case TEXT_MODE_TRACKER: |
---|
774 | if( inf->trackerCount > 0 ) { |
---|
775 | char * pch = g_utf8_casefold( inf->trackers[0].announce, -1 ); |
---|
776 | ret = !key || ( strstr( pch, key ) != NULL ); |
---|
777 | g_free( pch ); |
---|
778 | } |
---|
779 | break; |
---|
780 | |
---|
781 | default: /* NAME */ |
---|
782 | if( !inf->name ) |
---|
783 | ret = TRUE; |
---|
784 | else { |
---|
785 | char * pch = g_utf8_casefold( inf->name, -1 ); |
---|
786 | ret = !key || ( strstr( pch, key ) != NULL ); |
---|
787 | g_free( pch ); |
---|
788 | } |
---|
789 | break; |
---|
790 | } |
---|
791 | |
---|
792 | return ret; |
---|
793 | } |
---|
794 | |
---|
795 | |
---|
796 | #ifdef USE_SEXY |
---|
797 | static void |
---|
798 | entry_icon_released( SexyIconEntry * entry UNUSED, |
---|
799 | SexyIconEntryPosition icon_pos, |
---|
800 | int button UNUSED, |
---|
801 | gpointer menu ) |
---|
802 | { |
---|
803 | if( icon_pos == SEXY_ICON_ENTRY_PRIMARY ) |
---|
804 | gtk_menu_popup( GTK_MENU( menu ), NULL, NULL, NULL, NULL, 0, |
---|
805 | gtk_get_current_event_time( ) ); |
---|
806 | } |
---|
807 | #else |
---|
808 | static void |
---|
809 | entry_icon_release( GtkEntry * entry UNUSED, |
---|
810 | GtkEntryIconPosition icon_pos, |
---|
811 | GdkEventButton * event UNUSED, |
---|
812 | gpointer menu ) |
---|
813 | { |
---|
814 | if( icon_pos == GTK_ENTRY_ICON_SECONDARY ) |
---|
815 | gtk_entry_set_text( entry, "" ); |
---|
816 | |
---|
817 | if( icon_pos == GTK_ENTRY_ICON_PRIMARY ) |
---|
818 | gtk_menu_popup( GTK_MENU( menu ), NULL, NULL, NULL, NULL, 0, |
---|
819 | gtk_get_current_event_time( ) ); |
---|
820 | } |
---|
821 | #endif |
---|
822 | |
---|
823 | static void |
---|
824 | filter_entry_changed( GtkEditable * e, gpointer filter_model ) |
---|
825 | { |
---|
826 | char * pch; |
---|
827 | char * folded; |
---|
828 | |
---|
829 | pch = gtk_editable_get_chars( e, 0, -1 ); |
---|
830 | folded = g_utf8_casefold( pch, -1 ); |
---|
831 | g_object_set_data_full( filter_model, TEXT_KEY, folded, g_free ); |
---|
832 | g_free( pch ); |
---|
833 | |
---|
834 | gtk_tree_model_filter_refilter( GTK_TREE_MODEL_FILTER( filter_model ) ); |
---|
835 | } |
---|
836 | |
---|
837 | static void |
---|
838 | filter_text_toggled_cb( GtkCheckMenuItem * menu_item, gpointer filter_model ) |
---|
839 | { |
---|
840 | g_object_set_data( filter_model, TEXT_MODE_KEY, |
---|
841 | g_object_get_data( G_OBJECT( menu_item ), TEXT_MODE_KEY ) ); |
---|
842 | gtk_tree_model_filter_refilter( GTK_TREE_MODEL_FILTER( filter_model ) ); |
---|
843 | } |
---|
844 | |
---|
845 | /***** |
---|
846 | ****** |
---|
847 | ****** |
---|
848 | ****** |
---|
849 | *****/ |
---|
850 | |
---|
851 | struct filter_data |
---|
852 | { |
---|
853 | GtkWidget * state; |
---|
854 | GtkWidget * category; |
---|
855 | GtkWidget * entry; |
---|
856 | GtkTreeModel * filter_model; |
---|
857 | }; |
---|
858 | |
---|
859 | static gboolean |
---|
860 | is_row_visible( GtkTreeModel * model, GtkTreeIter * iter, gpointer vdata ) |
---|
861 | { |
---|
862 | int mode; |
---|
863 | gboolean b; |
---|
864 | const char * text; |
---|
865 | tr_torrent * tor; |
---|
866 | struct filter_data * data = vdata; |
---|
867 | GObject * o = G_OBJECT( data->filter_model ); |
---|
868 | |
---|
869 | gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 ); |
---|
870 | |
---|
871 | text = (const char*) g_object_get_data( o, TEXT_KEY ); |
---|
872 | mode = GPOINTER_TO_INT( g_object_get_data( o, TEXT_MODE_KEY ) ); |
---|
873 | |
---|
874 | b = ( tor != NULL ) && testCategory( data->category, tor ) |
---|
875 | && testState( data->state, tor ) |
---|
876 | && testText( tor, text, mode ); |
---|
877 | |
---|
878 | return b; |
---|
879 | } |
---|
880 | |
---|
881 | static void |
---|
882 | selection_changed_cb( GtkComboBox * combo UNUSED, gpointer vdata ) |
---|
883 | { |
---|
884 | struct filter_data * data = vdata; |
---|
885 | gtk_tree_model_filter_refilter( GTK_TREE_MODEL_FILTER( data->filter_model ) ); |
---|
886 | } |
---|
887 | |
---|
888 | GtkWidget * |
---|
889 | gtr_filter_bar_new( GtkTreeModel * tmodel, GtkTreeModel ** filter_model ) |
---|
890 | { |
---|
891 | int i; |
---|
892 | GtkWidget * l; |
---|
893 | GtkWidget * w; |
---|
894 | GtkWidget * h; |
---|
895 | GtkWidget * s; |
---|
896 | GtkWidget * menu; |
---|
897 | GtkWidget * state; |
---|
898 | GtkWidget * category; |
---|
899 | GSList * sl; |
---|
900 | const char * str; |
---|
901 | struct filter_data * data; |
---|
902 | const char * filter_text_names[] = { |
---|
903 | N_( "Name" ), N_( "Files" ), N_( "Tracker" ) |
---|
904 | }; |
---|
905 | |
---|
906 | |
---|
907 | data = g_new( struct filter_data, 1 ); |
---|
908 | data->state = state = state_combo_box_new( tmodel ); |
---|
909 | data->category = category = category_combo_box_new( tmodel ); |
---|
910 | data->entry = NULL; |
---|
911 | data->filter_model = gtk_tree_model_filter_new( tmodel, NULL ); |
---|
912 | |
---|
913 | g_object_set( G_OBJECT( data->category ), "width-request", 150, NULL ); |
---|
914 | |
---|
915 | gtk_tree_model_filter_set_visible_func( GTK_TREE_MODEL_FILTER( data->filter_model ), |
---|
916 | is_row_visible, data, g_free ); |
---|
917 | |
---|
918 | g_signal_connect( data->category, "changed", G_CALLBACK( selection_changed_cb ), data ); |
---|
919 | g_signal_connect( data->state, "changed", G_CALLBACK( selection_changed_cb ), data ); |
---|
920 | |
---|
921 | |
---|
922 | h = gtk_hbox_new( FALSE, GUI_PAD_SMALL ); |
---|
923 | |
---|
924 | /* add the category combobox */ |
---|
925 | str = _( "Show _Category:" ); |
---|
926 | w = category; |
---|
927 | l = gtk_label_new( NULL ); |
---|
928 | gtk_label_set_markup_with_mnemonic( GTK_LABEL( l ), str ); |
---|
929 | gtk_label_set_mnemonic_widget( GTK_LABEL( l ), w ); |
---|
930 | gtk_box_pack_start( GTK_BOX( h ), l, FALSE, FALSE, 0 ); |
---|
931 | gtk_box_pack_start( GTK_BOX( h ), w, TRUE, TRUE, 0 ); |
---|
932 | |
---|
933 | /* add a spacer */ |
---|
934 | w = gtk_alignment_new( 0.0f, 0.0f, 0.0f, 0.0f ); |
---|
935 | gtk_widget_set_size_request( w, 0u, GUI_PAD_BIG ); |
---|
936 | gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 ); |
---|
937 | |
---|
938 | /* add the state combobox */ |
---|
939 | str = _( "_State:" ); |
---|
940 | w = state; |
---|
941 | l = gtk_label_new( NULL ); |
---|
942 | gtk_label_set_markup_with_mnemonic( GTK_LABEL( l ), str ); |
---|
943 | gtk_label_set_mnemonic_widget( GTK_LABEL( l ), w ); |
---|
944 | gtk_box_pack_start( GTK_BOX( h ), l, FALSE, FALSE, 0 ); |
---|
945 | gtk_box_pack_start( GTK_BOX( h ), w, TRUE, TRUE, 0 ); |
---|
946 | |
---|
947 | /* add a spacer */ |
---|
948 | w = gtk_alignment_new( 0.0f, 0.0f, 0.0f, 0.0f ); |
---|
949 | gtk_widget_set_size_request( w, 0u, GUI_PAD_BIG ); |
---|
950 | gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 ); |
---|
951 | |
---|
952 | /* add the entry field */ |
---|
953 | #ifdef USE_SEXY |
---|
954 | s = sexy_icon_entry_new( ); |
---|
955 | sexy_icon_entry_add_clear_button( SEXY_ICON_ENTRY( s ) ); |
---|
956 | w = gtk_image_new_from_stock( GTK_STOCK_FIND, GTK_ICON_SIZE_MENU ); |
---|
957 | sexy_icon_entry_set_icon( SEXY_ICON_ENTRY( s ), |
---|
958 | SEXY_ICON_ENTRY_PRIMARY, |
---|
959 | GTK_IMAGE( w ) ); |
---|
960 | g_object_unref( w ); |
---|
961 | sexy_icon_entry_set_icon_highlight( SEXY_ICON_ENTRY( s ), |
---|
962 | SEXY_ICON_ENTRY_PRIMARY, TRUE ); |
---|
963 | #else |
---|
964 | s = gtk_entry_new( ); |
---|
965 | gtk_entry_set_icon_from_stock( GTK_ENTRY( s ), |
---|
966 | GTK_ENTRY_ICON_PRIMARY, |
---|
967 | GTK_STOCK_FIND); |
---|
968 | gtk_entry_set_icon_from_stock( GTK_ENTRY( s ), |
---|
969 | GTK_ENTRY_ICON_SECONDARY, |
---|
970 | GTK_STOCK_CLEAR ); |
---|
971 | #endif |
---|
972 | |
---|
973 | menu = gtk_menu_new( ); |
---|
974 | sl = NULL; |
---|
975 | for( i=0; i<TEXT_MODE_N_TYPES; ++i ) |
---|
976 | { |
---|
977 | const char * name = _( filter_text_names[i] ); |
---|
978 | GtkWidget * w = gtk_radio_menu_item_new_with_label ( sl, name ); |
---|
979 | sl = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM( w ) ); |
---|
980 | g_object_set_data( G_OBJECT( w ), TEXT_MODE_KEY, GINT_TO_POINTER( i ) ); |
---|
981 | g_signal_connect( w, "toggled", G_CALLBACK( filter_text_toggled_cb ), data->filter_model ); |
---|
982 | gtk_menu_shell_append( GTK_MENU_SHELL( menu ), w ); |
---|
983 | gtk_widget_show( w ); |
---|
984 | } |
---|
985 | #ifdef USE_SEXY |
---|
986 | g_signal_connect( s, "icon-released", G_CALLBACK( entry_icon_released ), menu ); |
---|
987 | #else |
---|
988 | g_signal_connect( s, "icon-release", G_CALLBACK( entry_icon_release ), menu ); |
---|
989 | #endif |
---|
990 | |
---|
991 | gtk_box_pack_start( GTK_BOX( h ), s, TRUE, TRUE, 0 ); |
---|
992 | g_signal_connect( s, "changed", G_CALLBACK( filter_entry_changed ), data->filter_model ); |
---|
993 | |
---|
994 | *filter_model = data->filter_model; |
---|
995 | return h; |
---|
996 | } |
---|