source: branches/2.0x/gtk/filter.c @ 11106

Last change on this file since 11106 was 11106, checked in by Longinus00, 12 years ago

(2.0x) #3185: Number on drop-down menu "Activity" - "Active" seems does not follow a change in torrent status correctly

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