source: trunk/gtk/filter.c @ 13938

Last change on this file since 13938 was 13938, checked in by jordan, 8 years ago

(gtk) #4076 'free space indicator': tweak the GTK+ client's visible count label's behavior as the previous commit did for the Qt client

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