/*
 * This file Copyright (C) 2007-2009 Charles Kerr <charles@transmissionbt.com>
 *
 * This file is licensed by the GPL version 2.  Works owned by the
 * Transmission project are granted a special exemption to clause 2(b)
 * so that the bulk of its code can remain under the MIT license.
 * This exemption does not extend to derived works not owned by
 * the Transmission project.
 *
 * $Id: torrent-cell-renderer.c 9108 2009-09-12 07:26:14Z charles $
 */

#include "assert.h"
#include <string.h> /* strcmp() */
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <libtransmission/transmission.h>
#include <libtransmission/utils.h> /* tr_truncd() */
#include "hig.h"
#include "icons.h"
#include "torrent-cell-renderer.h"
#include "tr-torrent.h"
#include "util.h"

/* #define TEST_RTL */

enum
{
    P_TORRENT = 1,
    P_UPLOAD_SPEED,
    P_DOWNLOAD_SPEED,
    P_BAR_HEIGHT,
    P_MINIMAL
};

#define DEFAULT_BAR_HEIGHT 12
#define SMALL_SCALE 0.9
#define MINIMAL_ICON_SIZE GTK_ICON_SIZE_MENU
#define FULL_ICON_SIZE GTK_ICON_SIZE_DND

/***
****
***/

static char*
getProgressString( const tr_torrent * tor,
                   const tr_info    * info,
                   const tr_stat    * torStat )
{
    const int      isDone = torStat->leftUntilDone == 0;
    const uint64_t haveTotal = torStat->haveUnchecked + torStat->haveValid;
    const int      isSeed = torStat->haveValid >= info->totalSize;
    char           buf1[32], buf2[32], buf3[32], buf4[32];
    char *         str;
    double         seedRatio;
    gboolean       hasSeedRatio = FALSE;

    if( !isDone )
    {
        str = g_strdup_printf(
            /* %1$s is how much we've got,
               %2$s is how much we'll have when done,
               %3$.2f%% is a percentage of the two */
            _( "%1$s of %2$s (%3$.2f%%)" ),
            tr_strlsize( buf1, haveTotal, sizeof( buf1 ) ),
            tr_strlsize( buf2, torStat->sizeWhenDone, sizeof( buf2 ) ),
            tr_truncd( torStat->percentDone * 100.0, 2 ) );
    }
    else if( !isSeed )
    {
        str = g_strdup_printf(
            /* %1$s is how much we've got,
               %2$s is the torrent's total size,
               %3$.2f%% is a percentage of the two,
               %4$s is how much we've uploaded,
               %5$s is our upload-to-download ratio */
            _( "%1$s of %2$s (%3$.2f%%), uploaded %4$s (Ratio: %5$s)" ),
            tr_strlsize( buf1, haveTotal, sizeof( buf1 ) ),
            tr_strlsize( buf2, info->totalSize, sizeof( buf2 ) ),
            tr_truncd( torStat->percentComplete * 100.0, 2 ),
            tr_strlsize( buf3, torStat->uploadedEver, sizeof( buf3 ) ),
            tr_strlratio( buf4, torStat->ratio, sizeof( buf4 ) ) );
    }
    else if(( hasSeedRatio = tr_torrentGetSeedRatio( tor, &seedRatio )))
    {
        str = g_strdup_printf(
            /* %1$s is the torrent's total size,
               %2$s is how much we've uploaded,
               %3$s is our upload-to-download ratio,
               $4$s is the ratio we want to reach before we stop uploading */
            _( "%1$s, uploaded %2$s (Ratio: %3$s Goal: %4$s)" ),
            tr_strlsize( buf1, info->totalSize, sizeof( buf1 ) ),
            tr_strlsize( buf2, torStat->uploadedEver, sizeof( buf2 ) ),
            tr_strlratio( buf3, torStat->ratio, sizeof( buf3 ) ),
            tr_strlratio( buf4, seedRatio, sizeof( buf4 ) ) );
    }
    else /* seeding w/o a ratio */
    {
        str = g_strdup_printf(
            /* %1$s is the torrent's total size,
               %2$s is how much we've uploaded,
               %3$s is our upload-to-download ratio */
            _( "%1$s, uploaded %2$s (Ratio: %3$s)" ),
            tr_strlsize( buf1, info->totalSize, sizeof( buf1 ) ),
            tr_strlsize( buf2, torStat->uploadedEver, sizeof( buf2 ) ),
            tr_strlratio( buf3, torStat->ratio, sizeof( buf3 ) ) );
    }

    /* add time when downloading */
    if( hasSeedRatio || ( torStat->activity == TR_STATUS_DOWNLOAD ) )
    {
        const int eta = torStat->eta;
        GString * gstr = g_string_new( str );
        g_string_append( gstr, " - " );
        if( eta < 0 )
            g_string_append( gstr, _( "Remaining time unknown" ) );
        else
        {
            char timestr[128];
            tr_strltime( timestr, eta, sizeof( timestr ) );
            /* time remaining */
            g_string_append_printf( gstr, _( "%s remaining" ), timestr );
        }
        g_free( str );
        str = g_string_free( gstr, FALSE );
    }

    return str;
}

