source: trunk/gtk/filter.c @ 12357

Last change on this file since 12357 was 12357, checked in by jordan, 11 years ago

(trunk gtk) Avoid unnecessary GtkComboBox? queries.

Instead of calling gtk_combo_box_get_active_iter() on the filterbar's two comboboxes in every periodic update, keep the state information in a local struct and update it when the selection changes. That way the filter code doesn't even need to know about the GtkComboBox? or the GtkTreeModel?.

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