source: trunk/gtk/filter.c @ 13927

Last change on this file since 13927 was 13927, checked in by jordan, 10 years ago

in the Qt and GTK+ clients, move the torrent count indicator from the statusbar to the filterbar

  • Property svn:keywords set to Date Rev Author Id
File size: 29.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 13927 2013-02-01 20:58:55Z 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 (GtkTreeStore * store)
116{
117  int i, n;
118  int all = 0;
119  int store_pos;
120  GtkTreeIter iter;
121  GtkTreeModel * model = GTK_TREE_MODEL (store);
122  GPtrArray * hosts = g_ptr_array_new ();
123  GStringChunk * strings = g_string_chunk_new (4096);
124  GHashTable * hosts_hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
125  GObject * o = G_OBJECT (store);
126  GtkTreeModel * tmodel = GTK_TREE_MODEL (g_object_get_qdata (o, TORRENT_MODEL_KEY));
127  const int first_tracker_pos = 2; /* offset past the "All" and the separator */
128
129  g_object_steal_qdata (o, DIRTY_KEY);
130
131  /* Walk through all the torrents, tallying how many matches there are
132   * for the various categories. Also make a sorted list of all tracker
133   * hosts s.t. we can merge it with the existing list */
134  if (gtk_tree_model_iter_nth_child (tmodel, &iter, NULL, 0)) do
135    {
136      tr_torrent * tor;
137      const tr_info * inf;
138      int keyCount;
139      char ** keys;
140
141      gtk_tree_model_get (tmodel, &iter, MC_TORRENT, &tor, -1);
142      inf = tr_torrentInfo (tor);
143      keyCount = 0;
144      keys = g_new (char*, inf->trackerCount);
145
146      for (i=0, n=inf->trackerCount; i<n; ++i)
147        {
148          int k;
149          int * count;
150          char buf[1024];
151          char * key;
152
153          gtr_get_host_from_url (buf, sizeof (buf), inf->trackers[i].announce);
154          key = g_string_chunk_insert_const (strings, buf);
155
156          count = g_hash_table_lookup (hosts_hash, key);
157          if (count == NULL)
158            {
159              count = tr_new0 (int, 1);
160              g_hash_table_insert (hosts_hash, key, count);
161              g_ptr_array_add (hosts, key);
162            }
163
164          for (k=0; k<keyCount; ++k)
165            if (!strcmp (keys[k], key))
166              break;
167
168          if (k==keyCount)
169            keys[keyCount++] = key;
170        }
171
172      for (i=0; i<keyCount; ++i)
173        {
174          int * incrementme = g_hash_table_lookup (hosts_hash, keys[i]);
175          ++*incrementme;
176        }
177
178      g_free (keys);
179
180      ++all;
181    }
182  while (gtk_tree_model_iter_next (tmodel, &iter));
183
184  qsort (hosts->pdata, hosts->len, sizeof (char*), pstrcmp);
185
186  /* update the "all" count */
187  gtk_tree_model_iter_children (model, &iter, NULL);
188  tracker_model_update_count (store, &iter, all);
189
190  store_pos = first_tracker_pos;
191  for (i=0, n=hosts->len ; ;)
192    {
193      const gboolean new_hosts_done = i >= n;
194      const gboolean old_hosts_done = !gtk_tree_model_iter_nth_child (model, &iter, NULL, store_pos);
195      gboolean remove_row = FALSE;
196      gboolean insert_row = FALSE;
197
198      /* are we done yet? */
199      if (new_hosts_done && old_hosts_done)
200        break;
201
202      /* decide what to do */
203      if (new_hosts_done)
204        {
205          remove_row = TRUE;
206        }
207      else if (old_hosts_done)
208        {
209          insert_row = TRUE;
210        }
211      else
212        {
213          int cmp;
214          char * host;
215          gtk_tree_model_get (model, &iter, TRACKER_FILTER_COL_HOST, &host,  -1);
216          cmp = strcmp (host, hosts->pdata[i]);
217
218          if (cmp < 0)
219            remove_row = TRUE;
220          else if (cmp > 0)
221            insert_row = TRUE;
222
223          g_free (host);
224        }
225
226      /* do something */
227      if (remove_row)
228        {
229          /* g_message ("removing row and incrementing i"); */
230          gtk_tree_store_remove (store, &iter);
231        }
232      else if (insert_row)
233        {
234          GtkTreeIter add;
235          GtkTreePath * path;
236          GtkTreeRowReference * reference;
237          tr_session * session = g_object_get_qdata (G_OBJECT (store), SESSION_KEY);
238          const char * host = hosts->pdata[i];
239          char * name = get_name_from_host (host);
240          const int count = * (int*)g_hash_table_lookup (hosts_hash, host);
241          gtk_tree_store_insert_with_values (store, &add, NULL, store_pos,
242                                             TRACKER_FILTER_COL_HOST, host,
243                                             TRACKER_FILTER_COL_NAME, name,
244                                             TRACKER_FILTER_COL_COUNT, count,
245                                             TRACKER_FILTER_COL_TYPE, TRACKER_FILTER_TYPE_HOST,
246                                             -1);
247          path = gtk_tree_model_get_path (model, &add);
248          reference = gtk_tree_row_reference_new (model, path);
249          gtr_get_favicon (session, host, favicon_ready_cb, reference);
250          gtk_tree_path_free (path);
251          g_free (name);
252          ++store_pos;
253          ++i;
254        }
255      else /* update row */
256        {
257          const char * host = hosts->pdata[i];
258          const int count = * (int*)g_hash_table_lookup (hosts_hash, host);
259          tracker_model_update_count (store, &iter, count);
260          ++store_pos;
261          ++i;
262        }
263    }
264
265  /* cleanup */
266  g_ptr_array_free (hosts, TRUE);
267  g_hash_table_unref (hosts_hash);
268  g_string_chunk_free (strings);
269  return FALSE;
270}
271
272static GtkTreeModel *
273tracker_filter_model_new (GtkTreeModel * tmodel)
274{
275  GtkTreeStore * store = gtk_tree_store_new (TRACKER_FILTER_N_COLS,
276                                             G_TYPE_STRING,
277                                             G_TYPE_INT,
278                                             G_TYPE_INT,
279                                             G_TYPE_STRING,
280                                             GDK_TYPE_PIXBUF);
281
282  gtk_tree_store_insert_with_values (store, NULL, NULL, -1,
283                                     TRACKER_FILTER_COL_NAME, _("All"),
284                                     TRACKER_FILTER_COL_TYPE, TRACKER_FILTER_TYPE_ALL,
285                                     -1);
286  gtk_tree_store_insert_with_values (store, NULL, NULL, -1,
287                                     TRACKER_FILTER_COL_TYPE, TRACKER_FILTER_TYPE_SEPARATOR,
288                                     -1);
289
290  g_object_set_qdata (G_OBJECT (store), TORRENT_MODEL_KEY, tmodel);
291  tracker_filter_model_update (store);
292  return GTK_TREE_MODEL (store);
293}
294
295static gboolean
296is_it_a_separator (GtkTreeModel * m, GtkTreeIter * iter, gpointer data UNUSED)
297{
298  int type;
299  gtk_tree_model_get (m, iter, TRACKER_FILTER_COL_TYPE, &type, -1);
300  return type == TRACKER_FILTER_TYPE_SEPARATOR;
301}
302
303static void
304tracker_model_update_idle (gpointer tracker_model)
305{
306  GObject * o = G_OBJECT (tracker_model);
307  const gboolean pending = g_object_get_qdata (o, DIRTY_KEY) != NULL;
308  if (!pending)
309    {
310      GSourceFunc func = (GSourceFunc) tracker_filter_model_update;
311      g_object_set_qdata (o, DIRTY_KEY, GINT_TO_POINTER (1));
312      gdk_threads_add_idle (func, tracker_model);
313    }
314}
315
316static void
317torrent_model_row_changed (GtkTreeModel  * tmodel UNUSED,
318                           GtkTreePath   * path UNUSED,
319                           GtkTreeIter   * iter UNUSED,
320                           gpointer        tracker_model)
321{
322  tracker_model_update_idle (tracker_model);
323}
324
325static void
326torrent_model_row_deleted_cb (GtkTreeModel * tmodel UNUSED,
327                              GtkTreePath  * path UNUSED,
328                              gpointer       tracker_model)
329{
330  tracker_model_update_idle (tracker_model);
331}
332
333static void
334render_pixbuf_func (GtkCellLayout    * cell_layout UNUSED,
335                    GtkCellRenderer  * cell_renderer,
336                    GtkTreeModel     * tree_model,
337                    GtkTreeIter      * iter,
338                    gpointer           data UNUSED)
339{
340  int type;
341  int width;
342
343  gtk_tree_model_get (tree_model, iter, TRACKER_FILTER_COL_TYPE, &type, -1);
344  width = (type == TRACKER_FILTER_TYPE_HOST) ? 20 : 0;
345  g_object_set (cell_renderer, "width", width, NULL);
346}
347
348static void
349render_number_func (GtkCellLayout    * cell_layout UNUSED,
350                    GtkCellRenderer  * cell_renderer,
351                    GtkTreeModel     * tree_model,
352                    GtkTreeIter      * iter,
353                    gpointer           data UNUSED)
354{
355  int count;
356  char buf[32];
357
358  gtk_tree_model_get (tree_model, iter, TRACKER_FILTER_COL_COUNT, &count, -1);
359
360  if (count >= 0)
361    g_snprintf (buf, sizeof (buf), "%'d", count);
362  else
363    *buf = '\0';
364
365  g_object_set (cell_renderer, "text", buf,
366                               NULL);
367}
368
369static GtkCellRenderer *
370number_renderer_new (void)
371{
372  GtkCellRenderer * r = gtk_cell_renderer_text_new ();
373
374  g_object_set (G_OBJECT (r), "alignment", PANGO_ALIGN_RIGHT,
375                              "weight", PANGO_WEIGHT_ULTRALIGHT,
376                              "xalign", 1.0,
377                              "xpad", GUI_PAD,
378                              NULL);
379
380  return r;
381}
382
383static void
384disconnect_cat_model_callbacks (gpointer tmodel, GObject * cat_model)
385{
386  g_signal_handlers_disconnect_by_func (tmodel, torrent_model_row_changed, cat_model);
387  g_signal_handlers_disconnect_by_func (tmodel, torrent_model_row_deleted_cb, cat_model);
388}
389
390static GtkWidget *
391tracker_combo_box_new (GtkTreeModel * tmodel)
392{
393  GtkWidget * c;
394  GtkCellRenderer * r;
395  GtkTreeModel * cat_model;
396  GtkCellLayout * c_cell_layout;
397  GtkComboBox * c_combo_box;
398
399  /* create the tracker combobox */
400  cat_model = tracker_filter_model_new (tmodel);
401  c = gtk_combo_box_new_with_model (cat_model);
402  c_combo_box = GTK_COMBO_BOX (c);
403  c_cell_layout = GTK_CELL_LAYOUT (c);
404  g_object_unref (cat_model);
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 void
546activity_filter_model_update (GtkListStore * store)
547{
548  GtkTreeIter iter;
549  GtkTreeModel * model = GTK_TREE_MODEL (store);
550  GObject * o = G_OBJECT (store);
551  GtkTreeModel * tmodel = GTK_TREE_MODEL (g_object_get_qdata (o, TORRENT_MODEL_KEY));
552
553  g_object_steal_qdata (o, DIRTY_KEY);
554
555  if (gtk_tree_model_iter_nth_child (model, &iter, NULL, 0)) do
556    {
557      int hits;
558      int type;
559      GtkTreeIter torrent_iter;
560
561      gtk_tree_model_get (model, &iter, ACTIVITY_FILTER_COL_TYPE, &type, -1);
562
563      hits = 0;
564      if (gtk_tree_model_iter_nth_child (tmodel, &torrent_iter, NULL, 0)) do
565        {
566          tr_torrent * tor;
567          gtk_tree_model_get (tmodel, &torrent_iter, MC_TORRENT, &tor, -1);
568          if (test_torrent_activity (tor, type))
569            ++hits;
570        }
571      while (gtk_tree_model_iter_next (tmodel, &torrent_iter));
572
573      status_model_update_count (store, &iter, hits);
574
575    }
576  while (gtk_tree_model_iter_next (model, &iter));
577}
578
579static GtkTreeModel *
580activity_filter_model_new (GtkTreeModel * tmodel)
581{
582  int i, n;
583  struct {
584    int type;
585    const char * context;
586    const char * name;
587    const char * stock_id;
588  } types[] = {
589    { ACTIVITY_FILTER_ALL, NULL, N_("All"), NULL },
590    { ACTIVITY_FILTER_SEPARATOR, NULL, NULL, NULL },
591    { ACTIVITY_FILTER_ACTIVE, NULL, N_("Active"), GTK_STOCK_EXECUTE },
592    { ACTIVITY_FILTER_DOWNLOADING, "Verb", NC_("Verb", "Downloading"), GTK_STOCK_GO_DOWN },
593    { ACTIVITY_FILTER_SEEDING, "Verb", NC_("Verb", "Seeding"), GTK_STOCK_GO_UP },
594    { ACTIVITY_FILTER_PAUSED, NULL, N_("Paused"), GTK_STOCK_MEDIA_PAUSE },
595    { ACTIVITY_FILTER_FINISHED, NULL, N_("Finished"), NULL },
596    { ACTIVITY_FILTER_VERIFYING, "Verb", NC_("Verb", "Verifying"), GTK_STOCK_REFRESH },
597    { ACTIVITY_FILTER_ERROR, NULL, N_("Error"), GTK_STOCK_DIALOG_ERROR }
598  };
599
600  GtkListStore * store = gtk_list_store_new (ACTIVITY_FILTER_N_COLS,
601                                             G_TYPE_STRING,
602                                             G_TYPE_INT,
603                                             G_TYPE_INT,
604                                             G_TYPE_STRING);
605  for (i=0, n=G_N_ELEMENTS (types); i<n; ++i)
606    {
607      const char * name = types[i].context ? g_dpgettext2 (NULL, types[i].context, types[i].name)
608                                           : _ (types[i].name);
609      gtk_list_store_insert_with_values (store, NULL, -1,
610                                         ACTIVITY_FILTER_COL_NAME, name,
611                                         ACTIVITY_FILTER_COL_TYPE, types[i].type,
612                                         ACTIVITY_FILTER_COL_STOCK_ID, types[i].stock_id,
613                                         -1);
614    }
615
616  g_object_set_qdata (G_OBJECT (store), TORRENT_MODEL_KEY, tmodel);
617  activity_filter_model_update (store);
618  return GTK_TREE_MODEL (store);
619}
620
621static void
622render_activity_pixbuf_func (GtkCellLayout    * cell_layout UNUSED,
623                             GtkCellRenderer  * cell_renderer,
624                             GtkTreeModel     * tree_model,
625                             GtkTreeIter      * iter,
626                             gpointer           data UNUSED)
627{
628  int type;
629  int width;
630  int ypad;
631
632  gtk_tree_model_get (tree_model, iter, ACTIVITY_FILTER_COL_TYPE, &type, -1);
633  width = type == ACTIVITY_FILTER_ALL ? 0 : 20;
634  ypad = type == ACTIVITY_FILTER_ALL ? 0 : 2;
635
636  g_object_set (cell_renderer, "width", width,
637                               "ypad", ypad,
638                               NULL);
639}
640
641static void
642activity_model_update_idle (gpointer activity_model)
643{
644  GObject * o = G_OBJECT (activity_model);
645  const gboolean pending = g_object_get_qdata (o, DIRTY_KEY) != NULL;
646  if (!pending)
647    {
648      GSourceFunc func = (GSourceFunc) activity_filter_model_update;
649      g_object_set_qdata (o, DIRTY_KEY, GINT_TO_POINTER (1));
650      gdk_threads_add_idle (func, activity_model);
651    }
652}
653
654static void
655activity_torrent_model_row_changed (GtkTreeModel  * tmodel UNUSED,
656                                    GtkTreePath   * path UNUSED,
657                                    GtkTreeIter   * iter UNUSED,
658                                    gpointer        activity_model)
659{
660  activity_model_update_idle (activity_model);
661}
662
663static void
664activity_torrent_model_row_deleted_cb (GtkTreeModel  * tmodel UNUSED,
665                                       GtkTreePath   * path UNUSED,
666                                       gpointer        activity_model)
667{
668  activity_model_update_idle (activity_model);
669}
670
671static void
672disconnect_activity_model_callbacks (gpointer tmodel, GObject * cat_model)
673{
674  g_signal_handlers_disconnect_by_func (tmodel, activity_torrent_model_row_changed, cat_model);
675  g_signal_handlers_disconnect_by_func (tmodel, activity_torrent_model_row_deleted_cb, cat_model);
676}
677
678static GtkWidget *
679activity_combo_box_new (GtkTreeModel * tmodel)
680{
681  GtkWidget * c;
682  GtkCellRenderer * r;
683  GtkTreeModel * activity_model;
684  GtkComboBox * c_combo_box;
685  GtkCellLayout * c_cell_layout;
686
687  activity_model = activity_filter_model_new (tmodel);
688  c = gtk_combo_box_new_with_model (activity_model);
689  c_combo_box = GTK_COMBO_BOX (c);
690  c_cell_layout = GTK_CELL_LAYOUT (c);
691  g_object_unref (activity_model);
692  gtk_combo_box_set_row_separator_func (c_combo_box,
693                                        activity_is_it_a_separator, NULL, NULL);
694  gtk_combo_box_set_active (c_combo_box, 0);
695
696  r = gtk_cell_renderer_pixbuf_new ();
697  gtk_cell_layout_pack_start (c_cell_layout, r, FALSE);
698  gtk_cell_layout_set_attributes (c_cell_layout, r,
699                                  "stock-id", ACTIVITY_FILTER_COL_STOCK_ID,
700                                  NULL);
701  gtk_cell_layout_set_cell_data_func (c_cell_layout, r,
702                                      render_activity_pixbuf_func, NULL, NULL);
703
704  r = gtk_cell_renderer_text_new ();
705  gtk_cell_layout_pack_start (c_cell_layout, r, TRUE);
706  gtk_cell_layout_set_attributes (c_cell_layout, r,
707                                  "text", ACTIVITY_FILTER_COL_NAME,
708                                  NULL);
709
710  r = number_renderer_new ();
711  gtk_cell_layout_pack_end (c_cell_layout, r, TRUE);
712  gtk_cell_layout_set_cell_data_func (c_cell_layout, r,
713                                      render_number_func, NULL, NULL);
714
715  g_object_weak_ref (G_OBJECT (activity_model), disconnect_activity_model_callbacks, tmodel);
716  g_signal_connect (tmodel, "row-changed", G_CALLBACK (activity_torrent_model_row_changed), activity_model);
717  g_signal_connect (tmodel, "row-inserted", G_CALLBACK (activity_torrent_model_row_changed), activity_model);
718  g_signal_connect (tmodel, "row-deleted", G_CALLBACK (activity_torrent_model_row_deleted_cb), activity_model);
719
720  return c;
721}
722
723/****
724*****
725*****  ENTRY FIELD
726*****
727****/
728
729static gboolean
730testText (const tr_torrent * tor, const char * key)
731{
732  gboolean ret = FALSE;
733
734  if (!key || !*key)
735    {
736      ret = TRUE;
737    }
738  else
739    {
740      tr_file_index_t i;
741      const tr_info * inf = tr_torrentInfo (tor);
742
743      /* test the torrent name... */
744      {
745        char * pch = g_utf8_casefold (tr_torrentName (tor), -1);
746        ret = !key || strstr (pch, key) != NULL;
747        g_free (pch);
748      }
749
750      /* test the files... */
751      for (i=0; i<inf->fileCount && !ret; ++i)
752        {
753          char * pch = g_utf8_casefold (inf->files[i].name, -1);
754          ret = !key || strstr (pch, key) != NULL;
755          g_free (pch);
756        }
757    }
758
759  return ret;
760}
761
762static void
763entry_clear (GtkEntry * e)
764{
765  gtk_entry_set_text (e, "");
766}
767
768static void
769filter_entry_changed (GtkEditable * e, gpointer filter_model)
770{
771  char * pch;
772  char * folded;
773
774  pch = gtk_editable_get_chars (e, 0, -1);
775  folded = g_utf8_casefold (pch, -1);
776  g_strstrip (folded);
777  g_object_set_qdata_full (filter_model, TEXT_KEY, folded, g_free);
778  g_free (pch);
779
780  gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
781}
782
783/*****
784******
785******
786******
787*****/
788
789struct filter_data
790{
791  GtkWidget * activity;
792  GtkWidget * tracker;
793  GtkWidget * entry;
794  GtkWidget * show_lb;
795  GtkTreeModel * filter_model;
796  int active_activity_type;
797  int active_tracker_type;
798  char * active_tracker_host;
799};
800
801static gboolean
802is_row_visible (GtkTreeModel * model, GtkTreeIter * iter, gpointer vdata)
803{
804  const char * text;
805  tr_torrent * tor;
806  struct filter_data * data = vdata;
807  GObject * o = G_OBJECT (data->filter_model);
808
809  gtk_tree_model_get (model, iter, MC_TORRENT, &tor, -1);
810
811  text = (const char*) g_object_get_qdata (o, TEXT_KEY);
812
813  return (tor != NULL) && test_tracker (tor, data->active_tracker_type, data->active_tracker_host)
814                       && test_torrent_activity (tor, data->active_activity_type)
815                       && testText (tor, text);
816}
817
818static void
819selection_changed_cb (GtkComboBox * combo, gpointer vdata)
820{
821  int type;
822  char * host;
823  GtkTreeIter iter;
824  GtkTreeModel * model;
825  struct filter_data * data = vdata;
826
827  /* set data->active_activity_type from the activity combobox */
828  combo = GTK_COMBO_BOX (data->activity);
829  model = gtk_combo_box_get_model (combo);
830  if (gtk_combo_box_get_active_iter (combo, &iter))
831    gtk_tree_model_get (model, &iter, ACTIVITY_FILTER_COL_TYPE, &type, -1);
832  else
833    type = ACTIVITY_FILTER_ALL;
834  data->active_activity_type = type;
835
836  /* set the active tracker type & host from the tracker combobox */
837  combo = GTK_COMBO_BOX (data->tracker);
838  model = gtk_combo_box_get_model (combo);
839  if (gtk_combo_box_get_active_iter (combo, &iter))
840    {
841      gtk_tree_model_get (model, &iter, TRACKER_FILTER_COL_TYPE, &type,
842                                        TRACKER_FILTER_COL_HOST, &host,
843                                        -1);
844    }
845  else
846    {
847      type = TRACKER_FILTER_TYPE_ALL;
848      host = NULL;
849    }
850  g_free (data->active_tracker_host);
851  data->active_tracker_host = host;
852  data->active_tracker_type = type;
853
854  /* refilter */
855  gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (data->filter_model));
856}
857
858/***
859****
860***/
861
862static void
863update_count_label (struct filter_data * data)
864{
865  char buf[512];
866  GtkTreeModel * tmodel = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (data->filter_model));
867  const int torrentCount = gtk_tree_model_iter_n_children (tmodel, NULL);
868  const int visibleCount = gtk_tree_model_iter_n_children (data->filter_model, NULL);
869
870  /* set the text */
871  if (visibleCount == torrentCount)
872    g_snprintf (buf, sizeof(buf), _("_Show:"));
873  else
874    g_snprintf (buf, sizeof(buf), _("_Show %'d:"), visibleCount);
875  gtk_label_set_markup_with_mnemonic (GTK_LABEL (data->show_lb), buf);
876}
877
878static void
879on_filter_model_row_inserted (GtkTreeModel * tree_model UNUSED,
880                              GtkTreePath  * path       UNUSED,
881                              GtkTreeIter  * iter       UNUSED,
882                              gpointer       data)
883{
884  update_count_label (data);
885}
886
887static void
888on_filter_model_row_deleted (GtkTreeModel * tree_model UNUSED,
889                             GtkTreePath  * path       UNUSED,
890                             gpointer       data       UNUSED)
891{
892  update_count_label (data);
893}
894
895/***
896****
897***/
898
899GtkWidget *
900gtr_filter_bar_new (tr_session * session, GtkTreeModel * tmodel, GtkTreeModel ** filter_model)
901{
902  GtkWidget * l;
903  GtkWidget * w;
904  GtkWidget * h;
905  GtkWidget * s;
906  GtkWidget * activity;
907  GtkWidget * tracker;
908  GtkBox * h_box;
909  struct filter_data * data;
910
911  g_assert (DIRTY_KEY == 0);
912  TEXT_KEY = g_quark_from_static_string ("tr-filter-text-key");
913  DIRTY_KEY = g_quark_from_static_string ("tr-filter-dirty-key");
914  SESSION_KEY = g_quark_from_static_string ("tr-session-key");
915  TORRENT_MODEL_KEY = g_quark_from_static_string ("tr-filter-torrent-model-key");
916
917  data = g_new0 (struct filter_data, 1);
918  data->show_lb = gtk_label_new (NULL);
919  data->activity = activity = activity_combo_box_new (tmodel);
920  data->tracker = tracker = tracker_combo_box_new (tmodel);
921  data->filter_model = gtk_tree_model_filter_new (tmodel, NULL);
922  g_signal_connect (data->filter_model, "row-deleted", G_CALLBACK(on_filter_model_row_deleted), data);
923  g_signal_connect (data->filter_model, "row-inserted", G_CALLBACK(on_filter_model_row_inserted), data);
924
925  g_object_set (G_OBJECT (data->tracker), "width-request", 170, NULL);
926  g_object_set_qdata (G_OBJECT (gtk_combo_box_get_model (GTK_COMBO_BOX (data->tracker))), SESSION_KEY, session);
927
928  gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (data->filter_model),
929                                          is_row_visible, data, g_free);
930
931  g_signal_connect (data->tracker, "changed", G_CALLBACK (selection_changed_cb), data);
932  g_signal_connect (data->activity, "changed", G_CALLBACK (selection_changed_cb), data);
933
934
935  h = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, GUI_PAD_SMALL);
936  h_box = GTK_BOX (h);
937
938  /* add the activity combobox */
939  w = activity;
940  l = data->show_lb;
941  gtk_label_set_mnemonic_widget (GTK_LABEL (l), w);
942  gtk_box_pack_start (h_box, l, FALSE, FALSE, 0);
943  gtk_box_pack_start (h_box, w, TRUE, TRUE, 0);
944
945  /* add a spacer */
946  w = gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f);
947  gtk_widget_set_size_request (w, 0u, GUI_PAD_BIG);
948  gtk_box_pack_start (h_box, w, FALSE, FALSE, 0);
949
950  /* add the tracker combobox */
951  w = tracker;
952  gtk_box_pack_start (h_box, w, TRUE, TRUE, 0);
953
954  /* add a spacer */
955  w = gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f);
956  gtk_widget_set_size_request (w, 0u, GUI_PAD_BIG);
957  gtk_box_pack_start (h_box, w, FALSE, FALSE, 0);
958
959  /* add the entry field */
960  s = gtk_entry_new ();
961  gtk_entry_set_icon_from_stock (GTK_ENTRY (s), GTK_ENTRY_ICON_SECONDARY, GTK_STOCK_CLEAR);
962  g_signal_connect (s, "icon-release", G_CALLBACK (entry_clear), NULL);
963  gtk_box_pack_start (h_box, s, TRUE, TRUE, 0);
964
965  g_signal_connect (s, "changed", G_CALLBACK (filter_entry_changed), data->filter_model);
966  selection_changed_cb (NULL, data);
967
968  *filter_model = data->filter_model;
969  update_count_label (data);
970  return h;
971}
Note: See TracBrowser for help on using the repository browser.