source: branches/1.1x/gtk/torrent-cell-renderer.c @ 5537

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

#843: download eta should consider availability

  • Property svn:keywords set to Date Rev Author Id
File size: 22.7 KB
Line 
1/*
2 * This file Copyright (C) 2007-2008 Charles Kerr <charles@rebelbase.com>
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: torrent-cell-renderer.c 5537 2008-04-06 14:32:43Z charles $
11 */
12
13#include "assert.h"
14#include <gtk/gtk.h>
15#include <gtk/gtkcellrenderertext.h>
16#include <gtk/gtkcellrendererprogress.h>
17#include <glib/gi18n.h>
18#include <libtransmission/transmission.h>
19#include "hig.h"
20#include "torrent-cell-renderer.h"
21#include "tr-torrent.h"
22#include "util.h"
23
24/* #define TEST_RTL */
25
26enum
27{
28    P_TORRENT = 1,
29    P_BAR_HEIGHT,
30    P_MINIMAL
31};
32
33#define DEFAULT_BAR_HEIGHT 12
34
35/***
36****
37***/
38
39static char*
40getProgressString( const tr_info * info, const tr_stat * torStat )
41{
42    const int isDone = torStat->leftUntilDone == 0;
43    const uint64_t haveTotal = torStat->haveUnchecked + torStat->haveValid;
44    const int isSeed = torStat->haveValid >= info->totalSize;
45    char buf1[32], buf2[32], buf3[32], buf4[32];
46    char * str;
47
48    if( !isDone )
49        str = g_strdup_printf(
50                  /* %1$s is how much we've got,
51                     %2$s is how much we'll have when done,
52                     %3$.2f%% is a percentage of the two */
53                  _("%1$s of %2$s (%3$.2f%%)"),
54                  tr_strlsize( buf1, haveTotal, sizeof(buf1) ),
55                  tr_strlsize( buf2, torStat->desiredSize, sizeof(buf2) ),
56                  torStat->percentDone * 100.0 );
57    else if( !isSeed )
58        str = g_strdup_printf(
59                  /* %1$s is how much we've got,
60                     %2$s is the torrent's total size,
61                     %3$.2f%% is a percentage of the two,
62                     %4$s is how much we've uploaded,
63                     %5$s is our upload-to-download ratio */
64                  _("%1$s of %2$s (%3$.2f%%), uploaded %4$s (Ratio: %5$s)"),
65                  tr_strlsize( buf1, haveTotal, sizeof(buf1) ),
66                  tr_strlsize( buf2, info->totalSize, sizeof(buf2) ),
67                  torStat->percentComplete * 100.0,
68                  tr_strlsize( buf3, torStat->uploadedEver, sizeof(buf3) ),
69                  tr_strlratio( buf4, torStat->ratio, sizeof( buf4 ) ) );
70    else
71        str = g_strdup_printf(
72                  /* %1$s is the torrent's total size,
73                     %2$s is how much we've uploaded,
74                     %3$s is our upload-to-download ratio */
75                  _("%1$s, uploaded %2$s (Ratio: %3$s)"),
76                  tr_strlsize( buf1, info->totalSize, sizeof(buf1) ),
77                  tr_strlsize( buf2, torStat->uploadedEver, sizeof(buf2) ),
78                  tr_strlratio( buf3, torStat->ratio, sizeof( buf3 ) ) );
79
80    /* add time when downloading */
81    if( torStat->status == TR_STATUS_DOWNLOAD )
82    {
83        const int eta = torStat->eta;
84        GString * gstr = g_string_new( str );
85        g_string_append( gstr, " - " );
86        if( eta == TR_ETA_NOT_AVAIL )
87            g_string_append( gstr, _( "Not available" ) );
88         else if( eta == TR_ETA_UNKNOWN )
89            g_string_append( gstr, _( "Stalled" ) );
90        else {
91            char timestr[128];
92            tr_strltime( timestr, eta, sizeof( timestr ) );
93            /* time remaining */
94            g_string_append_printf( gstr, _( "%s remaining" ), timestr );
95        }
96        g_free( str );
97        str = g_string_free( gstr, FALSE );
98    }
99
100    return str;
101}
102
103static char*
104getShortTransferString( const tr_stat * torStat, char * buf, size_t buflen )
105{
106    char downStr[32], upStr[32];
107    const int haveDown = torStat->peersSendingToUs > 0;
108    const int haveUp = torStat->peersGettingFromUs > 0;
109
110    if( haveDown )
111        tr_strlspeed( downStr, torStat->rateDownload, sizeof(downStr) );
112    if( haveUp )
113        tr_strlspeed( upStr, torStat->rateUpload, sizeof(upStr) );
114
115    if( haveDown && haveUp )
116        /* Translators: do not translate the "speed|" disambiguation prefix.
117           %1$s is the download speed
118           %2$s is the upload speed */
119        g_snprintf( buf, buflen, Q_( "speed|Down: %1$s, Up: %2$s"), downStr, upStr );
120    else if( haveDown )
121        /* download speed */
122        g_snprintf( buf, buflen, _( "Down: %s" ), downStr );
123    else if( haveUp )
124        /* upload speed */
125        g_snprintf( buf, buflen, _( "Up: %s" ), upStr );
126    else
127        /* the torrent isn't uploading or downloading */
128        g_strlcpy( buf, _( "Idle" ), buflen );
129
130    return buf;
131}
132
133static char*
134getShortStatusString( const tr_stat * torStat )
135{
136    GString * gstr = g_string_new( NULL );
137
138    switch( torStat->status )
139    {
140        case TR_STATUS_STOPPED:
141            g_string_assign( gstr, _("Paused") );
142            break;
143
144        case TR_STATUS_CHECK_WAIT:
145            g_string_assign( gstr, _( "Waiting to verify local data" ) );
146            break;
147
148        case TR_STATUS_CHECK:
149            g_string_append_printf( gstr, _("Verifying local data (%.1f%% tested)"),
150                                    torStat->recheckProgress * 100.0 );
151            break;
152
153        case TR_STATUS_DOWNLOAD:
154        case TR_STATUS_SEED:
155        case TR_STATUS_DONE: {
156            char buf[128];
157            if( torStat->status != TR_STATUS_DOWNLOAD ) {
158                tr_strlratio( buf, torStat->ratio, sizeof( buf ) );
159                g_string_append_printf( gstr, _("Ratio: %s" ), buf );
160                g_string_append( gstr, ", " );
161            }
162            getShortTransferString( torStat, buf, sizeof( buf ) );
163            g_string_append( gstr, buf );
164            break;
165        }
166
167        default:
168            break;
169    }
170
171    return g_string_free( gstr, FALSE );
172}
173
174static char*
175getStatusString( const tr_stat * torStat )
176{
177    const int isActive = torStat->status != TR_STATUS_STOPPED;
178    const int isChecking = torStat->status == TR_STATUS_CHECK
179                        || torStat->status == TR_STATUS_CHECK_WAIT;
180
181    GString * gstr = g_string_new( NULL );
182
183    if( torStat->error )
184    {
185        g_string_assign( gstr, torStat->errorString );
186    }
187    else switch( torStat->status )
188    {
189        case TR_STATUS_STOPPED:
190        case TR_STATUS_CHECK_WAIT:
191        case TR_STATUS_CHECK: {
192            char * pch = getShortStatusString( torStat );
193            g_string_assign( gstr, pch );
194            g_free( pch );
195            break;
196        }
197
198        case TR_STATUS_DOWNLOAD:
199            g_string_append_printf( gstr,
200                ngettext( "Downloading from %1$'d of %2$'d connected peer",
201                          "Downloading from %1$'d of %2$'d connected peers",
202                          torStat->peersConnected ),
203                torStat->peersSendingToUs,
204                torStat->peersConnected );
205            break;
206
207        case TR_STATUS_DONE:
208        case TR_STATUS_SEED:
209            g_string_append_printf( gstr,
210                ngettext( "Seeding to %1$'d of %2$'d connected peer",
211                          "Seeding to %1$'d of %2$'d connected peers",
212                          torStat->peersConnected ),
213                torStat->peersGettingFromUs,
214                torStat->peersConnected );
215            break;
216    }
217
218    if( isActive && !isChecking )
219    {
220        char buf[256];
221        getShortTransferString( torStat, buf, sizeof(buf) );
222        g_string_append_printf( gstr, " - %s", buf );
223    }
224
225    return g_string_free( gstr, FALSE );
226}
227
228/***
229****
230***/
231
232static GtkCellRendererClass * parent_class = NULL;
233
234struct TorrentCellRendererPrivate
235{
236    tr_torrent * tor;
237    GtkCellRenderer * text_renderer;
238    GtkCellRenderer * text_renderer_err;
239    GtkCellRenderer * progress_renderer;
240    int bar_height;
241    gboolean minimal;
242};
243
244static void
245torrent_cell_renderer_get_size( GtkCellRenderer  * cell,
246                                GtkWidget        * widget,
247                                GdkRectangle     * cell_area,
248                                gint             * x_offset,
249                                gint             * y_offset,
250                                gint             * width,
251                                gint             * height)
252{
253    TorrentCellRenderer * self = TORRENT_CELL_RENDERER( cell );
254
255    if( self && self->priv->tor )
256    {
257        const tr_torrent * tor = self->priv->tor;
258        const tr_info * info = tr_torrentInfo( tor );
259        const char * name = info->name;
260        const tr_stat * torStat = tr_torrentStatCached( (tr_torrent*)tor );
261        char * str;
262        int w=0, h=0;
263        struct TorrentCellRendererPrivate * p = self->priv;
264        GtkCellRenderer * text_renderer = torStat->error != 0
265            ? p->text_renderer_err
266            : p->text_renderer;
267
268        g_object_set( text_renderer, "ellipsize", PANGO_ELLIPSIZE_NONE, NULL );
269
270        /* above the progressbar */
271        if( p->minimal )
272        {
273            int w1, w2, h1, h2;
274            char * shortStatus = getShortStatusString( torStat );
275            g_object_set( text_renderer, "text", name, NULL );
276            gtk_cell_renderer_get_size( text_renderer,
277                                        widget, NULL, NULL, NULL, &w1, &h1 );
278            str = g_markup_printf_escaped( "<small>%s</small>", shortStatus );
279            g_object_set( text_renderer, "markup", str, NULL );
280            gtk_cell_renderer_get_size( text_renderer,
281                                        widget, NULL, NULL, NULL, &w2, &h2 );
282            h += MAX( h1, h2 );
283            w = MAX( w, w1+GUI_PAD_BIG+w2 );
284            g_free( str );
285            g_free( shortStatus );
286        }
287        else
288        {
289            int w1, h1;
290            char * progressString = getProgressString( info, torStat );
291            str = g_markup_printf_escaped( "<b>%s</b>\n<small>%s</small>",
292                                           name, progressString );
293            g_object_set( text_renderer, "markup", str, NULL );
294            gtk_cell_renderer_get_size( text_renderer,
295                                        widget, NULL, NULL, NULL, &w1, &h1 );
296            h += h1;
297            w = MAX( w, w1 );
298            g_free( str );
299            g_free( progressString );
300        }
301
302        /* below the progressbar */
303        if( !p->minimal )
304        {
305            int w1, h1;
306            char * statusString = getStatusString( torStat );
307            str = g_markup_printf_escaped( "<small>%s</small>", statusString );
308            g_object_set( text_renderer, "markup", str, NULL );
309            gtk_cell_renderer_get_size( text_renderer,
310                                        widget, NULL, NULL, NULL, &w1, &h1 );
311            h += h1;
312            w = MAX( w, w1 );
313            g_free( str );
314            g_free( statusString );
315        }
316
317        h += p->bar_height;
318
319        if( cell_area ) {
320            if( x_offset ) *x_offset = 0;
321            if( y_offset ) {
322                *y_offset = 0.5 * (cell_area->height - (h + (2 * cell->ypad)));
323                *y_offset = MAX( *y_offset, 0 );
324            }
325        }
326
327        *width = w + cell->xpad*2;
328        *height = h + cell->ypad*2;
329    }
330}
331
332static void
333torrent_cell_renderer_render( GtkCellRenderer      * cell,
334                              GdkDrawable          * window,
335                              GtkWidget            * widget,
336                              GdkRectangle         * background_area,
337                              GdkRectangle         * cell_area UNUSED,
338                              GdkRectangle         * expose_area UNUSED,
339                              GtkCellRendererState   flags)
340{
341    TorrentCellRenderer * self = TORRENT_CELL_RENDERER( cell );
342
343#ifdef TEST_RTL
344    GtkTextDirection real_dir = gtk_widget_get_direction( widget );
345    gtk_widget_set_direction( widget, GTK_TEXT_DIR_RTL );
346#endif
347
348    if( self && self->priv->tor )
349    {
350        const tr_torrent * tor = self->priv->tor;
351        const tr_info * info = tr_torrentInfo( tor );
352        const char * name = info->name;
353        const tr_stat * torStat = tr_torrentStatCached( (tr_torrent*)tor );
354        GdkRectangle my_bg;
355        GdkRectangle my_cell;
356        GdkRectangle my_expose;
357        int w, h;
358        struct TorrentCellRendererPrivate * p = self->priv;
359        GtkCellRenderer * text_renderer = torStat->error != 0
360            ? p->text_renderer_err
361            : p->text_renderer;
362        const gboolean isActive = torStat->status != TR_STATUS_STOPPED;
363
364        my_bg = *background_area; 
365        my_bg.x += cell->xpad;
366        my_bg.y += cell->ypad;
367        my_bg.width -= cell->xpad*2;
368        my_cell = my_expose = my_bg;
369
370        g_object_set( text_renderer, "sensitive", isActive, NULL );
371        g_object_set( p->progress_renderer, "sensitive", isActive, NULL );
372
373        /* above the progressbar */
374        if( !p->minimal )
375        {
376            char * progressString = getProgressString( info, torStat );
377            char * str = g_markup_printf_escaped( "<b>%s</b>\n<small>%s</small>",
378                                                  name, progressString );
379            g_object_set( text_renderer, "markup", str,
380                                            "ellipsize", PANGO_ELLIPSIZE_NONE,
381                                            NULL );
382            gtk_cell_renderer_get_size( text_renderer,
383                                        widget, NULL, NULL, NULL, &w, &h );
384            my_bg.height     = 
385            my_cell.height   =
386            my_expose.height = h;
387            g_object_set( text_renderer, "ellipsize", PANGO_ELLIPSIZE_END,
388                                            NULL );
389            gtk_cell_renderer_render( text_renderer,
390                                      window, widget,
391                                      &my_bg, &my_cell, &my_expose, flags );
392            my_bg.y += h;
393            my_cell.y += h;
394            my_expose.y += h;
395
396            g_free( str );
397            g_free( progressString );
398        }
399        else
400        {
401            char * statusStr = getShortStatusString( torStat );
402            char * str = g_markup_printf_escaped( "<small>%s</small>", statusStr );
403            int w1, w2, h1, h2, tmp_h;
404            GdkRectangle tmp_bg, tmp_cell, tmp_expose;
405
406            /* get the dimensions for the name */
407            g_object_set( text_renderer, "text", name,
408                                         "ellipsize", PANGO_ELLIPSIZE_NONE,
409                                         NULL );
410            gtk_cell_renderer_get_size( text_renderer,
411                                        widget, NULL, NULL, NULL, &w1, &h1 );
412
413            /* get the dimensions for the short status string */
414            g_object_set( text_renderer, "markup", str,
415                                         "ellipsize", PANGO_ELLIPSIZE_NONE,
416                                         NULL );
417            gtk_cell_renderer_get_size( text_renderer,
418                                        widget, NULL, NULL, NULL, &w2, &h2 );
419
420            tmp_h = MAX( h1, h2 );
421
422            /* short status */
423            tmp_bg.x = my_bg.width - w2;
424            tmp_bg.y = my_bg.y + (h2-h1)/2;
425            tmp_bg.width = w2;
426            tmp_bg.height = tmp_h;
427            tmp_expose = tmp_cell = tmp_bg;
428            g_object_set( text_renderer, "markup", str,
429                                         "ellipsize", PANGO_ELLIPSIZE_END,
430                                         NULL );
431            gtk_cell_renderer_render( text_renderer,
432                                      window, widget,
433                                      &tmp_bg, &tmp_cell, &tmp_expose, flags );
434
435            /* name */
436            tmp_bg.x = my_bg.x;
437            tmp_bg.width = my_bg.width - w2 - GUI_PAD_BIG;
438            tmp_expose = tmp_cell = tmp_bg;
439            g_object_set( text_renderer, "text", name,
440                                         "ellipsize", PANGO_ELLIPSIZE_END,
441                                         NULL );
442            gtk_cell_renderer_render( text_renderer,
443                                      window, widget,
444                                      &tmp_bg, &tmp_cell, &tmp_expose, flags );
445
446            my_bg.y = tmp_bg.y + tmp_bg.height;
447            my_cell.y = tmp_cell.y + tmp_cell.height;
448            my_expose.y += tmp_expose.y + tmp_cell.height;
449
450            g_free( str );
451            g_free( statusStr );
452        }
453
454        /* the progressbar */
455        my_cell.height = p->bar_height;
456        if( 1 )
457        {
458            const double havePercent = ( torStat->haveValid + torStat->haveUnchecked )
459                                                              / (double)info->totalSize;
460            g_object_set( p->progress_renderer, "value", (int)(havePercent*100.0), 
461                                                "text", "",
462                                                NULL );
463            gtk_cell_renderer_render( p->progress_renderer,
464                                      window, widget,
465                                      &my_cell, &my_cell, &my_cell, flags );
466 
467        }
468        my_bg.y     += my_cell.height;
469        my_cell.y   += my_cell.height;
470        my_expose.y += my_cell.height;
471
472        /* below progressbar */
473        if( !p->minimal )
474        {
475            char * statusString = getStatusString( torStat );
476            char * str = g_markup_printf_escaped( "<small>%s</small>",
477                                                  statusString );
478            g_object_set( text_renderer, "markup", str,
479                                         "ellipsize", PANGO_ELLIPSIZE_END,
480                                         NULL );
481            gtk_cell_renderer_get_size( text_renderer,
482                                        widget, NULL, NULL, NULL, &w, &h );
483            my_bg.height      =
484            my_cell.height    =
485            my_expose.height  = h;
486            gtk_cell_renderer_render( text_renderer,
487                                      window, widget,
488                                      &my_bg, &my_cell, &my_expose, flags );
489
490            g_free( str );
491            g_free( statusString );
492        }
493    }
494
495#ifdef TEST_RTL
496    gtk_widget_set_direction( widget, real_dir );
497#endif
498}
499
500static void
501torrent_cell_renderer_set_property( GObject      * object,
502                                    guint          property_id,
503                                    const GValue * v,
504                                    GParamSpec   * pspec)
505{
506    TorrentCellRenderer * self = TORRENT_CELL_RENDERER( object );
507    struct TorrentCellRendererPrivate * p = self->priv;
508
509    switch( property_id )
510    {
511        case P_TORRENT:     p->tor = g_value_get_pointer( v ); break;
512        case P_BAR_HEIGHT:  p->bar_height = g_value_get_int( v ); break;
513        case P_MINIMAL:     p->minimal  = g_value_get_boolean( v ); break;
514        default:
515            G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, pspec );
516            break;
517    }
518}
519
520static void
521torrent_cell_renderer_get_property( GObject      * object,
522                                    guint          property_id,
523                                    GValue       * v,
524                                    GParamSpec   * pspec)
525{
526    const TorrentCellRenderer * self = TORRENT_CELL_RENDERER( object );
527    struct TorrentCellRendererPrivate * p = self->priv;
528
529    switch( property_id )
530    {
531        case P_TORRENT:     g_value_set_pointer( v, p->tor ); break;
532        case P_BAR_HEIGHT:  g_value_set_int( v, p->bar_height ); break;
533        case P_MINIMAL:     g_value_set_boolean( v, p->minimal ); break;
534        default:
535            G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, pspec );
536            break;
537    }
538}
539
540static void
541torrent_cell_renderer_dispose( GObject * o )
542{
543    TorrentCellRenderer * r = TORRENT_CELL_RENDERER( o );
544    GObjectClass * parent;
545
546    if( r && r->priv )
547    {
548        g_object_unref( G_OBJECT( r->priv->text_renderer ) );
549        g_object_unref( G_OBJECT( r->priv->text_renderer_err ) );
550        g_object_unref( G_OBJECT( r->priv->progress_renderer ) );
551        r->priv = NULL;
552    }
553
554    parent = g_type_class_peek( g_type_parent( TORRENT_CELL_RENDERER_TYPE ) );
555    parent->dispose( o );
556}
557
558static void
559torrent_cell_renderer_class_init( TorrentCellRendererClass * klass )
560{
561    GObjectClass * gobject_class = G_OBJECT_CLASS( klass );
562    GtkCellRendererClass * cell_class = GTK_CELL_RENDERER_CLASS( klass );
563
564    g_type_class_add_private( klass,
565                              sizeof(struct TorrentCellRendererPrivate) );
566
567    parent_class = (GtkCellRendererClass*) g_type_class_peek_parent( klass );
568
569    cell_class->render = torrent_cell_renderer_render;
570    cell_class->get_size = torrent_cell_renderer_get_size;
571    gobject_class->set_property = torrent_cell_renderer_set_property;
572    gobject_class->get_property = torrent_cell_renderer_get_property;
573    gobject_class->dispose = torrent_cell_renderer_dispose;
574
575    g_object_class_install_property( gobject_class, P_TORRENT,
576        g_param_spec_pointer( "torrent", NULL, "tr_torrent*",
577                              G_PARAM_READWRITE ) );
578
579    g_object_class_install_property( gobject_class, P_BAR_HEIGHT,
580        g_param_spec_int( "bar-height", NULL, "Bar Height",
581                          1, INT_MAX, DEFAULT_BAR_HEIGHT, G_PARAM_READWRITE ) );
582
583    g_object_class_install_property( gobject_class, P_MINIMAL,
584        g_param_spec_boolean( "minimal", NULL, "Minimal Mode",
585                              FALSE, G_PARAM_READWRITE ) );
586}
587
588static void
589torrent_cell_renderer_init( GTypeInstance * instance, gpointer g_class UNUSED )
590{
591    TorrentCellRenderer * self = TORRENT_CELL_RENDERER( instance );
592    struct TorrentCellRendererPrivate * p;
593   
594    p = self->priv = G_TYPE_INSTANCE_GET_PRIVATE( self,
595                         TORRENT_CELL_RENDERER_TYPE,
596                         struct TorrentCellRendererPrivate );
597
598    p->tor = NULL;
599    p->text_renderer = gtk_cell_renderer_text_new( );
600    p->text_renderer_err = gtk_cell_renderer_text_new(  );
601    p->progress_renderer = gtk_cell_renderer_progress_new(  );
602    g_object_set( p->text_renderer_err, "foreground", "red", NULL );
603    tr_object_ref_sink( p->text_renderer );
604    tr_object_ref_sink( p->text_renderer_err );
605    tr_object_ref_sink( p->progress_renderer );
606
607    p->bar_height = DEFAULT_BAR_HEIGHT;
608}
609
610GType
611torrent_cell_renderer_get_type( void )
612{
613    static GType type = 0;
614
615    if( !type )
616    {
617        static const GTypeInfo info =
618        {
619            sizeof( TorrentCellRendererClass ),
620            NULL, /* base_init */
621            NULL, /* base_finalize */
622            (GClassInitFunc)torrent_cell_renderer_class_init,
623            NULL, /* class_finalize */
624            NULL, /* class_data */
625            sizeof( TorrentCellRenderer ),
626            0, /* n_preallocs */
627            (GInstanceInitFunc)torrent_cell_renderer_init,
628            NULL
629        };
630
631        type = g_type_register_static( GTK_TYPE_CELL_RENDERER,
632                                       "TorrentCellRenderer",
633                                       &info, (GTypeFlags)0 );
634    }
635
636    return type;
637}
638
639GtkCellRenderer *
640torrent_cell_renderer_new( void )
641{
642    return (GtkCellRenderer *) g_object_new( TORRENT_CELL_RENDERER_TYPE, NULL );
643}
Note: See TracBrowser for help on using the repository browser.