static char*
getShortTransferString( const tr_stat * torStat,
                        double          uploadSpeed,
                        double          downloadSpeed,
                        char          * buf,
                        size_t          buflen )
{
    char      downStr[32], upStr[32];
    const int haveDown = torStat->peersSendingToUs > 0;
    const int haveUp = torStat->peersGettingFromUs > 0;

    if( haveDown )
        tr_strlspeed( downStr, downloadSpeed, sizeof( downStr ) );
    if( haveUp )
        tr_strlspeed( upStr, uploadSpeed, sizeof( upStr ) );

    if( haveDown && haveUp )
        /* Translators: "speed|" is here for disambiguation.  Please remove it from your translation.
           %1$s is the download speed
           %2$s is the upload speed */
        g_snprintf( buf, buflen, Q_(
                        "speed|Down: %1$s, Up: %2$s" ), downStr, upStr );
    else if( haveDown )
        /* download speed */
        g_snprintf( buf, buflen, _( "Down: %s" ), downStr );
    else if( haveUp )
        /* upload speed */
        g_snprintf( buf, buflen, _( "Up: %s" ), upStr );
    else
        /* the torrent isn't uploading or downloading */
        g_strlcpy( buf, _( "Idle" ), buflen );

    return buf;
}

static char*
getShortStatusString( const tr_stat * torStat,
                      double          uploadSpeed,
                      double          downloadSpeed )
{
    GString * gstr = g_string_new( NULL );

    switch( torStat->activity )
    {
        case TR_STATUS_STOPPED:
            g_string_assign( gstr, _( "Paused" ) );
            break;

        case TR_STATUS_CHECK_WAIT:
            g_string_assign( gstr, _( "Waiting to verify local data" ) );
            break;

        case TR_STATUS_CHECK:
            g_string_append_printf( gstr,
                                    _( "Verifying local data (%.1f%% tested)" ),
                                    tr_truncd( torStat->recheckProgress * 100.0, 1 ) );
            break;

        case TR_STATUS_DOWNLOAD:
        case TR_STATUS_SEED:
        {
            char buf[512];
            if( torStat->activity != TR_STATUS_DOWNLOAD )
            {
                tr_strlratio( buf, torStat->ratio, sizeof( buf ) );
                g_string_append_printf( gstr, _( "Ratio: %s" ), buf );
                g_string_append( gstr, ", " );
            }
            getShortTransferString( torStat, uploadSpeed, downloadSpeed, buf, sizeof( buf ) );
            g_string_append( gstr, buf );
            break;
        }

        default:
            break;
    }

    return g_string_free( gstr, FALSE );
}

