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

Last change on this file since 13999 was 13999, checked in by jordan, 9 years ago

(gtk) #5284 'file tree should show the wanted size of the subtree, not the entire size': done.

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