/* * This file Copyright (C) Mnemosyne LLC * * This file is licensed by the GPL version 2. Works owned by the * Transmission project are granted a special exemption to clause 2 (b) * so that the bulk of its code can remain under the MIT license. * This exemption does not extend to derived works not owned by * the Transmission project. * * $Id$ */ #include /* qsort () */ #include #include #include #include #include "favicon.h" /* gtr_get_favicon () */ #include "filter.h" #include "hig.h" /* GUI_PAD */ #include "tr-core.h" /* MC_TORRENT */ #include "util.h" /* gtr_get_host_from_url () */ static GQuark DIRTY_KEY = 0; static GQuark SESSION_KEY = 0; static GQuark TEXT_KEY = 0; static GQuark TORRENT_MODEL_KEY = 0; /*** **** **** CATEGORIES **** ***/ enum { CAT_FILTER_TYPE_ALL, CAT_FILTER_TYPE_PRIVATE, CAT_FILTER_TYPE_PUBLIC, CAT_FILTER_TYPE_HOST, CAT_FILTER_TYPE_PARENT, CAT_FILTER_TYPE_PRI_HIGH, CAT_FILTER_TYPE_PRI_NORMAL, CAT_FILTER_TYPE_PRI_LOW, CAT_FILTER_TYPE_TAG, CAT_FILTER_TYPE_SEPARATOR, }; enum { CAT_FILTER_COL_NAME, /* human-readable name; ie, Legaltorrents */ CAT_FILTER_COL_COUNT, /* how many matches there are */ CAT_FILTER_COL_TYPE, CAT_FILTER_COL_HOST, /* pattern-matching text; ie, legaltorrents.com */ CAT_FILTER_COL_PIXBUF, CAT_FILTER_N_COLS }; static int pstrcmp (const void * a, const void * b) { return strcmp (* (const char**)a, * (const char**)b); } /* human-readable name; ie, Legaltorrents */ static char* get_name_from_host (const char * host) { char * name; const char * dot = strrchr (host, '.'); if (tr_addressIsIP (host)) name = g_strdup (host); else if (dot) name = g_strndup (host, dot - host); else name = g_strdup (host); *name = g_ascii_toupper (*name); return name; } static void category_model_update_count (GtkTreeStore * store, GtkTreeIter * iter, int n) { int count; GtkTreeModel * model = GTK_TREE_MODEL (store); gtk_tree_model_get (model, iter, CAT_FILTER_COL_COUNT, &count, -1); if (n != count) gtk_tree_store_set (store, iter, CAT_FILTER_COL_COUNT, n, -1); } static void favicon_ready_cb (gpointer pixbuf, gpointer vreference) { GtkTreeIter iter; GtkTreeRowReference * reference = vreference; if (pixbuf != NULL) { GtkTreePath * path = gtk_tree_row_reference_get_path (reference); GtkTreeModel * model = gtk_tree_row_reference_get_model (reference); if (gtk_tree_model_get_iter (model, &iter, path)) gtk_tree_store_set (GTK_TREE_STORE (model), &iter, CAT_FILTER_COL_PIXBUF, pixbuf, -1); gtk_tree_path_free (path); g_object_unref (pixbuf); } gtk_tree_row_reference_free (reference); } static gboolean category_filter_model_update (GtkTreeStore * store) { int i, n; int low = 0; int all = 0; int high = 0; int public = 0; int normal = 0; int private = 0; int store_pos; GtkTreeIter top; GtkTreeIter iter; GtkTreeModel * model = GTK_TREE_MODEL (store); GPtrArray * hosts = g_ptr_array_new (); GStringChunk * strings = g_string_chunk_new (4096); GHashTable * hosts_hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free); GObject * o = G_OBJECT (store); GtkTreeModel * tmodel = GTK_TREE_MODEL (g_object_get_qdata (o, TORRENT_MODEL_KEY)); g_object_steal_qdata (o, DIRTY_KEY); /* Walk through all the torrents, tallying how many matches there are * for the various categories. Also make a sorted list of all tracker * hosts s.t. we can merge it with the existing list */ if (gtk_tree_model_iter_nth_child (tmodel, &iter, NULL, 0)) do { tr_torrent * tor; const tr_info * inf; int keyCount; char ** keys; gtk_tree_model_get (tmodel, &iter, MC_TORRENT, &tor, -1); inf = tr_torrentInfo (tor); keyCount = 0; keys = g_new (char*, inf->trackerCount); for (i=0, n=inf->trackerCount; itrackers[i].announce); key = g_string_chunk_insert_const (strings, buf); count = g_hash_table_lookup (hosts_hash, key); if (count == NULL) { count = tr_new0 (int, 1); g_hash_table_insert (hosts_hash, key, count); g_ptr_array_add (hosts, key); } for (k=0; kisPrivate) ++private; else ++public; switch (tr_torrentGetPriority (tor)) { case TR_PRI_HIGH: ++high; break; case TR_PRI_LOW: ++low; break; default: ++normal; break; } } while (gtk_tree_model_iter_next (tmodel, &iter)); qsort (hosts->pdata, hosts->len, sizeof (char*), pstrcmp); /* update the "all" count */ gtk_tree_model_iter_children (model, &top, NULL); category_model_update_count (store, &top, all); /* skip separator */ gtk_tree_model_iter_next (model, &top); /* update the "hosts" subtree */ gtk_tree_model_iter_next (model, &top); for (i=store_pos=0, n=hosts->len ; ;) { const gboolean new_hosts_done = i >= n; const gboolean old_hosts_done = !gtk_tree_model_iter_nth_child (model, &iter, &top, store_pos); gboolean remove_row = FALSE; gboolean insert_row = FALSE; /* are we done yet? */ if (new_hosts_done && old_hosts_done) break; /* decide what to do */ if (new_hosts_done) remove_row = TRUE; else if (old_hosts_done) insert_row = TRUE; else { int cmp; char * host; gtk_tree_model_get (model, &iter, CAT_FILTER_COL_HOST, &host, -1); cmp = strcmp (host, hosts->pdata[i]); if (cmp < 0) remove_row = TRUE; else if (cmp > 0) insert_row = TRUE; g_free (host); } /* do something */ if (remove_row) { /* g_message ("removing row and incrementing i"); */ gtk_tree_store_remove (store, &iter); } else if (insert_row) { GtkTreeIter add; GtkTreePath * path; GtkTreeRowReference * reference; tr_session * session = g_object_get_qdata (G_OBJECT (store), SESSION_KEY); const char * host = hosts->pdata[i]; char * name = get_name_from_host (host); const int count = * (int*)g_hash_table_lookup (hosts_hash, host); gtk_tree_store_insert_with_values (store, &add, &top, store_pos, CAT_FILTER_COL_HOST, host, CAT_FILTER_COL_NAME, name, CAT_FILTER_COL_COUNT, count, CAT_FILTER_COL_TYPE, CAT_FILTER_TYPE_HOST, -1); path = gtk_tree_model_get_path (model, &add); reference = gtk_tree_row_reference_new (model, path); gtr_get_favicon (session, host, favicon_ready_cb, reference); gtk_tree_path_free (path); g_free (name); ++store_pos; ++i; } else { /* update row */ const char * host = hosts->pdata[i]; const int count = * (int*)g_hash_table_lookup (hosts_hash, host); category_model_update_count (store, &iter, count); ++store_pos; ++i; } } /* update the "public" subtree */ gtk_tree_model_iter_next (model, &top); gtk_tree_model_iter_children (model, &iter, &top); category_model_update_count (store, &iter, public); gtk_tree_model_iter_next (model, &iter); category_model_update_count (store, &iter, private); /* update the "priority" subtree */ gtk_tree_model_iter_next (model, &top); gtk_tree_model_iter_children (model, &iter, &top); category_model_update_count (store, &iter, high); gtk_tree_model_iter_next (model, &iter); category_model_update_count (store, &iter, normal); gtk_tree_model_iter_next (model, &iter); category_model_update_count (store, &iter, low); /* cleanup */ g_ptr_array_free (hosts, TRUE); g_hash_table_unref (hosts_hash); g_string_chunk_free (strings); return FALSE; } static GtkTreeModel * category_filter_model_new (GtkTreeModel * tmodel) { GtkTreeIter iter; const int invisible_number = -1; /* doesn't get rendered */ GtkTreeStore * store = gtk_tree_store_new (CAT_FILTER_N_COLS, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING, GDK_TYPE_PIXBUF); gtk_tree_store_insert_with_values (store, NULL, NULL, -1, CAT_FILTER_COL_NAME, _("All"), CAT_FILTER_COL_TYPE, CAT_FILTER_TYPE_ALL, -1); gtk_tree_store_insert_with_values (store, NULL, NULL, -1, CAT_FILTER_COL_TYPE, CAT_FILTER_TYPE_SEPARATOR, -1); gtk_tree_store_insert_with_values (store, &iter, NULL, -1, CAT_FILTER_COL_NAME, _("Trackers"), CAT_FILTER_COL_COUNT, invisible_number, CAT_FILTER_COL_TYPE, CAT_FILTER_TYPE_PARENT, -1); gtk_tree_store_insert_with_values (store, &iter, NULL, -1, CAT_FILTER_COL_NAME, _("Privacy"), CAT_FILTER_COL_COUNT, invisible_number, CAT_FILTER_COL_TYPE, CAT_FILTER_TYPE_PARENT, -1); gtk_tree_store_insert_with_values (store, NULL, &iter, -1, CAT_FILTER_COL_NAME, _("Public"), CAT_FILTER_COL_TYPE, CAT_FILTER_TYPE_PUBLIC, -1); gtk_tree_store_insert_with_values (store, NULL, &iter, -1, CAT_FILTER_COL_NAME, _("Private"), CAT_FILTER_COL_TYPE, CAT_FILTER_TYPE_PRIVATE, -1); gtk_tree_store_insert_with_values (store, &iter, NULL, -1, CAT_FILTER_COL_NAME, _("Priority"), CAT_FILTER_COL_COUNT, invisible_number, CAT_FILTER_COL_TYPE, CAT_FILTER_TYPE_PARENT, -1); gtk_tree_store_insert_with_values (store, NULL, &iter, -1, CAT_FILTER_COL_NAME, _("High"), CAT_FILTER_COL_TYPE, CAT_FILTER_TYPE_PRI_HIGH, -1); gtk_tree_store_insert_with_values (store, NULL, &iter, -1, CAT_FILTER_COL_NAME, _("Normal"), CAT_FILTER_COL_TYPE, CAT_FILTER_TYPE_PRI_NORMAL, -1); gtk_tree_store_insert_with_values (store, NULL, &iter, -1, CAT_FILTER_COL_NAME, _("Low"), CAT_FILTER_COL_TYPE, CAT_FILTER_TYPE_PRI_LOW, -1); g_object_set_qdata (G_OBJECT (store), TORRENT_MODEL_KEY, tmodel); category_filter_model_update (store); return GTK_TREE_MODEL (store); } static gboolean is_it_a_separator (GtkTreeModel * m, GtkTreeIter * iter, gpointer data UNUSED) { int type; gtk_tree_model_get (m, iter, CAT_FILTER_COL_TYPE, &type, -1); return type == CAT_FILTER_TYPE_SEPARATOR; } static void category_model_update_idle (gpointer category_model) { GObject * o = G_OBJECT (category_model); const gboolean pending = g_object_get_qdata (o, DIRTY_KEY) != NULL; if (!pending) { GSourceFunc func = (GSourceFunc) category_filter_model_update; g_object_set_qdata (o, DIRTY_KEY, GINT_TO_POINTER (1)); gdk_threads_add_idle (func, category_model); } } static void torrent_model_row_changed (GtkTreeModel * tmodel UNUSED, GtkTreePath * path UNUSED, GtkTreeIter * iter UNUSED, gpointer category_model) { category_model_update_idle (category_model); } static void torrent_model_row_deleted_cb (GtkTreeModel * tmodel UNUSED, GtkTreePath * path UNUSED, gpointer category_model) { category_model_update_idle (category_model); } static void render_pixbuf_func (GtkCellLayout * cell_layout UNUSED, GtkCellRenderer * cell_renderer, GtkTreeModel * tree_model, GtkTreeIter * iter, gpointer data UNUSED) { int type; int width = 0; const gboolean leaf = !gtk_tree_model_iter_has_child (tree_model, iter); gtk_tree_model_get (tree_model, iter, CAT_FILTER_COL_TYPE, &type, -1); if (type == CAT_FILTER_TYPE_HOST) width = 20; g_object_set (cell_renderer, "width", width, "sensitive", leaf, NULL); } static void is_capital_sensitive (GtkCellLayout * cell_layout UNUSED, GtkCellRenderer * cell_renderer, GtkTreeModel * tree_model, GtkTreeIter * iter, gpointer data UNUSED) { const gboolean leaf = !gtk_tree_model_iter_has_child (tree_model, iter); g_object_set (cell_renderer, "sensitive", leaf, NULL); } static void render_number_func (GtkCellLayout * cell_layout UNUSED, GtkCellRenderer * cell_renderer, GtkTreeModel * tree_model, GtkTreeIter * iter, gpointer data UNUSED) { int count; char buf[32]; const gboolean leaf = !gtk_tree_model_iter_has_child (tree_model, iter); gtk_tree_model_get (tree_model, iter, CAT_FILTER_COL_COUNT, &count, -1); if (count >= 0) g_snprintf (buf, sizeof (buf), "%'d", count); else *buf = '\0'; g_object_set (cell_renderer, "text", buf, "sensitive", leaf, NULL); } static GtkCellRenderer * number_renderer_new (void) { GtkCellRenderer * r = gtk_cell_renderer_text_new (); g_object_set (G_OBJECT (r), "alignment", PANGO_ALIGN_RIGHT, "weight", PANGO_WEIGHT_ULTRALIGHT, "xalign", 1.0, "xpad", GUI_PAD, NULL); return r; } static void disconnect_cat_model_callbacks (gpointer tmodel, GObject * cat_model) { g_signal_handlers_disconnect_by_func (tmodel, torrent_model_row_changed, cat_model); g_signal_handlers_disconnect_by_func (tmodel, torrent_model_row_deleted_cb, cat_model); } static GtkWidget * category_combo_box_new (GtkTreeModel * tmodel) { GtkWidget * c; GtkCellRenderer * r; GtkTreeModel * cat_model; GtkCellLayout * c_cell_layout; GtkComboBox * c_combo_box; /* create the category combobox */ cat_model = category_filter_model_new (tmodel); c = gtk_combo_box_new_with_model (cat_model); c_combo_box = GTK_COMBO_BOX (c); c_cell_layout = GTK_CELL_LAYOUT (c); g_object_unref (cat_model); gtk_combo_box_set_row_separator_func (c_combo_box, is_it_a_separator, NULL, NULL); gtk_combo_box_set_active (c_combo_box, 0); r = gtk_cell_renderer_pixbuf_new (); gtk_cell_layout_pack_start (c_cell_layout, r, FALSE); gtk_cell_layout_set_cell_data_func (c_cell_layout, r, render_pixbuf_func, NULL, NULL); gtk_cell_layout_set_attributes (c_cell_layout, r, "pixbuf", CAT_FILTER_COL_PIXBUF, NULL); r = gtk_cell_renderer_text_new (); gtk_cell_layout_pack_start (c_cell_layout, r, FALSE); gtk_cell_layout_set_attributes (c_cell_layout, r, "text", CAT_FILTER_COL_NAME, NULL); gtk_cell_layout_set_cell_data_func (c_cell_layout, r, is_capital_sensitive, NULL, NULL); r = number_renderer_new (); gtk_cell_layout_pack_end (c_cell_layout, r, TRUE); gtk_cell_layout_set_cell_data_func (c_cell_layout, r, render_number_func, NULL, NULL); g_object_weak_ref (G_OBJECT (cat_model), disconnect_cat_model_callbacks, tmodel); g_signal_connect (tmodel, "row-changed", G_CALLBACK (torrent_model_row_changed), cat_model); g_signal_connect (tmodel, "row-inserted", G_CALLBACK (torrent_model_row_changed), cat_model); g_signal_connect (tmodel, "row-deleted", G_CALLBACK (torrent_model_row_deleted_cb), cat_model); return c; } static gboolean test_category (tr_torrent * tor, int active_category_type, const char * host) { const tr_info * const inf = tr_torrentInfo (tor); switch (active_category_type) { case CAT_FILTER_TYPE_ALL: return TRUE; case CAT_FILTER_TYPE_PRIVATE: return inf->isPrivate; case CAT_FILTER_TYPE_PUBLIC: return !inf->isPrivate; case CAT_FILTER_TYPE_PRI_HIGH: return tr_torrentGetPriority (tor) == TR_PRI_HIGH; case CAT_FILTER_TYPE_PRI_NORMAL: return tr_torrentGetPriority (tor) == TR_PRI_NORMAL; case CAT_FILTER_TYPE_PRI_LOW: return tr_torrentGetPriority (tor) == TR_PRI_LOW; case CAT_FILTER_TYPE_HOST: { int i; char tmp[1024]; for (i=0; itrackerCount; ++i) { gtr_get_host_from_url (tmp, sizeof (tmp), inf->trackers[i].announce); if (!strcmp (tmp, host)) break; } return i < inf->trackerCount; } case CAT_FILTER_TYPE_TAG: /* FIXME */ return TRUE; default: return TRUE; } } /*** **** **** ACTIVITY **** ***/ enum { ACTIVITY_FILTER_ALL, ACTIVITY_FILTER_DOWNLOADING, ACTIVITY_FILTER_SEEDING, ACTIVITY_FILTER_ACTIVE, ACTIVITY_FILTER_PAUSED, ACTIVITY_FILTER_FINISHED, ACTIVITY_FILTER_VERIFYING, ACTIVITY_FILTER_ERROR, ACTIVITY_FILTER_SEPARATOR }; enum { ACTIVITY_FILTER_COL_NAME, ACTIVITY_FILTER_COL_COUNT, ACTIVITY_FILTER_COL_TYPE, ACTIVITY_FILTER_COL_STOCK_ID, ACTIVITY_FILTER_N_COLS }; static gboolean activity_is_it_a_separator (GtkTreeModel * m, GtkTreeIter * i, gpointer d UNUSED) { int type; gtk_tree_model_get (m, i, ACTIVITY_FILTER_COL_TYPE, &type, -1); return type == ACTIVITY_FILTER_SEPARATOR; } static gboolean test_torrent_activity (tr_torrent * tor, int type) { const tr_stat * st = tr_torrentStatCached (tor); switch (type) { case ACTIVITY_FILTER_DOWNLOADING: return (st->activity == TR_STATUS_DOWNLOAD) || (st->activity == TR_STATUS_DOWNLOAD_WAIT); case ACTIVITY_FILTER_SEEDING: return (st->activity == TR_STATUS_SEED) || (st->activity == TR_STATUS_SEED_WAIT); case ACTIVITY_FILTER_ACTIVE: return (st->peersSendingToUs > 0) || (st->peersGettingFromUs > 0) || (st->webseedsSendingToUs > 0) || (st->activity == TR_STATUS_CHECK); case ACTIVITY_FILTER_PAUSED: return st->activity == TR_STATUS_STOPPED; case ACTIVITY_FILTER_FINISHED: return st->finished == TRUE; case ACTIVITY_FILTER_VERIFYING: return (st->activity == TR_STATUS_CHECK) || (st->activity == TR_STATUS_CHECK_WAIT); case ACTIVITY_FILTER_ERROR: return st->error != 0; default: /* ACTIVITY_FILTER_ALL */ return TRUE; } } static void status_model_update_count (GtkListStore * store, GtkTreeIter * iter, int n) { int count; GtkTreeModel * model = GTK_TREE_MODEL (store); gtk_tree_model_get (model, iter, ACTIVITY_FILTER_COL_COUNT, &count, -1); if (n != count) gtk_list_store_set (store, iter, ACTIVITY_FILTER_COL_COUNT, n, -1); } static void activity_filter_model_update (GtkListStore * store) { GtkTreeIter iter; GtkTreeModel * model = GTK_TREE_MODEL (store); GObject * o = G_OBJECT (store); GtkTreeModel * tmodel = GTK_TREE_MODEL (g_object_get_qdata (o, TORRENT_MODEL_KEY)); g_object_steal_qdata (o, DIRTY_KEY); if (gtk_tree_model_iter_nth_child (model, &iter, NULL, 0)) do { int hits; int type; GtkTreeIter torrent_iter; gtk_tree_model_get (model, &iter, ACTIVITY_FILTER_COL_TYPE, &type, -1); hits = 0; if (gtk_tree_model_iter_nth_child (tmodel, &torrent_iter, NULL, 0)) do { tr_torrent * tor; gtk_tree_model_get (tmodel, &torrent_iter, MC_TORRENT, &tor, -1); if (test_torrent_activity (tor, type)) ++hits; } while (gtk_tree_model_iter_next (tmodel, &torrent_iter)); status_model_update_count (store, &iter, hits); } while (gtk_tree_model_iter_next (model, &iter)); } static GtkTreeModel * activity_filter_model_new (GtkTreeModel * tmodel) { int i, n; struct { int type; const char * context; const char * name; const char * stock_id; } types[] = { { ACTIVITY_FILTER_ALL, NULL, N_("All"), NULL }, { ACTIVITY_FILTER_SEPARATOR, NULL, NULL, NULL }, { ACTIVITY_FILTER_ACTIVE, NULL, N_("Active"), GTK_STOCK_EXECUTE }, { ACTIVITY_FILTER_DOWNLOADING, "Verb", NC_("Verb", "Downloading"), GTK_STOCK_GO_DOWN }, { ACTIVITY_FILTER_SEEDING, "Verb", NC_("Verb", "Seeding"), GTK_STOCK_GO_UP }, { ACTIVITY_FILTER_PAUSED, NULL, N_("Paused"), GTK_STOCK_MEDIA_PAUSE }, { ACTIVITY_FILTER_FINISHED, NULL, N_("Finished"), NULL }, { ACTIVITY_FILTER_VERIFYING, "Verb", NC_("Verb", "Verifying"), GTK_STOCK_REFRESH }, { ACTIVITY_FILTER_ERROR, NULL, N_("Error"), GTK_STOCK_DIALOG_ERROR } }; GtkListStore * store = gtk_list_store_new (ACTIVITY_FILTER_N_COLS, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING); for (i=0, n=G_N_ELEMENTS (types); ifileCount && !ret; ++i) { char * pch = g_utf8_casefold (inf->files[i].name, -1); ret = !key || strstr (pch, key) != NULL; g_free (pch); } } return ret; } static void entry_clear (GtkEntry * e) { gtk_entry_set_text (e, ""); } static void filter_entry_changed (GtkEditable * e, gpointer filter_model) { char * pch; char * folded; pch = gtk_editable_get_chars (e, 0, -1); folded = g_utf8_casefold (pch, -1); g_strstrip (folded); g_object_set_qdata_full (filter_model, TEXT_KEY, folded, g_free); g_free (pch); gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model)); } /***** ****** ****** ****** *****/ struct filter_data { GtkWidget * activity; GtkWidget * category; GtkWidget * entry; GtkTreeModel * filter_model; int active_activity_type; int active_category_type; char * active_category_host; }; static gboolean is_row_visible (GtkTreeModel * model, GtkTreeIter * iter, gpointer vdata) { const char * text; tr_torrent * tor; struct filter_data * data = vdata; GObject * o = G_OBJECT (data->filter_model); gtk_tree_model_get (model, iter, MC_TORRENT, &tor, -1); text = (const char*) g_object_get_qdata (o, TEXT_KEY); return (tor != NULL) && test_category (tor, data->active_category_type, data->active_category_host) && test_torrent_activity (tor, data->active_activity_type) && testText (tor, text); } static void selection_changed_cb (GtkComboBox * combo, gpointer vdata) { int type; char * host; GtkTreeIter iter; GtkTreeModel * model; struct filter_data * data = vdata; /* set data->active_activity_type from the activity combobox */ combo = GTK_COMBO_BOX (data->activity); model = gtk_combo_box_get_model (combo); if (gtk_combo_box_get_active_iter (combo, &iter)) gtk_tree_model_get (model, &iter, ACTIVITY_FILTER_COL_TYPE, &type, -1); else type = ACTIVITY_FILTER_ALL; data->active_activity_type = type; /* set the active category type & host from the category combobox */ combo = GTK_COMBO_BOX (data->category); model = gtk_combo_box_get_model (combo); if (gtk_combo_box_get_active_iter (combo, &iter)) { gtk_tree_model_get (model, &iter, CAT_FILTER_COL_TYPE, &type, CAT_FILTER_COL_HOST, &host, -1); } else { type = CAT_FILTER_TYPE_ALL; host = NULL; } g_free (data->active_category_host); data->active_category_host = host; data->active_category_type = type; /* refilter */ gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (data->filter_model)); } GtkWidget * gtr_filter_bar_new (tr_session * session, GtkTreeModel * tmodel, GtkTreeModel ** filter_model) { GtkWidget * l; GtkWidget * w; GtkWidget * h; GtkWidget * s; GtkWidget * activity; GtkWidget * category; GtkBox * h_box; const char * str; struct filter_data * data; g_assert (DIRTY_KEY == 0); TEXT_KEY = g_quark_from_static_string ("tr-filter-text-key"); DIRTY_KEY = g_quark_from_static_string ("tr-filter-dirty-key"); SESSION_KEY = g_quark_from_static_string ("tr-session-key"); TORRENT_MODEL_KEY = g_quark_from_static_string ("tr-filter-torrent-model-key"); data = g_new0 (struct filter_data, 1); data->activity = activity = activity_combo_box_new (tmodel); data->category = category = category_combo_box_new (tmodel); data->filter_model = gtk_tree_model_filter_new (tmodel, NULL); g_object_set (G_OBJECT (data->category), "width-request", 170, NULL); g_object_set_qdata (G_OBJECT (gtk_combo_box_get_model (GTK_COMBO_BOX (data->category))), SESSION_KEY, session); gtk_tree_model_filter_set_visible_func ( GTK_TREE_MODEL_FILTER (data->filter_model), is_row_visible, data, g_free); g_signal_connect (data->category, "changed", G_CALLBACK (selection_changed_cb), data); g_signal_connect (data->activity, "changed", G_CALLBACK (selection_changed_cb), data); h = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, GUI_PAD_SMALL); h_box = GTK_BOX (h); /* add the activity combobox */ str = _("_Show:"); w = activity; l = gtk_label_new (NULL); gtk_label_set_markup_with_mnemonic (GTK_LABEL (l), str); gtk_label_set_mnemonic_widget (GTK_LABEL (l), w); gtk_box_pack_start (h_box, l, FALSE, FALSE, 0); gtk_box_pack_start (h_box, w, TRUE, TRUE, 0); /* add a spacer */ w = gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f); gtk_widget_set_size_request (w, 0u, GUI_PAD_BIG); gtk_box_pack_start (h_box, w, FALSE, FALSE, 0); /* add the category combobox */ w = category; gtk_box_pack_start (h_box, w, TRUE, TRUE, 0); /* add a spacer */ w = gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f); gtk_widget_set_size_request (w, 0u, GUI_PAD_BIG); gtk_box_pack_start (h_box, w, FALSE, FALSE, 0); /* add the entry field */ s = gtk_entry_new (); gtk_entry_set_icon_from_stock (GTK_ENTRY (s), GTK_ENTRY_ICON_SECONDARY, GTK_STOCK_CLEAR); g_signal_connect (s, "icon-release", G_CALLBACK (entry_clear), NULL); gtk_box_pack_start (h_box, s, TRUE, TRUE, 0); g_signal_connect (s, "changed", G_CALLBACK (filter_entry_changed), data->filter_model); selection_changed_cb (NULL, data); *filter_model = data->filter_model; return h; }