static char*
getStatusString( const tr_stat * torStat,
                 const double    uploadSpeed,
                 const double    downloadSpeed )
{
    const int isActive = torStat->activity != TR_STATUS_STOPPED;
    const int isChecking = torStat->activity == TR_STATUS_CHECK
                        || torStat->activity == TR_STATUS_CHECK_WAIT;

    GString * gstr = g_string_new( NULL );

    if( torStat->error )
    {
        const char * fmt[] = { NULL, N_( "Tracker gave a warning: \"%s\"" ),
                                     N_( "Tracker gave an error: \"%s\"" ),
                                     N_( "Error: %s" ) };
        g_string_append_printf( gstr, _( fmt[torStat->error] ), torStat->errorString );
    }
    else switch( torStat->activity )
        {
            case TR_STATUS_STOPPED:
            case TR_STATUS_CHECK_WAIT:
            case TR_STATUS_CHECK:
            {
                char * pch = getShortStatusString( torStat, uploadSpeed, downloadSpeed );
                g_string_assign( gstr, pch );
                g_free( pch );
                break;
            }

            case TR_STATUS_DOWNLOAD:
                g_string_append_printf( gstr,
                    ngettext( "Downloading from %1$'d of %2$'d connected peer",
                              "Downloading from %1$'d of %2$'d connected peers",
                              torStat->peersConnected ),
                    torStat->peersSendingToUs +
                    torStat->webseedsSendingToUs,
                    torStat->peersConnected +
                    torStat->webseedsSendingToUs );
                break;

            case TR_STATUS_SEED:
                g_string_append_printf( gstr,
                    ngettext( "Seeding to %1$'d of %2$'d connected peer",
                              "Seeding to %1$'d of %2$'d connected peers",
                              torStat->peersConnected ),
                    torStat->peersGettingFromUs,
                    torStat->peersConnected );
                break;
        }

    if( isActive && !isChecking )
    {
        char buf[256];
        getShortTransferString( torStat, uploadSpeed, downloadSpeed, buf, sizeof( buf ) );
        g_string_append_printf( gstr, " - %s", buf );
    }

    return g_string_free( gstr, FALSE );
}

/***
****
***/

static GtkCellRendererClass * parent_class = NULL;

struct TorrentCellRendererPrivate
{
    tr_torrent       * tor;
    GtkCellRenderer  * text_renderer;
    GtkCellRenderer  * text_renderer_err;
    GtkCellRenderer  * progress_renderer;
    GtkCellRenderer  * icon_renderer;
    int bar_height;

    /* use this instead of tr_stat.pieceUploadSpeed so that the model can
       control when the speed displays get updated.  this is done to keep
       the individual torrents' speeds and the status bar's overall speed
       in sync even if they refresh at slightly different times */
    double upload_speed;

    /* @see upload_speed */
    double download_speed;

    gboolean minimal;
};

/***
****
***/

static GdkPixbuf*
get_icon( const tr_torrent * tor, GtkIconSize icon_size, GtkWidget * for_widget )
{
    const char * mime_type;
    const tr_info * info = tr_torrentInfo( tor );

    if( info->fileCount > 1 )
        mime_type = DIRECTORY_MIME_TYPE;
    else
        mime_type = get_mime_type_from_filename( info->files[0].name );

    return get_mime_type_icon( mime_type, icon_size, for_widget );
}

static GtkCellRenderer*
get_text_renderer( const tr_stat * st, TorrentCellRenderer * r )
{
    return st->error ? r->priv->text_renderer_err : r->priv->text_renderer;
}

/***
****
***/

static void
get_size_minimal( TorrentCellRenderer * cell,
                  GtkWidget           * widget,
                  gint                * width,
                  gint                * height )
{
    int w, h;
    GdkRectangle icon_area;
    GdkRectangle name_area;
    GdkRectangle stat_area;
    const char * name;
    char * status;
    GdkPixbuf * icon;
    GtkCellRenderer * text_renderer;

    struct TorrentCellRendererPrivate * p = cell->priv;
    const tr_torrent * tor = p->tor;
    const tr_stat * st = tr_torrentStatCached( (tr_torrent*)tor );

    icon = get_icon( tor, MINIMAL_ICON_SIZE, widget );
    name = tr_torrentInfo( tor )->name;
    status = getShortStatusString( st, p->upload_speed, p->download_speed );

    /* get the idealized cell dimensions */
    g_object_set( p->icon_renderer, "pixbuf", icon, NULL );
    gtk_cell_renderer_get_size( p->icon_renderer, widget, NULL, NULL, NULL, &w, &h );
    icon_area.width = w;
    icon_area.height = h;
    text_renderer = get_text_renderer( st, cell );
    g_object_set( text_renderer, "text", name, "ellipsize", PANGO_ELLIPSIZE_NONE,  "scale", 1.0, NULL );
    gtk_cell_renderer_get_size( text_renderer, widget, NULL, NULL, NULL, &w, &h );
    name_area.width = w;
    name_area.height = h;
    g_object_set( text_renderer, "text", status, "scale", SMALL_SCALE, NULL );
    gtk_cell_renderer_get_size( text_renderer, widget, NULL, NULL, NULL, &w, &h );
    stat_area.width = w;
    stat_area.height = h;

    /**
    *** LAYOUT
    **/

    if( width != NULL )
        *width = cell->parent.xpad * 2 + icon_area.width + GUI_PAD + name_area.width + GUI_PAD + stat_area.width;
    if( height != NULL )
        *height = cell->parent.ypad * 2 + name_area.height + p->bar_height;

    /* cleanup */
    g_free( status );
    g_object_unref( icon );
}

