source: trunk/gtk/torrent-cell-renderer.c @ 5457

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

remove `TR_STATUS_DONE' from libtransmission's public API. It's useful as an internal state but not for code calling libtransmission.

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