source: trunk/gtk/filter.c @ 13656

Last change on this file since 13656 was 13656, checked in by jordan, 9 years ago

fewer redundant gobject dynamic casts

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