transmission: update from 2.13 to 2.22
[tomato.git] / release / src / router / transmission / gtk / file-list.c
blobf8cb44b530787e3c33f0978753ce2925e82162a2
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 12036 2011-02-24 16:00:34Z jordan $
13 #include <stddef.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <glib/gi18n.h>
18 #include <gtk/gtk.h>
20 #include <libtransmission/transmission.h>
22 #include "file-list.h"
23 #include "hig.h"
24 #include "icons.h"
25 #include "tr-prefs.h"
27 #define TR_DOWNLOAD_KEY "tr-download-key"
28 #define TR_COLUMN_ID_KEY "tr-model-column-id-key"
29 #define TR_PRIORITY_KEY "tr-priority-key"
31 enum
33 /* these two fields could be any number at all so long as they're not
34 * TR_PRI_LOW, TR_PRI_NORMAL, TR_PRI_HIGH, TRUE, or FALSE */
35 NOT_SET = 1000,
36 MIXED = 1001
39 enum
41 FC_ICON,
42 FC_LABEL,
43 FC_PROG,
44 FC_INDEX,
45 FC_SIZE,
46 FC_SIZE_STR,
47 FC_HAVE,
48 FC_PRIORITY,
49 FC_ENABLED,
50 N_FILE_COLS
53 typedef struct
55 TrCore * core;
56 GtkWidget * top;
57 GtkWidget * view;
58 GtkTreeModel * model; /* same object as store, but recast */
59 GtkTreeStore * store; /* same object as model, but recast */
60 int torrentId;
61 guint timeout_tag;
63 FileData;
65 static void
66 clearData( FileData * data )
68 data->torrentId = -1;
70 if( data->timeout_tag ) {
71 g_source_remove( data->timeout_tag );
72 data->timeout_tag = 0;
76 static void
77 freeData( gpointer data )
79 clearData( data );
80 g_free( data );
83 /***
84 ****
85 ***/
87 struct RefreshData
89 int sort_column_id;
90 gboolean resort_needed;
92 tr_file_stat * refresh_file_stat;
93 tr_torrent * tor;
95 FileData * file_data;
98 static gboolean
99 refreshFilesForeach( GtkTreeModel * model,
100 GtkTreePath * path UNUSED,
101 GtkTreeIter * iter,
102 gpointer gdata )
104 struct RefreshData * refresh_data = gdata;
105 FileData * data = refresh_data->file_data;
106 unsigned int index;
107 uint64_t size;
108 uint64_t old_have;
109 int old_prog;
110 int old_priority;
111 int old_enabled;
112 const gboolean is_file = !gtk_tree_model_iter_has_child( model, iter );
114 gtk_tree_model_get( model, iter, FC_ENABLED, &old_enabled,
115 FC_PRIORITY, &old_priority,
116 FC_INDEX, &index,
117 FC_HAVE, &old_have,
118 FC_SIZE, &size,
119 FC_PROG, &old_prog,
120 -1 );
122 if( is_file )
124 tr_torrent * tor = refresh_data->tor;
125 const tr_info * inf = tr_torrentInfo( tor );
126 const int enabled = !inf->files[index].dnd;
127 const int priority = inf->files[index].priority;
128 const uint64_t have = refresh_data->refresh_file_stat[index].bytesCompleted;
129 const int prog = size ? (int)((100.0*have)/size) : 1;
131 if( (priority!=old_priority) || (enabled!=old_enabled) || (have!=old_have) || (prog!=old_prog) )
133 /* Changing a value in the sort column can trigger a resort
134 * which breaks this foreach() call. (See #3529)
135 * As a workaround: if that's about to happen, temporarily disable
136 * sorting until we finish walking the tree. */
137 if( !refresh_data->resort_needed )
139 if(( refresh_data->resort_needed =
140 (( refresh_data->sort_column_id==FC_PRIORITY ) && ( priority!=old_priority )) ||
141 (( refresh_data->sort_column_id==FC_ENABLED ) && ( enabled!=old_enabled ))))
143 refresh_data->resort_needed = TRUE;
144 gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE( data->model ),
145 GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID,
146 GTK_SORT_ASCENDING );
150 gtk_tree_store_set( data->store, iter, FC_PRIORITY, priority,
151 FC_ENABLED, enabled,
152 FC_HAVE, have,
153 FC_PROG, prog,
154 -1 );
157 else
159 GtkTreeIter child;
160 uint64_t sub_size = 0;
161 uint64_t have = 0;
162 int prog;
163 int enabled = NOT_SET;
164 int priority = NOT_SET;
166 /* since gtk_tree_model_foreach() is depth-first, we can
167 * get the `sub' info by walking the immediate children */
169 if( gtk_tree_model_iter_children( model, &child, iter ) ) do
171 int child_enabled;
172 int child_priority;
173 int64_t child_have, child_size;
175 gtk_tree_model_get( model, &child, FC_SIZE, &child_size,
176 FC_HAVE, &child_have,
177 FC_PRIORITY, &child_priority,
178 FC_ENABLED, &child_enabled,
179 -1 );
181 sub_size += child_size;
182 have += child_have;
184 if( enabled == NOT_SET )
185 enabled = child_enabled;
186 else if( enabled != child_enabled )
187 enabled = MIXED;
189 if( priority == NOT_SET )
190 priority = child_priority;
191 else if( priority != child_priority )
192 priority = MIXED;
194 while( gtk_tree_model_iter_next( model, &child ) );
196 prog = sub_size ? (int)((100.0*have)/sub_size) : 1;
198 if( (size!=sub_size) || (have!=old_have)
199 || (priority!=old_priority)
200 || (enabled!=old_enabled)
201 || (prog!=old_prog) )
203 char size_str[64];
204 tr_strlsize( size_str, sub_size, sizeof size_str );
205 gtk_tree_store_set( data->store, iter, FC_SIZE, sub_size,
206 FC_SIZE_STR, size_str,
207 FC_HAVE, have,
208 FC_PRIORITY, priority,
209 FC_ENABLED, enabled,
210 FC_PROG, prog,
211 -1 );
215 return FALSE; /* keep walking */
218 static void
219 gtr_tree_model_foreach_postorder_subtree( GtkTreeModel * model,
220 GtkTreeIter * parent,
221 GtkTreeModelForeachFunc func,
222 gpointer data )
224 GtkTreeIter child;
225 if( gtk_tree_model_iter_children( model, &child, parent ) ) do
226 gtr_tree_model_foreach_postorder_subtree( model, &child, func, data );
227 while( gtk_tree_model_iter_next( model, &child ) );
228 if( parent )
229 func( model, NULL, parent, data );
232 static void
233 gtr_tree_model_foreach_postorder( GtkTreeModel * model,
234 GtkTreeModelForeachFunc func,
235 gpointer data )
237 GtkTreeIter iter;
238 if( gtk_tree_model_get_iter_first( model, &iter ) ) do
239 gtr_tree_model_foreach_postorder_subtree( model, &iter, func, data );
240 while( gtk_tree_model_iter_next( model, &iter ) );
243 static void
244 refresh( FileData * data )
246 tr_torrent * tor = NULL;
247 tr_session * session = tr_core_session( data->core );
249 if( session != NULL )
250 tor = tr_torrentFindFromId( session, data->torrentId );
252 if( tor == NULL )
254 gtr_file_list_clear( data->top );
256 else
258 GtkSortType order;
259 int sort_column_id;
260 tr_file_index_t fileCount;
261 struct RefreshData refresh_data;
262 GtkTreeSortable * sortable = GTK_TREE_SORTABLE( data->model );
263 gtk_tree_sortable_get_sort_column_id( sortable, &sort_column_id, &order );
265 refresh_data.sort_column_id = sort_column_id;
266 refresh_data.resort_needed = FALSE;
267 refresh_data.refresh_file_stat = tr_torrentFiles( tor, &fileCount );
268 refresh_data.tor = tr_torrentFindFromId( session, data->torrentId );
269 refresh_data.file_data = data;
271 gtr_tree_model_foreach_postorder( data->model, refreshFilesForeach, &refresh_data );
273 if( refresh_data.resort_needed )
274 gtk_tree_sortable_set_sort_column_id( sortable, sort_column_id, order );
276 tr_torrentFilesFree( refresh_data.refresh_file_stat, fileCount );
280 static gboolean
281 refreshModel( gpointer file_data )
283 refresh( file_data );
284 return TRUE;
287 /***
288 ****
289 ***/
291 struct ActiveData
293 GtkTreeSelection * sel;
294 GArray * array;
297 static gboolean
298 getSelectedFilesForeach( GtkTreeModel * model,
299 GtkTreePath * path UNUSED,
300 GtkTreeIter * iter,
301 gpointer gdata )
303 const gboolean is_file = !gtk_tree_model_iter_has_child( model, iter );
305 if( is_file )
307 struct ActiveData * data = gdata;
309 /* active means: if it's selected or any ancestor is selected */
311 gboolean is_active = gtk_tree_selection_iter_is_selected( data->sel, iter );
313 if( !is_active )
315 GtkTreeIter walk = *iter;
316 GtkTreeIter parent;
317 while( !is_active && gtk_tree_model_iter_parent( model, &parent, &walk ) )
319 is_active = gtk_tree_selection_iter_is_selected( data->sel, &parent );
320 walk = parent;
324 if( is_active )
326 unsigned int i;
327 gtk_tree_model_get( model, iter, FC_INDEX, &i, -1 );
328 g_array_append_val( data->array, i );
332 return FALSE; /* keep walking */
335 static GArray*
336 getSelectedFilesAndDescendants( GtkTreeView * view )
338 struct ActiveData data;
340 data.sel = gtk_tree_view_get_selection( view );
341 data.array = g_array_new( FALSE, FALSE, sizeof( tr_file_index_t ) );
342 gtk_tree_model_foreach( gtk_tree_view_get_model( view ),
343 getSelectedFilesForeach, &data );
344 return data.array;
347 struct SubtreeForeachData
349 GArray * array;
350 GtkTreePath * path;
353 static gboolean
354 getSubtreeForeach( GtkTreeModel * model,
355 GtkTreePath * path,
356 GtkTreeIter * iter,
357 gpointer gdata )
359 const gboolean is_file = !gtk_tree_model_iter_has_child( model, iter );
361 if( is_file )
363 struct SubtreeForeachData * data = gdata;
365 if( !gtk_tree_path_compare( path, data->path ) || gtk_tree_path_is_descendant( path, data->path ) )
367 unsigned int i;
368 gtk_tree_model_get( model, iter, FC_INDEX, &i, -1 );
369 g_array_append_val( data->array, i );
373 return FALSE; /* keep walking */
376 static void
377 getSubtree( GtkTreeView * view, GtkTreePath * path, GArray * indices )
379 struct SubtreeForeachData tmp;
380 tmp.array = indices;
381 tmp.path = path;
382 gtk_tree_model_foreach( gtk_tree_view_get_model( view ), getSubtreeForeach, &tmp );
385 /* if `path' is a selected row, all selected rows are returned.
386 * otherwise, only the row indicated by `path' is returned.
387 * this is for toggling all the selected rows' states in a batch.
389 static GArray*
390 getActiveFilesForPath( GtkTreeView * view, GtkTreePath * path )
392 GArray * indices;
393 GtkTreeSelection * sel = gtk_tree_view_get_selection( view );
395 if( gtk_tree_selection_path_is_selected( sel, path ) )
397 /* clicked in a selected row... use the current selection */
398 indices = getSelectedFilesAndDescendants( view );
400 else
402 /* clicked OUTSIDE of the selected row... just use the clicked row */
403 indices = g_array_new( FALSE, FALSE, sizeof( tr_file_index_t ) );
404 getSubtree( view, path, indices );
407 return indices;
410 /***
411 ****
412 ***/
414 void
415 gtr_file_list_clear( GtkWidget * w )
417 gtr_file_list_set_torrent( w, -1 );
420 struct build_data
422 GtkWidget * w;
423 tr_torrent * tor;
424 GtkTreeIter * iter;
425 GtkTreeStore * store;
428 struct row_struct
430 uint64_t length;
431 char * name;
432 int index;
435 static void
436 buildTree( GNode * node, gpointer gdata )
438 char size_str[64];
439 GtkTreeIter child_iter;
440 struct build_data * build = gdata;
441 struct row_struct *child_data = node->data;
442 const gboolean isLeaf = node->children == NULL;
444 const char * mime_type = isLeaf ? gtr_get_mime_type_from_filename( child_data->name ) : DIRECTORY_MIME_TYPE;
445 GdkPixbuf * icon = gtr_get_mime_type_icon( mime_type, GTK_ICON_SIZE_MENU, build->w );
446 const tr_info * inf = tr_torrentInfo( build->tor );
447 const int priority = isLeaf ? inf->files[ child_data->index ].priority : 0;
448 const gboolean enabled = isLeaf ? !inf->files[ child_data->index ].dnd : TRUE;
450 tr_strlsize( size_str, child_data->length, sizeof size_str );
452 #if GTK_CHECK_VERSION(2,10,0)
453 gtk_tree_store_insert_with_values( build->store, &child_iter, build->iter, INT_MAX,
454 FC_INDEX, child_data->index,
455 FC_LABEL, child_data->name,
456 FC_SIZE, child_data->length,
457 FC_SIZE_STR, size_str,
458 FC_ICON, icon,
459 FC_PRIORITY, priority,
460 FC_ENABLED, enabled,
461 -1 );
462 #else
463 gtk_tree_store_append( build->store, &child_iter, build->iter );
464 gtk_tree_store_set( build->store, &child_iter,
465 FC_INDEX, child_data->index,
466 FC_LABEL, child_data->name,
467 FC_SIZE, child_data->length,
468 FC_SIZE_STR, size_str,
469 FC_ICON, icon,
470 FC_PRIORITY, priority,
471 FC_ENABLED, enabled,
472 -1 );
473 #endif
475 if( !isLeaf )
477 struct build_data b = *build;
478 b.iter = &child_iter;
479 g_node_children_foreach( node, G_TRAVERSE_ALL, buildTree, &b );
482 g_object_unref( icon );
484 /* we're done with this node */
485 g_free( child_data->name );
486 g_free( child_data );
489 static GNode*
490 find_child( GNode* parent, const char * name )
492 GNode * child = parent->children;
493 while( child ) {
494 const struct row_struct * child_data = child->data;
495 if( ( *child_data->name == *name ) && !strcmp( child_data->name, name ) )
496 break;
497 child = child->next;
499 return child;
502 void
503 gtr_file_list_set_torrent( GtkWidget * w, int torrentId )
505 GtkTreeStore * store;
506 FileData * data = g_object_get_data( G_OBJECT( w ), "file-data" );
508 /* unset the old fields */
509 clearData( data );
511 /* instantiate the model */
512 store = gtk_tree_store_new ( N_FILE_COLS,
513 GDK_TYPE_PIXBUF, /* icon */
514 G_TYPE_STRING, /* label */
515 G_TYPE_INT, /* prog [0..100] */
516 G_TYPE_UINT, /* index */
517 G_TYPE_UINT64, /* size */
518 G_TYPE_STRING, /* size str */
519 G_TYPE_UINT64, /* have */
520 G_TYPE_INT, /* priority */
521 G_TYPE_INT ); /* dl enabled */
523 data->store = store;
524 data->model = GTK_TREE_MODEL( store );
525 data->torrentId = torrentId;
527 /* populate the model */
528 if( torrentId > 0 )
530 tr_session * session = tr_core_session( data->core );
531 tr_torrent * tor = tr_torrentFindFromId( session, torrentId );
532 if( tor != NULL )
534 tr_file_index_t i;
535 const tr_info * inf = tr_torrentInfo( tor );
536 struct row_struct * root_data;
537 GNode * root;
538 struct build_data build;
540 /* build a GNode tree of the files */
541 root_data = g_new0( struct row_struct, 1 );
542 root_data->name = g_strdup( inf->name );
543 root_data->index = -1;
544 root_data->length = 0;
545 root = g_node_new( root_data );
546 for( i=0; i<inf->fileCount; ++i ) {
547 int j;
548 GNode * parent = root;
549 const tr_file * file = &inf->files[i];
550 char ** tokens = g_strsplit( file->name, G_DIR_SEPARATOR_S, 0 );
551 for( j=0; tokens[j]; ++j ) {
552 const gboolean isLeaf = tokens[j+1] == NULL;
553 const char * name = tokens[j];
554 GNode * node = find_child( parent, name );
555 if( node == NULL ) {
556 struct row_struct * row = g_new( struct row_struct, 1 );
557 row->name = g_strdup( name );
558 row->index = isLeaf ? (int)i : -1;
559 row->length = isLeaf ? file->length : 0;
560 node = g_node_new( row );
561 g_node_append( parent, node );
563 parent = node;
565 g_strfreev( tokens );
568 /* now, add them to the model */
569 build.w = w;
570 build.tor = tor;
571 build.store = data->store;
572 build.iter = NULL;
573 g_node_children_foreach( root, G_TRAVERSE_ALL, buildTree, &build );
575 /* cleanup */
576 g_node_destroy( root );
577 g_free( root_data->name );
578 g_free( root_data );
581 refresh( data );
582 data->timeout_tag = gtr_timeout_add_seconds( SECONDARY_WINDOW_REFRESH_INTERVAL_SECONDS, refreshModel, data );
585 gtk_tree_view_set_model( GTK_TREE_VIEW( data->view ), data->model );
586 gtk_tree_view_expand_all( GTK_TREE_VIEW( data->view ) );
589 /***
590 ****
591 ***/
593 static void
594 renderDownload( GtkTreeViewColumn * column UNUSED,
595 GtkCellRenderer * renderer,
596 GtkTreeModel * model,
597 GtkTreeIter * iter,
598 gpointer data UNUSED )
600 gboolean enabled;
601 gtk_tree_model_get( model, iter, FC_ENABLED, &enabled, -1 );
602 g_object_set( renderer, "inconsistent", (enabled==MIXED),
603 "active", (enabled==TRUE),
604 NULL );
607 static void
608 renderPriority( GtkTreeViewColumn * column UNUSED,
609 GtkCellRenderer * renderer,
610 GtkTreeModel * model,
611 GtkTreeIter * iter,
612 gpointer data UNUSED )
614 int priority;
615 const char * text;
616 gtk_tree_model_get( model, iter, FC_PRIORITY, &priority, -1 );
617 switch( priority ) {
618 case TR_PRI_HIGH: text = _( "High" ); break;
619 case TR_PRI_NORMAL: text = _( "Normal" ); break;
620 case TR_PRI_LOW: text = _( "Low" ); break;
621 default: text = _( "Mixed" ); break;
623 g_object_set( renderer, "text", text, NULL );
626 /* build a filename from tr_torrentGetCurrentDir() + the model's FC_LABELs */
627 static char*
628 buildFilename( tr_torrent * tor, GtkTreeModel * model,
629 GtkTreePath * path, GtkTreeIter * iter )
631 char * ret;
632 GtkTreeIter child;
633 GtkTreeIter parent = *iter;
634 int n = gtk_tree_path_get_depth( path );
635 char ** tokens = g_new0( char*, n + 2 );
636 tokens[0] = g_strdup( tr_torrentGetCurrentDir( tor ) );
637 do {
638 child = parent;
639 gtk_tree_model_get( model, &child, FC_LABEL, &tokens[n--], -1 );
640 } while( gtk_tree_model_iter_parent( model, &parent, &child ) );
641 ret = g_build_filenamev( tokens );
642 g_strfreev( tokens );
643 return ret;
646 static gboolean
647 onRowActivated( GtkTreeView * view, GtkTreePath * path,
648 GtkTreeViewColumn * col UNUSED, gpointer gdata )
650 gboolean handled = FALSE;
651 FileData * data = gdata;
652 tr_torrent * tor = tr_torrentFindFromId( tr_core_session( data->core ), data->torrentId );
654 if( tor != NULL )
656 GtkTreeIter iter;
657 GtkTreeModel * model = gtk_tree_view_get_model( view );
659 if( gtk_tree_model_get_iter( model, &iter, path ) )
661 int prog;
662 char * filename = buildFilename( tor, model, path, &iter );
663 gtk_tree_model_get( model, &iter, FC_PROG, &prog, -1 );
665 /* if the file's not done, walk up the directory tree until we find
666 * an ancestor that exists, and open that instead */
667 if( filename && ( prog<100 || !g_file_test( filename, G_FILE_TEST_EXISTS ) ) ) do
669 char * tmp = g_path_get_dirname( filename );
670 g_free( filename );
671 filename = tmp;
673 while( filename && *filename && !g_file_test( filename, G_FILE_TEST_EXISTS ) );
675 if(( handled = filename && *filename ))
676 gtr_open_file( filename );
680 return handled;
683 static gboolean
684 onViewPathToggled( GtkTreeView * view,
685 GtkTreeViewColumn * col,
686 GtkTreePath * path,
687 FileData * data )
689 int cid;
690 tr_torrent * tor;
691 gboolean handled = FALSE;
693 if( !col || !path )
694 return FALSE;
696 cid = GPOINTER_TO_INT( g_object_get_data( G_OBJECT( col ), TR_COLUMN_ID_KEY ) );
697 tor = tr_torrentFindFromId( tr_core_session( data->core ), data->torrentId );
698 if( ( tor != NULL ) && ( ( cid == FC_PRIORITY ) || ( cid == FC_ENABLED ) ) )
700 GtkTreeIter iter;
701 GArray * indices = getActiveFilesForPath( view, path );
702 GtkTreeModel * model = data->model;
704 gtk_tree_model_get_iter( model, &iter, path );
706 if( cid == FC_PRIORITY )
708 int priority;
709 gtk_tree_model_get( model, &iter, FC_PRIORITY, &priority, -1 );
710 switch( priority ) {
711 case TR_PRI_NORMAL: priority = TR_PRI_HIGH; break;
712 case TR_PRI_HIGH: priority = TR_PRI_LOW; break;
713 default: priority = TR_PRI_NORMAL; break;
715 tr_torrentSetFilePriorities( tor,
716 (tr_file_index_t *) indices->data,
717 (tr_file_index_t) indices->len,
718 priority );
720 else
722 int enabled;
723 gtk_tree_model_get( model, &iter, FC_ENABLED, &enabled, -1 );
724 enabled = !enabled;
726 tr_torrentSetFileDLs( tor,
727 (tr_file_index_t *) indices->data,
728 (tr_file_index_t) indices->len,
729 enabled );
732 refresh( data );
733 g_array_free( indices, TRUE );
734 handled = TRUE;
737 return handled;
741 * @note 'col' and 'path' are assumed not to be NULL.
743 static gboolean
744 getAndSelectEventPath( GtkTreeView * treeview,
745 GdkEventButton * event,
746 GtkTreeViewColumn ** col,
747 GtkTreePath ** path )
749 GtkTreeSelection * sel;
751 if( gtk_tree_view_get_path_at_pos( treeview,
752 event->x, event->y,
753 path, col, NULL, NULL ) )
755 sel = gtk_tree_view_get_selection( treeview );
756 if( !gtk_tree_selection_path_is_selected( sel, *path ) )
758 gtk_tree_selection_unselect_all( sel );
759 gtk_tree_selection_select_path( sel, *path );
761 return TRUE;
764 return FALSE;
767 static gboolean
768 onViewButtonPressed( GtkWidget * w, GdkEventButton * event, gpointer gdata )
770 tr_torrent * tor;
771 GtkTreeViewColumn * col = NULL;
772 GtkTreePath * path = NULL;
773 FileData * data = gdata;
774 gboolean handled = FALSE;
775 GtkTreeView * treeview = GTK_TREE_VIEW( w );
777 tor = tr_torrentFindFromId( tr_core_session( data->core ),
778 data->torrentId );
779 if( tor == NULL )
780 return FALSE;
782 if( event->type == GDK_BUTTON_PRESS && event->button == 1
783 && !( event->state & ( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) )
784 && getAndSelectEventPath( treeview, event, &col, &path ) )
786 handled = onViewPathToggled( treeview, col, path, data );
789 gtk_tree_path_free( path );
790 return handled;
793 GtkWidget *
794 gtr_file_list_new( TrCore * core, int torrentId )
796 int size;
797 int width;
798 GtkWidget * ret;
799 GtkWidget * view;
800 GtkWidget * scroll;
801 GtkCellRenderer * rend;
802 GtkTreeSelection * sel;
803 GtkTreeViewColumn * col;
804 GtkTreeView * tree_view;
805 const char * title;
806 PangoLayout * pango_layout;
807 PangoContext * pango_context;
808 PangoFontDescription * pango_font_description;
809 FileData * data = g_new0( FileData, 1 );
811 data->core = core;
813 /* create the view */
814 view = gtk_tree_view_new( );
815 tree_view = GTK_TREE_VIEW( view );
816 gtk_tree_view_set_rules_hint( tree_view, TRUE );
817 gtk_container_set_border_width( GTK_CONTAINER( view ), GUI_PAD_BIG );
818 g_signal_connect( view, "button-press-event",
819 G_CALLBACK( onViewButtonPressed ), data );
820 g_signal_connect( view, "row_activated",
821 G_CALLBACK( onRowActivated ), data );
822 g_signal_connect( view, "button-release-event",
823 G_CALLBACK( on_tree_view_button_released ), NULL );
826 pango_context = gtk_widget_create_pango_context( view );
827 pango_font_description = pango_font_description_copy( pango_context_get_font_description( pango_context ) );
828 size = pango_font_description_get_size( pango_font_description );
829 pango_font_description_set_size( pango_font_description, size * 0.8 );
830 g_object_unref( G_OBJECT( pango_context ) );
832 /* set up view */
833 sel = gtk_tree_view_get_selection( tree_view );
834 gtk_tree_selection_set_mode( sel, GTK_SELECTION_MULTIPLE );
835 gtk_tree_view_expand_all( tree_view );
836 gtk_tree_view_set_search_column( tree_view, FC_LABEL );
838 /* add file column */
839 col = GTK_TREE_VIEW_COLUMN ( g_object_new ( GTK_TYPE_TREE_VIEW_COLUMN,
840 "expand", TRUE,
841 "title", _( "Name" ),
842 NULL ) );
843 gtk_tree_view_column_set_resizable( col, TRUE );
844 rend = gtk_cell_renderer_pixbuf_new( );
845 gtk_tree_view_column_pack_start( col, rend, FALSE );
846 gtk_tree_view_column_add_attribute( col, rend, "pixbuf", FC_ICON );
847 /* add text renderer */
848 rend = gtk_cell_renderer_text_new( );
849 g_object_set( rend, "ellipsize", PANGO_ELLIPSIZE_END, "font-desc", pango_font_description, NULL );
850 gtk_tree_view_column_pack_start( col, rend, TRUE );
851 gtk_tree_view_column_set_attributes( col, rend, "text", FC_LABEL, NULL );
852 gtk_tree_view_column_set_sort_column_id( col, FC_LABEL );
853 gtk_tree_view_append_column( tree_view, col );
855 /* add "size" column */
856 title = _( "Size" );
857 rend = gtk_cell_renderer_text_new( );
858 g_object_set( rend, "alignment", PANGO_ALIGN_RIGHT,
859 "font-desc", pango_font_description,
860 "xpad", GUI_PAD,
861 "xalign", 1.0f,
862 "yalign", 0.5f,
863 NULL );
864 col = gtk_tree_view_column_new_with_attributes( title, rend, NULL );
865 gtk_tree_view_column_set_sizing( col, GTK_TREE_VIEW_COLUMN_GROW_ONLY );
866 gtk_tree_view_column_set_sort_column_id( col, FC_SIZE );
867 gtk_tree_view_column_set_attributes( col, rend, "text", FC_SIZE_STR, NULL );
868 gtk_tree_view_append_column( tree_view, col );
870 /* add "progress" column */
871 title = _( "Have" );
872 pango_layout = gtk_widget_create_pango_layout( view, title );
873 pango_layout_get_pixel_size( pango_layout, &width, NULL );
874 width += 30; /* room for the sort indicator */
875 g_object_unref( G_OBJECT( pango_layout ) );
876 rend = gtk_cell_renderer_progress_new( );
877 col = gtk_tree_view_column_new_with_attributes( title, rend, "value", FC_PROG, NULL );
878 gtk_tree_view_column_set_fixed_width( col, width );
879 gtk_tree_view_column_set_sizing( col, GTK_TREE_VIEW_COLUMN_FIXED );
880 gtk_tree_view_column_set_sort_column_id( col, FC_PROG );
881 gtk_tree_view_append_column( tree_view, col );
883 /* add "enabled" column */
884 title = _( "Download" );
885 pango_layout = gtk_widget_create_pango_layout( view, title );
886 pango_layout_get_pixel_size( pango_layout, &width, NULL );
887 width += 30; /* room for the sort indicator */
888 g_object_unref( G_OBJECT( pango_layout ) );
889 rend = gtk_cell_renderer_toggle_new( );
890 col = gtk_tree_view_column_new_with_attributes( title, rend, NULL );
891 g_object_set_data( G_OBJECT( col ), TR_COLUMN_ID_KEY,
892 GINT_TO_POINTER( FC_ENABLED ) );
893 gtk_tree_view_column_set_fixed_width( col, width );
894 gtk_tree_view_column_set_sizing( col, GTK_TREE_VIEW_COLUMN_FIXED );
895 gtk_tree_view_column_set_cell_data_func( col, rend, renderDownload, NULL, NULL );
896 gtk_tree_view_column_set_sort_column_id( col, FC_ENABLED );
897 gtk_tree_view_append_column( tree_view, col );
899 /* add priority column */
900 title = _( "Priority" );
901 pango_layout = gtk_widget_create_pango_layout( view, title );
902 pango_layout_get_pixel_size( pango_layout, &width, NULL );
903 width += 30; /* room for the sort indicator */
904 g_object_unref( G_OBJECT( pango_layout ) );
905 rend = gtk_cell_renderer_text_new( );
906 g_object_set( rend, "xalign", (gfloat)0.5, "yalign", (gfloat)0.5, NULL );
907 col = gtk_tree_view_column_new_with_attributes( title, rend, NULL );
908 g_object_set_data( G_OBJECT( col ), TR_COLUMN_ID_KEY,
909 GINT_TO_POINTER( FC_PRIORITY ) );
910 gtk_tree_view_column_set_fixed_width( col, width );
911 gtk_tree_view_column_set_sizing( col, GTK_TREE_VIEW_COLUMN_FIXED );
912 gtk_tree_view_column_set_sort_column_id( col, FC_PRIORITY );
913 gtk_tree_view_column_set_cell_data_func( col, rend, renderPriority, NULL, NULL );
914 gtk_tree_view_append_column( tree_view, col );
916 /* create the scrolled window and stick the view in it */
917 scroll = gtk_scrolled_window_new( NULL, NULL );
918 gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scroll ),
919 GTK_POLICY_AUTOMATIC,
920 GTK_POLICY_AUTOMATIC );
921 gtk_scrolled_window_set_shadow_type ( GTK_SCROLLED_WINDOW( scroll ),
922 GTK_SHADOW_IN );
923 gtk_container_add( GTK_CONTAINER( scroll ), view );
924 gtk_widget_set_size_request ( scroll, -1, 200 );
926 ret = scroll;
927 data->view = view;
928 data->top = scroll;
929 g_object_set_data_full( G_OBJECT( ret ), "file-data", data, freeData );
930 gtr_file_list_set_torrent( ret, torrentId );
932 pango_font_description_free( pango_font_description );
933 return ret;