/* * 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 #include #include #include #include #include #include #include "file-list.h" #include "hig.h" #include "icons.h" #include "tr-prefs.h" #include "util.h" #define TR_DOWNLOAD_KEY "tr-download-key" #define TR_COLUMN_ID_KEY "tr-model-column-id-key" #define TR_PRIORITY_KEY "tr-priority-key" enum { /* these two fields could be any number at all so long as they're not * TR_PRI_LOW, TR_PRI_NORMAL, TR_PRI_HIGH, TRUE, or FALSE */ NOT_SET = 1000, MIXED = 1001 }; enum { FC_ICON, FC_LABEL, FC_PROG, FC_INDEX, FC_SIZE, FC_SIZE_STR, FC_HAVE, FC_PRIORITY, FC_ENABLED, N_FILE_COLS }; typedef struct { TrCore * core; GtkWidget * top; GtkWidget * view; GtkTreeModel * model; /* same object as store, but recast */ GtkTreeStore * store; /* same object as model, but recast */ int torrentId; guint timeout_tag; } FileData; static void clearData( FileData * data ) { data->torrentId = -1; if( data->timeout_tag ) { g_source_remove( data->timeout_tag ); data->timeout_tag = 0; } } static void freeData( gpointer data ) { clearData( data ); g_free( data ); } /*** **** ***/ struct RefreshData { int sort_column_id; gboolean resort_needed; tr_file_stat * refresh_file_stat; tr_torrent * tor; FileData * file_data; }; static gboolean refreshFilesForeach( GtkTreeModel * model, GtkTreePath * path UNUSED, GtkTreeIter * iter, gpointer gdata ) { struct RefreshData * refresh_data = gdata; FileData * data = refresh_data->file_data; unsigned int index; uint64_t size; uint64_t old_have; int old_prog; int old_priority; int old_enabled; const gboolean is_file = !gtk_tree_model_iter_has_child( model, iter ); gtk_tree_model_get( model, iter, FC_ENABLED, &old_enabled, FC_PRIORITY, &old_priority, FC_INDEX, &index, FC_HAVE, &old_have, FC_SIZE, &size, FC_PROG, &old_prog, -1 ); if( is_file ) { tr_torrent * tor = refresh_data->tor; const tr_info * inf = tr_torrentInfo( tor ); const int enabled = !inf->files[index].dnd; const int priority = inf->files[index].priority; const uint64_t have = refresh_data->refresh_file_stat[index].bytesCompleted; const int prog = size ? (int)((100.0*have)/size) : 1; if( (priority!=old_priority) || (enabled!=old_enabled) || (have!=old_have) || (prog!=old_prog) ) { /* Changing a value in the sort column can trigger a resort * which breaks this foreach() call. (See #3529) * As a workaround: if that's about to happen, temporarily disable * sorting until we finish walking the tree. */ if( !refresh_data->resort_needed ) { if(( refresh_data->resort_needed = (( refresh_data->sort_column_id==FC_PRIORITY ) && ( priority!=old_priority )) || (( refresh_data->sort_column_id==FC_ENABLED ) && ( enabled!=old_enabled )))) { refresh_data->resort_needed = TRUE; gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE( data->model ), GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING ); } } gtk_tree_store_set( data->store, iter, FC_PRIORITY, priority, FC_ENABLED, enabled, FC_HAVE, have, FC_PROG, prog, -1 ); } } else { GtkTreeIter child; uint64_t sub_size = 0; uint64_t have = 0; int prog; int enabled = NOT_SET; int priority = NOT_SET; /* since gtk_tree_model_foreach() is depth-first, we can * get the `sub' info by walking the immediate children */ if( gtk_tree_model_iter_children( model, &child, iter ) ) do { int child_enabled; int child_priority; int64_t child_have, child_size; gtk_tree_model_get( model, &child, FC_SIZE, &child_size, FC_HAVE, &child_have, FC_PRIORITY, &child_priority, FC_ENABLED, &child_enabled, -1 ); sub_size += child_size; have += child_have; if( enabled == NOT_SET ) enabled = child_enabled; else if( enabled != child_enabled ) enabled = MIXED; if( priority == NOT_SET ) priority = child_priority; else if( priority != child_priority ) priority = MIXED; } while( gtk_tree_model_iter_next( model, &child ) ); prog = sub_size ? (int)((100.0*have)/sub_size) : 1; if( (size!=sub_size) || (have!=old_have) || (priority!=old_priority) || (enabled!=old_enabled) || (prog!=old_prog) ) { char size_str[64]; tr_strlsize( size_str, sub_size, sizeof size_str ); gtk_tree_store_set( data->store, iter, FC_SIZE, sub_size, FC_SIZE_STR, size_str, FC_HAVE, have, FC_PRIORITY, priority, FC_ENABLED, enabled, FC_PROG, prog, -1 ); } } return FALSE; /* keep walking */ } static void gtr_tree_model_foreach_postorder_subtree( GtkTreeModel * model, GtkTreeIter * parent, GtkTreeModelForeachFunc func, gpointer data ) { GtkTreeIter child; if( gtk_tree_model_iter_children( model, &child, parent ) ) do gtr_tree_model_foreach_postorder_subtree( model, &child, func, data ); while( gtk_tree_model_iter_next( model, &child ) ); if( parent ) func( model, NULL, parent, data ); } static void gtr_tree_model_foreach_postorder( GtkTreeModel * model, GtkTreeModelForeachFunc func, gpointer data ) { GtkTreeIter iter; if( gtk_tree_model_get_iter_first( model, &iter ) ) do gtr_tree_model_foreach_postorder_subtree( model, &iter, func, data ); while( gtk_tree_model_iter_next( model, &iter ) ); } static void refresh( FileData * data ) { tr_torrent * tor = gtr_core_find_torrent( data->core, data->torrentId ); if( tor == NULL ) { gtr_file_list_clear( data->top ); } else { GtkSortType order; int sort_column_id; tr_file_index_t fileCount; struct RefreshData refresh_data; GtkTreeSortable * sortable = GTK_TREE_SORTABLE( data->model ); gtk_tree_sortable_get_sort_column_id( sortable, &sort_column_id, &order ); refresh_data.sort_column_id = sort_column_id; refresh_data.resort_needed = FALSE; refresh_data.refresh_file_stat = tr_torrentFiles( tor, &fileCount ); refresh_data.tor = tor; refresh_data.file_data = data; gtr_tree_model_foreach_postorder( data->model, refreshFilesForeach, &refresh_data ); if( refresh_data.resort_needed ) gtk_tree_sortable_set_sort_column_id( sortable, sort_column_id, order ); tr_torrentFilesFree( refresh_data.refresh_file_stat, fileCount ); } } static gboolean refreshModel( gpointer file_data ) { refresh( file_data ); return TRUE; } /*** **** ***/ struct ActiveData { GtkTreeSelection * sel; GArray * array; }; static gboolean getSelectedFilesForeach( GtkTreeModel * model, GtkTreePath * path UNUSED, GtkTreeIter * iter, gpointer gdata ) { const gboolean is_file = !gtk_tree_model_iter_has_child( model, iter ); if( is_file ) { struct ActiveData * data = gdata; /* active means: if it's selected or any ancestor is selected */ gboolean is_active = gtk_tree_selection_iter_is_selected( data->sel, iter ); if( !is_active ) { GtkTreeIter walk = *iter; GtkTreeIter parent; while( !is_active && gtk_tree_model_iter_parent( model, &parent, &walk ) ) { is_active = gtk_tree_selection_iter_is_selected( data->sel, &parent ); walk = parent; } } if( is_active ) { unsigned int i; gtk_tree_model_get( model, iter, FC_INDEX, &i, -1 ); g_array_append_val( data->array, i ); } } return FALSE; /* keep walking */ } static GArray* getSelectedFilesAndDescendants( GtkTreeView * view ) { struct ActiveData data; data.sel = gtk_tree_view_get_selection( view ); data.array = g_array_new( FALSE, FALSE, sizeof( tr_file_index_t ) ); gtk_tree_model_foreach( gtk_tree_view_get_model( view ), getSelectedFilesForeach, &data ); return data.array; } struct SubtreeForeachData { GArray * array; GtkTreePath * path; }; static gboolean getSubtreeForeach( GtkTreeModel * model, GtkTreePath * path, GtkTreeIter * iter, gpointer gdata ) { const gboolean is_file = !gtk_tree_model_iter_has_child( model, iter ); if( is_file ) { struct SubtreeForeachData * data = gdata; if( !gtk_tree_path_compare( path, data->path ) || gtk_tree_path_is_descendant( path, data->path ) ) { unsigned int i; gtk_tree_model_get( model, iter, FC_INDEX, &i, -1 ); g_array_append_val( data->array, i ); } } return FALSE; /* keep walking */ } static void getSubtree( GtkTreeView * view, GtkTreePath * path, GArray * indices ) { struct SubtreeForeachData tmp; tmp.array = indices; tmp.path = path; gtk_tree_model_foreach( gtk_tree_view_get_model( view ), getSubtreeForeach, &tmp ); } /* if `path' is a selected row, all selected rows are returned. * otherwise, only the row indicated by `path' is returned. * this is for toggling all the selected rows' states in a batch. */ static GArray* getActiveFilesForPath( GtkTreeView * view, GtkTreePath * path ) { GArray * indices; GtkTreeSelection * sel = gtk_tree_view_get_selection( view ); if( gtk_tree_selection_path_is_selected( sel, path ) ) { /* clicked in a selected row... use the current selection */ indices = getSelectedFilesAndDescendants( view ); } else { /* clicked OUTSIDE of the selected row... just use the clicked row */ indices = g_array_new( FALSE, FALSE, sizeof( tr_file_index_t ) ); getSubtree( view, path, indices ); } return indices; } /*** **** ***/ void gtr_file_list_clear( GtkWidget * w ) { gtr_file_list_set_torrent( w, -1 ); } struct build_data { GtkWidget * w; tr_torrent * tor; GtkTreeIter * iter; GtkTreeStore * store; }; struct row_struct { uint64_t length; char * name; int index; }; static void buildTree( GNode * node, gpointer gdata ) { char size_str[64]; GtkTreeIter child_iter; struct build_data * build = gdata; struct row_struct *child_data = node->data; const gboolean isLeaf = node->children == NULL; const char * mime_type = isLeaf ? gtr_get_mime_type_from_filename( child_data->name ) : DIRECTORY_MIME_TYPE; GdkPixbuf * icon = gtr_get_mime_type_icon( mime_type, GTK_ICON_SIZE_MENU, build->w ); const tr_info * inf = tr_torrentInfo( build->tor ); const int priority = isLeaf ? inf->files[ child_data->index ].priority : 0; const gboolean enabled = isLeaf ? !inf->files[ child_data->index ].dnd : TRUE; tr_strlsize( size_str, child_data->length, sizeof size_str ); #if GTK_CHECK_VERSION(2,10,0) gtk_tree_store_insert_with_values( build->store, &child_iter, build->iter, INT_MAX, FC_INDEX, child_data->index, FC_LABEL, child_data->name, FC_SIZE, child_data->length, FC_SIZE_STR, size_str, FC_ICON, icon, FC_PRIORITY, priority, FC_ENABLED, enabled, -1 ); #else gtk_tree_store_append( build->store, &child_iter, build->iter ); gtk_tree_store_set( build->store, &child_iter, FC_INDEX, child_data->index, FC_LABEL, child_data->name, FC_SIZE, child_data->length, FC_SIZE_STR, size_str, FC_ICON, icon, FC_PRIORITY, priority, FC_ENABLED, enabled, -1 ); #endif if( !isLeaf ) { struct build_data b = *build; b.iter = &child_iter; g_node_children_foreach( node, G_TRAVERSE_ALL, buildTree, &b ); } g_object_unref( icon ); /* we're done with this node */ g_free( child_data->name ); g_free( child_data ); } static GNode* find_child( GNode* parent, const char * name ) { GNode * child = parent->children; while( child ) { const struct row_struct * child_data = child->data; if( ( *child_data->name == *name ) && !strcmp( child_data->name, name ) ) break; child = child->next; } return child; } void gtr_file_list_set_torrent( GtkWidget * w, int torrentId ) { GtkTreeStore * store; FileData * data = g_object_get_data( G_OBJECT( w ), "file-data" ); /* unset the old fields */ clearData( data ); /* instantiate the model */ store = gtk_tree_store_new ( N_FILE_COLS, GDK_TYPE_PIXBUF, /* icon */ G_TYPE_STRING, /* label */ G_TYPE_INT, /* prog [0..100] */ G_TYPE_UINT, /* index */ G_TYPE_UINT64, /* size */ G_TYPE_STRING, /* size str */ G_TYPE_UINT64, /* have */ G_TYPE_INT, /* priority */ G_TYPE_INT ); /* dl enabled */ data->store = store; data->model = GTK_TREE_MODEL( store ); data->torrentId = torrentId; /* populate the model */ if( torrentId > 0 ) { tr_torrent * tor = gtr_core_find_torrent( data->core, torrentId ); if( tor != NULL ) { tr_file_index_t i; const tr_info * inf = tr_torrentInfo( tor ); struct row_struct * root_data; GNode * root; struct build_data build; /* build a GNode tree of the files */ root_data = g_new0( struct row_struct, 1 ); root_data->name = g_strdup( tr_torrentName( tor ) ); root_data->index = -1; root_data->length = 0; root = g_node_new( root_data ); for( i=0; ifileCount; ++i ) { int j; GNode * parent = root; const tr_file * file = &inf->files[i]; char ** tokens = g_strsplit( file->name, G_DIR_SEPARATOR_S, 0 ); for( j=0; tokens[j]; ++j ) { const gboolean isLeaf = tokens[j+1] == NULL; const char * name = tokens[j]; GNode * node = find_child( parent, name ); if( node == NULL ) { struct row_struct * row = g_new( struct row_struct, 1 ); row->name = g_strdup( name ); row->index = isLeaf ? (int)i : -1; row->length = isLeaf ? file->length : 0; node = g_node_new( row ); g_node_append( parent, node ); } parent = node; } g_strfreev( tokens ); } /* now, add them to the model */ build.w = w; build.tor = tor; build.store = data->store; build.iter = NULL; g_node_children_foreach( root, G_TRAVERSE_ALL, buildTree, &build ); /* cleanup */ g_node_destroy( root ); g_free( root_data->name ); g_free( root_data ); } refresh( data ); data->timeout_tag = gtr_timeout_add_seconds( SECONDARY_WINDOW_REFRESH_INTERVAL_SECONDS, refreshModel, data ); } gtk_tree_view_set_model( GTK_TREE_VIEW( data->view ), data->model ); gtk_tree_view_expand_all( GTK_TREE_VIEW( data->view ) ); g_object_unref( data->model ); } /*** **** ***/ static void renderDownload( GtkTreeViewColumn * column UNUSED, GtkCellRenderer * renderer, GtkTreeModel * model, GtkTreeIter * iter, gpointer data UNUSED ) { gboolean enabled; gtk_tree_model_get( model, iter, FC_ENABLED, &enabled, -1 ); g_object_set( renderer, "inconsistent", (enabled==MIXED), "active", (enabled==TRUE), NULL ); } static void renderPriority( GtkTreeViewColumn * column UNUSED, GtkCellRenderer * renderer, GtkTreeModel * model, GtkTreeIter * iter, gpointer data UNUSED ) { int priority; const char * text; gtk_tree_model_get( model, iter, FC_PRIORITY, &priority, -1 ); switch( priority ) { case TR_PRI_HIGH: text = _( "High" ); break; case TR_PRI_NORMAL: text = _( "Normal" ); break; case TR_PRI_LOW: text = _( "Low" ); break; default: text = _( "Mixed" ); break; } g_object_set( renderer, "text", text, NULL ); } /* build a filename from tr_torrentGetCurrentDir() + the model's FC_LABELs */ static char* buildFilename( tr_torrent * tor, GtkTreeModel * model, GtkTreePath * path, GtkTreeIter * iter ) { char * ret; GtkTreeIter child; GtkTreeIter parent = *iter; int n = gtk_tree_path_get_depth( path ); char ** tokens = g_new0( char*, n + 2 ); tokens[0] = g_strdup( tr_torrentGetCurrentDir( tor ) ); do { child = parent; gtk_tree_model_get( model, &child, FC_LABEL, &tokens[n--], -1 ); } while( gtk_tree_model_iter_parent( model, &parent, &child ) ); ret = g_build_filenamev( tokens ); g_strfreev( tokens ); return ret; } static gboolean onRowActivated( GtkTreeView * view, GtkTreePath * path, GtkTreeViewColumn * col UNUSED, gpointer gdata ) { gboolean handled = FALSE; FileData * data = gdata; tr_torrent * tor = gtr_core_find_torrent( data->core, data->torrentId ); if( tor != NULL ) { GtkTreeIter iter; GtkTreeModel * model = gtk_tree_view_get_model( view ); if( gtk_tree_model_get_iter( model, &iter, path ) ) { int prog; char * filename = buildFilename( tor, model, path, &iter ); gtk_tree_model_get( model, &iter, FC_PROG, &prog, -1 ); /* if the file's not done, walk up the directory tree until we find * an ancestor that exists, and open that instead */ if( filename && ( prog<100 || !g_file_test( filename, G_FILE_TEST_EXISTS ) ) ) do { char * tmp = g_path_get_dirname( filename ); g_free( filename ); filename = tmp; } while( filename && *filename && !g_file_test( filename, G_FILE_TEST_EXISTS ) ); if(( handled = filename && *filename )) gtr_open_file( filename ); } } return handled; } static gboolean onViewPathToggled( GtkTreeView * view, GtkTreeViewColumn * col, GtkTreePath * path, FileData * data ) { int cid; tr_torrent * tor; gboolean handled = FALSE; if( !col || !path ) return FALSE; cid = GPOINTER_TO_INT( g_object_get_data( G_OBJECT( col ), TR_COLUMN_ID_KEY ) ); tor = gtr_core_find_torrent( data->core, data->torrentId ); if( ( tor != NULL ) && ( ( cid == FC_PRIORITY ) || ( cid == FC_ENABLED ) ) ) { GtkTreeIter iter; GArray * indices = getActiveFilesForPath( view, path ); GtkTreeModel * model = data->model; gtk_tree_model_get_iter( model, &iter, path ); if( cid == FC_PRIORITY ) { int priority; gtk_tree_model_get( model, &iter, FC_PRIORITY, &priority, -1 ); switch( priority ) { case TR_PRI_NORMAL: priority = TR_PRI_HIGH; break; case TR_PRI_HIGH: priority = TR_PRI_LOW; break; default: priority = TR_PRI_NORMAL; break; } tr_torrentSetFilePriorities( tor, (tr_file_index_t *) indices->data, (tr_file_index_t) indices->len, priority ); } else { int enabled; gtk_tree_model_get( model, &iter, FC_ENABLED, &enabled, -1 ); enabled = !enabled; tr_torrentSetFileDLs( tor, (tr_file_index_t *) indices->data, (tr_file_index_t) indices->len, enabled ); } refresh( data ); g_array_free( indices, TRUE ); handled = TRUE; } return handled; } /** * @note 'col' and 'path' are assumed not to be NULL. */ static gboolean getAndSelectEventPath( GtkTreeView * treeview, GdkEventButton * event, GtkTreeViewColumn ** col, GtkTreePath ** path ) { GtkTreeSelection * sel; if( gtk_tree_view_get_path_at_pos( treeview, event->x, event->y, path, col, NULL, NULL ) ) { sel = gtk_tree_view_get_selection( treeview ); if( !gtk_tree_selection_path_is_selected( sel, *path ) ) { gtk_tree_selection_unselect_all( sel ); gtk_tree_selection_select_path( sel, *path ); } return TRUE; } return FALSE; } static gboolean onViewButtonPressed( GtkWidget * w, GdkEventButton * event, gpointer gdata ) { tr_torrent * tor; gboolean handled = FALSE; FileData * data = gdata; if(( tor = gtr_core_find_torrent( data->core, data->torrentId ))) { GtkTreeViewColumn * col; GtkTreePath * path = NULL; GtkTreeView * treeview = GTK_TREE_VIEW( w ); if( event->type == GDK_BUTTON_PRESS && event->button == 1 && !( event->state & ( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) && getAndSelectEventPath( treeview, event, &col, &path ) ) handled = onViewPathToggled( treeview, col, path, data ); if( path != NULL ) gtk_tree_path_free( path ); } return handled; } GtkWidget * gtr_file_list_new( TrCore * core, int torrentId ) { int size; int width; GtkWidget * ret; GtkWidget * view; GtkWidget * scroll; GtkCellRenderer * rend; GtkTreeSelection * sel; GtkTreeViewColumn * col; GtkTreeView * tree_view; const char * title; PangoLayout * pango_layout; PangoContext * pango_context; PangoFontDescription * pango_font_description; FileData * data = g_new0( FileData, 1 ); data->core = core; /* create the view */ view = gtk_tree_view_new( ); tree_view = GTK_TREE_VIEW( view ); gtk_tree_view_set_rules_hint( tree_view, TRUE ); gtk_container_set_border_width( GTK_CONTAINER( view ), GUI_PAD_BIG ); g_signal_connect( view, "button-press-event", G_CALLBACK( onViewButtonPressed ), data ); g_signal_connect( view, "row_activated", G_CALLBACK( onRowActivated ), data ); g_signal_connect( view, "button-release-event", G_CALLBACK( on_tree_view_button_released ), NULL ); pango_context = gtk_widget_create_pango_context( view ); pango_font_description = pango_font_description_copy( pango_context_get_font_description( pango_context ) ); size = pango_font_description_get_size( pango_font_description ); pango_font_description_set_size( pango_font_description, size * 0.8 ); g_object_unref( G_OBJECT( pango_context ) ); /* set up view */ sel = gtk_tree_view_get_selection( tree_view ); gtk_tree_selection_set_mode( sel, GTK_SELECTION_MULTIPLE ); gtk_tree_view_expand_all( tree_view ); gtk_tree_view_set_search_column( tree_view, FC_LABEL ); /* add file column */ col = GTK_TREE_VIEW_COLUMN ( g_object_new ( GTK_TYPE_TREE_VIEW_COLUMN, "expand", TRUE, "title", _( "Name" ), NULL ) ); gtk_tree_view_column_set_resizable( col, TRUE ); rend = gtk_cell_renderer_pixbuf_new( ); gtk_tree_view_column_pack_start( col, rend, FALSE ); gtk_tree_view_column_add_attribute( col, rend, "pixbuf", FC_ICON ); /* add text renderer */ rend = gtk_cell_renderer_text_new( ); g_object_set( rend, "ellipsize", PANGO_ELLIPSIZE_END, "font-desc", pango_font_description, NULL ); gtk_tree_view_column_pack_start( col, rend, TRUE ); gtk_tree_view_column_set_attributes( col, rend, "text", FC_LABEL, NULL ); gtk_tree_view_column_set_sort_column_id( col, FC_LABEL ); gtk_tree_view_append_column( tree_view, col ); /* add "size" column */ title = _( "Size" ); rend = gtk_cell_renderer_text_new( ); g_object_set( rend, "alignment", PANGO_ALIGN_RIGHT, "font-desc", pango_font_description, "xpad", GUI_PAD, "xalign", 1.0f, "yalign", 0.5f, NULL ); col = gtk_tree_view_column_new_with_attributes( title, rend, NULL ); gtk_tree_view_column_set_sizing( col, GTK_TREE_VIEW_COLUMN_GROW_ONLY ); gtk_tree_view_column_set_sort_column_id( col, FC_SIZE ); gtk_tree_view_column_set_attributes( col, rend, "text", FC_SIZE_STR, NULL ); gtk_tree_view_append_column( tree_view, col ); /* add "progress" column */ title = _( "Have" ); pango_layout = gtk_widget_create_pango_layout( view, title ); pango_layout_get_pixel_size( pango_layout, &width, NULL ); width += 30; /* room for the sort indicator */ g_object_unref( G_OBJECT( pango_layout ) ); rend = gtk_cell_renderer_progress_new( ); col = gtk_tree_view_column_new_with_attributes( title, rend, "value", FC_PROG, NULL ); gtk_tree_view_column_set_fixed_width( col, width ); gtk_tree_view_column_set_sizing( col, GTK_TREE_VIEW_COLUMN_FIXED ); gtk_tree_view_column_set_sort_column_id( col, FC_PROG ); gtk_tree_view_append_column( tree_view, col ); /* add "enabled" column */ title = _( "Download" ); pango_layout = gtk_widget_create_pango_layout( view, title ); pango_layout_get_pixel_size( pango_layout, &width, NULL ); width += 30; /* room for the sort indicator */ g_object_unref( G_OBJECT( pango_layout ) ); rend = gtk_cell_renderer_toggle_new( ); col = gtk_tree_view_column_new_with_attributes( title, rend, NULL ); g_object_set_data( G_OBJECT( col ), TR_COLUMN_ID_KEY, GINT_TO_POINTER( FC_ENABLED ) ); gtk_tree_view_column_set_fixed_width( col, width ); gtk_tree_view_column_set_sizing( col, GTK_TREE_VIEW_COLUMN_FIXED ); gtk_tree_view_column_set_cell_data_func( col, rend, renderDownload, NULL, NULL ); gtk_tree_view_column_set_sort_column_id( col, FC_ENABLED ); gtk_tree_view_append_column( tree_view, col ); /* add priority column */ title = _( "Priority" ); pango_layout = gtk_widget_create_pango_layout( view, title ); pango_layout_get_pixel_size( pango_layout, &width, NULL ); width += 30; /* room for the sort indicator */ g_object_unref( G_OBJECT( pango_layout ) ); rend = gtk_cell_renderer_text_new( ); g_object_set( rend, "xalign", (gfloat)0.5, "yalign", (gfloat)0.5, NULL ); col = gtk_tree_view_column_new_with_attributes( title, rend, NULL ); g_object_set_data( G_OBJECT( col ), TR_COLUMN_ID_KEY, GINT_TO_POINTER( FC_PRIORITY ) ); gtk_tree_view_column_set_fixed_width( col, width ); gtk_tree_view_column_set_sizing( col, GTK_TREE_VIEW_COLUMN_FIXED ); gtk_tree_view_column_set_sort_column_id( col, FC_PRIORITY ); gtk_tree_view_column_set_cell_data_func( col, rend, renderPriority, NULL, NULL ); gtk_tree_view_append_column( tree_view, col ); /* create the scrolled window and stick the view in it */ scroll = gtk_scrolled_window_new( NULL, NULL ); gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scroll ), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC ); gtk_scrolled_window_set_shadow_type ( GTK_SCROLLED_WINDOW( scroll ), GTK_SHADOW_IN ); gtk_container_add( GTK_CONTAINER( scroll ), view ); gtk_widget_set_size_request ( scroll, -1, 200 ); ret = scroll; data->view = view; data->top = scroll; g_object_set_data_full( G_OBJECT( ret ), "file-data", data, freeData ); gtr_file_list_set_torrent( ret, torrentId ); pango_font_description_free( pango_font_description ); return ret; }