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

Last change on this file since 12277 was 12277, checked in by jordan, 11 years ago

(trunk gtk) remove "dead nested assignment" wart detected by clang static analyzer

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