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

Last change on this file since 5329 was 5329, checked in by charles, 14 years ago

fix bug that caused very large torrents to crash Transmission

  • Property svn:keywords set to Date Rev Author Id
File size: 18.5 KB
Line 
1/******************************************************************************
2 * $Id: file-list.c 5329 2008-03-22 18:10:59Z charles $
3 *
4 * Copyright (c) 2005-2008 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include <stddef.h>
26#include <stdio.h>
27#include <stdlib.h>
28#include <glib/gi18n.h>
29#include <gtk/gtk.h>
30
31#include <libtransmission/transmission.h>
32
33#include "file-list.h"
34#include "hig.h"
35
36enum
37{
38  FC_STOCK,
39  FC_LABEL,
40  FC_PROG,
41  FC_KEY,
42  FC_INDEX,
43  FC_SIZE,
44  FC_PRIORITY,
45  FC_ENABLED,
46  N_FILE_COLS
47};
48
49typedef struct
50{
51  TrTorrent * gtor;
52  GtkWidget * top;
53  GtkWidget * view;
54  GtkTreeModel * model; /* same object as store, but recast */
55  GtkTreeStore * store; /* same object as model, but recast */
56  guint timeout_tag;
57}
58FileData;
59
60static const char*
61priorityToString( const int priority )
62{
63    switch( priority ) {
64        /* this refers to priority */
65        case TR_PRI_HIGH:   return _("High");
66        /* this refers to priority */
67        case TR_PRI_NORMAL: return _("Normal");
68        /* this refers to priority */
69        case TR_PRI_LOW:    return _("Low");
70        default:            return "BUG!";
71    }
72}
73
74static tr_priority_t
75stringToPriority( const char* str )
76{
77    if( !strcmp( str, priorityToString( TR_PRI_HIGH ) ) ) return TR_PRI_HIGH;
78    if( !strcmp( str, priorityToString( TR_PRI_LOW ) ) ) return TR_PRI_LOW;
79    return TR_PRI_NORMAL;
80}
81
82static void
83parsepath( const tr_torrent  * tor,
84           GtkTreeStore      * store,
85           GtkTreeIter       * ret,
86           const char        * path,
87           int                 index,
88           uint64_t            size )
89{
90    GtkTreeModel * model;
91    GtkTreeIter  * parent, start, iter;
92    char         * file, * lower, * mykey, *escaped=0;
93    const char   * stock;
94    int            priority = 0;
95    gboolean       enabled = TRUE;
96
97    model  = GTK_TREE_MODEL( store );
98    parent = NULL;
99    file   = g_path_get_basename( path );
100    if( 0 != strcmp( file, path ) )
101    {
102        char * dir = g_path_get_dirname( path );
103        parsepath( tor, store, &start, dir, index, size );
104        parent = &start;
105        g_free( dir );
106    }
107
108    lower = g_utf8_casefold( file, -1 );
109    mykey = g_utf8_collate_key( lower, -1 );
110    if( gtk_tree_model_iter_children( model, &iter, parent ) ) do
111    {
112        gboolean stop;
113        char * modelkey;
114        gtk_tree_model_get( model, &iter, FC_KEY, &modelkey, -1 );
115        stop = (modelkey!=NULL) && !strcmp(mykey,modelkey);
116        g_free (modelkey);
117        if (stop) goto done;
118    }
119    while( gtk_tree_model_iter_next( model, &iter ) );
120
121    gtk_tree_store_append( store, &iter, parent );
122    if( NULL == ret )
123    {
124        stock = GTK_STOCK_FILE;
125    }
126    else
127    {
128        stock = GTK_STOCK_DIRECTORY;
129        size  = 0;
130        index = -1;
131    }
132
133    if (index != -1) {
134        priority = tr_torrentGetFilePriority( tor, index );
135        enabled  = tr_torrentGetFileDL( tor, index );
136    }
137
138    escaped = g_markup_escape_text (file, -1); 
139    gtk_tree_store_set( store, &iter, FC_INDEX, index,
140                                      FC_LABEL, escaped,
141                                      FC_KEY, mykey,
142                                      FC_STOCK, stock,
143                                      FC_PRIORITY, priorityToString(priority),
144                                      FC_ENABLED, enabled,
145                                      FC_SIZE, size, -1 );
146  done:
147    g_free( escaped );
148    g_free( mykey );
149    g_free( lower );
150    g_free( file );
151    if( NULL != ret )
152      *ret = iter;
153}
154
155static uint64_t
156getdirtotals( GtkTreeStore * store, GtkTreeIter * parent )
157{
158    GtkTreeModel * model;
159    GtkTreeIter    iter;
160    uint64_t       mysize, subsize;
161    char         * name, * label;
162
163    model  = GTK_TREE_MODEL( store );
164    mysize = 0;
165    if( gtk_tree_model_iter_children( model, &iter, parent ) ) do
166    {
167         char sizeStr[64];
168        if( gtk_tree_model_iter_has_child( model, &iter ) )
169        {
170            subsize = getdirtotals( store, &iter );
171            gtk_tree_store_set( store, &iter, FC_SIZE, subsize, -1 );
172        }
173        else
174        {
175            gtk_tree_model_get( model, &iter, FC_SIZE, &subsize, -1 );
176        }
177        gtk_tree_model_get( model, &iter, FC_LABEL, &name, -1 );
178        tr_strlsize( sizeStr, subsize, sizeof( sizeStr ) );
179        label = g_markup_printf_escaped( "<small>%s (%s)</small>",
180                                          name, sizeStr );
181        g_free( name );
182        gtk_tree_store_set( store, &iter, FC_LABEL, label, -1 );
183        g_free( label );
184        mysize += subsize;
185    }
186    while( gtk_tree_model_iter_next( model, &iter ) );
187
188    return mysize;
189}
190
191static void
192updateprogress( GtkTreeModel   * model,
193                GtkTreeStore   * store,
194                GtkTreeIter    * parent,
195                tr_file_stat   * fileStats,
196                guint64        * setmeGotSize,
197                guint64        * setmeTotalSize)
198{
199    GtkTreeIter iter;
200    guint64 gotSize=0, totalSize=0;
201
202    if( gtk_tree_model_iter_children( model, &iter, parent ) ) do
203    {
204        int oldProg, newProg;
205        guint64 subGot, subTotal;
206
207        if (gtk_tree_model_iter_has_child( model, &iter ) )
208        {
209            updateprogress( model, store, &iter, fileStats, &subGot, &subTotal);
210        }
211        else
212        {
213            int index, percent;
214            gtk_tree_model_get( model, &iter, FC_SIZE, &subTotal,
215                                              FC_INDEX, &index,
216                                              -1 );
217            g_assert( 0 <= index );
218            percent = (int)(fileStats[index].progress * 100.0); /* [0...100] */
219            subGot = (guint64)(subTotal * percent/100.0);
220        }
221
222        if (!subTotal) subTotal = 1; /* avoid div by zero */
223        g_assert (subGot <= subTotal);
224
225        /* why not just set it every time?
226           because that causes the "priorities" combobox to pop down */
227        gtk_tree_model_get (model, &iter, FC_PROG, &oldProg, -1);
228        newProg = (int)(100.0*subGot/subTotal);
229        if (oldProg != newProg)
230          gtk_tree_store_set (store, &iter,
231                              FC_PROG, (int)(100.0*subGot/subTotal), -1);
232
233        gotSize += subGot;
234        totalSize += subTotal;
235    }
236    while( gtk_tree_model_iter_next( model, &iter ) );
237
238    *setmeGotSize = gotSize;
239    *setmeTotalSize = totalSize;
240}
241
242static GtkTreeModel*
243priority_model_new (void)
244{
245  GtkTreeIter iter;
246  GtkListStore * store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT);
247  gtk_list_store_append (store, &iter);
248  gtk_list_store_set (store, &iter, 0, priorityToString( TR_PRI_HIGH ), 1, TR_PRI_HIGH, -1);
249  gtk_list_store_append (store, &iter);
250  gtk_list_store_set (store, &iter, 0, priorityToString( TR_PRI_NORMAL ), 1, TR_PRI_NORMAL, -1);
251  gtk_list_store_append (store, &iter);
252  gtk_list_store_set (store, &iter, 0, priorityToString( TR_PRI_LOW ), 1, TR_PRI_LOW, -1);
253  return GTK_TREE_MODEL (store);
254}
255
256static void
257subtree_walk_dnd( GtkTreeStore   * store,
258                  GtkTreeIter    * iter,
259                  tr_torrent     * tor,
260                  gboolean         enabled,
261                  GArray         * indices )
262{
263    int index;
264    GtkTreeIter child;
265 
266    /* update this node */ 
267    gtk_tree_model_get( GTK_TREE_MODEL(store), iter, FC_INDEX, &index, -1  );
268    if (index >= 0) {
269      tr_file_index_t fi = index;
270      g_array_append_val( indices, fi );
271    }
272    gtk_tree_store_set( store, iter, FC_ENABLED, enabled, -1 );
273
274    /* visit the children */
275    if( gtk_tree_model_iter_children( GTK_TREE_MODEL(store), &child, iter ) ) do
276      subtree_walk_dnd( store, &child, tor, enabled, indices );
277    while( gtk_tree_model_iter_next( GTK_TREE_MODEL(store), &child ) );
278}
279
280static void
281set_subtree_dnd( GtkTreeStore   * store,
282                 GtkTreeIter    * iter,
283                 tr_torrent     * tor,
284                 gboolean         enabled )
285{
286    GArray * indices = g_array_new( FALSE, FALSE, sizeof(tr_file_index_t) );
287    subtree_walk_dnd( store, iter, tor, enabled, indices );
288    tr_torrentSetFileDLs( tor, (tr_file_index_t*)indices->data, (tr_file_index_t)indices->len, enabled );
289    g_array_free( indices, TRUE );
290}
291
292static void
293subtree_walk_priority( GtkTreeStore   * store,
294                       GtkTreeIter    * iter,
295                       tr_torrent     * tor,
296                       int              priority,
297                       GArray         * indices )
298{
299    int index;
300    GtkTreeIter child;
301
302    /* update this node */ 
303    gtk_tree_model_get( GTK_TREE_MODEL(store), iter, FC_INDEX, &index, -1  );
304    if( index >= 0 )
305        g_array_append_val( indices, index );
306    gtk_tree_store_set( store, iter, FC_PRIORITY, priorityToString(priority), -1 );
307
308    /* visit the children */
309    if( gtk_tree_model_iter_children( GTK_TREE_MODEL(store), &child, iter ) ) do
310        subtree_walk_priority( store, &child, tor, priority, indices );
311    while( gtk_tree_model_iter_next( GTK_TREE_MODEL(store), &child ) );
312
313}
314
315static void
316set_subtree_priority( GtkTreeStore * store,
317                      GtkTreeIter * iter,
318                      tr_torrent * tor,
319                      int priority )
320{
321    GArray * indices = g_array_new( FALSE, FALSE, sizeof(int) );
322    subtree_walk_priority( store, iter, tor, priority, indices );
323    tr_torrentSetFilePriorities( tor, (tr_file_index_t*)indices->data, (tr_file_index_t)indices->len, priority );
324    g_array_free( indices, TRUE );
325}
326
327static void
328priority_changed_cb (GtkCellRendererText * cell UNUSED,
329                     const gchar         * path,
330                     const gchar         * value,
331                     void                * file_data)
332{
333    GtkTreeIter iter;
334    FileData * d = file_data;
335    if (gtk_tree_model_get_iter_from_string (d->model, &iter, path))
336    {
337        tr_torrent  * tor = tr_torrent_handle( d->gtor );
338        const tr_priority_t priority = stringToPriority( value );
339        set_subtree_priority( d->store, &iter, tor, priority );
340    }
341}
342
343static void
344enabled_toggled (GtkCellRendererToggle  * cell UNUSED,
345                 const gchar            * path_str,
346                 gpointer                 data_gpointer)
347{
348  FileData * data = data_gpointer;
349  GtkTreePath * path = gtk_tree_path_new_from_string( path_str );
350  GtkTreeModel * model = data->model;
351  GtkTreeIter iter;
352  gboolean enabled;
353
354  gtk_tree_model_get_iter( model, &iter, path );
355  gtk_tree_model_get( model, &iter, FC_ENABLED, &enabled, -1 );
356  enabled = !enabled;
357  set_subtree_dnd( GTK_TREE_STORE(model),
358                   &iter,
359                   tr_torrent_handle( data->gtor ),
360                   enabled );
361
362  gtk_tree_path_free( path );
363}
364
365static gboolean
366refreshModel( gpointer gdata )
367{
368    FileData * data  = gdata;
369
370    g_assert( data != NULL );
371
372    if( data->gtor )
373    {
374        guint64 foo, bar;
375        tr_file_index_t fileCount;
376        tr_torrent * tor;
377        tr_file_stat * fileStats;
378
379        tor = tr_torrent_handle( data->gtor );
380        fileCount = 0;
381        fileStats = tr_torrentFiles( tor, &fileCount );
382        updateprogress (data->model, data->store, NULL, fileStats, &foo, &bar);
383        tr_torrentFilesFree( fileStats, fileCount );
384    }
385
386    return TRUE;
387}
388
389static void
390clearData( FileData * data )
391{
392    data->gtor = NULL;
393
394    if( data->timeout_tag ) {
395        g_source_remove( data->timeout_tag );
396        data->timeout_tag = 0;
397    }
398}
399
400void
401file_list_set_torrent( GtkWidget * w, TrTorrent * gtor )
402{
403    GtkTreeStore        * store;
404    FileData            * data;
405
406    data = g_object_get_data( G_OBJECT( w ), "file-data" );
407
408    /* unset the old fields */
409    clearData( data );
410
411    /* instantiate the model */
412    store = gtk_tree_store_new ( N_FILE_COLS,
413                                 G_TYPE_STRING,    /* stock */
414                                 G_TYPE_STRING,    /* label */
415                                 G_TYPE_INT,       /* prog [0..100] */
416                                 G_TYPE_STRING,    /* key */
417                                 G_TYPE_INT,       /* index */
418                                 G_TYPE_UINT64,    /* size */
419                                 G_TYPE_STRING,    /* priority */
420                                 G_TYPE_BOOLEAN ); /* dl enabled */
421    data->store = store;
422    data->model = GTK_TREE_MODEL( store );
423    data->gtor = gtor;
424
425
426    /* populate the model */
427    if( gtor )
428    {
429        tr_file_index_t i;
430        const tr_info * inf = tr_torrent_info( gtor );
431        tr_torrent * tor = tr_torrent_handle( gtor );
432
433        for( i=0; inf && i<inf->fileCount; ++i )
434        {
435            const char * path = inf->files[i].name;
436            const char * base = g_path_is_absolute( path ) ? g_path_skip_root( path ) : path;
437            parsepath( tor, store, NULL, base, i, inf->files[i].length );
438        }
439
440        getdirtotals( store, NULL );
441
442        data->timeout_tag = g_timeout_add( 1000, refreshModel, data );
443    }
444
445    gtk_tree_view_set_model( GTK_TREE_VIEW( data->view ), GTK_TREE_MODEL( store ) );
446    gtk_tree_view_expand_all( GTK_TREE_VIEW( data->view ) );
447}
448
449static void
450freeData( gpointer gdata )
451{
452    FileData * data = gdata;
453    clearData( data );
454    g_free( data );
455}
456
457GtkWidget *
458file_list_new( TrTorrent * gtor )
459{
460    GtkWidget           * ret;
461    FileData            * data;
462    GtkWidget           * view, * scroll;
463    GtkCellRenderer     * rend;
464    GtkCellRenderer     * priority_rend;
465    GtkCellRenderer     * enabled_rend;
466    GtkTreeViewColumn   * col;
467    GtkTreeSelection    * sel;
468    GtkTreeModel        * model;
469
470    /* create the view */
471    view = gtk_tree_view_new( );
472    gtk_container_set_border_width( GTK_CONTAINER( view ), GUI_PAD_BIG );
473
474    /* add file column */
475   
476    col = GTK_TREE_VIEW_COLUMN (g_object_new (GTK_TYPE_TREE_VIEW_COLUMN,
477        "expand", TRUE,
478    /* Translators: this is a column header in Files tab, Details dialog;
479       Don't include the prefix "filedetails|" in the translation. */                                 
480        "title", Q_("filedetails|File"),
481        NULL));
482    rend = gtk_cell_renderer_pixbuf_new();
483    gtk_tree_view_column_pack_start( col, rend, FALSE );
484    gtk_tree_view_column_add_attribute( col, rend, "stock-id", FC_STOCK );
485    /* add text renderer */
486    rend = gtk_cell_renderer_text_new();
487    g_object_set( rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL );
488    gtk_tree_view_column_pack_start( col, rend, TRUE );
489    gtk_tree_view_column_add_attribute( col, rend, "markup", FC_LABEL );
490    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
491    /* add progress column */
492    rend = gtk_cell_renderer_progress_new();
493    /* Translators: this is a column header in Files tab, Details dialog;
494       Don't include the prefix "filedetails|" in the translation. */ 
495    col = gtk_tree_view_column_new_with_attributes (Q_("filedetails|Progress"),
496                                                    rend,
497                                                    "value", FC_PROG,
498                                                    NULL);
499    gtk_tree_view_column_set_sort_column_id( col, FC_PROG );
500    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
501    /* set up view */
502    sel = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) );
503    gtk_tree_view_expand_all( GTK_TREE_VIEW( view ) );
504    gtk_tree_view_set_search_column( GTK_TREE_VIEW( view ), FC_LABEL );
505
506    /* add "download" checkbox column */
507    col = gtk_tree_view_column_new ();
508    gtk_tree_view_column_set_sort_column_id( col, FC_ENABLED );
509    rend = enabled_rend = gtk_cell_renderer_toggle_new  ();
510    /* Translators: this is a column header in Files tab, Details dialog;
511       Don't include the prefix "filedetails|" in the translation.
512       Please note the items for this column are checkboxes (yes/no) */ 
513    col = gtk_tree_view_column_new_with_attributes (Q_("filedetails|Download"),
514                                                    rend,
515                                                    "active", FC_ENABLED,
516                                                    NULL);
517    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
518
519    /* add priority column */
520    model = priority_model_new ();
521    col = gtk_tree_view_column_new ();
522    gtk_tree_view_column_set_sort_column_id( col, FC_PRIORITY );
523    /* Translators: this is a column header in Files tab, Details dialog;
524       Don't include the prefix "filedetails|" in the translation. */ 
525    gtk_tree_view_column_set_title (col, Q_("filedetails|Priority"));
526    rend = priority_rend = gtk_cell_renderer_combo_new ();
527    gtk_tree_view_column_pack_start (col, rend, TRUE);
528    g_object_set (G_OBJECT(rend), "model", model,
529                                  "editable", TRUE,
530                                  "has-entry", FALSE,
531                                  "text-column", 0,
532                                  NULL);
533    g_object_unref (G_OBJECT(model));
534    gtk_tree_view_column_add_attribute (col, rend, "text", FC_PRIORITY);
535    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
536
537    /* create the scrolled window and stick the view in it */
538    scroll = gtk_scrolled_window_new( NULL, NULL );
539    gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scroll ),
540                                    GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC );
541    gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(scroll),
542                                         GTK_SHADOW_IN);
543    gtk_container_add( GTK_CONTAINER( scroll ), view );
544    gtk_widget_set_size_request (scroll, 0u, 200u);
545
546    ret = scroll;
547    data = g_new0( FileData, 1 );
548    data->view = view;
549    data->top = scroll;
550    g_signal_connect (G_OBJECT(priority_rend), "edited", G_CALLBACK(priority_changed_cb), data);
551    g_signal_connect(enabled_rend, "toggled", G_CALLBACK(enabled_toggled), data );
552    g_object_set_data_full( G_OBJECT( ret ), "file-data", data, freeData );
553    file_list_set_torrent( ret, gtor );
554
555    return ret;
556}
Note: See TracBrowser for help on using the repository browser.