source: trunk/gtk/filter.c @ 11519

Last change on this file since 11519 was 11519, checked in by charles, 11 years ago

(trunk gtk, qt) "Show 'queued to verify' torrents in the 'verifying' filter" -- added to trunk.

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