source: trunk/gtk/file-list.c @ 11599

Last change on this file since 11599 was 11599, checked in by charles, 11 years ago

(trunk) Join the 21st century and use only 1 space at the end sentences. This commit is nearly as important as the semi-annual ones that remove trailing spaces from the ends of lines of code... :)

  • Property svn:keywords set to Date Rev Author Id
File size: 29.7 KB
Line 
1/*
2 * This file Copyright (C) 2007-2010 Mnemosyne LLC
3 *
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.
9 *
10 * $Id: file-list.c 11599 2010-12-27 19:18:17Z charles $
11 */
12
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>
19
20#include <libtransmission/transmission.h>
21
22#include "file-list.h"
23#include "hig.h"
24#include "icons.h"
25#include "tr-prefs.h"
26
27enum
28{
29    /* these two fields could be any number at all so long as they're not
30     * TR_PRI_LOW, TR_PRI_NORMAL, TR_PRI_HIGH, TRUE, or FALSE */
31    NOT_SET = 1000,
32    MIXED = 1001
33};
34
35enum
36{
37    FC_ICON,
38    FC_LABEL,
39    FC_PROG,
40    FC_INDEX,
41    FC_SIZE,
42    FC_HAVE,
43    FC_PRIORITY,
44    FC_ENABLED,
45    N_FILE_COLS
46};
47
48typedef struct
49{
50    TrCore        * core;
51    GtkWidget     * top;
52    GtkWidget     * view;
53    GtkTreeModel  * model; /* same object as store, but recast */
54    GtkTreeStore  * store; /* same object as model, but recast */
55    int             torrentId;
56    guint           timeout_tag;
57}
58FileData;
59
60static void
61clearData( FileData * data )
62{
63    data->torrentId = -1;
64
65    if( data->timeout_tag ) {
66        g_source_remove( data->timeout_tag );
67        data->timeout_tag = 0;
68    }
69}
70
71static void
72freeData( gpointer data )
73{
74    clearData( data );
75    g_free( data );
76}
77
78/***
79****
80***/
81
82struct RefreshData
83{
84    int sort_column_id;
85    gboolean resort_needed;
86
87    tr_file_stat  * refresh_file_stat;
88    tr_torrent * tor;
89
90    FileData * file_data;
91};
92
93static gboolean
94refreshFilesForeach( GtkTreeModel * model,
95                     GtkTreePath  * path UNUSED,
96                     GtkTreeIter  * iter,
97                     gpointer       gdata )
98{
99    struct RefreshData * refresh_data = gdata;
100    FileData * data = refresh_data->file_data;
101    unsigned int index;
102    uint64_t size;
103    uint64_t old_have;
104    int old_prog;
105    int old_priority;
106    int old_enabled;
107    const gboolean is_file = !gtk_tree_model_iter_has_child( model, iter );
108
109    gtk_tree_model_get( model, iter, FC_ENABLED, &old_enabled,
110                                     FC_PRIORITY, &old_priority,
111                                     FC_INDEX, &index,
112                                     FC_HAVE, &old_have,
113                                     FC_SIZE, &size,
114                                     FC_PROG, &old_prog,
115                                     -1  );
116
117    if( is_file )
118    {
119        tr_torrent * tor = refresh_data->tor;
120        const tr_info * inf = tr_torrentInfo( tor );
121        const int enabled = !inf->files[index].dnd;
122        const int priority = inf->files[index].priority;
123        const uint64_t have = refresh_data->refresh_file_stat[index].bytesCompleted;
124        const int prog = size ? (int)((100.0*have)/size) : 1;
125
126        if( (priority!=old_priority) || (enabled!=old_enabled) || (have!=old_have) || (prog!=old_prog) )
127        {
128            /* Changing a value in the sort column can trigger a resort
129             * which breaks this foreach() call. (See #3529)
130             * As a workaround: if that's about to happen, temporarily disable
131             * sorting until we finish walking the tree. */
132            if( !refresh_data->resort_needed )
133            {
134                if(( refresh_data->resort_needed =
135                    (( refresh_data->sort_column_id==FC_PRIORITY ) && ( priority!=old_priority )) ||
136                    (( refresh_data->sort_column_id==FC_ENABLED ) && ( enabled!=old_enabled ))))
137                {
138                    refresh_data->resort_needed = TRUE;
139                    gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE( data->model ),
140                                                          GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID,
141                                                          GTK_SORT_ASCENDING );
142                }
143            }
144
145            gtk_tree_store_set( data->store, iter, FC_PRIORITY, priority,
146                                                   FC_ENABLED, enabled,
147                                                   FC_HAVE, have,
148                                                   FC_PROG, prog,
149                                                   -1 );
150        }
151    }
152    else
153    {
154        GtkTreeIter child;
155        uint64_t sub_size = 0;
156        uint64_t have = 0;
157        int prog;
158        int enabled = NOT_SET;
159        int priority = NOT_SET;
160
161        /* since gtk_tree_model_foreach() is depth-first, we can
162         * get the `sub' info by walking the immediate children */
163
164        if( gtk_tree_model_iter_children( model, &child, iter ) ) do
165        {
166            int child_enabled;
167            int child_priority;
168            int64_t child_have, child_size;
169
170            gtk_tree_model_get( model, &child, FC_SIZE, &child_size,
171                                               FC_HAVE, &child_have,
172                                               FC_PRIORITY, &child_priority,
173                                               FC_ENABLED, &child_enabled,
174                                               -1 );
175
176            sub_size += child_size;
177            have += child_have;
178
179            if( enabled == NOT_SET )
180                enabled = child_enabled;
181            else if( enabled != child_enabled )
182                enabled = MIXED;
183
184            if( priority == NOT_SET )
185                priority = child_priority;
186            else if( priority != child_priority )
187                priority = MIXED;
188        }
189        while( gtk_tree_model_iter_next( model, &child ) );
190
191        prog = sub_size ? (int)((100.0*have)/sub_size) : 1;
192
193        if( (size!=sub_size) || (have!=old_have)
194                             || (priority!=old_priority)
195                             || (enabled!=old_enabled)
196                             || (prog!=old_prog) )
197            gtk_tree_store_set( data->store, iter, FC_SIZE, sub_size,
198                                                   FC_HAVE, have,
199                                                   FC_PRIORITY, priority,
200                                                   FC_ENABLED, enabled,
201                                                   FC_PROG, prog,
202                                                   -1 );
203    }
204
205    return FALSE; /* keep walking */
206}
207
208static void
209gtr_tree_model_foreach_postorder_subtree( GtkTreeModel            * model,
210                                          GtkTreeIter             * parent,
211                                          GtkTreeModelForeachFunc   func,
212                                          gpointer                  data )
213{
214    GtkTreeIter child;
215    if( gtk_tree_model_iter_children( model, &child, parent ) ) do
216        gtr_tree_model_foreach_postorder_subtree( model, &child, func, data );
217    while( gtk_tree_model_iter_next( model, &child ) );
218    if( parent )
219        func( model, NULL, parent, data );
220}
221
222static void
223gtr_tree_model_foreach_postorder( GtkTreeModel            * model,
224                                  GtkTreeModelForeachFunc   func,
225                                  gpointer                  data )
226{
227    GtkTreeIter iter;
228    if( gtk_tree_model_get_iter_first( model, &iter ) ) do
229        gtr_tree_model_foreach_postorder_subtree( model, &iter, func, data );
230    while( gtk_tree_model_iter_next( model, &iter ) );
231}
232
233static void
234refresh( FileData * data )
235{
236    tr_torrent * tor = NULL;
237    tr_session * session = tr_core_session( data->core );
238
239    if( session != NULL )
240        tor = tr_torrentFindFromId( session, data->torrentId );
241
242    if( tor == NULL )
243    {
244        gtr_file_list_clear( data->top );
245    }
246    else
247    {
248        GtkSortType order;
249        int sort_column_id;
250        tr_file_index_t fileCount;
251        struct RefreshData refresh_data;
252        GtkTreeSortable * sortable = GTK_TREE_SORTABLE( data->model );
253        gtk_tree_sortable_get_sort_column_id( sortable, &sort_column_id, &order );
254
255        refresh_data.sort_column_id = sort_column_id;
256        refresh_data.resort_needed = FALSE;
257        refresh_data.refresh_file_stat = tr_torrentFiles( tor, &fileCount );
258        refresh_data.tor = tr_torrentFindFromId( session, data->torrentId );
259        refresh_data.file_data = data;
260
261        gtr_tree_model_foreach_postorder( data->model, refreshFilesForeach, &refresh_data );
262
263        if( refresh_data.resort_needed )
264            gtk_tree_sortable_set_sort_column_id( sortable, sort_column_id, order );
265
266        tr_torrentFilesFree( refresh_data.refresh_file_stat, fileCount );
267    }
268}
269
270static gboolean
271refreshModel( gpointer file_data )
272{
273    refresh( file_data );
274    return TRUE;
275}
276
277/***
278****
279***/
280
281struct ActiveData
282{
283    GtkTreeSelection  * sel;
284    GArray            * array;
285};
286
287static gboolean
288getSelectedFilesForeach( GtkTreeModel * model,
289                         GtkTreePath  * path UNUSED,
290                         GtkTreeIter  * iter,
291                         gpointer       gdata )
292{
293    const gboolean is_file = !gtk_tree_model_iter_has_child( model, iter );
294
295    if( is_file )
296    {
297        struct ActiveData * data = gdata;
298
299        /* active means: if it's selected or any ancestor is selected */
300
301        gboolean is_active = gtk_tree_selection_iter_is_selected( data->sel, iter );
302
303        if( !is_active )
304        {
305            GtkTreeIter walk = *iter;
306            GtkTreeIter parent;
307            while( !is_active && gtk_tree_model_iter_parent( model, &parent, &walk ) )
308            {
309                is_active = gtk_tree_selection_iter_is_selected( data->sel, &parent );
310                walk = parent;
311            }
312        }
313
314        if( is_active )
315        {
316            unsigned int i;
317            gtk_tree_model_get( model, iter, FC_INDEX, &i, -1 );
318            g_array_append_val( data->array, i );
319        }
320    }
321
322    return FALSE; /* keep walking */
323}
324
325static void
326getSelectedFilesAndDescendants( GtkTreeView * view, GArray * indices )
327{
328    struct ActiveData data;
329
330    data.sel = gtk_tree_view_get_selection( view );
331    data.array  = indices;
332    gtk_tree_model_foreach( gtk_tree_view_get_model( view ),
333                            getSelectedFilesForeach, &data );
334}
335
336struct SubtreeForeachData
337{
338    GArray       * array;
339    GtkTreePath  * path;
340};
341
342static gboolean
343getSubtreeForeach( GtkTreeModel   * model,
344                   GtkTreePath    * path,
345                   GtkTreeIter    * iter,
346                   gpointer         gdata )
347{
348    const gboolean is_file = !gtk_tree_model_iter_has_child( model, iter );
349
350    if( is_file )
351    {
352        struct SubtreeForeachData * data = gdata;
353
354        if( !gtk_tree_path_compare( path, data->path ) || gtk_tree_path_is_descendant( path, data->path ) )
355        {
356            unsigned int i;
357            gtk_tree_model_get( model, iter, FC_INDEX, &i, -1 );
358            g_array_append_val( data->array, i );
359        }
360    }
361
362    return FALSE; /* keep walking */
363}
364
365static void
366getSubtree( GtkTreeView * view, GtkTreePath * path, GArray * indices )
367{
368    struct SubtreeForeachData tmp;
369    tmp.array = indices;
370    tmp.path = path;
371    gtk_tree_model_foreach( gtk_tree_view_get_model( view ), getSubtreeForeach, &tmp );
372}
373
374/* if `path' is a selected row, all selected rows are returned.
375 * otherwise, only the row indicated by `path' is returned.
376 * this is for toggling all the selected rows' states in a batch.
377 */
378static GArray*
379getActiveFilesForPath( GtkTreeView * view, GtkTreePath * path )
380{
381    GtkTreeSelection * sel = gtk_tree_view_get_selection( view );
382    GArray * indices = g_array_new( FALSE, FALSE, sizeof( tr_file_index_t ) );
383
384    if( gtk_tree_selection_path_is_selected( sel, path ) )
385    {
386        /* clicked in a selected row... use the current selection */
387        getSelectedFilesAndDescendants( view, indices );
388    }
389    else
390    {
391        /* clicked OUTSIDE of the selected row... just use the clicked row */
392        getSubtree( view, path, indices );
393    }
394
395    return indices;
396}
397
398/***
399****
400***/
401
402void
403gtr_file_list_clear( GtkWidget * w )
404{
405    gtr_file_list_set_torrent( w, -1 );
406}
407
408struct build_data
409{
410    GtkWidget    * w;
411    tr_torrent   * tor;
412    GtkTreeIter  * iter;
413    GtkTreeStore * store;
414};
415
416struct row_struct
417{
418    uint64_t    length;
419    char      * name;
420    int         index;
421};
422
423static void
424buildTree( GNode * node, gpointer gdata )
425{
426    GtkTreeIter child_iter;
427    struct build_data * build = gdata;
428    struct row_struct *child_data = node->data;
429    const gboolean isLeaf = node->children == NULL;
430
431    const char * mime_type = isLeaf ? gtr_get_mime_type_from_filename( child_data->name ) : DIRECTORY_MIME_TYPE;
432    GdkPixbuf * icon = gtr_get_mime_type_icon( mime_type, GTK_ICON_SIZE_MENU, build->w );
433    const tr_info * inf = tr_torrentInfo( build->tor );
434    const int priority = isLeaf ? inf->files[ child_data->index ].priority : 0;
435    const gboolean enabled = isLeaf ? !inf->files[ child_data->index ].dnd : TRUE;
436#if GTK_CHECK_VERSION(2,10,0)
437    gtk_tree_store_insert_with_values( build->store, &child_iter, build->iter, INT_MAX,
438                                       FC_INDEX, child_data->index,
439                                       FC_LABEL, child_data->name,
440                                       FC_SIZE, child_data->length,
441                                       FC_ICON, icon,
442                                       FC_PRIORITY, priority,
443                                       FC_ENABLED, enabled,
444                                       -1 );
445#else
446    gtk_tree_store_append( build->store, &child_iter, build->iter );
447    gtk_tree_store_set( build->store, &child_iter,
448                        FC_INDEX, child_data->index,
449                        FC_LABEL, child_data->name,
450                        FC_SIZE, child_data->length,
451                        FC_ICON, icon,
452                        FC_PRIORITY, priority,
453                        FC_ENABLED, enabled,
454                        -1 );
455#endif
456
457    if( !isLeaf )
458    {
459        struct build_data b = *build;
460        b.iter = &child_iter;
461        g_node_children_foreach( node, G_TRAVERSE_ALL, buildTree, &b );
462    }
463
464    g_object_unref( icon );
465
466    /* we're done with this node */
467    g_free( child_data->name );
468    g_free( child_data );
469}
470
471static GNode*
472find_child( GNode* parent, const char * name )
473{
474    GNode * child = parent->children;
475    while( child ) {
476        const struct row_struct * child_data = child->data;
477        if( ( *child_data->name == *name ) && !strcmp( child_data->name, name ) )
478            break;
479        child = child->next;
480    }
481    return child;
482}
483
484void
485gtr_file_list_set_torrent( GtkWidget * w, int torrentId )
486{
487    GtkTreeStore * store;
488    FileData * data = g_object_get_data( G_OBJECT( w ), "file-data" );
489
490    /* unset the old fields */
491    clearData( data );
492
493    /* instantiate the model */
494    store = gtk_tree_store_new ( N_FILE_COLS,
495                                 GDK_TYPE_PIXBUF,  /* icon */
496                                 G_TYPE_STRING,    /* label */
497                                 G_TYPE_INT,       /* prog [0..100] */
498                                 G_TYPE_UINT,      /* index */
499                                 G_TYPE_UINT64,    /* size */
500                                 G_TYPE_UINT64,    /* have */
501                                 G_TYPE_INT,       /* priority */
502                                 G_TYPE_INT );     /* dl enabled */
503
504    data->store = store;
505    data->model = GTK_TREE_MODEL( store );
506    data->torrentId = torrentId;
507
508    /* populate the model */
509    if( torrentId > 0 )
510    {
511        tr_session * session = tr_core_session( data->core );
512        tr_torrent * tor = tr_torrentFindFromId( session, torrentId );
513        if( tor != NULL )
514        {
515            tr_file_index_t i;
516            const tr_info * inf = tr_torrentInfo( tor );
517            struct row_struct * root_data;
518            GNode * root;
519            struct build_data build;
520
521            /* build a GNode tree of the files */
522            root_data = g_new0( struct row_struct, 1 );
523            root_data->name = g_strdup( inf->name );
524            root_data->index = -1;
525            root_data->length = 0;
526            root = g_node_new( root_data );
527            for( i=0; i<inf->fileCount; ++i ) {
528                int j;
529                GNode * parent = root;
530                const tr_file * file = &inf->files[i];
531                char ** tokens = g_strsplit( file->name, G_DIR_SEPARATOR_S, 0 );
532                for( j=0; tokens[j]; ++j ) {
533                    const gboolean isLeaf = tokens[j+1] == NULL;
534                    const char * name = tokens[j];
535                    GNode * node = find_child( parent, name );
536                    if( node == NULL ) {
537                        struct row_struct * row = g_new( struct row_struct, 1 );
538                        row->name = g_strdup( name );
539                        row->index = isLeaf ? (int)i : -1;
540                        row->length = isLeaf ? file->length : 0;
541                        node = g_node_new( row );
542                        g_node_append( parent, node );
543                    }
544                    parent = node;
545                }
546                g_strfreev( tokens );
547            }
548
549            /* now, add them to the model */
550            build.w = w;
551            build.tor = tor;
552            build.store = data->store;
553            build.iter = NULL;
554            g_node_children_foreach( root, G_TRAVERSE_ALL, buildTree, &build );
555
556            /* cleanup */
557            g_node_destroy( root );
558            g_free( root_data->name );
559            g_free( root_data );
560        }
561
562        refresh( data );
563        data->timeout_tag = gtr_timeout_add_seconds( SECONDARY_WINDOW_REFRESH_INTERVAL_SECONDS, refreshModel, data );
564    }
565
566    gtk_tree_view_set_model( GTK_TREE_VIEW( data->view ), data->model );
567    gtk_tree_view_expand_all( GTK_TREE_VIEW( data->view ) );
568}
569
570/***
571****
572***/
573
574static void
575renderFilename( GtkTreeViewColumn  * column UNUSED,
576                GtkCellRenderer    * renderer,
577                GtkTreeModel       * model,
578                GtkTreeIter        * iter,
579                gpointer             data UNUSED )
580{
581    char *   filename;
582    char *   str;
583    int64_t  size;
584    char     buf[64];
585
586    gtk_tree_model_get( model, iter, FC_LABEL, &filename,
587                                     FC_SIZE, &size,
588                                     -1 );
589    tr_strlsize( buf, size, sizeof( buf ) );
590    str = g_markup_printf_escaped( "<small>%s (%s)</small>", filename, buf );
591    g_object_set( renderer, "markup", str, NULL );
592    g_free( str );
593    g_free( filename );
594}
595
596static void
597renderDownload( GtkTreeViewColumn  * column UNUSED,
598                GtkCellRenderer    * renderer,
599                GtkTreeModel       * model,
600                GtkTreeIter        * iter,
601                gpointer             data   UNUSED )
602{
603    gboolean enabled;
604    gtk_tree_model_get( model, iter, FC_ENABLED, &enabled, -1 );
605    g_object_set( renderer, "inconsistent", (enabled==MIXED),
606                            "active", (enabled==TRUE),
607                            NULL );
608}
609
610static void
611renderPriority( GtkTreeViewColumn  * column UNUSED,
612                GtkCellRenderer    * renderer,
613                GtkTreeModel       * model,
614                GtkTreeIter        * iter,
615                gpointer             data   UNUSED )
616{
617    int priority;
618    const char * text;
619    gtk_tree_model_get( model, iter, FC_PRIORITY, &priority, -1 );
620    switch( priority ) {
621        case TR_PRI_HIGH:   text = _( "High" ); break;
622        case TR_PRI_NORMAL: text = _( "Normal" ); break;
623        case TR_PRI_LOW:    text = _( "Low" ); break;
624        default:            text = _( "Mixed" ); break;
625    }
626    g_object_set( renderer, "text", text, NULL );
627}
628
629/* build a filename from tr_torrentGetCurrentDir() + the model's FC_LABELs */
630static char*
631buildFilename( tr_torrent * tor, GtkTreeModel * model,
632               GtkTreePath * path, GtkTreeIter * iter )
633{
634    char * ret;
635    GtkTreeIter child;
636    GtkTreeIter parent = *iter;
637    int n = gtk_tree_path_get_depth( path );
638    char ** tokens = g_new0( char*, n + 2 );
639    tokens[0] = g_strdup( tr_torrentGetCurrentDir( tor ) );
640    do {
641        child = parent;
642        gtk_tree_model_get( model, &child, FC_LABEL, &tokens[n--], -1 );
643    } while( gtk_tree_model_iter_parent( model, &parent, &child ) );
644    ret = g_build_filenamev( tokens );
645    g_strfreev( tokens );
646    return ret;
647}
648
649static gboolean
650onRowActivated( GtkTreeView * view, GtkTreePath * path,
651                GtkTreeViewColumn * col UNUSED, gpointer gdata )
652{
653    gboolean handled = FALSE;
654    FileData * data = gdata;
655    tr_torrent * tor = tr_torrentFindFromId( tr_core_session( data->core ), data->torrentId );
656
657    if( tor != NULL )
658    {
659        GtkTreeIter iter;
660        GtkTreeModel * model = gtk_tree_view_get_model( view );
661
662        if( gtk_tree_model_get_iter( model, &iter, path ) )
663        {
664            int prog;
665            char * filename = buildFilename( tor, model, path, &iter );
666            gtk_tree_model_get( model, &iter, FC_PROG, &prog, -1 );
667
668            /* if the file's not done, walk up the directory tree until we find
669             * an ancestor that exists, and open that instead */
670            if( filename && ( prog<100 || !g_file_test( filename, G_FILE_TEST_EXISTS ) ) ) do
671            {
672                char * tmp = g_path_get_dirname( filename );
673                g_free( filename );
674                filename = tmp;
675            }
676            while( filename && *filename && !g_file_test( filename, G_FILE_TEST_EXISTS ) );
677
678            if(( handled = filename && *filename ))
679                gtr_open_file( filename );
680        }
681    }
682
683    return handled;
684}
685
686
687static gboolean
688onViewButtonPressed( GtkWidget * w, GdkEventButton * event, gpointer gdata )
689{
690    FileData * data = gdata;
691    gboolean   handled = FALSE;
692    tr_torrent * tor = tr_torrentFindFromId( tr_core_session( data->core ), data->torrentId );
693
694    if( tor == NULL )
695        return handled;
696
697    if( ( event->type == GDK_BUTTON_PRESS ) && ( event->button == 1 )
698      && !( event->state & ( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) )
699    {
700        GtkTreeView *       view = GTK_TREE_VIEW( w );
701        GtkTreePath *       path;
702        GtkTreeViewColumn * column;
703        int                 cell_x;
704        int                 cell_y;
705        if( gtk_tree_view_get_path_at_pos( view, event->x, event->y,
706                                           &path, &column, &cell_x, &cell_y ) )
707        {
708            const char * column_title = gtk_tree_view_column_get_title( column );
709            const gboolean downloadColumn = !strcmp( column_title, _( "Download" ) );
710            const gboolean priorityColumn = !strcmp( column_title, _( "Priority" ) );
711            if( downloadColumn || priorityColumn )
712            {
713                GArray * a = getActiveFilesForPath( view, path );
714                GtkTreeSelection * sel = gtk_tree_view_get_selection( view );
715                const gboolean isSelected = gtk_tree_selection_path_is_selected( sel, path );
716                GtkTreeModel * model = data->model;
717                GtkTreeIter iter;
718
719                gtk_tree_model_get_iter( model, &iter, path );
720
721                if( priorityColumn )
722                {
723                    int priority;
724
725                    /* get the `priority' state of the clicked row */
726                    gtk_tree_model_get( model, &iter, FC_PRIORITY, &priority, -1 );
727
728                    /* twiddle it to the next state */
729                    switch( priority ) {
730                        case TR_PRI_NORMAL: priority = TR_PRI_HIGH; break;
731                        case TR_PRI_HIGH:   priority = TR_PRI_LOW; break;
732                        default:            priority = TR_PRI_NORMAL; break;
733                    }
734
735                    /* apply that new state to the active files */
736                    tr_torrentSetFilePriorities( tor,
737                                                 (tr_file_index_t*)a->data,
738                                                 (tr_file_index_t)a->len,
739                                                 priority );
740                }
741                else if( downloadColumn )
742                {
743                    int enabled;
744
745                    /* get the `enabled' state of the clicked row */
746                    gtk_tree_model_get( model, &iter, FC_ENABLED, &enabled, -1 );
747
748                    /* twiddle it to the next state */
749                    enabled = !enabled;
750
751                    /* apply that new state to the active files */
752                    tr_torrentSetFileDLs( tor,
753                                          (tr_file_index_t*)a->data,
754                                          (tr_file_index_t)a->len,
755                                          enabled );
756                }
757
758                refresh( data );
759
760                /* the click was meant to change the priority or enabled state,
761                   not to alter which rows were selected, so don't pass this
762                   event on to the other handlers. */
763                handled = isSelected;
764
765                /* cleanup */
766                g_array_free( a, TRUE );
767            }
768
769            gtk_tree_path_free( path );
770        }
771    }
772
773    return handled;
774}
775
776GtkWidget *
777gtr_file_list_new( TrCore * core, int torrentId )
778{
779    int width;
780    GtkWidget * ret;
781    GtkWidget * view;
782    GtkWidget * scroll;
783    GtkCellRenderer * rend;
784    GtkTreeSelection * sel;
785    GtkTreeViewColumn * col;
786    GtkTreeView * tree_view;
787    const char * title;
788    PangoLayout * pango_layout;
789    FileData * data = g_new0( FileData, 1 );
790
791    data->core = core;
792
793    /* create the view */
794    view = gtk_tree_view_new( );
795    tree_view = GTK_TREE_VIEW( view );
796    gtk_tree_view_set_rules_hint( tree_view, TRUE );
797    gtk_container_set_border_width( GTK_CONTAINER( view ), GUI_PAD_BIG );
798    g_signal_connect( view, "button-press-event",
799                      G_CALLBACK( onViewButtonPressed ), data );
800    g_signal_connect( view, "row_activated",
801                      G_CALLBACK( onRowActivated ), data );
802    g_signal_connect( view, "button-release-event",
803                      G_CALLBACK( on_tree_view_button_released ), NULL );
804
805
806    /* set up view */
807    sel = gtk_tree_view_get_selection( tree_view );
808    gtk_tree_selection_set_mode( sel, GTK_SELECTION_MULTIPLE );
809    gtk_tree_view_expand_all( tree_view );
810    gtk_tree_view_set_search_column( tree_view, FC_LABEL );
811
812    /* add file column */
813
814    col = GTK_TREE_VIEW_COLUMN ( g_object_new ( GTK_TYPE_TREE_VIEW_COLUMN,
815                                                "expand", TRUE,
816                                                "title", _( "File" ),
817                                                NULL ) );
818    gtk_tree_view_column_set_resizable( col, TRUE );
819    rend = gtk_cell_renderer_pixbuf_new( );
820    gtk_tree_view_column_pack_start( col, rend, FALSE );
821    gtk_tree_view_column_add_attribute( col, rend, "pixbuf", FC_ICON );
822    /* add text renderer */
823    rend = gtk_cell_renderer_text_new( );
824    g_object_set( rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL );
825    gtk_tree_view_column_pack_start( col, rend, TRUE );
826    gtk_tree_view_column_set_cell_data_func( col, rend, renderFilename, NULL, NULL );
827    gtk_tree_view_column_set_sort_column_id( col, FC_LABEL );
828    gtk_tree_view_append_column( tree_view, col );
829
830    /* add "progress" column */
831    title = _( "Progress" );
832    pango_layout = gtk_widget_create_pango_layout( view, title );
833    pango_layout_get_pixel_size( pango_layout, &width, NULL );
834    width += 30; /* room for the sort indicator */
835    g_object_unref( G_OBJECT( pango_layout ) );
836    rend = gtk_cell_renderer_progress_new( );
837    col = gtk_tree_view_column_new_with_attributes( title, rend, "value", FC_PROG, NULL );
838    gtk_tree_view_column_set_fixed_width( col, width );
839    gtk_tree_view_column_set_sizing( col, GTK_TREE_VIEW_COLUMN_FIXED );
840    gtk_tree_view_column_set_sort_column_id( col, FC_PROG );
841    gtk_tree_view_append_column( tree_view, col );
842
843    /* add "enabled" column */
844    title = _( "Download" );
845    pango_layout = gtk_widget_create_pango_layout( view, title );
846    pango_layout_get_pixel_size( pango_layout, &width, NULL );
847    width += 30; /* room for the sort indicator */
848    g_object_unref( G_OBJECT( pango_layout ) );
849    rend = gtk_cell_renderer_toggle_new( );
850    col = gtk_tree_view_column_new_with_attributes( title, rend, NULL );
851    gtk_tree_view_column_set_fixed_width( col, width );
852    gtk_tree_view_column_set_sizing( col, GTK_TREE_VIEW_COLUMN_FIXED );
853    gtk_tree_view_column_set_cell_data_func( col, rend, renderDownload, NULL, NULL );
854    gtk_tree_view_column_set_sort_column_id( col, FC_ENABLED );
855    gtk_tree_view_append_column( tree_view, col );
856
857    /* add priority column */
858    title = _( "Priority" );
859    pango_layout = gtk_widget_create_pango_layout( view, title );
860    pango_layout_get_pixel_size( pango_layout, &width, NULL );
861    width += 30; /* room for the sort indicator */
862    g_object_unref( G_OBJECT( pango_layout ) );
863    rend = gtk_cell_renderer_text_new( );
864    g_object_set( rend, "xalign", (gfloat)0.5, "yalign", (gfloat)0.5, NULL );
865    col = gtk_tree_view_column_new_with_attributes( title, rend, NULL );
866    gtk_tree_view_column_set_fixed_width( col, width );
867    gtk_tree_view_column_set_sizing( col, GTK_TREE_VIEW_COLUMN_FIXED );
868    gtk_tree_view_column_set_sort_column_id( col, FC_PRIORITY );
869    gtk_tree_view_column_set_cell_data_func( col, rend, renderPriority, NULL, NULL );
870    gtk_tree_view_append_column( tree_view, col );
871
872    /* create the scrolled window and stick the view in it */
873    scroll = gtk_scrolled_window_new( NULL, NULL );
874    gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scroll ),
875                                    GTK_POLICY_AUTOMATIC,
876                                    GTK_POLICY_AUTOMATIC );
877    gtk_scrolled_window_set_shadow_type ( GTK_SCROLLED_WINDOW( scroll ),
878                                          GTK_SHADOW_IN );
879    gtk_container_add( GTK_CONTAINER( scroll ), view );
880    gtk_widget_set_size_request ( scroll, -1, 200 );
881
882    ret = scroll;
883    data->view = view;
884    data->top = scroll;
885    g_object_set_data_full( G_OBJECT( ret ), "file-data", data, freeData );
886    gtr_file_list_set_torrent( ret, torrentId );
887
888    return ret;
889}
Note: See TracBrowser for help on using the repository browser.