#define MAX3(a,b,c) MAX(a,MAX(b,c))

static void
get_size_full( TorrentCellRenderer * cell,
               GtkWidget           * widget,
               gint                * width,
               gint                * height )
{
    int w, h;
    GdkRectangle icon_area;
    GdkRectangle name_area;
    GdkRectangle stat_area;
    GdkRectangle prog_area;
    const char * name;
    char * status;
    char * progress;
    GdkPixbuf * icon;
    GtkCellRenderer * text_renderer;

    struct TorrentCellRendererPrivate * p = cell->priv;
    const tr_torrent * tor = p->tor;
    const tr_stat * st = tr_torrentStatCached( (tr_torrent*)tor );
    const tr_info * inf = tr_torrentInfo( tor );

    icon = get_icon( tor, FULL_ICON_SIZE, widget );
    name = inf->name;
    status = getStatusString( st, p->upload_speed, p->download_speed );
    progress = getProgressString( tor, inf, st );

    /* get the idealized cell dimensions */
    g_object_set( p->icon_renderer, "pixbuf", icon, NULL );
    gtk_cell_renderer_get_size( p->icon_renderer, widget, NULL, NULL, NULL, &w, &h );
    icon_area.width = w;
    icon_area.height = h;
    text_renderer = get_text_renderer( st, cell );
    g_object_set( text_renderer, "text", name, "weight", PANGO_WEIGHT_BOLD, "scale", 1.0, "ellipsize", PANGO_ELLIPSIZE_NONE, NULL );
    gtk_cell_renderer_get_size( text_renderer, widget, NULL, NULL, NULL, &w, &h );
    name_area.width = w;
    name_area.height = h;
    g_object_set( text_renderer, "text", progress, "weight", PANGO_WEIGHT_NORMAL, "scale", SMALL_SCALE, NULL );
    gtk_cell_renderer_get_size( text_renderer, widget, NULL, NULL, NULL, &w, &h );
    prog_area.width = w;
    prog_area.height = h;
    g_object_set( text_renderer, "text", status, NULL );
    gtk_cell_renderer_get_size( text_renderer, widget, NULL, NULL, NULL, &w, &h );
    stat_area.width = w;
    stat_area.height = h;

    /**
    *** LAYOUT
    **/

    if( width != NULL )
        *width = cell->parent.xpad * 2 + icon_area.width + GUI_PAD + MAX3( name_area.width, prog_area.width, stat_area.width );
    if( height != NULL )
        *height = cell->parent.ypad * 2 + name_area.height + prog_area.height + GUI_PAD_SMALL + p->bar_height + GUI_PAD_SMALL + stat_area.height;

    /* cleanup */
    g_free( status );
    g_free( progress );
    g_object_unref( icon );
}


static void
torrent_cell_renderer_get_size( GtkCellRenderer  * cell,
                                GtkWidget        * widget,
                                GdkRectangle     * cell_area,
                                gint             * x_offset,
                                gint             * y_offset,
                                gint             * width,
                                gint             * height )
{
    TorrentCellRenderer * self = TORRENT_CELL_RENDERER( cell );

    if( self && self->priv->tor )
    {
        struct TorrentCellRendererPrivate * p = self->priv;
        int w, h;

        if( p->minimal )
            get_size_minimal( TORRENT_CELL_RENDERER( cell ), widget, &w, &h );
        else
            get_size_full( TORRENT_CELL_RENDERER( cell ), widget, &w, &h );

        if( width )
            *width = w;

        if( height )
            *height = h;

        if( cell_area ) {
            if( x_offset ) *x_offset = 0;
            if( y_offset ) {
                *y_offset = 0.5 * ( cell_area->height - ( h + ( 2 * cell->ypad ) ) );
                *y_offset = MAX( *y_offset, 0 );
            }
        }
    }
}

