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

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

(trunk) make tr_torrentName() a public function.

This has been a private function in libtransmission for awhile now but it makes more sense as a public function.

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