1/*
2 * This file Copyright (C) Mnemosyne LLC
3 *
4 * This file is licensed by the GPL version 2. Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: torrent-cell-renderer.c 13553 2012-10-07 17:51:56Z jordan $
11 */
12
13#include <gtk/gtk.h>
14#include <glib/gi18n.h>
15#include <libtransmission/transmission.h>
16#include <libtransmission/utils.h> /* tr_truncd() */
17#include "hig.h"
18#include "icons.h"
19#include "torrent-cell-renderer.h"
20#include "util.h"
21
22/* #define TEST_RTL */
23
24enum
25{
26    P_TORRENT = 1,
27    P_UPLOAD_SPEED,
28    P_DOWNLOAD_SPEED,
29    P_BAR_HEIGHT,
30    P_COMPACT
31};
32
33#define DEFAULT_BAR_HEIGHT 12
34#define SMALL_SCALE 0.9
35#define COMPACT_ICON_SIZE GTK_ICON_SIZE_MENU
36#define FULL_ICON_SIZE GTK_ICON_SIZE_DND
37
38/***
39****
40***/
41
42static void
43getProgressString( GString          * gstr,
44                   const tr_torrent * tor,
45                   const tr_info    * info,
46                   const tr_stat    * st )
47{
48    const int      isDone = st->leftUntilDone == 0;
49    const uint64_t haveTotal = st->haveUnchecked + st->haveValid;
50    const int      isSeed = st->haveValid >= info->totalSize;
51    char           buf1[32], buf2[32], buf3[32], buf4[32], buf5[32], buf6[32];
52    double         seedRatio;
53    const gboolean hasSeedRatio = tr_torrentGetSeedRatio( tor, &seedRatio );
54
55    if( !isDone ) /* downloading */
56    {
57        g_string_append_printf( gstr,
58            /* %1$s is how much we've got,
59               %2$s is how much we'll have when done,
60               %3$s%% is a percentage of the two */
61            _( "%1$s of %2$s (%3$s%%)" ),
62            tr_strlsize( buf1, haveTotal, sizeof( buf1 ) ),
63            tr_strlsize( buf2, st->sizeWhenDone, sizeof( buf2 ) ),
64            tr_strlpercent( buf3, st->percentDone * 100.0, sizeof( buf3 ) ) );
65    }
66    else if( !isSeed ) /* partial seeds */
67    {
68        if( hasSeedRatio )
69        {
70            g_string_append_printf( gstr,
71                /* %1$s is how much we've got,
72                   %2$s is the torrent's total size,
73                   %3$s%% is a percentage of the two,
74                   %4$s is how much we've uploaded,
75                   %5$s is our upload-to-download ratio,
76                   %6$s is the ratio we want to reach before we stop uploading */
77                _( "%1$s of %2$s (%3$s%%), uploaded %4$s (Ratio: %5$s Goal: %6$s)" ),
78                tr_strlsize( buf1, haveTotal, sizeof( buf1 ) ),
79                tr_strlsize( buf2, info->totalSize, sizeof( buf2 ) ),
80                tr_strlpercent( buf3, st->percentComplete * 100.0, sizeof( buf3 ) ),
81                tr_strlsize( buf4, st->uploadedEver, sizeof( buf4 ) ),
82                tr_strlratio( buf5, st->ratio, sizeof( buf5 ) ),
83                tr_strlratio( buf6, seedRatio, sizeof( buf6 ) ) );
84        }
85        else
86        {
87            g_string_append_printf( gstr,
88                /* %1$s is how much we've got,
89                   %2$s is the torrent's total size,
90                   %3$s%% is a percentage of the two,
91                   %4$s is how much we've uploaded,
92                   %5$s is our upload-to-download ratio */
93                _( "%1$s of %2$s (%3$s%%), uploaded %4$s (Ratio: %5$s)" ),
94                tr_strlsize( buf1, haveTotal, sizeof( buf1 ) ),
95                tr_strlsize( buf2, info->totalSize, sizeof( buf2 ) ),
96                tr_strlpercent( buf3, st->percentComplete * 100.0, sizeof( buf3 ) ),
97                tr_strlsize( buf4, st->uploadedEver, sizeof( buf4 ) ),
98                tr_strlratio( buf5, st->ratio, sizeof( buf5 ) ) );
99        }
100    }
101    else /* seeding */
102    {
103        if( hasSeedRatio )
104        {
105            g_string_append_printf( gstr,
106                /* %1$s is the torrent's total size,
107                   %2$s is how much we've uploaded,
108                   %3$s is our upload-to-download ratio,
109                   %4$s is the ratio we want to reach before we stop uploading */
110                _( "%1$s, uploaded %2$s (Ratio: %3$s Goal: %4$s)" ),
111                tr_strlsize( buf1, info->totalSize, sizeof( buf1 ) ),
112                tr_strlsize( buf2, st->uploadedEver, sizeof( buf2 ) ),
113                tr_strlratio( buf3, st->ratio, sizeof( buf3 ) ),
114                tr_strlratio( buf4, seedRatio, sizeof( buf4 ) ) );
115        }
116        else /* seeding w/o a ratio */
117        {
118            g_string_append_printf( gstr,
119                /* %1$s is the torrent's total size,
120                   %2$s is how much we've uploaded,
121                   %3$s is our upload-to-download ratio */
122                _( "%1$s, uploaded %2$s (Ratio: %3$s)" ),
123                tr_strlsize( buf1, info->totalSize, sizeof( buf1 ) ),
124                tr_strlsize( buf2, st->uploadedEver, sizeof( buf2 ) ),
125                tr_strlratio( buf3, st->ratio, sizeof( buf3 ) ) );
126        }
127    }
128
129    /* add time when downloading */
130    if( ( st->activity == TR_STATUS_DOWNLOAD )
131        || ( hasSeedRatio && ( st->activity == TR_STATUS_SEED ) ) )
132    {
133        const int eta = st->eta;
134        g_string_append( gstr, " - " );
135        if( eta < 0 )
136            g_string_append( gstr, _( "Remaining time unknown" ) );
137        else
138        {
139            char timestr[128];
140            tr_strltime( timestr, eta, sizeof( timestr ) );
141            /* time remaining */
142            g_string_append_printf( gstr, _( "%s remaining" ), timestr );
143        }
144    }
145}
146
147static char*
148getShortTransferString( const tr_torrent  * tor,
149                        const tr_stat     * st,
150                        double              uploadSpeed_KBps,
151                        double              downloadSpeed_KBps,
152                        char              * buf,
153                        size_t              buflen )
154{
155    char downStr[32], upStr[32];
156    const int haveMeta = tr_torrentHasMetadata( tor );
157    const int haveUp = haveMeta && st->peersGettingFromUs > 0;
158    const int haveDown = haveMeta && ( ( st->peersSendingToUs > 0 ) || ( st->webseedsSendingToUs > 0 ) );
159
160    if( haveDown )
161        tr_formatter_speed_KBps( downStr, downloadSpeed_KBps, sizeof( downStr ) );
162    if( haveUp )
163        tr_formatter_speed_KBps( upStr, uploadSpeed_KBps, sizeof( upStr ) );
164
165    if( haveDown && haveUp )
166        /* 1==down arrow, 2==down speed, 3==up arrow, 4==down speed */
167        g_snprintf( buf, buflen, _( "%1$s %2$s, %3$s %4$s" ),
168                    gtr_get_unicode_string( GTR_UNICODE_DOWN ), downStr,
169                    gtr_get_unicode_string( GTR_UNICODE_UP ), upStr );
170    else if( haveDown )
171        /* bandwidth speed + unicode arrow */
172        g_snprintf( buf, buflen, _( "%1$s %2$s" ),
173                    gtr_get_unicode_string( GTR_UNICODE_DOWN ), downStr );
174    else if( haveUp )
175        /* bandwidth speed + unicode arrow */
176        g_snprintf( buf, buflen, _( "%1$s %2$s" ),
177                    gtr_get_unicode_string( GTR_UNICODE_UP ), upStr );
178    else if( st->isStalled )
179        g_strlcpy( buf, _( "Stalled" ), buflen );
180    else if( haveMeta )
181        g_strlcpy( buf, _( "Idle" ), buflen );
182    else
183        *buf = '\0';
184
185    return buf;
186}
187
188static void
189getShortStatusString( GString           * gstr,
190                      const tr_torrent  * tor,
191                      const tr_stat     * st,
192                      double              uploadSpeed_KBps,
193                      double              downloadSpeed_KBps )
194{
195    switch( st->activity )
196    {
197        case TR_STATUS_STOPPED:
198            g_string_append( gstr, st->finished ? _( "Finished" ) : _( "Paused" ) );
199            break;
200        case TR_STATUS_CHECK_WAIT:
201            g_string_append( gstr, _( "Queued for verification" ) );
202            break;
203        case TR_STATUS_DOWNLOAD_WAIT:
204            g_string_append( gstr, _( "Queued for download" ) );
205            break;
206        case TR_STATUS_SEED_WAIT:
207            g_string_append( gstr, _( "Queued for seeding" ) );
208            break;
209
210        case TR_STATUS_CHECK:
211            g_string_append_printf( gstr, _( "Verifying local data (%.1f%% tested)" ),
212                                    tr_truncd( st->recheckProgress * 100.0, 1 ) );
213            break;
214
215        case TR_STATUS_DOWNLOAD:
216        case TR_STATUS_SEED:
217        {
218            char buf[512];
219            if( st->activity != TR_STATUS_DOWNLOAD )
220            {
221                tr_strlratio( buf, st->ratio, sizeof( buf ) );
222                g_string_append_printf( gstr, _( "Ratio %s" ), buf );
223                g_string_append( gstr, ", " );
224            }
225            getShortTransferString( tor, st, uploadSpeed_KBps, downloadSpeed_KBps, buf, sizeof( buf ) );
226            g_string_append( gstr, buf );
227            break;
228        }
229
230        default:
231            break;
232    }
233}
234
235static void
236getStatusString( GString           * gstr,
237                 const tr_torrent  * tor,
238                 const tr_stat     * st,
239                 const double        uploadSpeed_KBps,
240                 const double        downloadSpeed_KBps )
241{
242    if( st->error )
243    {
244        const char * fmt[] = { NULL, N_( "Tracker gave a warning: \"%s\"" ),
245                                     N_( "Tracker gave an error: \"%s\"" ),
246                                     N_( "Error: %s" ) };
247        g_string_append_printf( gstr, _( fmt[st->error] ), st->errorString );
248    }
249    else switch( st->activity )
250    {
251        case TR_STATUS_STOPPED:
252        case TR_STATUS_CHECK_WAIT:
253        case TR_STATUS_CHECK:
254        case TR_STATUS_DOWNLOAD_WAIT:
255        case TR_STATUS_SEED_WAIT:
256        {
257            getShortStatusString( gstr, tor, st, uploadSpeed_KBps, downloadSpeed_KBps );
258            break;
259        }
260
261        case TR_STATUS_DOWNLOAD:
262        {
263            if( !tr_torrentHasMetadata( tor ) )
264            {
265                /* Downloading metadata from 2 peer(s) (50% done) */
266                g_string_append_printf( gstr, _("Downloading metadata from %1$'d %2$s (%3$d%% done)"),
267                                        st->peersConnected,
268                                        ngettext("peer","peers",st->peersConnected),
269                                        (int)(100.0*st->metadataPercentComplete) );
270            }
271            else if (st->peersSendingToUs && st->webseedsSendingToUs)
272            {
273                /* Downloading from 2 of 3 peer(s) and 2 webseed(s) */
274                g_string_append_printf (gstr, _("Downloading from %1$'d of %2$'d %3$s and %4$'d %5$s"),
275                                        st->peersSendingToUs,
276                                        st->peersConnected,
277                                        ngettext("peer","peers",st->peersSendingToUs),
278                                        st->webseedsSendingToUs,
279                                        ngettext("web seed","web seeds",st->webseedsSendingToUs));
280            }
281            else if (st->webseedsSendingToUs)
282            {
283                /* Downloading from 3 web seed(s) */
284                g_string_append_printf (gstr, _("Downloading from %1$'d %2$s"),
285                                        st->webseedsSendingToUs,
286                                        ngettext("web seed","web seeds",st->webseedsSendingToUs));
287            }
288            else
289            {
290                /* Downloading from 2 of 3 peer(s) */
291                g_string_append_printf (gstr, _("Downloading from %1$'d of %2$'d %3$s"),
292                                        st->peersSendingToUs,
293                                        st->peersConnected,
294                                        ngettext("peer","peers",st->peersSendingToUs));
295            }
296            break;
297        }
298
299        case TR_STATUS_SEED:
300            g_string_append_printf( gstr,
301                ngettext( "Seeding to %1$'d of %2$'d connected peer",
302                          "Seeding to %1$'d of %2$'d connected peers",
303                          st->peersConnected ),
304                st->peersGettingFromUs,
305                st->peersConnected );
306                break;
307    }
308
309    if( ( st->activity != TR_STATUS_CHECK_WAIT ) &&
310        ( st->activity != TR_STATUS_CHECK ) &&
311        ( st->activity != TR_STATUS_DOWNLOAD_WAIT ) &&
312        ( st->activity != TR_STATUS_SEED_WAIT ) &&
313        ( st->activity != TR_STATUS_STOPPED ) )
314    {
315        char buf[256];
316        getShortTransferString( tor, st, uploadSpeed_KBps, downloadSpeed_KBps, buf, sizeof( buf ) );
317        if( *buf )
318            g_string_append_printf( gstr, " - %s", buf );
319    }
320}
321
322/***
323****
324***/
325
326struct TorrentCellRendererPrivate
327{
328    tr_torrent       * tor;
329    GtkCellRenderer  * text_renderer;
330    GtkCellRenderer  * progress_renderer;
331    GtkCellRenderer  * icon_renderer;
332    GString          * gstr1;
333    GString          * gstr2;
334    int bar_height;
335
336    /* Use this instead of tr_stat.pieceUploadSpeed so that the model can
337       control when the speed displays get updated. This is done to keep
338       the individual torrents' speeds and the status bar's overall speed
339       in sync even if they refresh at slightly different times */
340    double upload_speed_KBps;
341
342    /* @see upload_speed_Bps */
343    double download_speed_KBps;
344
345    gboolean compact;
346};
347
348/***
349****
350***/
351
352static GdkPixbuf*
353get_icon( const tr_torrent * tor, GtkIconSize icon_size, GtkWidget * for_widget )
354{
355    const char * mime_type;
356    const tr_info * info = tr_torrentInfo( tor );
357
358    if( info->fileCount == 0  )
359        mime_type = UNKNOWN_MIME_TYPE;
360    else if( info->fileCount > 1 )
361        mime_type = DIRECTORY_MIME_TYPE;
362    else if( strchr( info->files[0].name, '/' ) != NULL )
363        mime_type = DIRECTORY_MIME_TYPE;
364    else
365        mime_type = gtr_get_mime_type_from_filename( info->files[0].name );
366
367    return gtr_get_mime_type_icon( mime_type, icon_size, for_widget );
368}
369
370/***
371****
372***/
373
374static void
375gtr_cell_renderer_get_preferred_size( GtkCellRenderer  * renderer,
376                                      GtkWidget        * widget,
377                                      GtkRequisition   * minimum_size,
378                                      GtkRequisition   * natural_size )
379{
380    gtk_cell_renderer_get_preferred_size( renderer, widget, minimum_size, natural_size );
381}
382
383static void
384get_size_compact( TorrentCellRenderer * cell,
385                  GtkWidget           * widget,
386                  gint                * width,
387                  gint                * height )
388{
389    int xpad, ypad;
390    GtkRequisition icon_size;
391    GtkRequisition name_size;
392    GtkRequisition stat_size;
393    const char * name;
394    GdkPixbuf * icon;
395
396    struct TorrentCellRendererPrivate * p = cell->priv;
397    const tr_torrent * tor = p->tor;
398    const tr_stat * st = tr_torrentStatCached( (tr_torrent*)tor );
399    GString * gstr_stat = p->gstr1;
400
401    icon = get_icon( tor, COMPACT_ICON_SIZE, widget );
402    name = tr_torrentName( tor );
403    g_string_truncate( gstr_stat, 0 );
404    getShortStatusString( gstr_stat, tor, st, p->upload_speed_KBps, p->download_speed_KBps );
405    gtk_cell_renderer_get_padding( GTK_CELL_RENDERER( cell ), &xpad, &ypad );
406
407    /* get the idealized cell dimensions */
408    g_object_set( p->icon_renderer, "pixbuf", icon, NULL );
409    gtr_cell_renderer_get_preferred_size( p->icon_renderer, widget, NULL, &icon_size );
410    g_object_set( p->text_renderer, "text", name, "ellipsize", PANGO_ELLIPSIZE_NONE,  "scale", 1.0, NULL );
411    gtr_cell_renderer_get_preferred_size( p->text_renderer, widget, NULL, &name_size );
412    g_object_set( p->text_renderer, "text", gstr_stat->str, "scale", SMALL_SCALE, NULL );
413    gtr_cell_renderer_get_preferred_size( p->text_renderer, widget, NULL, &stat_size );
414
415    /**
416    *** LAYOUT
417    **/
418
419#define BAR_WIDTH 50
420    if( width != NULL )
421        *width = xpad * 2 + icon_size.width + GUI_PAD + name_size.width + GUI_PAD + BAR_WIDTH + GUI_PAD + stat_size.width;
422    if( height != NULL )
423        *height = ypad * 2 + MAX( name_size.height, p->bar_height );
424
425    /* cleanup */
426    g_object_unref( icon );
427}
428
429#define MAX3(a,b,c) MAX(a,MAX(b,c))
430
431static void
432get_size_full( TorrentCellRenderer * cell,
433               GtkWidget           * widget,
434               gint                * width,
435               gint                * height )
436{
437    int xpad, ypad;
438    GtkRequisition icon_size;
439    GtkRequisition name_size;
440    GtkRequisition stat_size;
441    GtkRequisition prog_size;
442    const char * name;
443    GdkPixbuf * icon;
444
445    struct TorrentCellRendererPrivate * p = cell->priv;
446    const tr_torrent * tor = p->tor;
447    const tr_stat * st = tr_torrentStatCached( (tr_torrent*)tor );
448    const tr_info * inf = tr_torrentInfo( tor );
449    GString * gstr_prog = p->gstr1;
450    GString * gstr_stat = p->gstr2;
451
452    icon = get_icon( tor, FULL_ICON_SIZE, widget );
453    name = tr_torrentName( tor );
454    g_string_truncate( gstr_stat, 0 );
455    getStatusString( gstr_stat, tor, st, p->upload_speed_KBps, p->download_speed_KBps );
456    g_string_truncate( gstr_prog, 0 );
457    getProgressString( gstr_prog, tor, inf, st );
458    gtk_cell_renderer_get_padding( GTK_CELL_RENDERER( cell ), &xpad, &ypad );
459
460    /* get the idealized cell dimensions */
461    g_object_set( p->icon_renderer, "pixbuf", icon, NULL );
462    gtr_cell_renderer_get_preferred_size( p->icon_renderer, widget, NULL, &icon_size );
463    g_object_set( p->text_renderer, "text", name, "weight", PANGO_WEIGHT_BOLD, "scale", 1.0, "ellipsize", PANGO_ELLIPSIZE_NONE, NULL );
464    gtr_cell_renderer_get_preferred_size( p->text_renderer, widget, NULL, &name_size );
465    g_object_set( p->text_renderer, "text", gstr_prog->str, "weight", PANGO_WEIGHT_NORMAL, "scale", SMALL_SCALE, NULL );
466    gtr_cell_renderer_get_preferred_size( p->text_renderer, widget, NULL, &prog_size );
467    g_object_set( p->text_renderer, "text", gstr_stat->str, NULL );
468    gtr_cell_renderer_get_preferred_size( p->text_renderer, widget, NULL, &stat_size );
469
470    /**
471    *** LAYOUT
472    **/
473
474    if( width != NULL )
475        *width = xpad * 2 + icon_size.width + GUI_PAD + MAX3( name_size.width, prog_size.width, stat_size.width );
476    if( height != NULL )
477        *height = ypad * 2 + name_size.height + prog_size.height + GUI_PAD_SMALL + p->bar_height + GUI_PAD_SMALL + stat_size.height;
478
479    /* cleanup */
480    g_object_unref( icon );
481}
482
483
484static void
485torrent_cell_renderer_get_size( GtkCellRenderer     * cell,
486                                GtkWidget           * widget,
487                                const GdkRectangle  * cell_area,
488                                gint                * x_offset,
489                                gint                * y_offset,
490                                gint                * width,
491                                gint                * height )
492{
493    TorrentCellRenderer * self = TORRENT_CELL_RENDERER( cell );
494
495    if( self && self->priv->tor )
496    {
497        int w, h;
498        struct TorrentCellRendererPrivate * p = self->priv;
499
500        if( p->compact )
501            get_size_compact( TORRENT_CELL_RENDERER( cell ), widget, &w, &h );
502        else
503            get_size_full( TORRENT_CELL_RENDERER( cell ), widget, &w, &h );
504
505        if( width )
506            *width = w;
507
508        if( height )
509            *height = h;
510
511        if( x_offset )
512            *x_offset = cell_area ? cell_area->x : 0;
513
514        if( y_offset ) {
515            int xpad, ypad;
516            gtk_cell_renderer_get_padding( cell, &xpad, &ypad );
517            *y_offset = cell_area ? (int)((cell_area->height - (ypad*2 +h)) / 2.0) : 0;
518        }
519    }
520}
521
522typedef GdkRGBA GtrColor;
523#define FOREGROUND_COLOR_KEY "foreground-rgba"
524
525static void
526get_text_color( GtkWidget * w, const tr_stat * st, GtrColor * setme )
527{
528    static const GdkRGBA red = { 1.0, 0, 0, 0 };
529    if( st->error )
530        *setme = red;
531    else if( st->activity == TR_STATUS_STOPPED )
532        gtk_style_context_get_color( gtk_widget_get_style_context( w ), GTK_STATE_FLAG_INSENSITIVE, setme );
533    else
534        gtk_style_context_get_color( gtk_widget_get_style_context( w ), GTK_STATE_FLAG_NORMAL, setme );
535}
536
537
538static double
539get_percent_done( const tr_torrent * tor, const tr_stat * st, bool * seed )
540{
541    double d;
542
543    if( ( st->activity == TR_STATUS_SEED ) && tr_torrentGetSeedRatio( tor, &d ) )
544    {
545        *seed = true;
546        d = MAX( 0.0, st->seedRatioPercentDone );
547    }
548    else
549    {
550        *seed = false;
551        d = MAX( 0.0, st->percentDone );
552    }
553
554    return d;
555}
556
557typedef cairo_t GtrDrawable;
558
559static void
560gtr_cell_renderer_render( GtkCellRenderer       * renderer,
561                          GtrDrawable           * drawable,
562                          GtkWidget             * widget,
563                          const GdkRectangle    * area,
564                          GtkCellRendererState    flags)
565{
566    gtk_cell_renderer_render( renderer, drawable, widget, area, area, flags );
567}
568
569static void
570render_compact( TorrentCellRenderer   * cell,
571                GtrDrawable           * window,
572                GtkWidget             * widget,
573                const GdkRectangle    * background_area,
574                const GdkRectangle    * cell_area UNUSED,
575                GtkCellRendererState    flags )
576{
577    int xpad, ypad;
578    GtkRequisition size;
579    GdkRectangle icon_area;
580    GdkRectangle name_area;
581    GdkRectangle stat_area;
582    GdkRectangle prog_area;
583    GdkRectangle fill_area;
584    const char * name;
585    GdkPixbuf * icon;
586    GtrColor text_color;
587    bool seed;
588
589    struct TorrentCellRendererPrivate * p = cell->priv;
590    const tr_torrent * tor = p->tor;
591    const tr_stat * st = tr_torrentStatCached( (tr_torrent*)tor );
592    const gboolean active = ( st->activity != TR_STATUS_STOPPED ) && ( st->activity != TR_STATUS_DOWNLOAD_WAIT ) && ( st->activity != TR_STATUS_SEED_WAIT );
593    const double percentDone = get_percent_done( tor, st, &seed );
594    const gboolean sensitive = active || st->error;
595    GString * gstr_stat = p->gstr1;
596
597    icon = get_icon( tor, COMPACT_ICON_SIZE, widget );
598    name = tr_torrentName( tor );
599    g_string_truncate( gstr_stat, 0 );
600    getShortStatusString( gstr_stat, tor, st, p->upload_speed_KBps, p->download_speed_KBps );
601    gtk_cell_renderer_get_padding( GTK_CELL_RENDERER( cell ), &xpad, &ypad );
602    get_text_color( widget, st, &text_color );
603
604    fill_area = *background_area;
605    fill_area.x += xpad;
606    fill_area.y += ypad;
607    fill_area.width -= xpad * 2;
608    fill_area.height -= ypad * 2;
609    icon_area = name_area = stat_area = prog_area = fill_area;
610
611    g_object_set( p->icon_renderer, "pixbuf", icon, NULL );
612    gtr_cell_renderer_get_preferred_size( p->icon_renderer, widget, NULL, &size );
613    icon_area.width = size.width;
614    g_object_set( p->text_renderer, "text", name, "ellipsize", PANGO_ELLIPSIZE_NONE, "scale", 1.0, NULL );
615    gtr_cell_renderer_get_preferred_size( p->text_renderer, widget, NULL, &size );
616    name_area.width = size.width;
617    g_object_set( p->text_renderer, "text", gstr_stat->str, "scale", SMALL_SCALE, NULL );
618    gtr_cell_renderer_get_preferred_size( p->text_renderer, widget, NULL, &size );
619    stat_area.width = size.width;
620
621    icon_area.x = fill_area.x;
622    prog_area.x = fill_area.x + fill_area.width - BAR_WIDTH;
623    prog_area.width = BAR_WIDTH;
624    stat_area.x = prog_area.x - GUI_PAD - stat_area.width;
625    name_area.x = icon_area.x + icon_area.width + GUI_PAD;
626    name_area.y = fill_area.y;
627    name_area.width = stat_area.x - GUI_PAD - name_area.x;
628
629    /**
630    *** RENDER
631    **/
632
633    g_object_set( p->icon_renderer, "pixbuf", icon, "sensitive", sensitive, NULL );
634    gtr_cell_renderer_render( p->icon_renderer, window, widget, &icon_area, flags );
635    g_object_set( p->progress_renderer, "value", (int)(percentDone*100.0), "text", NULL, "sensitive", sensitive, NULL );
636    gtr_cell_renderer_render( p->progress_renderer, window, widget, &prog_area, flags );
637    g_object_set( p->text_renderer, "text", gstr_stat->str, "scale", SMALL_SCALE, "ellipsize", PANGO_ELLIPSIZE_END, FOREGROUND_COLOR_KEY, &text_color, NULL );
638    gtr_cell_renderer_render( p->text_renderer, window, widget, &stat_area, flags );
639    g_object_set( p->text_renderer, "text", name, "scale", 1.0, FOREGROUND_COLOR_KEY, &text_color, NULL );
640    gtr_cell_renderer_render( p->text_renderer, window, widget, &name_area, flags );
641
642    /* cleanup */
643    g_object_unref( icon );
644}
645
646static void
647render_full( TorrentCellRenderer   * cell,
648             GtrDrawable           * window,
649             GtkWidget             * widget,
650             const GdkRectangle    * background_area,
651             const GdkRectangle    * cell_area UNUSED,
652             GtkCellRendererState    flags )
653{
654    int xpad, ypad;
655    GtkRequisition size;
656    GdkRectangle fill_area;
657    GdkRectangle icon_area;
658    GdkRectangle name_area;
659    GdkRectangle stat_area;
660    GdkRectangle prog_area;
661    GdkRectangle prct_area;
662    const char * name;
663    GdkPixbuf * icon;
664    GtrColor text_color;
665    bool seed;
666
667    struct TorrentCellRendererPrivate * p = cell->priv;
668    const tr_torrent * tor = p->tor;
669    const tr_stat * st = tr_torrentStatCached( (tr_torrent*)tor );
670    const tr_info * inf = tr_torrentInfo( tor );
671    const gboolean active = ( st->activity != TR_STATUS_STOPPED ) && ( st->activity != TR_STATUS_DOWNLOAD_WAIT ) && ( st->activity != TR_STATUS_SEED_WAIT );
672    const double percentDone = get_percent_done( tor, st, &seed );
673    const gboolean sensitive = active || st->error;
674    GString * gstr_prog = p->gstr1;
675    GString * gstr_stat = p->gstr2;
676
677    icon = get_icon( tor, FULL_ICON_SIZE, widget );
678    name = tr_torrentName( tor );
679    g_string_truncate( gstr_prog, 0 );
680    getProgressString( gstr_prog, tor, inf, st );
681    g_string_truncate( gstr_stat, 0 );
682    getStatusString( gstr_stat, tor, st, p->upload_speed_KBps, p->download_speed_KBps );
683    gtk_cell_renderer_get_padding( GTK_CELL_RENDERER( cell ), &xpad, &ypad );
684    get_text_color( widget, st, &text_color );
685
686    /* get the idealized cell dimensions */
687    g_object_set( p->icon_renderer, "pixbuf", icon, NULL );
688    gtr_cell_renderer_get_preferred_size( p->icon_renderer, widget, NULL, &size );
689    icon_area.width = size.width;
690    icon_area.height = size.height;
691    g_object_set( p->text_renderer, "text", name, "weight", PANGO_WEIGHT_BOLD, "ellipsize", PANGO_ELLIPSIZE_NONE, "scale", 1.0, NULL );
692    gtr_cell_renderer_get_preferred_size( p->text_renderer, widget, NULL, &size );
693    name_area.width = size.width;
694    name_area.height = size.height;
695    g_object_set( p->text_renderer, "text", gstr_prog->str, "weight", PANGO_WEIGHT_NORMAL, "scale", SMALL_SCALE, NULL );
696    gtr_cell_renderer_get_preferred_size( p->text_renderer, widget, NULL, &size );
697    prog_area.width = size.width;
698    prog_area.height = size.height;
699    g_object_set( p->text_renderer, "text", gstr_stat->str, NULL );
700    gtr_cell_renderer_get_preferred_size( p->text_renderer, widget, NULL, &size );
701    stat_area.width = size.width;
702    stat_area.height = size.height;
703
704    /**
705    *** LAYOUT
706    **/
707
708    fill_area = *background_area;
709    fill_area.x += xpad;
710    fill_area.y += ypad;
711    fill_area.width -= xpad * 2;
712    fill_area.height -= ypad * 2;
713
714    /* icon */
715    icon_area.x = fill_area.x;
716    icon_area.y = fill_area.y + ( fill_area.height - icon_area.height ) / 2;
717
718    /* name */
719    name_area.x = icon_area.x + icon_area.width + GUI_PAD;
720    name_area.y = fill_area.y;
721    name_area.width = fill_area.width - GUI_PAD - icon_area.width - GUI_PAD_SMALL;
722
723    /* prog */
724    prog_area.x = name_area.x;
725    prog_area.y = name_area.y + name_area.height;
726    prog_area.width = name_area.width;
727
728    /* progressbar */
729    prct_area.x = prog_area.x;
730    prct_area.y = prog_area.y + prog_area.height + GUI_PAD_SMALL;
731    prct_area.width = prog_area.width;
732    prct_area.height = p->bar_height;
733
734    /* status */
735    stat_area.x = prct_area.x;
736    stat_area.y = prct_area.y + prct_area.height + GUI_PAD_SMALL;
737    stat_area.width = prct_area.width;
738
739    /**
740    *** RENDER
741    **/
742
743    g_object_set( p->icon_renderer, "pixbuf", icon, "sensitive", sensitive, NULL );
744    gtr_cell_renderer_render( p->icon_renderer, window, widget, &icon_area, flags );
745    g_object_set( p->text_renderer, "text", name, "scale", 1.0, FOREGROUND_COLOR_KEY, &text_color, "ellipsize", PANGO_ELLIPSIZE_END, "weight", PANGO_WEIGHT_BOLD, NULL );
746    gtr_cell_renderer_render( p->text_renderer, window, widget, &name_area, flags );
747    g_object_set( p->text_renderer, "text", gstr_prog->str, "scale", SMALL_SCALE, "weight", PANGO_WEIGHT_NORMAL, NULL );
748    gtr_cell_renderer_render( p->text_renderer, window, widget, &prog_area, flags );
749    g_object_set( p->progress_renderer, "value", (int)(percentDone*100.0), "text", "", "sensitive", sensitive, NULL );
750    gtr_cell_renderer_render( p->progress_renderer, window, widget, &prct_area, flags );
751    g_object_set( p->text_renderer, "text", gstr_stat->str, FOREGROUND_COLOR_KEY, &text_color, NULL );
752    gtr_cell_renderer_render( p->text_renderer, window, widget, &stat_area, flags );
753
754    /* cleanup */
755    g_object_unref( icon );
756}
757
758static void
759torrent_cell_renderer_render( GtkCellRenderer       * cell,
760                              GtrDrawable           * window,
761                              GtkWidget             * widget,
762                              const GdkRectangle    * background_area,
763                              const GdkRectangle    * cell_area,
764                              GtkCellRendererState    flags )
765{
766    TorrentCellRenderer * self = TORRENT_CELL_RENDERER( cell );
767
768#ifdef TEST_RTL
769    GtkTextDirection      real_dir = gtk_widget_get_direction( widget );
770    gtk_widget_set_direction( widget, GTK_TEXT_DIR_RTL );
771#endif
772
773    if( self && self->priv->tor )
774    {
775        struct TorrentCellRendererPrivate * p = self->priv;
776        if( p->compact )
777            render_compact( self, window, widget, background_area, cell_area, flags );
778        else
779            render_full( self, window, widget, background_area, cell_area, flags );
780    }
781
782#ifdef TEST_RTL
783    gtk_widget_set_direction( widget, real_dir );
784#endif
785}
786
787static void
788torrent_cell_renderer_set_property( GObject      * object,
789                                    guint          property_id,
790                                    const GValue * v,
791                                    GParamSpec   * pspec )
792{
793    TorrentCellRenderer * self = TORRENT_CELL_RENDERER( object );
794    struct TorrentCellRendererPrivate * p = self->priv;
795
796    switch( property_id )
797    {
798        case P_TORRENT:        p->tor                 = g_value_get_pointer( v ); break;
799        case P_UPLOAD_SPEED:   p->upload_speed_KBps   = g_value_get_double( v ); break;
800        case P_DOWNLOAD_SPEED: p->download_speed_KBps = g_value_get_double( v ); break;
801        case P_BAR_HEIGHT:     p->bar_height          = g_value_get_int( v ); break;
802        case P_COMPACT:        p->compact             = g_value_get_boolean( v ); break;
803        default: G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, pspec ); break;
804    }
805}
806
807static void
808torrent_cell_renderer_get_property( GObject     * object,
809                                    guint         property_id,
810                                    GValue      * v,
811                                    GParamSpec  * pspec )
812{
813    const TorrentCellRenderer * self = TORRENT_CELL_RENDERER( object );
814    struct TorrentCellRendererPrivate * p = self->priv;
815
816    switch( property_id )
817    {
818        case P_TORRENT:        g_value_set_pointer( v, p->tor ); break;
819        case P_UPLOAD_SPEED:   g_value_set_double( v, p->upload_speed_KBps ); break;
820        case P_DOWNLOAD_SPEED: g_value_set_double( v, p->download_speed_KBps ); break;
821        case P_BAR_HEIGHT:     g_value_set_int( v, p->bar_height ); break;
822        case P_COMPACT:        g_value_set_boolean( v, p->compact ); break;
823        default: G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, pspec ); break;
824    }
825}
826
827G_DEFINE_TYPE (TorrentCellRenderer, torrent_cell_renderer, GTK_TYPE_CELL_RENDERER)
828
829static void
830torrent_cell_renderer_dispose( GObject * o )
831{
832    TorrentCellRenderer * r = TORRENT_CELL_RENDERER( o );
833
834    if( r && r->priv )
835    {
836        g_string_free( r->priv->gstr1, TRUE );
837        g_string_free( r->priv->gstr2, TRUE );
838        g_object_unref( G_OBJECT( r->priv->text_renderer ) );
839        g_object_unref( G_OBJECT( r->priv->progress_renderer ) );
840        g_object_unref( G_OBJECT( r->priv->icon_renderer ) );
841        r->priv = NULL;
842    }
843
844    G_OBJECT_CLASS( torrent_cell_renderer_parent_class )->dispose( o );
845}
846
847static void
848torrent_cell_renderer_class_init( TorrentCellRendererClass * klass )
849{
850    GObjectClass *         gobject_class = G_OBJECT_CLASS( klass );
851    GtkCellRendererClass * cell_class = GTK_CELL_RENDERER_CLASS( klass );
852
853    g_type_class_add_private( klass,
854                             sizeof( struct TorrentCellRendererPrivate ) );
855
856    cell_class->render = torrent_cell_renderer_render;
857    cell_class->get_size = torrent_cell_renderer_get_size;
858    gobject_class->set_property = torrent_cell_renderer_set_property;
859    gobject_class->get_property = torrent_cell_renderer_get_property;
860    gobject_class->dispose = torrent_cell_renderer_dispose;
861
862    g_object_class_install_property( gobject_class, P_TORRENT,
863                                    g_param_spec_pointer( "torrent", NULL,
864                                                          "tr_torrent*",
865                                                          G_PARAM_READWRITE ) );
866
867    g_object_class_install_property( gobject_class, P_UPLOAD_SPEED,
868                                    g_param_spec_double( "piece-upload-speed", NULL,
869                                                         "tr_stat.pieceUploadSpeed_KBps",
870                                                         0, INT_MAX, 0,
871                                                         G_PARAM_READWRITE ) );
872
873    g_object_class_install_property( gobject_class, P_DOWNLOAD_SPEED,
874                                    g_param_spec_double( "piece-download-speed", NULL,
875                                                         "tr_stat.pieceDownloadSpeed_KBps",
876                                                         0, INT_MAX, 0,
877                                                         G_PARAM_READWRITE ) );
878
879    g_object_class_install_property( gobject_class, P_BAR_HEIGHT,
880                                    g_param_spec_int( "bar-height", NULL,
881                                                      "Bar Height",
882                                                      1, INT_MAX,
883                                                      DEFAULT_BAR_HEIGHT,
884                                                      G_PARAM_READWRITE ) );
885
886    g_object_class_install_property( gobject_class, P_COMPACT,
887                                    g_param_spec_boolean( "compact", NULL,
888                                                          "Compact Mode",
889                                                          FALSE,
890                                                          G_PARAM_READWRITE ) );
891}
892
893static void
894torrent_cell_renderer_init( TorrentCellRenderer * self )
895{
896    struct TorrentCellRendererPrivate * p;
897
898    p = self->priv = G_TYPE_INSTANCE_GET_PRIVATE(
899            self,
900            TORRENT_CELL_RENDERER_TYPE,
901            struct
902            TorrentCellRendererPrivate );
903
904    p->tor = NULL;
905    p->gstr1 = g_string_new( NULL );
906    p->gstr2 = g_string_new( NULL );
907    p->text_renderer = gtk_cell_renderer_text_new( );
908    g_object_set( p->text_renderer, "xpad", 0, "ypad", 0, NULL );
909    p->progress_renderer = gtk_cell_renderer_progress_new(  );
910    p->icon_renderer = gtk_cell_renderer_pixbuf_new(  );
911    g_object_ref_sink( p->text_renderer );
912    g_object_ref_sink( p->progress_renderer );
913    g_object_ref_sink( p->icon_renderer );
914
915    p->bar_height = DEFAULT_BAR_HEIGHT;
916}
917
918
919GtkCellRenderer *
920torrent_cell_renderer_new( void )
921{
922    return (GtkCellRenderer *) g_object_new( TORRENT_CELL_RENDERER_TYPE,
923                                             NULL );
924}
925
926