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

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

(trunk gtk) #3866 "Popup menu for file list manipulation" -- patch by ijuxda + random bugs by me

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