From 824182df54dcf3859dedf47afd41c46745769fc8 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 15 Mar 2010 02:53:31 +0000 Subject: [PATCH] (trunk gtk) add favicon support to the new filterbar --- gtk/Makefile.am | 2 + gtk/favicon.c | 151 ++++++++++++++++++++++++++++++++++++++++++++++++ gtk/favicon.h | 24 ++++++++ gtk/filter.c | 60 ++++++++++++++++++- gtk/filter.h | 5 +- gtk/tr-window.c | 31 +--------- 6 files changed, 241 insertions(+), 32 deletions(-) create mode 100644 gtk/favicon.c create mode 100644 gtk/favicon.h diff --git a/gtk/Makefile.am b/gtk/Makefile.am index 25b8a5c2b..c93b9f4c3 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -42,6 +42,7 @@ noinst_HEADERS = \ conf.h \ details.h \ dialogs.h \ + favicon.h \ filter.h \ hig.h \ icons.h \ @@ -79,6 +80,7 @@ transmission_SOURCES = \ conf.c \ details.c \ dialogs.c \ + favicon.c \ file-list.c \ filter.c \ hig.c \ diff --git a/gtk/favicon.c b/gtk/favicon.c new file mode 100644 index 000000000..f0ffdb5fc --- /dev/null +++ b/gtk/favicon.c @@ -0,0 +1,151 @@ +/* + * This file Copyright (C) 2010 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 /* g_remove */ +#include + +#include +#include /* tr_webRun */ + +#include "favicon.h" +#include "util.h" /* gtr_mkdir_with_parents(), gtr_idle_add() */ + +struct favicon_data +{ + GFunc func; + gpointer data; + char * host; + char * contents; + size_t len; +}; + +static char* +favicon_get_cache_dir( void ) +{ + static char * dir = NULL; + + if( dir == NULL ) + { + dir = g_build_filename( g_get_user_cache_dir(), "transmission", "favicons", NULL ); + gtr_mkdir_with_parents( dir, 0777 ); + } + + return dir; +} + +static char* +favicon_get_cache_filename( const char * host ) +{ + return g_build_filename( favicon_get_cache_dir(), host, NULL ); +} + +static void +favicon_save_cache_file( const char * host, const void * data, size_t len ) +{ + char * filename = favicon_get_cache_filename( host ); + g_file_set_contents( filename, data, len, NULL ); + g_free( filename ); +} + +static GdkPixbuf* +favicon_load_from_data( const void * data, size_t len ) +{ + GdkPixbuf * pixbuf = NULL; + + if( len > 0 ) + { + GInputStream * input_stream = g_memory_input_stream_new_from_data( data, len, NULL ); + pixbuf = gdk_pixbuf_new_from_stream_at_scale( input_stream, 16, 16, TRUE, NULL, NULL ); + g_object_unref( input_stream ); + } + + return pixbuf; +} + +static gboolean +favicon_web_done_idle_cb( gpointer vfav ) +{ + struct favicon_data * fav = vfav; + GdkPixbuf * pixbuf = favicon_load_from_data( fav->contents, fav->len ); + + if( pixbuf != NULL ) + favicon_save_cache_file( fav->host, fav->contents, fav->len ); + + fav->func( pixbuf, fav->data ); + + g_free( fav->host ); + g_free( fav->contents ); + g_free( fav ); + return FALSE; +} + +static void +favicon_web_done_cb( tr_session * session UNUSED, + long code UNUSED, + const void * data, + size_t len, + void * vfav ) +{ + struct favicon_data * fav = vfav; + fav->contents = g_memdup( data, len ); + fav->len = len; + + gtr_idle_add( favicon_web_done_idle_cb, fav ); +} + +static GdkPixbuf* +favicon_load_from_file( const char * host ) +{ + gsize len; + char * data; + char * path = favicon_get_cache_filename( host ); + GdkPixbuf * pixbuf = NULL; + + if( g_file_get_contents( path, &data, &len, NULL ) ) + { + pixbuf = favicon_load_from_data( data, len ); + if( pixbuf == NULL ) /* bad file... delete it from the cache */ + g_remove( path ); + g_free( data ); + } + + g_free( path ); + return pixbuf; +} + +void +gtr_get_favicon( tr_session * session, + const char * host, + GFunc pixbuf_ready_func, + gpointer pixbuf_ready_func_data ) +{ + GdkPixbuf * pixbuf = favicon_load_from_file( host ); + + if( pixbuf != NULL ) + { + pixbuf_ready_func( pixbuf, pixbuf_ready_func_data ); + } + else + { + struct favicon_data * data; + char * url = g_strdup_printf( "http://%s/favicon.ico", host ); + + g_debug( "trying favicon from \"%s\"", url ); + data = g_new( struct favicon_data, 1 ); + data->func = pixbuf_ready_func; + data->data = pixbuf_ready_func_data; + data->host = g_strdup( host ); + tr_webRun( session, url, NULL, favicon_web_done_cb, data ); + + g_free( url ); + } +} diff --git a/gtk/favicon.h b/gtk/favicon.h new file mode 100644 index 000000000..b3de32eb6 --- /dev/null +++ b/gtk/favicon.h @@ -0,0 +1,24 @@ +/* + * This file Copyright (C) 2010 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:$ + */ + +#ifndef GTR_FAVICON_CACHE_H +#define GTR_FAVICON_CACHE_H + +#include +#include + +void gtr_get_favicon( tr_session * session, + const char * host, + GFunc pixbuf_ready_func, + gpointer pixbuf_ready_func_data ); + +#endif diff --git a/gtk/filter.c b/gtk/filter.c index 512b4585f..cd06f2362 100644 --- a/gtk/filter.c +++ b/gtk/filter.c @@ -16,12 +16,14 @@ #include #include +#include "favicon.h" #include "filter.h" #include "hig.h" /* GUI_PAD */ #include "tr-core.h" #include "util.h" /* gtr_idle_add() */ #define DIRTY_KEY "tr-filter-dirty" +#define SESSION_KEY "tr-session-key" #define TEXT_KEY "tr-filter-text" #define TEXT_MODE_KEY "tr-filter-text-mode" #define TORRENT_MODEL_KEY "tr-filter-torrent-model-key" @@ -59,6 +61,7 @@ enum 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 }; @@ -115,6 +118,28 @@ category_model_update_count( GtkTreeStore * store, GtkTreeIter * iter, int n ) 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 ) { @@ -263,15 +288,23 @@ category_filter_model_update( GtkTreeStore * store ) /* g_message( "removing row and incrementing i" ); */ gtk_tree_store_remove( store, &iter ); } else if( insert_row ) { + GtkTreeIter child; + GtkTreePath * path; + GtkTreeRowReference * reference; + tr_session * session = g_object_get_data( 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, NULL, &parent, store_pos, + gtk_tree_store_insert_with_values( store, &child, &parent, 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, &child ); + 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; @@ -302,7 +335,8 @@ category_filter_model_new( GtkTreeModel * tmodel ) G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, - G_TYPE_STRING ); + G_TYPE_STRING, + GDK_TYPE_PIXBUF ); gtk_tree_store_insert_with_values( store, NULL, NULL, -1, CAT_FILTER_COL_NAME, _( "All" ), @@ -393,6 +427,17 @@ torrent_model_row_deleted_cb( GtkTreeModel * tmodel UNUSED, 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; + gtk_tree_model_get( tree_model, iter, CAT_FILTER_COL_TYPE, &type, -1 ); + g_object_set( cell_renderer, "width", type==CAT_FILTER_TYPE_HOST ? 16 : 0, NULL ); +} static void render_hit_count_func( GtkCellLayout * cell_layout UNUSED, GtkCellRenderer * cell_renderer, @@ -441,6 +486,14 @@ category_combo_box_new( GtkTreeModel * tmodel ) is_it_a_separator, NULL, NULL ); gtk_combo_box_set_active( GTK_COMBO_BOX( c ), 0 ); + r = gtk_cell_renderer_pixbuf_new( ); + gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( c ), r, FALSE ); + gtk_cell_layout_set_cell_data_func( GTK_CELL_LAYOUT( c ), r, render_pixbuf_func, NULL, NULL ); + gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT( c ), r, + "pixbuf", CAT_FILTER_COL_PIXBUF, + NULL ); + + r = gtk_cell_renderer_text_new( ); gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( c ), r, FALSE ); gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT( c ), r, @@ -888,7 +941,7 @@ selection_changed_cb( GtkComboBox * combo UNUSED, gpointer vdata ) } GtkWidget * -gtr_filter_bar_new( GtkTreeModel * tmodel, GtkTreeModel ** filter_model ) +gtr_filter_bar_new( tr_session * session, GtkTreeModel * tmodel, GtkTreeModel ** filter_model ) { int i; GtkWidget * l; @@ -913,6 +966,7 @@ gtr_filter_bar_new( GtkTreeModel * tmodel, GtkTreeModel ** filter_model ) data->filter_model = gtk_tree_model_filter_new( tmodel, NULL ); g_object_set( G_OBJECT( data->category ), "width-request", 150, NULL ); + g_object_set_data( 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 ); diff --git a/gtk/filter.h b/gtk/filter.h index 4a951022d..02d86626c 100644 --- a/gtk/filter.h +++ b/gtk/filter.h @@ -14,7 +14,10 @@ #define GTR_FILTER_H #include +#include -GtkWidget * gtr_filter_bar_new( GtkTreeModel * torrent_model, GtkTreeModel ** filter_model ); +GtkWidget * gtr_filter_bar_new( tr_session * session, + GtkTreeModel * torrent_model, + GtkTreeModel ** filter_model ); #endif diff --git a/gtk/tr-window.c b/gtk/tr-window.c index 1d441ad09..900fb7bb0 100644 --- a/gtk/tr-window.c +++ b/gtk/tr-window.c @@ -58,26 +58,6 @@ gtk_tree_view_column_queue_resize( GtkTreeViewColumn * column ) /* yuck */ #endif -typedef enum -{ - FILTER_TEXT_MODE_NAME, - FILTER_TEXT_MODE_FILES, - FILTER_TEXT_MODE_TRACKER, - FILTER_TEXT_MODE_QTY -} -filter_text_mode_t; - -typedef enum -{ - FILTER_MODE_ALL, - FILTER_MODE_ACTIVE, - FILTER_MODE_DOWNLOADING, - FILTER_MODE_SEEDING, - FILTER_MODE_PAUSED, - FILTER_MODE_QTY -} -filter_mode_t; - typedef struct { GtkWidget * speedlimit_on_item[2]; @@ -103,10 +83,6 @@ typedef struct GtkTreeModel * filter_model; TrCore * core; gulong pref_handler_id; - filter_mode_t filter_mode; - filter_text_mode_t filter_text_mode; - char * filter_text; - GtkToggleButton * filter_toggles[FILTER_MODE_QTY]; } PrivateData; @@ -241,7 +217,6 @@ privateFree( gpointer vprivate ) { PrivateData * p = vprivate; g_signal_handler_disconnect( p->core, p->pref_handler_id ); - g_free( p->filter_text ); g_free( p ); } @@ -607,8 +582,6 @@ tr_window_new( GtkUIManager * ui_mgr, TrCore * core ) GSList * l; p = g_new0( PrivateData, 1 ); - p->filter_text_mode = FILTER_TEXT_MODE_NAME; - p->filter_text = NULL; /* make the window */ self = gtk_window_new ( GTK_WINDOW_TOPLEVEL ); @@ -644,7 +617,9 @@ tr_window_new( GtkUIManager * ui_mgr, TrCore * core ) action_set_important( "show-torrent-properties", TRUE ); /* filter */ - h = filter = p->filter = gtr_filter_bar_new( tr_core_model( core ), &p->filter_model ); + h = filter = p->filter = gtr_filter_bar_new( tr_core_session( core ), + tr_core_model( core ), + &p->filter_model ); gtk_container_set_border_width( GTK_CONTAINER( h ), GUI_PAD_SMALL ); /* status menu */ -- 2.40.0