source: trunk/gtk/filter.c @ 13952

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

(gtk) #5271 'once we start shutting down the application, stop updating the Action states': fixed

  • Property svn:keywords set to Date Rev Author Id
File size: 30.2 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 13952 2013-02-04 16:11:08Z 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  gtk_combo_box_set_row_separator_func (c_combo_box,
406                                        is_it_a_separator, NULL, NULL);
407  gtk_combo_box_set_active (c_combo_box, 0);
408
409  r = gtk_cell_renderer_pixbuf_new ();
410  gtk_cell_layout_pack_start (c_cell_layout, r, FALSE);
411  gtk_cell_layout_set_cell_data_func (c_cell_layout, r,
412                                      render_pixbuf_func, NULL, NULL);
413  gtk_cell_layout_set_attributes (c_cell_layout, r,
414                                  "pixbuf", TRACKER_FILTER_COL_PIXBUF,
415                                  NULL);
416
417  r = gtk_cell_renderer_text_new ();
418  gtk_cell_layout_pack_start (c_cell_layout, r, FALSE);
419  gtk_cell_layout_set_attributes (c_cell_layout, r,
420                                  "text", TRACKER_FILTER_COL_NAME,
421                                  NULL);
422
423  r = number_renderer_new ();
424  gtk_cell_layout_pack_end (c_cell_layout, r, TRUE);
425  gtk_cell_layout_set_cell_data_func (c_cell_layout, r,
426                                      render_number_func, NULL, NULL);
427
428  g_object_weak_ref (G_OBJECT (cat_model), disconnect_cat_model_callbacks, tmodel);
429  g_signal_connect (tmodel, "row-changed", G_CALLBACK (torrent_model_row_changed), cat_model);
430  g_signal_connect (tmodel, "row-inserted", G_CALLBACK (torrent_model_row_changed), cat_model);
431  g_signal_connect (tmodel, "row-deleted", G_CALLBACK (torrent_model_row_deleted_cb), cat_model);
432
433  return c;
434}
435
436static gboolean
437test_tracker (tr_torrent * tor, int active_tracker_type, const char * host)
438{
439  gboolean matches = TRUE;
440
441  if (active_tracker_type == TRACKER_FILTER_TYPE_HOST)
442    {
443      unsigned int i;
444      char tmp[1024];
445      const tr_info * const inf = tr_torrentInfo (tor);
446
447      for (i=0; i<inf->trackerCount; ++i)
448        {
449          gtr_get_host_from_url (tmp, sizeof (tmp), inf->trackers[i].announce);
450          if (!strcmp (tmp, host))
451            break;
452        }
453
454      matches = i < inf->trackerCount;
455    }
456
457  return matches;
458}
459
460/***
461****
462****  ACTIVITY
463****
464***/
465
466enum
467{
468  ACTIVITY_FILTER_ALL,
469  ACTIVITY_FILTER_DOWNLOADING,
470  ACTIVITY_FILTER_SEEDING,
471  ACTIVITY_FILTER_ACTIVE,
472  ACTIVITY_FILTER_PAUSED,
473  ACTIVITY_FILTER_FINISHED,
474  ACTIVITY_FILTER_VERIFYING,
475  ACTIVITY_FILTER_ERROR,
476  ACTIVITY_FILTER_SEPARATOR
477};
478
479enum
480{
481  ACTIVITY_FILTER_COL_NAME,
482  ACTIVITY_FILTER_COL_COUNT,
483  ACTIVITY_FILTER_COL_TYPE,
484  ACTIVITY_FILTER_COL_STOCK_ID,
485  ACTIVITY_FILTER_N_COLS
486};
487
488static gboolean
489activity_is_it_a_separator (GtkTreeModel * m, GtkTreeIter * i, gpointer d UNUSED)
490{
491  int type;
492  gtk_tree_model_get (m, i, ACTIVITY_FILTER_COL_TYPE, &type, -1);
493  return type == ACTIVITY_FILTER_SEPARATOR;
494}
495
496static gboolean
497test_torrent_activity (tr_torrent * tor, int type)
498{
499  const tr_stat * st = tr_torrentStatCached (tor);
500
501  switch (type)
502    {
503      case ACTIVITY_FILTER_DOWNLOADING:
504        return (st->activity == TR_STATUS_DOWNLOAD)
505            || (st->activity == TR_STATUS_DOWNLOAD_WAIT);
506
507      case ACTIVITY_FILTER_SEEDING:
508        return (st->activity == TR_STATUS_SEED)
509            || (st->activity == TR_STATUS_SEED_WAIT);
510
511      case ACTIVITY_FILTER_ACTIVE:
512        return (st->peersSendingToUs > 0)
513            || (st->peersGettingFromUs > 0)
514            || (st->webseedsSendingToUs > 0)
515            || (st->activity == TR_STATUS_CHECK);
516
517      case ACTIVITY_FILTER_PAUSED:
518        return st->activity == TR_STATUS_STOPPED;
519
520      case ACTIVITY_FILTER_FINISHED:
521        return st->finished == TRUE;
522
523      case ACTIVITY_FILTER_VERIFYING:
524        return (st->activity == TR_STATUS_CHECK)
525            || (st->activity == TR_STATUS_CHECK_WAIT);
526
527      case ACTIVITY_FILTER_ERROR:
528        return st->error != 0;
529
530      default: /* ACTIVITY_FILTER_ALL */
531        return TRUE;
532    }
533}
534
535static void
536status_model_update_count (GtkListStore * store, GtkTreeIter * iter, int n)
537{
538  int count;
539  GtkTreeModel * model = GTK_TREE_MODEL (store);
540  gtk_tree_model_get (model, iter, ACTIVITY_FILTER_COL_COUNT, &count, -1);
541  if (n != count)
542    gtk_list_store_set (store, iter, ACTIVITY_FILTER_COL_COUNT, n, -1);
543}
544
545static gboolean
546activity_filter_model_update (gpointer gstore)
547{
548  GtkTreeIter iter;
549  GObject * o = G_OBJECT (gstore);
550  GtkListStore * store = GTK_LIST_STORE (gstore);
551  GtkTreeModel * model = GTK_TREE_MODEL (store);
552  GtkTreeModel * tmodel = GTK_TREE_MODEL (g_object_get_qdata (o, TORRENT_MODEL_KEY));
553
554  g_object_steal_qdata (o, DIRTY_KEY);
555
556  if (gtk_tree_model_iter_nth_child (model, &iter, NULL, 0)) do
557    {
558      int hits;
559      int type;
560      GtkTreeIter torrent_iter;
561
562      gtk_tree_model_get (model, &iter, ACTIVITY_FILTER_COL_TYPE, &type, -1);
563
564      hits = 0;
565      if (gtk_tree_model_iter_nth_child (tmodel, &torrent_iter, NULL, 0)) do
566        {
567          tr_torrent * tor;
568          gtk_tree_model_get (tmodel, &torrent_iter, MC_TORRENT, &tor, -1);
569          if (test_torrent_activity (tor, type))
570            ++hits;
571        }
572      while (gtk_tree_model_iter_next (tmodel, &torrent_iter));
573
574      status_model_update_count (store, &iter, hits);
575    }
576  while (gtk_tree_model_iter_next (model, &iter));
577
578  return G_SOURCE_REMOVE;
579}
580
581static GtkTreeModel *
582activity_filter_model_new (GtkTreeModel * tmodel)
583{
584  int i, n;
585  struct {
586    int type;
587    const char * context;
588    const char * name;
589    const char * stock_id;
590  } types[] = {
591    { ACTIVITY_FILTER_ALL, NULL, N_("All"), NULL },
592    { ACTIVITY_FILTER_SEPARATOR, NULL, NULL, NULL },
593    { ACTIVITY_FILTER_ACTIVE, NULL, N_("Active"), GTK_STOCK_EXECUTE },
594    { ACTIVITY_FILTER_DOWNLOADING, "Verb", NC_("Verb", "Downloading"), GTK_STOCK_GO_DOWN },
595    { ACTIVITY_FILTER_SEEDING, "Verb", NC_("Verb", "Seeding"), GTK_STOCK_GO_UP },
596    { ACTIVITY_FILTER_PAUSED, NULL, N_("Paused"), GTK_STOCK_MEDIA_PAUSE },
597    { ACTIVITY_FILTER_FINISHED, NULL, N_("Finished"), NULL },
598    { ACTIVITY_FILTER_VERIFYING, "Verb", NC_("Verb", "Verifying"), GTK_STOCK_REFRESH },
599    { ACTIVITY_FILTER_ERROR, NULL, N_("Error"), GTK_STOCK_DIALOG_ERROR }
600  };
601
602  GtkListStore * store = gtk_list_store_new (ACTIVITY_FILTER_N_COLS,
603                                             G_TYPE_STRING,
604                                             G_TYPE_INT,
605                                             G_TYPE_INT,
606                                             G_TYPE_STRING);
607  for (i=0, n=G_N_ELEMENTS (types); i<n; ++i)
608    {
609      const char * name = types[i].context ? g_dpgettext2 (NULL, types[i].context, types[i].name)
610                                           : _ (types[i].name);
611      gtk_list_store_insert_with_values (store, NULL, -1,
612                                         ACTIVITY_FILTER_COL_NAME, name,
613                                         ACTIVITY_FILTER_COL_TYPE, types[i].type,
614                                         ACTIVITY_FILTER_COL_STOCK_ID, types[i].stock_id,
615                                         -1);
616    }
617
618  g_object_set_qdata (G_OBJECT (store), TORRENT_MODEL_KEY, tmodel);
619  activity_filter_model_update (store);
620  return GTK_TREE_MODEL (store);
621}
622
623static void
624render_activity_pixbuf_func (GtkCellLayout    * cell_layout UNUSED,
625                             GtkCellRenderer  * cell_renderer,
626                             GtkTreeModel     * tree_model,
627                             GtkTreeIter      * iter,
628                             gpointer           data UNUSED)
629{
630  int type;
631  int width;
632  int ypad;
633
634  gtk_tree_model_get (tree_model, iter, ACTIVITY_FILTER_COL_TYPE, &type, -1);
635  width = type == ACTIVITY_FILTER_ALL ? 0 : 20;
636  ypad = type == ACTIVITY_FILTER_ALL ? 0 : 2;
637
638  g_object_set (cell_renderer, "width", width,
639                               "ypad", ypad,
640                               NULL);
641}
642
643static void
644activity_model_update_idle (gpointer activity_model)
645{
646  GObject * o = G_OBJECT (activity_model);
647  const gboolean pending = g_object_get_qdata (o, DIRTY_KEY) != NULL;
648  if (!pending)
649    {
650      GSourceFunc func = activity_filter_model_update;
651      g_object_set_qdata (o, DIRTY_KEY, GINT_TO_POINTER (1));
652      gdk_threads_add_idle (func, activity_model);
653    }
654}
655
656static void
657activity_torrent_model_row_changed (GtkTreeModel  * tmodel UNUSED,
658                                    GtkTreePath   * path UNUSED,
659                                    GtkTreeIter   * iter UNUSED,
660                                    gpointer        activity_model)
661{
662  activity_model_update_idle (activity_model);
663}
664
665static void
666activity_torrent_model_row_deleted_cb (GtkTreeModel  * tmodel UNUSED,
667                                       GtkTreePath   * path UNUSED,
668                                       gpointer        activity_model)
669{
670  activity_model_update_idle (activity_model);
671}
672
673static void
674disconnect_activity_model_callbacks (gpointer tmodel, GObject * cat_model)
675{
676  g_signal_handlers_disconnect_by_func (tmodel, activity_torrent_model_row_changed, cat_model);
677  g_signal_handlers_disconnect_by_func (tmodel, activity_torrent_model_row_deleted_cb, cat_model);
678}
679
680static GtkWidget *
681activity_combo_box_new (GtkTreeModel * tmodel)
682{
683  GtkWidget * c;
684  GtkCellRenderer * r;
685  GtkTreeModel * activity_model;
686  GtkComboBox * c_combo_box;
687  GtkCellLayout * c_cell_layout;
688
689  activity_model = activity_filter_model_new (tmodel);
690  c = gtk_combo_box_new_with_model (activity_model);
691  c_combo_box = GTK_COMBO_BOX (c);
692  c_cell_layout = GTK_CELL_LAYOUT (c);
693  gtk_combo_box_set_row_separator_func (c_combo_box,
694                                        activity_is_it_a_separator, NULL, NULL);
695  gtk_combo_box_set_active (c_combo_box, 0);
696
697  r = gtk_cell_renderer_pixbuf_new ();
698  gtk_cell_layout_pack_start (c_cell_layout, r, FALSE);
699  gtk_cell_layout_set_attributes (c_cell_layout, r,
700                                  "stock-id", ACTIVITY_FILTER_COL_STOCK_ID,
701                                  NULL);
702  gtk_cell_layout_set_cell_data_func (c_cell_layout, r,
703                                      render_activity_pixbuf_func, NULL, NULL);
704
705  r = gtk_cell_renderer_text_new ();
706  gtk_cell_layout_pack_start (c_cell_layout, r, TRUE);
707  gtk_cell_layout_set_attributes (c_cell_layout, r,
708                                  "text", ACTIVITY_FILTER_COL_NAME,
709                                  NULL);
710
711  r = number_renderer_new ();
712  gtk_cell_layout_pack_end (c_cell_layout, r, TRUE);
713  gtk_cell_layout_set_cell_data_func (c_cell_layout, r,
714                                      render_number_func, NULL, NULL);
715
716  g_object_weak_ref (G_OBJECT (activity_model), disconnect_activity_model_callbacks, tmodel);
717  g_signal_connect (tmodel, "row-changed", G_CALLBACK (activity_torrent_model_row_changed), activity_model);
718  g_signal_connect (tmodel, "row-inserted", G_CALLBACK (activity_torrent_model_row_changed), activity_model);
719  g_signal_connect (tmodel, "row-deleted", G_CALLBACK (activity_torrent_model_row_deleted_cb), activity_model);
720
721  return c;
722}
723
724/****
725*****
726*****  ENTRY FIELD
727*****
728****/
729
730static gboolean
731testText (const tr_torrent * tor, const char * key)
732{
733  gboolean ret = FALSE;
734
735  if (!key || !*key)
736    {
737      ret = TRUE;
738    }
739  else
740    {
741      tr_file_index_t i;
742      const tr_info * inf = tr_torrentInfo (tor);
743
744      /* test the torrent name... */
745      {
746        char * pch = g_utf8_casefold (tr_torrentName (tor), -1);
747        ret = !key || strstr (pch, key) != NULL;
748        g_free (pch);
749      }
750
751      /* test the files... */
752      for (i=0; i<inf->fileCount && !ret; ++i)
753        {
754          char * pch = g_utf8_casefold (inf->files[i].name, -1);
755          ret = !key || strstr (pch, key) != NULL;
756          g_free (pch);
757        }
758    }
759
760  return ret;
761}
762
763static void
764entry_clear (GtkEntry * e)
765{
766  gtk_entry_set_text (e, "");
767}
768
769static void
770filter_entry_changed (GtkEditable * e, gpointer filter_model)
771{
772  char * pch;
773  char * folded;
774
775  pch = gtk_editable_get_chars (e, 0, -1);
776  folded = g_utf8_casefold (pch, -1);
777  g_strstrip (folded);
778  g_object_set_qdata_full (filter_model, TEXT_KEY, folded, g_free);
779  g_free (pch);
780
781  gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
782}
783
784/*****
785******
786******
787******
788*****/
789
790struct filter_data
791{
792  GtkWidget * activity;
793  GtkWidget * tracker;
794  GtkWidget * entry;
795  GtkWidget * show_lb;
796  GtkTreeModel * filter_model;
797  int active_activity_type;
798  int active_tracker_type;
799  char * active_tracker_host;
800};
801
802static gboolean
803is_row_visible (GtkTreeModel * model, GtkTreeIter * iter, gpointer vdata)
804{
805  const char * text;
806  tr_torrent * tor;
807  struct filter_data * data = vdata;
808  GObject * o = G_OBJECT (data->filter_model);
809
810  gtk_tree_model_get (model, iter, MC_TORRENT, &tor, -1);
811
812  text = (const char*) g_object_get_qdata (o, TEXT_KEY);
813
814  return (tor != NULL) && test_tracker (tor, data->active_tracker_type, data->active_tracker_host)
815                       && test_torrent_activity (tor, data->active_activity_type)
816                       && testText (tor, text);
817}
818
819static void
820selection_changed_cb (GtkComboBox * combo, gpointer vdata)
821{
822  int type;
823  char * host;
824  GtkTreeIter iter;
825  GtkTreeModel * model;
826  struct filter_data * data = vdata;
827
828  /* set data->active_activity_type from the activity combobox */
829  combo = GTK_COMBO_BOX (data->activity);
830  model = gtk_combo_box_get_model (combo);
831  if (gtk_combo_box_get_active_iter (combo, &iter))
832    gtk_tree_model_get (model, &iter, ACTIVITY_FILTER_COL_TYPE, &type, -1);
833  else
834    type = ACTIVITY_FILTER_ALL;
835  data->active_activity_type = type;
836
837  /* set the active tracker type & host from the tracker combobox */
838  combo = GTK_COMBO_BOX (data->tracker);
839  model = gtk_combo_box_get_model (combo);
840  if (gtk_combo_box_get_active_iter (combo, &iter))
841    {
842      gtk_tree_model_get (model, &iter, TRACKER_FILTER_COL_TYPE, &type,
843                                        TRACKER_FILTER_COL_HOST, &host,
844                                        -1);
845    }
846  else
847    {
848      type = TRACKER_FILTER_TYPE_ALL;
849      host = NULL;
850    }
851  g_free (data->active_tracker_host);
852  data->active_tracker_host = host;
853  data->active_tracker_type = type;
854
855  /* refilter */
856  gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (data->filter_model));
857}
858
859/***
860****
861***/
862
863static gboolean
864update_count_label (gpointer gdata)
865{
866  char buf[512];
867  int visibleCount;
868  int trackerCount;
869  int activityCount;
870  GtkTreeModel * model;
871  GtkComboBox * combo;
872  GtkTreeIter iter;
873  struct filter_data * data = gdata;
874
875  /* get the visible count */
876  visibleCount = gtk_tree_model_iter_n_children (data->filter_model, NULL);
877
878  /* get the tracker count */
879  combo = GTK_COMBO_BOX (data->tracker);
880  model = gtk_combo_box_get_model (combo);
881  if (gtk_combo_box_get_active_iter (combo, &iter))
882    gtk_tree_model_get (model, &iter, TRACKER_FILTER_COL_COUNT, &trackerCount, -1);
883  else
884    trackerCount = 0;
885
886  /* get the activity count */
887  combo = GTK_COMBO_BOX (data->activity);
888  model = gtk_combo_box_get_model (combo);
889  if (gtk_combo_box_get_active_iter (combo, &iter))
890    gtk_tree_model_get (model, &iter, ACTIVITY_FILTER_COL_COUNT, &activityCount, -1);
891  else
892    activityCount = 0;
893
894  /* set the text */
895  if (visibleCount == MIN (activityCount, trackerCount))
896    g_snprintf (buf, sizeof(buf), _("_Show:"));
897  else
898    g_snprintf (buf, sizeof(buf), _("_Show %'d of:"), visibleCount);
899  gtk_label_set_markup_with_mnemonic (GTK_LABEL (data->show_lb), buf);
900
901  g_object_steal_qdata (G_OBJECT(data->show_lb), DIRTY_KEY);
902  return G_SOURCE_REMOVE;
903}
904
905static void
906update_count_label_idle (struct filter_data * data)
907{
908  GObject * o = G_OBJECT (data->show_lb);
909  const gboolean pending = g_object_get_qdata (o, DIRTY_KEY) != NULL;
910  if (!pending)
911    {
912      g_object_set_qdata (o, DIRTY_KEY, GINT_TO_POINTER (1));
913      gdk_threads_add_idle (update_count_label, data);
914    }
915}
916
917static void
918on_filter_model_row_inserted (GtkTreeModel * tree_model UNUSED,
919                              GtkTreePath  * path       UNUSED,
920                              GtkTreeIter  * iter       UNUSED,
921                              gpointer       data)
922{
923  update_count_label_idle (data);
924}
925
926static void
927on_filter_model_row_deleted (GtkTreeModel * tree_model UNUSED,
928                             GtkTreePath  * path       UNUSED,
929                             gpointer       data       UNUSED)
930{
931  update_count_label_idle (data);
932}
933
934/***
935****
936***/
937
938GtkWidget *
939gtr_filter_bar_new (tr_session * session, GtkTreeModel * tmodel, GtkTreeModel ** filter_model)
940{
941  GtkWidget * l;
942  GtkWidget * w;
943  GtkWidget * h;
944  GtkWidget * s;
945  GtkWidget * activity;
946  GtkWidget * tracker;
947  GtkBox * h_box;
948  struct filter_data * data;
949
950  g_assert (DIRTY_KEY == 0);
951  TEXT_KEY = g_quark_from_static_string ("tr-filter-text-key");
952  DIRTY_KEY = g_quark_from_static_string ("tr-filter-dirty-key");
953  SESSION_KEY = g_quark_from_static_string ("tr-session-key");
954  TORRENT_MODEL_KEY = g_quark_from_static_string ("tr-filter-torrent-model-key");
955
956  data = g_new0 (struct filter_data, 1);
957  data->show_lb = gtk_label_new (NULL);
958  data->activity = activity = activity_combo_box_new (tmodel);
959  data->tracker = tracker = tracker_combo_box_new (tmodel);
960  data->filter_model = gtk_tree_model_filter_new (tmodel, NULL);
961  g_signal_connect (data->filter_model, "row-deleted", G_CALLBACK(on_filter_model_row_deleted), data);
962  g_signal_connect (data->filter_model, "row-inserted", G_CALLBACK(on_filter_model_row_inserted), data);
963
964  g_object_set (G_OBJECT (data->tracker), "width-request", 170, NULL);
965  g_object_set_qdata (G_OBJECT (gtk_combo_box_get_model (GTK_COMBO_BOX (data->tracker))), SESSION_KEY, session);
966
967  gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (data->filter_model),
968                                          is_row_visible, data, g_free);
969
970  g_signal_connect (data->tracker, "changed", G_CALLBACK (selection_changed_cb), data);
971  g_signal_connect (data->activity, "changed", G_CALLBACK (selection_changed_cb), data);
972
973
974  h = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, GUI_PAD_SMALL);
975  h_box = GTK_BOX (h);
976
977  /* add the activity combobox */
978  w = activity;
979  l = data->show_lb;
980  gtk_label_set_mnemonic_widget (GTK_LABEL (l), w);
981  gtk_box_pack_start (h_box, l, FALSE, FALSE, 0);
982  gtk_box_pack_start (h_box, w, TRUE, TRUE, 0);
983
984  /* add a spacer */
985  w = gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f);
986  gtk_widget_set_size_request (w, 0u, GUI_PAD_BIG);
987  gtk_box_pack_start (h_box, w, FALSE, FALSE, 0);
988
989  /* add the tracker combobox */
990  w = tracker;
991  gtk_box_pack_start (h_box, w, TRUE, TRUE, 0);
992
993  /* add a spacer */
994  w = gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f);
995  gtk_widget_set_size_request (w, 0u, GUI_PAD_BIG);
996  gtk_box_pack_start (h_box, w, FALSE, FALSE, 0);
997
998  /* add the entry field */
999  s = gtk_entry_new ();
1000  gtk_entry_set_icon_from_stock (GTK_ENTRY (s), GTK_ENTRY_ICON_SECONDARY, GTK_STOCK_CLEAR);
1001  g_signal_connect (s, "icon-release", G_CALLBACK (entry_clear), NULL);
1002  gtk_box_pack_start (h_box, s, TRUE, TRUE, 0);
1003
1004  g_signal_connect (s, "changed", G_CALLBACK (filter_entry_changed), data->filter_model);
1005  selection_changed_cb (NULL, data);
1006
1007  *filter_model = data->filter_model;
1008  update_count_label (data);
1009  return h;
1010}
Note: See TracBrowser for help on using the repository browser.