source: trunk/gtk/filter.c @ 14526

Last change on this file since 14526 was 14526, checked in by mikedld, 6 years ago

Fix some issues revealed by coverity

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