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

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

strings work: (1) folding similar strings together for easier tranlation (2) gtk: use consistent shortcuts/phrases/terminology between the preferences and "open torrent" dialogs (3) promote the port forwarding messages from Debug to Info as per BMW's request

  • Property svn:keywords set to Date Rev Author Id
File size: 18.4 KB
Line 
1/******************************************************************************
2 * $Id: file-list.c 5290 2008-03-18 19:14:21Z 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      g_array_append_val( indices, index );
270    gtk_tree_store_set( store, iter, FC_ENABLED, enabled, -1 );
271
272    /* visit the children */
273    if( gtk_tree_model_iter_children( GTK_TREE_MODEL(store), &child, iter ) ) do
274      subtree_walk_dnd( store, &child, tor, enabled, indices );
275    while( gtk_tree_model_iter_next( GTK_TREE_MODEL(store), &child ) );
276}
277
278static void
279set_subtree_dnd( GtkTreeStore   * store,
280                 GtkTreeIter    * iter,
281                 tr_torrent     * tor,
282                 gboolean         enabled )
283{
284    GArray * indices = g_array_new( FALSE, FALSE, sizeof(int) );
285    subtree_walk_dnd( store, iter, tor, enabled, indices );
286    tr_torrentSetFileDLs( tor, (int*)indices->data, (int)indices->len, enabled );
287    g_array_free( indices, TRUE );
288}
289
290static void
291subtree_walk_priority( GtkTreeStore   * store,
292                       GtkTreeIter    * iter,
293                       tr_torrent     * tor,
294                       int              priority,
295                       GArray         * indices )
296{
297    int index;
298    GtkTreeIter child;
299
300    /* update this node */ 
301    gtk_tree_model_get( GTK_TREE_MODEL(store), iter, FC_INDEX, &index, -1  );
302    if( index >= 0 )
303        g_array_append_val( indices, index );
304    gtk_tree_store_set( store, iter, FC_PRIORITY, priorityToString(priority), -1 );
305
306    /* visit the children */
307    if( gtk_tree_model_iter_children( GTK_TREE_MODEL(store), &child, iter ) ) do
308        subtree_walk_priority( store, &child, tor, priority, indices );
309    while( gtk_tree_model_iter_next( GTK_TREE_MODEL(store), &child ) );
310
311}
312
313static void
314set_subtree_priority( GtkTreeStore * store,
315                      GtkTreeIter * iter,
316                      tr_torrent * tor,
317                      int priority )
318{
319    GArray * indices = g_array_new( FALSE, FALSE, sizeof(int) );
320    subtree_walk_priority( store, iter, tor, priority, indices );
321    tr_torrentSetFilePriorities( tor, (int*)indices->data, (int)indices->len, priority );
322    g_array_free( indices, TRUE );
323}
324
325static void
326priority_changed_cb (GtkCellRendererText * cell UNUSED,
327                     const gchar         * path,
328                     const gchar         * value,
329                     void                * file_data)
330{
331    GtkTreeIter iter;
332    FileData * d = file_data;
333    if (gtk_tree_model_get_iter_from_string (d->model, &iter, path))
334    {
335        tr_torrent  * tor = tr_torrent_handle( d->gtor );
336        const tr_priority_t priority = stringToPriority( value );
337        set_subtree_priority( d->store, &iter, tor, priority );
338    }
339}
340
341static void
342enabled_toggled (GtkCellRendererToggle  * cell UNUSED,
343                 const gchar            * path_str,
344                 gpointer                 data_gpointer)
345{
346  FileData * data = data_gpointer;
347  GtkTreePath * path = gtk_tree_path_new_from_string( path_str );
348  GtkTreeModel * model = data->model;
349  GtkTreeIter iter;
350  gboolean enabled;
351
352  gtk_tree_model_get_iter( model, &iter, path );
353  gtk_tree_model_get( model, &iter, FC_ENABLED, &enabled, -1 );
354  enabled = !enabled;
355  set_subtree_dnd( GTK_TREE_STORE(model),
356                   &iter,
357                   tr_torrent_handle( data->gtor ),
358                   enabled );
359
360  gtk_tree_path_free( path );
361}
362
363static gboolean
364refreshModel( gpointer gdata )
365{
366    FileData * data  = gdata;
367
368    g_assert( data != NULL );
369
370    if( data->gtor )
371    {
372        guint64 foo, bar;
373        int fileCount;
374        tr_torrent * tor;
375        tr_file_stat * fileStats;
376
377        tor = tr_torrent_handle( data->gtor );
378        fileCount = 0;
379        fileStats = tr_torrentFiles( tor, &fileCount );
380        updateprogress (data->model, data->store, NULL, fileStats, &foo, &bar);
381        tr_torrentFilesFree( fileStats, fileCount );
382    }
383
384    return TRUE;
385}
386
387static void
388clearData( FileData * data )
389{
390    data->gtor = NULL;
391
392    if( data->timeout_tag ) {
393        g_source_remove( data->timeout_tag );
394        data->timeout_tag = 0;
395    }
396}
397
398void
399file_list_set_torrent( GtkWidget * w, TrTorrent * gtor )
400{
401    GtkTreeStore        * store;
402    FileData            * data;
403
404    data = g_object_get_data( G_OBJECT( w ), "file-data" );
405
406    /* unset the old fields */
407    clearData( data );
408
409    /* instantiate the model */
410    store = gtk_tree_store_new ( N_FILE_COLS,
411                                 G_TYPE_STRING,    /* stock */
412                                 G_TYPE_STRING,    /* label */
413                                 G_TYPE_INT,       /* prog [0..100] */
414                                 G_TYPE_STRING,    /* key */
415                                 G_TYPE_INT,       /* index */
416                                 G_TYPE_UINT64,    /* size */
417                                 G_TYPE_STRING,    /* priority */
418                                 G_TYPE_BOOLEAN ); /* dl enabled */
419    data->store = store;
420    data->model = GTK_TREE_MODEL( store );
421    data->gtor = gtor;
422
423
424    /* populate the model */
425    if( gtor )
426    {
427        int i;
428        const tr_info * inf = tr_torrent_info( gtor );
429        tr_torrent * tor = tr_torrent_handle( gtor );
430
431        for( i=0; inf && i<inf->fileCount; ++i )
432        {
433            const char * path = inf->files[i].name;
434            const char * base = g_path_is_absolute( path ) ? g_path_skip_root( path ) : path;
435            parsepath( tor, store, NULL, base, i, inf->files[i].length );
436        }
437
438        getdirtotals( store, NULL );
439
440        data->timeout_tag = g_timeout_add( 1000, refreshModel, data );
441    }
442
443    gtk_tree_view_set_model( GTK_TREE_VIEW( data->view ), GTK_TREE_MODEL( store ) );
444    gtk_tree_view_expand_all( GTK_TREE_VIEW( data->view ) );
445}
446
447static void
448freeData( gpointer gdata )
449{
450    FileData * data = gdata;
451    clearData( data );
452    g_free( data );
453}
454
455GtkWidget *
456file_list_new( TrTorrent * gtor )
457{
458    GtkWidget           * ret;
459    FileData            * data;
460    GtkWidget           * view, * scroll;
461    GtkCellRenderer     * rend;
462    GtkCellRenderer     * priority_rend;
463    GtkCellRenderer     * enabled_rend;
464    GtkTreeViewColumn   * col;
465    GtkTreeSelection    * sel;
466    GtkTreeModel        * model;
467
468    /* create the view */
469    view = gtk_tree_view_new( );
470    gtk_container_set_border_width( GTK_CONTAINER( view ), GUI_PAD_BIG );
471
472    /* add file column */
473   
474    col = GTK_TREE_VIEW_COLUMN (g_object_new (GTK_TYPE_TREE_VIEW_COLUMN,
475        "expand", TRUE,
476    /* Translators: this is a column header in Files tab, Details dialog;
477       Don't include the prefix "filedetails|" in the translation. */                                 
478        "title", Q_("filedetails|File"),
479        NULL));
480    rend = gtk_cell_renderer_pixbuf_new();
481    gtk_tree_view_column_pack_start( col, rend, FALSE );
482    gtk_tree_view_column_add_attribute( col, rend, "stock-id", FC_STOCK );
483    /* add text renderer */
484    rend = gtk_cell_renderer_text_new();
485    g_object_set( rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL );
486    gtk_tree_view_column_pack_start( col, rend, TRUE );
487    gtk_tree_view_column_add_attribute( col, rend, "markup", FC_LABEL );
488    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
489    /* add progress column */
490    rend = gtk_cell_renderer_progress_new();
491    /* Translators: this is a column header in Files tab, Details dialog;
492       Don't include the prefix "filedetails|" in the translation. */ 
493    col = gtk_tree_view_column_new_with_attributes (Q_("filedetails|Progress"),
494                                                    rend,
495                                                    "value", FC_PROG,
496                                                    NULL);
497    gtk_tree_view_column_set_sort_column_id( col, FC_PROG );
498    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
499    /* set up view */
500    sel = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) );
501    gtk_tree_view_expand_all( GTK_TREE_VIEW( view ) );
502    gtk_tree_view_set_search_column( GTK_TREE_VIEW( view ), FC_LABEL );
503
504    /* add "download" checkbox column */
505    col = gtk_tree_view_column_new ();
506    gtk_tree_view_column_set_sort_column_id( col, FC_ENABLED );
507    rend = enabled_rend = gtk_cell_renderer_toggle_new  ();
508    /* Translators: this is a column header in Files tab, Details dialog;
509       Don't include the prefix "filedetails|" in the translation.
510       Please note the items for this column are checkboxes (yes/no) */ 
511    col = gtk_tree_view_column_new_with_attributes (Q_("filedetails|Download"),
512                                                    rend,
513                                                    "active", FC_ENABLED,
514                                                    NULL);
515    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
516
517    /* add priority column */
518    model = priority_model_new ();
519    col = gtk_tree_view_column_new ();
520    gtk_tree_view_column_set_sort_column_id( col, FC_PRIORITY );
521    /* Translators: this is a column header in Files tab, Details dialog;
522       Don't include the prefix "filedetails|" in the translation. */ 
523    gtk_tree_view_column_set_title (col, Q_("filedetails|Priority"));
524    rend = priority_rend = gtk_cell_renderer_combo_new ();
525    gtk_tree_view_column_pack_start (col, rend, TRUE);
526    g_object_set (G_OBJECT(rend), "model", model,
527                                  "editable", TRUE,
528                                  "has-entry", FALSE,
529                                  "text-column", 0,
530                                  NULL);
531    g_object_unref (G_OBJECT(model));
532    gtk_tree_view_column_add_attribute (col, rend, "text", FC_PRIORITY);
533    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
534
535    /* create the scrolled window and stick the view in it */
536    scroll = gtk_scrolled_window_new( NULL, NULL );
537    gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scroll ),
538                                    GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC );
539    gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(scroll),
540                                         GTK_SHADOW_IN);
541    gtk_container_add( GTK_CONTAINER( scroll ), view );
542    gtk_widget_set_size_request (scroll, 0u, 200u);
543
544    ret = scroll;
545    data = g_new0( FileData, 1 );
546    data->view = view;
547    data->top = scroll;
548    g_signal_connect (G_OBJECT(priority_rend), "edited", G_CALLBACK(priority_changed_cb), data);
549    g_signal_connect(enabled_rend, "toggled", G_CALLBACK(enabled_toggled), data );
550    g_object_set_data_full( G_OBJECT( ret ), "file-data", data, freeData );
551    file_list_set_torrent( ret, gtor );
552
553    return ret;
554}
Note: See TracBrowser for help on using the repository browser.