static void
render_minimal( TorrentCellRenderer   * cell,
                GdkDrawable           * window,
                GtkWidget             * widget,
                GdkRectangle          * background_area,
                GdkRectangle          * cell_area UNUSED,
                GdkRectangle          * expose_area UNUSED,
                GtkCellRendererState    flags )
{
    int w, h;
    GdkRectangle icon_area;
    GdkRectangle name_area;
    GdkRectangle stat_area;
    GdkRectangle prog_area;
    GdkRectangle fill_area;
    const char * name;
    char * status;
    GdkPixbuf * icon;
    GtkCellRenderer * text_renderer;

    struct TorrentCellRendererPrivate * p = cell->priv;
    const tr_torrent * tor = p->tor;
    const tr_stat * st = tr_torrentStatCached( (tr_torrent*)tor );
    const gboolean active = st->activity != TR_STATUS_STOPPED;
    const double percentDone = MAX( 0.0, st->percentDone );
    const gboolean sensitive = active || st->error;

    icon = get_icon( tor, MINIMAL_ICON_SIZE, widget );
    name = tr_torrentInfo( tor )->name;
    status = getShortStatusString( st, p->upload_speed, p->download_speed );

    /* get the cell dimensions */
    g_object_set( p->icon_renderer, "pixbuf", icon, NULL );
    gtk_cell_renderer_get_size( p->icon_renderer, widget, NULL, NULL, NULL, &w, &h );
    icon_area.width = w;
    icon_area.height = h;
    text_renderer = get_text_renderer( st, cell );
    g_object_set( text_renderer, "text", name, "ellipsize", PANGO_ELLIPSIZE_NONE, "scale", 1.0, NULL );
    gtk_cell_renderer_get_size( text_renderer, widget, NULL, NULL, NULL, &w, &h );
    name_area.width = w;
    name_area.height = h;
    g_object_set( text_renderer, "text", status, "scale", SMALL_SCALE, NULL );
    gtk_cell_renderer_get_size( text_renderer, widget, NULL, NULL, NULL, &w, &h );
    stat_area.width = w;
    stat_area.height = h;

    /**
    *** LAYOUT
    **/

    fill_area = *background_area;
    fill_area.x += cell->parent.xpad;
    fill_area.y += cell->parent.ypad;
    fill_area.width -= cell->parent.xpad * 2;
    fill_area.height -= cell->parent.ypad * 2;

    /* icon */
    icon_area.x = fill_area.x;
    icon_area.y = fill_area.y + ( fill_area.height - icon_area.height ) / 2;

    /* short status (right justified) */
    stat_area.x = fill_area.x + fill_area.width - stat_area.width;
    stat_area.y = fill_area.y + ( name_area.height - stat_area.height ) / 2;

    /* name */
    name_area.x = icon_area.x + icon_area.width + GUI_PAD;
    name_area.y = fill_area.y;
    name_area.width = stat_area.x - GUI_PAD - name_area.x;

    /* progressbar */
    prog_area.x = name_area.x;
    prog_area.y = name_area.y + name_area.height;
    prog_area.width = name_area.width + GUI_PAD + stat_area.width;
    prog_area.height = p->bar_height;

    /**
    *** RENDER
    **/

    g_object_set( p->icon_renderer, "pixbuf", icon, "sensitive", sensitive, NULL );
    gtk_cell_renderer_render( p->icon_renderer, window, widget, &icon_area, &icon_area, &icon_area, flags );
    g_object_set( text_renderer, "text", status, "scale", SMALL_SCALE, "sensitive", sensitive, "ellipsize", PANGO_ELLIPSIZE_END, NULL );
    gtk_cell_renderer_render( text_renderer, window, widget, &stat_area, &stat_area, &stat_area, flags );
    g_object_set( text_renderer, "text", name, "scale", 1.0, NULL );
    gtk_cell_renderer_render( text_renderer, window, widget, &name_area, &name_area, &name_area, flags );
    g_object_set( p->progress_renderer, "value", (int)(percentDone*100.0), "text", "", "sensitive", sensitive, NULL );
    gtk_cell_renderer_render( p->progress_renderer, window, widget, &prog_area, &prog_area, &prog_area, flags );

    /* cleanup */
    g_free( status );
    g_object_unref( icon );
}

