source: trunk/gtk/filter.c @ 13731

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

(gtk) copyediting: indentation & whitespace

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