transmission 2.51 update
[tomato.git] / release / src / router / transmission / gtk / file-list.c
blob131692720cc113c978ace1681e5702ccf7f63490
1 /*
2 * This file Copyright (C) Mnemosyne LLC
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.
10 * $Id: file-list.c 13243 2012-03-04 13:12:42Z jordan $
13 #include <stddef.h>
14 #include <stdio.h>
15 #include <string.h>
16 #include <glib/gi18n.h>
17 #include <gtk/gtk.h>
19 #include <libtransmission/transmission.h>
20 #include <libtransmission/utils.h>
22 #include "file-list.h"
23 #include "hig.h"
24 #include "icons.h"
25 #include "tr-prefs.h"
26 #include "util.h"
28 #define TR_DOWNLOAD_KEY "tr-download-key"
29 #define TR_COLUMN_ID_KEY "tr-model-column-id-key"
30 #define TR_PRIORITY_KEY "tr-priority-key"
32 enum
34 /* these two fields could be any number at all so long as they're not
35 * TR_PRI_LOW, TR_PRI_NORMAL, TR_PRI_HIGH, TRUE, or FALSE */
36 NOT_SET = 1000,
37 MIXED = 1001
40 enum
42 FC_ICON,
43 FC_LABEL,
44 FC_LABEL_ESC,
45 FC_PROG,
46 FC_INDEX,
47 FC_SIZE,
48 FC_SIZE_STR,
49 FC_HAVE,
50 FC_PRIORITY,
51 FC_ENABLED,
52 N_FILE_COLS
55 typedef struct
57 TrCore * core;
58 GtkWidget * top;
59 GtkWidget * view;
60 GtkTreeModel * model; /* same object as store, but recast */
61 GtkTreeStore * store; /* same object as model, but recast */
62 int torrentId;
63 guint timeout_tag;
65 FileData;
67 static void
68 clearData( FileData * data )
70 data->torrentId = -1;
72 if( data->timeout_tag ) {
73 g_source_remove( data->timeout_tag );
74 data->timeout_tag = 0;
78 static void
79 freeData( gpointer data )
81 clearData( data );
82 g_free( data );
85 /***
86 ****
87 ***/
89 struct RefreshData
91 int sort_column_id;
92 gboolean resort_needed;
94 tr_file_stat * refresh_file_stat;
95 tr_torrent * tor;
97 FileData * file_data;
100 static gboolean
101 refreshFilesForeach( GtkTreeModel * model,
102 GtkTreePath * path UNUSED,
103 GtkTreeIter * iter,
104 gpointer gdata )
106 struct RefreshData * refresh_data = gdata;
107 FileData * data = refresh_data->file_data;
108 unsigned int index;
109 uint64_t size;
110 uint64_t old_have;
111 int old_prog;
112 int old_priority;
113 int old_enabled;
114 const gboolean is_file = !gtk_tree_model_iter_has_child( model, iter );
116 gtk_tree_model_get( model, iter, FC_ENABLED, &old_enabled,
117 FC_PRIORITY, &old_priority,
118 FC_INDEX, &index,
119 FC_HAVE, &old_have,
120 FC_SIZE, &size,
121 FC_PROG, &old_prog,
122 -1 );
124 if( is_file )
126 tr_torrent * tor = refresh_data->tor;
127 const tr_info * inf = tr_torrentInfo( tor );
128 const int enabled = !inf->files[index].dnd;
129 const int priority = inf->files[index].priority;
130 const uint64_t have = refresh_data->refresh_file_stat[index].bytesCompleted;
131 const int prog = size ? (int)((100.0*have)/size) : 1;
133 if( (priority!=old_priority) || (enabled!=old_enabled) || (have!=old_have) || (prog!=old_prog) )
135 /* Changing a value in the sort column can trigger a resort
136 * which breaks this foreach() call. (See #3529)
137 * As a workaround: if that's about to happen, temporarily disable
138 * sorting until we finish walking the tree. */
139 if( !refresh_data->resort_needed )
141 if(( refresh_data->resort_needed =
142 (( refresh_data->sort_column_id==FC_PRIORITY ) && ( priority!=old_priority )) ||
143 (( refresh_data->sort_column_id==FC_ENABLED ) && ( enabled!=old_enabled ))))
145 refresh_data->resort_needed = TRUE;
146 gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE( data->model ),
147 GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID,
148 GTK_SORT_ASCENDING );
152 gtk_tree_store_set( data->store, iter, FC_PRIORITY, priority,
153 FC_ENABLED, enabled,
154 FC_HAVE, have,
155 FC_PROG, prog,
156 -1 );
159 else
161 GtkTreeIter child;
162 uint64_t sub_size = 0;
163 uint64_t have = 0;
164 int prog;
165 int enabled = NOT_SET;
166 int priority = NOT_SET;
168 /* since gtk_tree_model_foreach() is depth-first, we can
169 * get the `sub' info by walking the immediate children */
171 if( gtk_tree_model_iter_children( model, &child, iter ) ) do
173 int child_enabled;
174 int child_priority;
175 int64_t child_have, child_size;
177 gtk_tree_model_get( model, &child, FC_SIZE, &child_size,
178 FC_HAVE, &child_have,
179 FC_PRIORITY, &child_priority,
180 FC_ENABLED, &child_enabled,
181 -1 );
183 sub_size += child_size;
184 have += child_have;
186 if( enabled == NOT_SET )
187 enabled = child_enabled;
188 else if( enabled != child_enabled )
189 enabled = MIXED;
191 if( priority == NOT_SET )
192 priority = child_priority;
193 else if( priority != child_priority )
194 priority = MIXED;
196 while( gtk_tree_model_iter_next( model, &child ) );
198 prog = sub_size ? (int)((100.0*have)/sub_size) : 1;
200 if( (size!=sub_size) || (have!=old_have)
201 || (priority!=old_priority)
202 || (enabled!=old_enabled)
203 || (prog!=old_prog) )
205 char size_str[64];
206 tr_strlsize( size_str, sub_size, sizeof size_str );
207 gtk_tree_store_set( data->store, iter, FC_SIZE, sub_size,
208 FC_SIZE_STR, size_str,
209 FC_HAVE, have,
210 FC_PRIORITY, priority,
211 FC_ENABLED, enabled,
212 FC_PROG, prog,
213 -1 );
217 return FALSE; /* keep walking */
220 static void
221 gtr_tree_model_foreach_postorder_subtree( GtkTreeModel * model,
222 GtkTreeIter * parent,
223 GtkTreeModelForeachFunc func,
224 gpointer data )
226 GtkTreeIter child;
227 if( gtk_tree_model_iter_children( model, &child, parent ) ) do
228 gtr_tree_model_foreach_postorder_subtree( model, &child, func, data );
229 while( gtk_tree_model_iter_next( model, &child ) );
230 if( parent )
231 func( model, NULL, parent, data );
234 static void
235 gtr_tree_model_foreach_postorder( GtkTreeModel * model,
236 GtkTreeModelForeachFunc func,
237 gpointer data )
239 GtkTreeIter iter;
240 if( gtk_tree_model_iter_nth_child( model, &iter, NULL, 0 ) ) do
241 gtr_tree_model_foreach_postorder_subtree( model, &iter, func, data );
242 while( gtk_tree_model_iter_next( model, &iter ) );
245 static void
246 refresh( FileData * data )
248 tr_torrent * tor = gtr_core_find_torrent( data->core, data->torrentId );
250 if( tor == NULL )
252 gtr_file_list_clear( data->top );
254 else
256 GtkSortType order;
257 int sort_column_id;
258 tr_file_index_t fileCount;
259 struct RefreshData refresh_data;
260 GtkTreeSortable * sortable = GTK_TREE_SORTABLE( data->model );
261 gtk_tree_sortable_get_sort_column_id( sortable, &sort_column_id, &order );
263 refresh_data.sort_column_id = sort_column_id;
264 refresh_data.resort_needed = FALSE;
265 refresh_data.refresh_file_stat = tr_torrentFiles( tor, &fileCount );
266 refresh_data.tor = tor;
267 refresh_data.file_data = data;
269 gtr_tree_model_foreach_postorder( data->model, refreshFilesForeach, &refresh_data );
271 if( refresh_data.resort_needed )
272 gtk_tree_sortable_set_sort_column_id( sortable, sort_column_id, order );
274 tr_torrentFilesFree( refresh_data.refresh_file_stat, fileCount );
278 static gboolean
279 refreshModel( gpointer file_data )
281 refresh( file_data );
282 return TRUE;
285 /***
286 ****
287 ***/
289 struct ActiveData
291 GtkTreeSelection * sel;
292 GArray * array;
295 static gboolean
296 getSelectedFilesForeach( GtkTreeModel * model,
297 GtkTreePath * path UNUSED,
298 GtkTreeIter * iter,
299 gpointer gdata )
301 const gboolean is_file = !gtk_tree_model_iter_has_child( model, iter );
303 if( is_file )
305 struct ActiveData * data = gdata;
307 /* active means: if it's selected or any ancestor is selected */
309 gboolean is_active = gtk_tree_selection_iter_is_selected( data->sel, iter );
311 if( !is_active )
313 GtkTreeIter walk = *iter;
314 GtkTreeIter parent;
315 while( !is_active && gtk_tree_model_iter_parent( model, &parent, &walk ) )
317 is_active = gtk_tree_selection_iter_is_selected( data->sel, &parent );
318 walk = parent;
322 if( is_active )
324 unsigned int i;
325 gtk_tree_model_get( model, iter, FC_INDEX, &i, -1 );
326 g_array_append_val( data->array, i );
330 return FALSE; /* keep walking */
333 static GArray*
334 getSelectedFilesAndDescendants( GtkTreeView * view )
336 struct ActiveData data;
338 data.sel = gtk_tree_view_get_selection( view );
339 data.array = g_array_new( FALSE, FALSE, sizeof( tr_file_index_t ) );
340 gtk_tree_model_foreach( gtk_tree_view_get_model( view ),
341 getSelectedFilesForeach, &data );
342 return data.array;
345 struct SubtreeForeachData
347 GArray * array;
348 GtkTreePath * path;
351 static gboolean
352 getSubtreeForeach( GtkTreeModel * model,
353 GtkTreePath * path,
354 GtkTreeIter * iter,
355 gpointer gdata )
357 const gboolean is_file = !gtk_tree_model_iter_has_child( model, iter );
359 if( is_file )
361 struct SubtreeForeachData * data = gdata;
363 if( !gtk_tree_path_compare( path, data->path ) || gtk_tree_path_is_descendant( path, data->path ) )
365 unsigned int i;
366 gtk_tree_model_get( model, iter, FC_INDEX, &i, -1 );
367 g_array_append_val( data->array, i );
371 return FALSE; /* keep walking */
374 static void
375 getSubtree( GtkTreeView * view, GtkTreePath * path, GArray * indices )
377 struct SubtreeForeachData tmp;
378 tmp.array = indices;
379 tmp.path = path;
380 gtk_tree_model_foreach( gtk_tree_view_get_model( view ), getSubtreeForeach, &tmp );
383 /* if `path' is a selected row, all selected rows are returned.
384 * otherwise, only the row indicated by `path' is returned.
385 * this is for toggling all the selected rows' states in a batch.
387 static GArray*
388 getActiveFilesForPath( GtkTreeView * view, GtkTreePath * path )
390 GArray * indices;
391 GtkTreeSelection * sel = gtk_tree_view_get_selection( view );
393 if( gtk_tree_selection_path_is_selected( sel, path ) )
395 /* clicked in a selected row... use the current selection */
396 indices = getSelectedFilesAndDescendants( view );
398 else
400 /* clicked OUTSIDE of the selected row... just use the clicked row */
401 indices = g_array_new( FALSE, FALSE, sizeof( tr_file_index_t ) );
402 getSubtree( view, path, indices );
405 return indices;
408 /***
409 ****
410 ***/
412 void
413 gtr_file_list_clear( GtkWidget * w )
415 gtr_file_list_set_torrent( w, -1 );
418 struct build_data
420 GtkWidget * w;
421 tr_torrent * tor;
422 GtkTreeIter * iter;
423 GtkTreeStore * store;
426 struct row_struct
428 uint64_t length;
429 char * name;
430 int index;
433 static void
434 buildTree( GNode * node, gpointer gdata )
436 char size_str[64];
437 GtkTreeIter child_iter;
438 struct build_data * build = gdata;
439 struct row_struct *child_data = node->data;
440 const gboolean isLeaf = node->children == NULL;
442 const char * mime_type = isLeaf ? gtr_get_mime_type_from_filename( child_data->name ) : DIRECTORY_MIME_TYPE;
443 GdkPixbuf * icon = gtr_get_mime_type_icon( mime_type, GTK_ICON_SIZE_MENU, build->w );
444 const tr_info * inf = tr_torrentInfo( build->tor );
445 const int priority = isLeaf ? inf->files[ child_data->index ].priority : 0;
446 const gboolean enabled = isLeaf ? !inf->files[ child_data->index ].dnd : TRUE;
447 char * name_esc = g_markup_escape_text( child_data->name, -1 );
449 tr_strlsize( size_str, child_data->length, sizeof size_str );
451 gtk_tree_store_insert_with_values( build->store, &child_iter, build->iter, INT_MAX,
452 FC_INDEX, child_data->index,
453 FC_LABEL, child_data->name,
454 FC_LABEL_ESC, name_esc,
455 FC_SIZE, child_data->length,
456 FC_SIZE_STR, size_str,
457 FC_ICON, icon,
458 FC_PRIORITY, priority,
459 FC_ENABLED, enabled,
460 -1 );
462 if( !isLeaf )
464 struct build_data b = *build;
465 b.iter = &child_iter;
466 g_node_children_foreach( node, G_TRAVERSE_ALL, buildTree, &b );
469 g_free( name_esc );
470 g_object_unref( icon );
472 /* we're done with this node */
473 g_free( child_data->name );
474 g_free( child_data );
477 static GNode*
478 find_child( GNode* parent, const char * name )
480 GNode * child = parent->children;
481 while( child ) {
482 const struct row_struct * child_data = child->data;
483 if( ( *child_data->name == *name ) && !strcmp( child_data->name, name ) )
484 break;
485 child = child->next;
487 return child;
490 void
491 gtr_file_list_set_torrent( GtkWidget * w, int torrentId )
493 GtkTreeStore * store;
494 FileData * data = g_object_get_data( G_OBJECT( w ), "file-data" );
496 /* unset the old fields */
497 clearData( data );
499 /* instantiate the model */
500 store = gtk_tree_store_new ( N_FILE_COLS,
501 GDK_TYPE_PIXBUF, /* icon */
502 G_TYPE_STRING, /* label */
503 G_TYPE_STRING, /* label esc */
504 G_TYPE_INT, /* prog [0..100] */
505 G_TYPE_UINT, /* index */
506 G_TYPE_UINT64, /* size */
507 G_TYPE_STRING, /* size str */
508 G_TYPE_UINT64, /* have */
509 G_TYPE_INT, /* priority */
510 G_TYPE_INT ); /* dl enabled */
512 data->store = store;
513 data->model = GTK_TREE_MODEL( store );
514 data->torrentId = torrentId;
516 /* populate the model */
517 if( torrentId > 0 )
519 tr_torrent * tor = gtr_core_find_torrent( data->core, torrentId );
520 if( tor != NULL )
522 tr_file_index_t i;
523 const tr_info * inf = tr_torrentInfo( tor );
524 struct row_struct * root_data;
525 GNode * root;
526 struct build_data build;
528 /* build a GNode tree of the files */
529 root_data = g_new0( struct row_struct, 1 );
530 root_data->name = g_strdup( tr_torrentName( tor ) );
531 root_data->index = -1;
532 root_data->length = 0;
533 root = g_node_new( root_data );
534 for( i=0; i<inf->fileCount; ++i ) {
535 int j;
536 GNode * parent = root;
537 const tr_file * file = &inf->files[i];
538 char ** tokens = g_strsplit( file->name, G_DIR_SEPARATOR_S, 0 );
539 for( j=0; tokens[j]; ++j ) {
540 const gboolean isLeaf = tokens[j+1] == NULL;
541 const char * name = tokens[j];
542 GNode * node = find_child( parent, name );
543 if( node == NULL ) {
544 struct row_struct * row = g_new( struct row_struct, 1 );
545 row->name = g_strdup( name );
546 row->index = isLeaf ? (int)i : -1;
547 row->length = isLeaf ? file->length : 0;
548 node = g_node_new( row );
549 g_node_append( parent, node );
551 parent = node;
553 g_strfreev( tokens );
556 /* now, add them to the model */
557 build.w = w;
558 build.tor = tor;
559 build.store = data->store;
560 build.iter = NULL;
561 g_node_children_foreach( root, G_TRAVERSE_ALL, buildTree, &build );
563 /* cleanup */
564 g_node_destroy( root );
565 g_free( root_data->name );
566 g_free( root_data );
569 refresh( data );
570 data->timeout_tag = gdk_threads_add_timeout_seconds( SECONDARY_WINDOW_REFRESH_INTERVAL_SECONDS, refreshModel, data );
573 gtk_tree_view_set_model( GTK_TREE_VIEW( data->view ), data->model );
574 gtk_tree_view_expand_all( GTK_TREE_VIEW( data->view ) );
575 g_object_unref( data->model );
578 /***
579 ****
580 ***/
582 static void
583 renderDownload( GtkTreeViewColumn * column UNUSED,
584 GtkCellRenderer * renderer,
585 GtkTreeModel * model,
586 GtkTreeIter * iter,
587 gpointer data UNUSED )
589 gboolean enabled;
590 gtk_tree_model_get( model, iter, FC_ENABLED, &enabled, -1 );
591 g_object_set( renderer, "inconsistent", (enabled==MIXED),
592 "active", (enabled==TRUE),
593 NULL );
596 static void
597 renderPriority( GtkTreeViewColumn * column UNUSED,
598 GtkCellRenderer * renderer,
599 GtkTreeModel * model,
600 GtkTreeIter * iter,
601 gpointer data UNUSED )
603 int priority;
604 const char * text;
605 gtk_tree_model_get( model, iter, FC_PRIORITY, &priority, -1 );
606 switch( priority ) {
607 case TR_PRI_HIGH: text = _( "High" ); break;
608 case TR_PRI_NORMAL: text = _( "Normal" ); break;
609 case TR_PRI_LOW: text = _( "Low" ); break;
610 default: text = _( "Mixed" ); break;
612 g_object_set( renderer, "text", text, NULL );
615 /* build a filename from tr_torrentGetCurrentDir() + the model's FC_LABELs */
616 static char*
617 buildFilename( tr_torrent * tor, GtkTreeModel * model,
618 GtkTreePath * path, GtkTreeIter * iter )
620 char * ret;
621 GtkTreeIter child;
622 GtkTreeIter parent = *iter;
623 int n = gtk_tree_path_get_depth( path );
624 char ** tokens = g_new0( char*, n + 2 );
625 tokens[0] = g_strdup( tr_torrentGetCurrentDir( tor ) );
626 do {
627 child = parent;
628 gtk_tree_model_get( model, &child, FC_LABEL, &tokens[n--], -1 );
629 } while( gtk_tree_model_iter_parent( model, &parent, &child ) );
630 ret = g_build_filenamev( tokens );
631 g_strfreev( tokens );
632 return ret;
635 static gboolean
636 onRowActivated( GtkTreeView * view, GtkTreePath * path,
637 GtkTreeViewColumn * col UNUSED, gpointer gdata )
639 gboolean handled = FALSE;
640 FileData * data = gdata;
641 tr_torrent * tor = gtr_core_find_torrent( data->core, data->torrentId );
643 if( tor != NULL )
645 GtkTreeIter iter;
646 GtkTreeModel * model = gtk_tree_view_get_model( view );
648 if( gtk_tree_model_get_iter( model, &iter, path ) )
650 int prog;
651 char * filename = buildFilename( tor, model, path, &iter );
652 gtk_tree_model_get( model, &iter, FC_PROG, &prog, -1 );
654 /* if the file's not done, walk up the directory tree until we find
655 * an ancestor that exists, and open that instead */
656 if( filename && ( prog<100 || !g_file_test( filename, G_FILE_TEST_EXISTS ) ) ) do
658 char * tmp = g_path_get_dirname( filename );
659 g_free( filename );
660 filename = tmp;
662 while( filename && *filename && !g_file_test( filename, G_FILE_TEST_EXISTS ) );
664 if(( handled = filename && *filename ))
665 gtr_open_file( filename );
669 return handled;
672 static gboolean
673 onViewPathToggled( GtkTreeView * view,
674 GtkTreeViewColumn * col,
675 GtkTreePath * path,
676 FileData * data )
678 int cid;
679 tr_torrent * tor;
680 gboolean handled = FALSE;
682 if( !col || !path )
683 return FALSE;
685 cid = GPOINTER_TO_INT( g_object_get_data( G_OBJECT( col ), TR_COLUMN_ID_KEY ) );
686 tor = gtr_core_find_torrent( data->core, data->torrentId );
687 if( ( tor != NULL ) && ( ( cid == FC_PRIORITY ) || ( cid == FC_ENABLED ) ) )
689 GtkTreeIter iter;
690 GArray * indices = getActiveFilesForPath( view, path );
691 GtkTreeModel * model = data->model;
693 gtk_tree_model_get_iter( model, &iter, path );
695 if( cid == FC_PRIORITY )
697 int priority;
698 gtk_tree_model_get( model, &iter, FC_PRIORITY, &priority, -1 );
699 switch( priority ) {
700 case TR_PRI_NORMAL: priority = TR_PRI_HIGH; break;
701 case TR_PRI_HIGH: priority = TR_PRI_LOW; break;
702 default: priority = TR_PRI_NORMAL; break;
704 tr_torrentSetFilePriorities( tor,
705 (tr_file_index_t *) indices->data,
706 (tr_file_index_t) indices->len,
707 priority );
709 else
711 int enabled;
712 gtk_tree_model_get( model, &iter, FC_ENABLED, &enabled, -1 );
713 enabled = !enabled;
715 tr_torrentSetFileDLs( tor,
716 (tr_file_index_t *) indices->data,
717 (tr_file_index_t) indices->len,
718 enabled );
721 refresh( data );
722 g_array_free( indices, TRUE );
723 handled = TRUE;
726 return handled;
730 * @note 'col' and 'path' are assumed not to be NULL.
732 static gboolean
733 getAndSelectEventPath( GtkTreeView * treeview,
734 GdkEventButton * event,
735 GtkTreeViewColumn ** col,
736 GtkTreePath ** path )
738 GtkTreeSelection * sel;
740 if( gtk_tree_view_get_path_at_pos( treeview,
741 event->x, event->y,
742 path, col, NULL, NULL ) )
744 sel = gtk_tree_view_get_selection( treeview );
745 if( !gtk_tree_selection_path_is_selected( sel, *path ) )
747 gtk_tree_selection_unselect_all( sel );
748 gtk_tree_selection_select_path( sel, *path );
750 return TRUE;
753 return FALSE;
756 static gboolean
757 onViewButtonPressed( GtkWidget * w, GdkEventButton * event, gpointer gdata )
759 GtkTreeViewColumn * col;
760 GtkTreePath * path = NULL;
761 gboolean handled = FALSE;
762 GtkTreeView * treeview = GTK_TREE_VIEW( w );
763 FileData * data = gdata;
765 if( ( event->type == GDK_BUTTON_PRESS )
766 && ( event->button == 1 )
767 && !( event->state & ( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) )
768 && getAndSelectEventPath( treeview, event, &col, &path ) )
770 handled = onViewPathToggled( treeview, col, path, data );
772 if( path != NULL )
773 gtk_tree_path_free( path );
776 return handled;
779 GtkWidget *
780 gtr_file_list_new( TrCore * core, int torrentId )
782 int size;
783 int width;
784 GtkWidget * ret;
785 GtkWidget * view;
786 GtkWidget * scroll;
787 GtkCellRenderer * rend;
788 GtkTreeSelection * sel;
789 GtkTreeViewColumn * col;
790 GtkTreeView * tree_view;
791 const char * title;
792 PangoLayout * pango_layout;
793 PangoContext * pango_context;
794 PangoFontDescription * pango_font_description;
795 FileData * data = g_new0( FileData, 1 );
797 data->core = core;
799 /* create the view */
800 view = gtk_tree_view_new( );
801 tree_view = GTK_TREE_VIEW( view );
802 gtk_tree_view_set_rules_hint( tree_view, TRUE );
803 gtk_container_set_border_width( GTK_CONTAINER( view ), GUI_PAD_BIG );
804 g_signal_connect( view, "button-press-event",
805 G_CALLBACK( onViewButtonPressed ), data );
806 g_signal_connect( view, "row_activated",
807 G_CALLBACK( onRowActivated ), data );
808 g_signal_connect( view, "button-release-event",
809 G_CALLBACK( on_tree_view_button_released ), NULL );
812 pango_context = gtk_widget_create_pango_context( view );
813 pango_font_description = pango_font_description_copy( pango_context_get_font_description( pango_context ) );
814 size = pango_font_description_get_size( pango_font_description );
815 pango_font_description_set_size( pango_font_description, size * 0.8 );
816 g_object_unref( G_OBJECT( pango_context ) );
818 /* set up view */
819 sel = gtk_tree_view_get_selection( tree_view );
820 gtk_tree_selection_set_mode( sel, GTK_SELECTION_MULTIPLE );
821 gtk_tree_view_expand_all( tree_view );
822 gtk_tree_view_set_search_column( tree_view, FC_LABEL );
824 /* add file column */
825 col = GTK_TREE_VIEW_COLUMN ( g_object_new ( GTK_TYPE_TREE_VIEW_COLUMN,
826 "expand", TRUE,
827 "title", _( "Name" ),
828 NULL ) );
829 gtk_tree_view_column_set_resizable( col, TRUE );
830 rend = gtk_cell_renderer_pixbuf_new( );
831 gtk_tree_view_column_pack_start( col, rend, FALSE );
832 gtk_tree_view_column_add_attribute( col, rend, "pixbuf", FC_ICON );
833 /* add text renderer */
834 rend = gtk_cell_renderer_text_new( );
835 g_object_set( rend, "ellipsize", PANGO_ELLIPSIZE_END, "font-desc", pango_font_description, NULL );
836 gtk_tree_view_column_pack_start( col, rend, TRUE );
837 gtk_tree_view_column_set_attributes( col, rend, "text", FC_LABEL, NULL );
838 gtk_tree_view_column_set_sort_column_id( col, FC_LABEL );
839 gtk_tree_view_append_column( tree_view, col );
841 /* add "size" column */
842 title = _( "Size" );
843 rend = gtk_cell_renderer_text_new( );
844 g_object_set( rend, "alignment", PANGO_ALIGN_RIGHT,
845 "font-desc", pango_font_description,
846 "xpad", GUI_PAD,
847 "xalign", 1.0f,
848 "yalign", 0.5f,
849 NULL );
850 col = gtk_tree_view_column_new_with_attributes( title, rend, NULL );
851 gtk_tree_view_column_set_sizing( col, GTK_TREE_VIEW_COLUMN_GROW_ONLY );
852 gtk_tree_view_column_set_sort_column_id( col, FC_SIZE );
853 gtk_tree_view_column_set_attributes( col, rend, "text", FC_SIZE_STR, NULL );
854 gtk_tree_view_append_column( tree_view, col );
856 /* add "progress" column */
857 title = _( "Have" );
858 pango_layout = gtk_widget_create_pango_layout( view, title );
859 pango_layout_get_pixel_size( pango_layout, &width, NULL );
860 width += 30; /* room for the sort indicator */
861 g_object_unref( G_OBJECT( pango_layout ) );
862 rend = gtk_cell_renderer_progress_new( );
863 col = gtk_tree_view_column_new_with_attributes( title, rend, "value", FC_PROG, NULL );
864 gtk_tree_view_column_set_fixed_width( col, width );
865 gtk_tree_view_column_set_sizing( col, GTK_TREE_VIEW_COLUMN_FIXED );
866 gtk_tree_view_column_set_sort_column_id( col, FC_PROG );
867 gtk_tree_view_append_column( tree_view, col );
869 /* add "enabled" column */
870 title = _( "Download" );
871 pango_layout = gtk_widget_create_pango_layout( view, title );
872 pango_layout_get_pixel_size( pango_layout, &width, NULL );
873 width += 30; /* room for the sort indicator */
874 g_object_unref( G_OBJECT( pango_layout ) );
875 rend = gtk_cell_renderer_toggle_new( );
876 col = gtk_tree_view_column_new_with_attributes( title, rend, NULL );
877 g_object_set_data( G_OBJECT( col ), TR_COLUMN_ID_KEY,
878 GINT_TO_POINTER( FC_ENABLED ) );
879 gtk_tree_view_column_set_fixed_width( col, width );
880 gtk_tree_view_column_set_sizing( col, GTK_TREE_VIEW_COLUMN_FIXED );
881 gtk_tree_view_column_set_cell_data_func( col, rend, renderDownload, NULL, NULL );
882 gtk_tree_view_column_set_sort_column_id( col, FC_ENABLED );
883 gtk_tree_view_append_column( tree_view, col );
885 /* add priority column */
886 title = _( "Priority" );
887 pango_layout = gtk_widget_create_pango_layout( view, title );
888 pango_layout_get_pixel_size( pango_layout, &width, NULL );
889 width += 30; /* room for the sort indicator */
890 g_object_unref( G_OBJECT( pango_layout ) );
891 rend = gtk_cell_renderer_text_new( );
892 g_object_set( rend, "xalign", (gfloat)0.5, "yalign", (gfloat)0.5, NULL );
893 col = gtk_tree_view_column_new_with_attributes( title, rend, NULL );
894 g_object_set_data( G_OBJECT( col ), TR_COLUMN_ID_KEY,
895 GINT_TO_POINTER( FC_PRIORITY ) );
896 gtk_tree_view_column_set_fixed_width( col, width );
897 gtk_tree_view_column_set_sizing( col, GTK_TREE_VIEW_COLUMN_FIXED );
898 gtk_tree_view_column_set_sort_column_id( col, FC_PRIORITY );
899 gtk_tree_view_column_set_cell_data_func( col, rend, renderPriority, NULL, NULL );
900 gtk_tree_view_append_column( tree_view, col );
902 /* add tooltip to tree */
903 gtk_tree_view_set_tooltip_column( tree_view, FC_LABEL_ESC );
905 /* create the scrolled window and stick the view in it */
906 scroll = gtk_scrolled_window_new( NULL, NULL );
907 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scroll ),
908 GTK_POLICY_AUTOMATIC,
909 GTK_POLICY_AUTOMATIC );
910 gtk_scrolled_window_set_shadow_type ( GTK_SCROLLED_WINDOW( scroll ),
911 GTK_SHADOW_IN );
912 gtk_container_add( GTK_CONTAINER( scroll ), view );
913 gtk_widget_set_size_request ( scroll, -1, 200 );
915 ret = scroll;
916 data->view = view;
917 data->top = scroll;
918 g_object_set_data_full( G_OBJECT( ret ), "file-data", data, freeData );
919 gtr_file_list_set_torrent( ret, torrentId );
921 pango_font_description_free( pango_font_description );
922 return ret;