static void
render_full( TorrentCellRenderer   * cell,
             GdkDrawable           * window,
             GtkWidget             * widget,
             GdkRectangle          * background_area,
             GdkRectangle          * cell_area UNUSED,
             GdkRectangle          * expose_area UNUSED,
             GtkCellRendererState    flags )
{
    int w, h;
    GdkRectangle fill_area;
    GdkRectangle icon_area;
    GdkRectangle name_area;
    GdkRectangle stat_area;
    GdkRectangle prog_area;
    GdkRectangle prct_area;
    const char * name;
    char * status;
    char * progress;
    GdkPixbuf * icon;
    GtkCellRenderer * text_renderer;

    struct TorrentCellRendererPrivate * p = cell->priv;
    const tr_torrent * tor = p->tor;
    const tr_stat * st = tr_torrentStatCached( (tr_torrent*)tor );
    const tr_info * inf = tr_torrentInfo( tor );
    const gboolean active = st->activity != TR_STATUS_STOPPED;
    const double percentDone = MAX( 0.0, st->percentDone );
    const gboolean sensitive = active || st->error;

    icon = get_icon( tor, FULL_ICON_SIZE, widget );
    name = inf->name;
    status = getStatusString( st, p->upload_speed, p->download_speed );
    progress = getProgressString( tor, inf, st );

    /* get the idealized cell dimensions */
    g_object_set( p->icon_renderer, "pixbuf", icon, NULL );
    gtk_cell_renderer_get_size( p->icon_renderer, widget, NULL, NULL, NULL, &w, &h );
    icon_area.width = w;
    icon_area.height = h;
    text_renderer = get_text_renderer( st, cell );
    g_object_set( text_renderer, "text", name, "weight", PANGO_WEIGHT_BOLD, "ellipsize", PANGO_ELLIPSIZE_NONE, "scale", 1.0, NULL );
    gtk_cell_renderer_get_size( text_renderer, widget, NULL, NULL, NULL, &w, &h );
    name_area.width = w;
    name_area.height = h;
    g_object_set( text_renderer, "text", progress, "weight", PANGO_WEIGHT_NORMAL, "scale", SMALL_SCALE, NULL );
    gtk_cell_renderer_get_size( text_renderer, widget, NULL, NULL, NULL, &w, &h );
    prog_area.width = w;
    prog_area.height = h;
    g_object_set( text_renderer, "text", status, NULL );
    gtk_cell_renderer_get_size( text_renderer, widget, NULL, NULL, NULL, &w, &h );
    stat_area.width = w;
    stat_area.height = h;

    /**
    *** LAYOUT
    **/

    fill_area = *background_area;
    fill_area.x += cell->parent.xpad;
    fill_area.y += cell->parent.ypad;
    fill_area.width -= cell->parent.xpad * 2;
    fill_area.height -= cell->parent.ypad * 2;

    /* icon */
    icon_area.x = fill_area.x;
    icon_area.y = fill_area.y + ( fill_area.height - icon_area.height ) / 2;

    /* name */
    name_area.x = icon_area.x + icon_area.width + GUI_PAD;
    name_area.y = fill_area.y;
    name_area.width = fill_area.width - GUI_PAD - icon_area.width - GUI_PAD_SMALL;

    /* prog */
    prog_area.x = name_area.x;
    prog_area.y = name_area.y + name_area.height;
    prog_area.width = name_area.width;

    /* progressbar */
    prct_area.x = prog_area.x;
    prct_area.y = prog_area.y + prog_area.height + GUI_PAD_SMALL;
    prct_area.width = prog_area.width;
    prct_area.height = p->bar_height;

    /* status */
    stat_area.x = prct_area.x;
    stat_area.y = prct_area.y + prct_area.height + GUI_PAD_SMALL;
    stat_area.width = prct_area.width;

    /**
    *** RENDER
    **/

    g_object_set( p->icon_renderer, "pixbuf", icon, "sensitive", sensitive, NULL );
    gtk_cell_renderer_render( p->icon_renderer, window, widget, &icon_area, &icon_area, &icon_area, flags );
    g_object_set( text_renderer, "text", name, "scale", 1.0, "sensitive", sensitive, "ellipsize", PANGO_ELLIPSIZE_END, "weight", PANGO_WEIGHT_BOLD, NULL );
    gtk_cell_renderer_render( text_renderer, window, widget, &name_area, &name_area, &name_area, flags );
    g_object_set( text_renderer, "text", progress, "scale", SMALL_SCALE, "weight", PANGO_WEIGHT_NORMAL, NULL );
    gtk_cell_renderer_render( text_renderer, window, widget, &prog_area, &prog_area, &prog_area, flags );
    g_object_set( p->progress_renderer, "value", (int)(percentDone*100.0), "text", "", "sensitive", sensitive, NULL );
    gtk_cell_renderer_render( p->progress_renderer, window, widget, &prct_area, &prct_area, &prct_area, flags );
    g_object_set( text_renderer, "text", status, NULL );
    gtk_cell_renderer_render( text_renderer, window, widget, &stat_area, &stat_area, &stat_area, flags );

    /* cleanup */
    g_free( status );
    g_free( progress );
    g_object_unref( icon );
}

static void
torrent_cell_renderer_render( GtkCellRenderer       * cell,
                              GdkDrawable           * window,
                              GtkWidget             * widget,
                              GdkRectangle          * background_area,
                              GdkRectangle          * cell_area,
                              GdkRectangle          * expose_area,
                              GtkCellRendererState    flags )
{
    TorrentCellRenderer * self = TORRENT_CELL_RENDERER( cell );

#ifdef TEST_RTL
    GtkTextDirection      real_dir = gtk_widget_get_direction( widget );
    gtk_widget_set_direction( widget, GTK_TEXT_DIR_RTL );
#endif

    if( self && self->priv->tor )
    {
        struct TorrentCellRendererPrivate * p = self->priv;
        if( p->minimal )
            render_minimal( self, window, widget, background_area, cell_area, expose_area, flags );
        else
            render_full( self, window, widget, background_area, cell_area, expose_area, flags );
    }

#ifdef TEST_RTL
    gtk_widget_set_direction( widget, real_dir );
#endif
}

static void
torrent_cell_renderer_set_property( GObject      * object,
                                    guint          property_id,
                                    const GValue * v,
                                    GParamSpec   * pspec )
{
    TorrentCellRenderer * self = TORRENT_CELL_RENDERER( object );
    struct TorrentCellRendererPrivate * p = self->priv;

    switch( property_id )
    {
        case P_TORRENT:        p->tor            = g_value_get_pointer( v ); break;
        case P_UPLOAD_SPEED:   p->upload_speed   = g_value_get_double( v ); break;
        case P_DOWNLOAD_SPEED: p->download_speed = g_value_get_double( v ); break;
        case P_BAR_HEIGHT:     p->bar_height     = g_value_get_int( v ); break;
        case P_MINIMAL:        p->minimal        = g_value_get_boolean( v ); break;
        default: G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, pspec ); break;
    }
}

static void
torrent_cell_renderer_get_property( GObject     * object,
                                    guint         property_id,
                                    GValue      * v,
                                    GParamSpec  * pspec )
{
    const TorrentCellRenderer * self = TORRENT_CELL_RENDERER( object );
    struct TorrentCellRendererPrivate * p = self->priv;

    switch( property_id )
    {
        case P_TORRENT:        g_value_set_pointer( v, p->tor ); break;
        case P_UPLOAD_SPEED:   g_value_set_double( v, p->upload_speed ); break;
        case P_DOWNLOAD_SPEED: g_value_set_double( v, p->download_speed ); break;
        case P_BAR_HEIGHT:     g_value_set_int( v, p->bar_height ); break;
        case P_MINIMAL:        g_value_set_boolean( v, p->minimal ); break;
        default: G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, pspec ); break;
    }
}

static void
torrent_cell_renderer_dispose( GObject * o )
{
    TorrentCellRenderer * r = TORRENT_CELL_RENDERER( o );
    GObjectClass *        parent;

    if( r && r->priv )
    {
        g_object_unref( G_OBJECT( r->priv->text_renderer ) );
        g_object_unref( G_OBJECT( r->priv->text_renderer_err ) );
        g_object_unref( G_OBJECT( r->priv->progress_renderer ) );
        g_object_unref( G_OBJECT( r->priv->icon_renderer ) );
        r->priv = NULL;
    }

    parent = g_type_class_peek( g_type_parent( TORRENT_CELL_RENDERER_TYPE ) );
    parent->dispose( o );
}

static void
torrent_cell_renderer_class_init( TorrentCellRendererClass * klass )
{
    GObjectClass *         gobject_class = G_OBJECT_CLASS( klass );
    GtkCellRendererClass * cell_class = GTK_CELL_RENDERER_CLASS( klass );

    g_type_class_add_private( klass,
                             sizeof( struct TorrentCellRendererPrivate ) );

    parent_class = (GtkCellRendererClass*) g_type_class_peek_parent( klass );

    cell_class->render = torrent_cell_renderer_render;
    cell_class->get_size = torrent_cell_renderer_get_size;
    gobject_class->set_property = torrent_cell_renderer_set_property;
    gobject_class->get_property = torrent_cell_renderer_get_property;
    gobject_class->dispose = torrent_cell_renderer_dispose;

    g_object_class_install_property( gobject_class, P_TORRENT,
                                    g_param_spec_pointer( "torrent", NULL,
                                                          "tr_torrent*",
                                                          G_PARAM_READWRITE ) );

    g_object_class_install_property( gobject_class, P_UPLOAD_SPEED,
                                    g_param_spec_double( "piece-upload-speed", NULL,
                                                         "tr_stat.pieceUploadSpeed",
                                                         0, INT_MAX, 0,
                                                         G_PARAM_READWRITE ) );

    g_object_class_install_property( gobject_class, P_DOWNLOAD_SPEED,
                                    g_param_spec_double( "piece-download-speed", NULL,
                                                         "tr_stat.pieceDownloadSpeed",
                                                         0, INT_MAX, 0,
                                                         G_PARAM_READWRITE ) );

    g_object_class_install_property( gobject_class, P_BAR_HEIGHT,
                                    g_param_spec_int( "bar-height", NULL,
                                                      "Bar Height",
                                                      1, INT_MAX,
                                                      DEFAULT_BAR_HEIGHT,
                                                      G_PARAM_READWRITE ) );

    g_object_class_install_property( gobject_class, P_MINIMAL,
                                    g_param_spec_boolean( "minimal", NULL,
                                                          "Minimal Mode",
                                                          FALSE,
                                                          G_PARAM_READWRITE ) );
}

static void
torrent_cell_renderer_init( GTypeInstance *  instance,
                            gpointer g_class UNUSED )
{
    TorrentCellRenderer *               self = TORRENT_CELL_RENDERER(
        instance );
    struct TorrentCellRendererPrivate * p;

    p = self->priv = G_TYPE_INSTANCE_GET_PRIVATE(
            self,
            TORRENT_CELL_RENDERER_TYPE,
            struct
            TorrentCellRendererPrivate );

    p->tor = NULL;
    p->text_renderer = gtk_cell_renderer_text_new( );
    g_object_set( p->text_renderer, "xpad", 0, "ypad", 0, NULL );
    p->text_renderer_err = gtk_cell_renderer_text_new(  );
    g_object_set( p->text_renderer_err, "xpad", 0, "ypad", 0, NULL );
    p->progress_renderer = gtk_cell_renderer_progress_new(  );
    p->icon_renderer = gtk_cell_renderer_pixbuf_new(  );
    g_object_set( p->text_renderer_err, "foreground", "red", NULL );
    tr_object_ref_sink( p->text_renderer );
    tr_object_ref_sink( p->text_renderer_err );
    tr_object_ref_sink( p->progress_renderer );
    tr_object_ref_sink( p->icon_renderer );

    p->bar_height = DEFAULT_BAR_HEIGHT;
}

GType
torrent_cell_renderer_get_type( void )
{
    static GType type = 0;

    if( !type )
    {
        static const GTypeInfo info =
        {
            sizeof( TorrentCellRendererClass ),
            NULL,                                            /* base_init */
            NULL,                                            /* base_finalize */
            (GClassInitFunc)torrent_cell_renderer_class_init,
            NULL,                                            /* class_finalize
                                                               */
            NULL,                                            /* class_data */
            sizeof( TorrentCellRenderer ),
            0,                                               /* n_preallocs */
            (GInstanceInitFunc)torrent_cell_renderer_init,
            NULL
        };

        type = g_type_register_static( GTK_TYPE_CELL_RENDERER,
                                       "TorrentCellRenderer",
                                       &info, (GTypeFlags)0 );
    }

    return type;
}

GtkCellRenderer *
torrent_cell_renderer_new( void )
{
    return (GtkCellRenderer *) g_object_new( TORRENT_CELL_RENDERER_TYPE,
                                             NULL );
}

