source: trunk/gtk/details.c @ 7998

Last change on this file since 7998 was 7998, checked in by charles, 13 years ago

(trunk gtk) Use gtk_tree_view_set_fixed_height_mode() in a few more places

  • Property svn:keywords set to Date Rev Author Id
File size: 54.8 KB
Line 
1/*
2 * This file Copyright (C) 2007-2009 Charles Kerr <charles@transmissionbt.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: details.c 7998 2009-03-03 00:48:50Z charles $
11 */
12
13#include <errno.h>
14#include <math.h> /* ceil() */
15#include <stddef.h>
16#include <stdio.h>
17#include <stdlib.h>
18#include <glib/gi18n.h>
19#include <gtk/gtk.h>
20
21#include <libtransmission/transmission.h>
22#include <libtransmission/utils.h> /* tr_httpIsValidURL */
23
24#include "actions.h"
25#include "details.h"
26#include "file-list.h"
27#include "tr-torrent.h"
28#include "tracker-list.h"
29#include "hig.h"
30#include "util.h"
31
32#define UPDATE_INTERVAL_SECONDS 2
33typedef struct 
34{
35    gpointer    gtor;
36    TrCore    * core;
37    guint       handler;
38} ResponseData;
39
40
41/****
42*****  PIECES VIEW
43****/
44
45/* define SHOW_PIECES */
46
47#ifdef SHOW_PIECES
48static int
49getGridSize( int   pieceCount,
50             int * n_rows,
51             int * n_cols )
52{
53    const int MAX_ACROSS = 16;
54
55    if( pieceCount >= ( MAX_ACROSS * MAX_ACROSS ) )
56    {
57        *n_rows = *n_cols = MAX_ACROSS;
58        return MAX_ACROSS * MAX_ACROSS;
59    }
60    else
61    {
62        int i;
63        for( i = 0; ( i * i ) < pieceCount; ++i ) ;
64        *n_rows = *n_cols = i;
65        return pieceCount;
66    }
67}
68
69 #define TO16( a ) ( (guint16)( ( a << 8 ) | ( a ) ) )
70 #define RGB_2_GDK( R, G, B ) { 0, TO16( R ), TO16( G ), TO16( B ) }
71
72enum { DRAW_AVAIL, DRAW_PROG };
73
74static void
75release_gobject_array( gpointer data )
76{
77    int       i;
78    GObject **objects = (GObject**) data;
79
80    for( i = 0; objects[i] != NULL; ++i )
81        g_object_unref ( G_OBJECT( objects[i] ) );
82    g_free ( objects );
83}
84
85static gboolean
86refresh_pieces( GtkWidget *            da,
87                GdkEventExpose * event UNUSED,
88                gpointer               gtor )
89{
90    tr_torrent *       tor = tr_torrent_handle( TR_TORRENT( gtor ) );
91    const tr_info *    info = tr_torrent_info( TR_TORRENT( gtor ) );
92    int                mode =
93        GPOINTER_TO_INT ( g_object_get_data ( G_OBJECT( da ), "draw-mode" ) );
94
95    GdkColormap *      colormap = gtk_widget_get_colormap ( da );
96    const int          widget_w = da->allocation.width;
97    const int          widget_h = da->allocation.height;
98    int                n_rows, n_cols;
99    const int          n_cells = getGridSize ( info->pieceCount,  &n_rows,
100                                               &n_cols );
101    const GdkRectangle grid_bounds = { 0, 0, widget_w, widget_h };
102    const double       piece_w = grid_bounds.width / (double)n_cols;
103    const double       piece_h = grid_bounds.height / (double)n_rows;
104    const int          piece_w_int = (int) ( piece_w + 1 ); /* pad for roundoff
105                                                              */
106    const int          piece_h_int = (int) ( piece_h + 1 ); /* pad for roundoff
107                                                              */
108    const gboolean     rtl = gtk_widget_get_direction( da ) ==
109                             GTK_TEXT_DIR_RTL;
110
111    guint8 *           prev_color = NULL;
112    gboolean           first_time = FALSE;
113
114    int                i, x, y;
115    int8_t *           pieces = NULL;
116    float *            completeness = NULL;
117
118    /**
119    ***  Get the Graphics Contexts...
120    **/
121
122    enum { ALL, LOTS, SOME, FEW, NONE,
123           BLACK, GRAY, BLINK,
124           N_COLORS };
125    GdkGC **           gcs = (GdkGC**) g_object_get_data ( G_OBJECT(
126                                                               da ),
127                                                           "graphics-contexts" );
128    if( gcs == NULL )
129    {
130        const GdkColor colors[N_COLORS] = {
131            RGB_2_GDK ( 114, 159, 207 ), /* all */
132            RGB_2_GDK (  52, 101, 164 ), /* lots */
133            RGB_2_GDK (  32,  74, 135 ), /* some */
134            RGB_2_GDK (  85,  87, 83 ), /* few */
135            RGB_2_GDK ( 238, 238, 236 ), /* none - tango aluminum highlight */
136            RGB_2_GDK (  46,  52, 54 ), /* black - tango slate shadow */
137            RGB_2_GDK ( 186, 189, 182 ), /* gray - tango aluminum shadow */
138            RGB_2_GDK ( 252, 233, 79 ), /* blink - tango butter highlight */
139        };
140
141        gcs = g_new ( GdkGC *, N_COLORS + 1 );
142
143        for( i = 0; i < N_COLORS; ++i )
144        {
145            gcs[i] = gdk_gc_new ( da->window );
146            gdk_gc_set_colormap ( gcs[i], colormap );
147            gdk_gc_set_rgb_fg_color ( gcs[i], &colors[i] );
148            gdk_gc_set_rgb_bg_color ( gcs[i], &colors[i] );
149        }
150
151        gcs[N_COLORS] = NULL; /* a sentinel in the release function */
152        g_object_set_data_full ( G_OBJECT( da ), "graphics-contexts",
153                                 gcs, release_gobject_array );
154    }
155
156    /**
157    ***  Get the cells' previous colors...
158    ***  (this is used for blinking when the color changes)
159    **/
160
161    prev_color = (guint8*) g_object_get_data ( G_OBJECT( da ), "prev-color" );
162    if( prev_color == NULL )
163    {
164        first_time = TRUE;
165        prev_color = g_new0 ( guint8, n_cells );
166        g_object_set_data_full ( G_OBJECT(
167                                     da ), "prev-color", prev_color, g_free );
168    }
169
170    /**
171    ***  Get the piece data values...
172    **/
173
174    switch( mode )
175    {
176        case DRAW_AVAIL:
177            pieces = g_new ( int8_t, n_cells );
178            tr_torrentAvailability ( tor, pieces, n_cells );
179            break;
180
181        case DRAW_PROG:
182            completeness = g_new ( float, n_cells );
183            tr_torrentAmountFinished ( tor, completeness, n_cells );
184            break;
185
186        default:
187            g_error( "no mode defined!" );
188    }
189
190    /**
191    ***  Draw...
192    **/
193
194    i = 0;
195    for( y = 0; y < n_rows; ++y )
196    {
197        for( x = 0; x < n_cols; ++x )
198        {
199            int draw_x = grid_bounds.x + (int)( x * piece_w );
200            int draw_y = grid_bounds.y + (int)( y * piece_h );
201            int color = BLACK;
202            int border = BLACK;
203
204            if( rtl )
205                draw_x = grid_bounds.x + grid_bounds.width -
206                         (int)( ( x + 1 ) * piece_w );
207            else
208                draw_x = grid_bounds.x + (int)( x * piece_w );
209            draw_y = grid_bounds.y + (int)( y * piece_h );
210
211            if( i < n_cells )
212            {
213                border = GRAY;
214
215                if( mode == DRAW_AVAIL )
216                {
217                    const int8_t val = pieces[i];
218                    if( val <  0 ) color = ALL;
219                    else if( val == 0 ) color = NONE;
220                    else if( val <= 4 ) color = FEW;
221                    else if( val <= 8 ) color = SOME;
222                    else color = LOTS;
223                }
224                else /* completeness */
225                {
226                    const float val = completeness[i];
227                    if( val >= 1.00 ) color = ALL;
228                    else if( val >= 0.66 ) color = LOTS;
229                    else if( val >= 0.33 ) color = SOME;
230                    else if( val >= 0.01 ) color = FEW;
231                    else color = NONE;
232                }
233
234                /* draw a "blink" for one interval when a piece changes */
235                if( first_time )
236                    prev_color[i] = color;
237                else if( color != prev_color[i] )
238                {
239                    prev_color[i] = color;
240                    color = border = BLINK;
241                }
242            }
243
244            gdk_draw_rectangle ( da->window, gcs[color], TRUE,
245                                 draw_x, draw_y,
246                                 piece_w_int, piece_h_int );
247
248            if( i < n_cells )
249                gdk_draw_rectangle ( da->window, gcs[border], FALSE,
250                                     draw_x, draw_y,
251                                     piece_w_int, piece_h_int );
252
253            ++i;
254        }
255    }
256
257    gdk_draw_rectangle ( da->window, gcs[GRAY], FALSE,
258                         grid_bounds.x, grid_bounds.y,
259                         grid_bounds.width - 1, grid_bounds.height - 1 );
260
261    g_free ( pieces );
262    g_free ( completeness );
263    return FALSE;
264}
265
266#endif
267
268/****
269*****  PEERS TAB
270****/
271
272enum
273{
274    WEBSEED_COL_URL,
275    WEBSEED_COL_DOWNLOAD_RATE,
276    N_WEBSEED_COLS
277};
278
279static const char * webseed_column_names[N_WEBSEED_COLS] =
280{
281    N_( "Web Seeds" ),
282    /* 'download speed' column header. terse to keep the column narrow. */
283    N_( "Down" )
284};
285
286static GtkTreeModel*
287webseed_model_new( const tr_torrent * tor )
288{
289    int             i;
290    const tr_info * inf = tr_torrentInfo( tor );
291    float *         speeds = tr_torrentWebSpeeds( tor );
292    GtkListStore *  store = gtk_list_store_new( N_WEBSEED_COLS,
293                                                G_TYPE_STRING,
294                                                G_TYPE_FLOAT );
295
296    for( i = 0; i < inf->webseedCount; ++i )
297    {
298        GtkTreeIter iter;
299        gtk_list_store_append( store, &iter );
300        gtk_list_store_set( store, &iter,
301                            WEBSEED_COL_URL, inf->webseeds[i],
302                            WEBSEED_COL_DOWNLOAD_RATE, speeds[i],
303                            -1 );
304    }
305
306    tr_free( speeds );
307    return GTK_TREE_MODEL( store );
308}
309
310enum
311{
312    PEER_COL_ADDRESS,
313    PEER_COL_DOWNLOAD_RATE,
314    PEER_COL_UPLOAD_RATE,
315    PEER_COL_CLIENT,
316    PEER_COL_PROGRESS,
317    PEER_COL_IS_ENCRYPTED,
318    PEER_COL_STATUS,
319    N_PEER_COLS
320};
321
322static const char* peer_column_names[N_PEER_COLS] =
323{
324    N_( "Address" ),
325    /* 'download speed' column header. terse to keep the column narrow. */
326    N_( "Down" ),
327    /* 'upload speed' column header.  terse to keep the column narrow. */
328    N_( "Up" ),
329    N_( "Client" ),
330    /* 'percent done' column header. terse to keep the column narrow. */
331    N_( "%" ),
332    " ",
333    N_( "Status" )
334};
335
336static int
337compare_peers( const void * a, const void * b )
338{
339    const tr_peer_stat * pa = a;
340    const tr_peer_stat * pb = b;
341
342    return strcmp( pa->addr, pb->addr );
343}
344
345static int
346compare_addr_to_peer( const void * addr, const void * b )
347{
348    const tr_peer_stat * peer = b;
349
350    return strcmp( addr, peer->addr );
351}
352
353static void
354peer_row_set( GtkListStore        * store,
355              GtkTreeIter         * iter,
356              const tr_peer_stat  * peer )
357{
358    const char * client = peer->client;
359
360    if( !client || !strcmp( client, "Unknown Client" ) )
361        client = " ";
362
363    gtk_list_store_set( store, iter,
364                        PEER_COL_ADDRESS, peer->addr,
365                        PEER_COL_CLIENT, client,
366                        PEER_COL_IS_ENCRYPTED, peer->isEncrypted,
367                        PEER_COL_PROGRESS, (int)( 100.0 * peer->progress ),
368                        PEER_COL_DOWNLOAD_RATE, peer->rateToClient,
369                        PEER_COL_UPLOAD_RATE, peer->rateToPeer,
370                        PEER_COL_STATUS, peer->flagStr,
371                        -1 );
372}
373
374static void
375append_peers_to_model( GtkListStore *       store,
376                       const tr_peer_stat * peers,
377                       int                  n_peers )
378{
379    int i;
380
381    for( i = 0; i < n_peers; ++i )
382    {
383        GtkTreeIter iter;
384        gtk_list_store_append( store, &iter );
385        peer_row_set ( store, &iter, &peers[i] );
386    }
387}
388
389static GtkTreeModel*
390peer_model_new( tr_torrent * tor )
391{
392    GtkListStore * m = gtk_list_store_new ( N_PEER_COLS,
393                                            G_TYPE_STRING, /* addr */
394                                            G_TYPE_FLOAT, /* downloadFromRate */
395                                            G_TYPE_FLOAT, /* uploadToRate */
396                                            G_TYPE_STRING, /* client */
397                                            G_TYPE_INT,   /* progress [0..100] */
398                                            G_TYPE_BOOLEAN, /* isEncrypted */
399                                            G_TYPE_STRING ); /* flagString */
400
401    int            n_peers = 0;
402    tr_peer_stat * peers = tr_torrentPeers ( tor, &n_peers );
403
404    qsort ( peers, n_peers, sizeof( tr_peer_stat ), compare_peers );
405    append_peers_to_model ( m, peers, n_peers );
406    tr_torrentPeersFree( peers, 0 );
407    return GTK_TREE_MODEL ( m );
408}
409
410static void
411render_encrypted( GtkTreeViewColumn  * column UNUSED,
412                  GtkCellRenderer *           renderer,
413                  GtkTreeModel *              tree_model,
414                  GtkTreeIter *               iter,
415                  gpointer             data   UNUSED )
416{
417    gboolean is_encrypted = FALSE;
418
419    gtk_tree_model_get ( tree_model, iter, PEER_COL_IS_ENCRYPTED,
420                         &is_encrypted,
421                         -1 );
422    g_object_set ( renderer, "xalign", (gfloat)0.0,
423                   "yalign", (gfloat)0.5,
424                   "stock-id", ( is_encrypted ? "transmission-lock" : NULL ),
425                   NULL );
426}
427
428static void
429render_ul_rate( GtkTreeViewColumn  * column UNUSED,
430                GtkCellRenderer *           renderer,
431                GtkTreeModel *              tree_model,
432                GtkTreeIter *               iter,
433                gpointer             data   UNUSED )
434{
435    float rate = 0.0;
436
437    gtk_tree_model_get ( tree_model, iter, PEER_COL_UPLOAD_RATE, &rate, -1 );
438    if( rate < 0.01 )
439        g_object_set ( renderer, "text", "", NULL );
440    else
441    {
442        char speedStr[64];
443        tr_strlspeed( speedStr, rate, sizeof( speedStr ) );
444        g_object_set( renderer, "text", speedStr, NULL );
445    }
446}
447
448static void
449render_dl_rate( GtkTreeViewColumn  * column UNUSED,
450                GtkCellRenderer *           renderer,
451                GtkTreeModel *              tree_model,
452                GtkTreeIter *               iter,
453                gpointer             data   UNUSED )
454{
455    float rate = 0.0;
456
457    gtk_tree_model_get ( tree_model, iter, PEER_COL_DOWNLOAD_RATE, &rate,
458                         -1 );
459    if( rate < 0.01 )
460        g_object_set ( renderer, "text", "", NULL );
461    else
462    {
463        char speedStr[64];
464        tr_strlspeed( speedStr, rate, sizeof( speedStr ) );
465        g_object_set( renderer, "text", speedStr, NULL );
466    }
467}
468
469static void
470render_client( GtkTreeViewColumn   * column UNUSED,
471               GtkCellRenderer *            renderer,
472               GtkTreeModel *               tree_model,
473               GtkTreeIter *                iter,
474               gpointer              data   UNUSED )
475{
476    char * client = NULL;
477
478    gtk_tree_model_get ( tree_model, iter, PEER_COL_CLIENT, &client,
479                         -1 );
480    g_object_set ( renderer, "text", ( client ? client : "" ), NULL );
481    g_free ( client );
482}
483
484typedef struct
485{
486    TrTorrent *     gtor;
487    GtkTreeModel *  model; /* same object as store, but recast */
488    GtkListStore *  store; /* same object as model, but recast */
489    GtkListStore *  webseeds;
490    GtkWidget *     completeness;
491    GtkWidget *     seeders_lb;
492    GtkWidget *     leechers_lb;
493    GtkWidget *     completed_lb;
494    GtkWidget *     peer_tree_view;
495}
496PeerData;
497
498static void
499fmtpeercount( GtkWidget * l,
500              int         count )
501{
502    if( 0 > count )
503    {
504        gtk_label_set_text( GTK_LABEL( l ), "?" );
505    }
506    else
507    {
508        char str[16];
509        g_snprintf( str, sizeof str, "%'d", count );
510        gtk_label_set_text( GTK_LABEL( l ), str );
511    }
512}
513
514static void
515refresh_peers( GtkWidget * top )
516{
517    int             i;
518    int             n_peers;
519    GtkTreeIter     iter;
520    PeerData *      p = (PeerData*) g_object_get_data ( G_OBJECT(
521                                                            top ),
522                                                        "peer-data" );
523    tr_torrent *    tor = tr_torrent_handle ( p->gtor );
524    GtkTreeModel *  model = p->model;
525    GtkListStore *  store = p->store;
526    tr_peer_stat *  peers;
527    const tr_stat * stat = tr_torrent_stat( p->gtor );
528    const tr_info * inf = tr_torrent_info( p->gtor );
529
530    if( inf->webseedCount )
531    {
532        float * speeds = tr_torrentWebSpeeds( tor );
533        for( i = 0; i < inf->webseedCount; ++i )
534        {
535            GtkTreeIter iter;
536            gtk_tree_model_iter_nth_child( GTK_TREE_MODEL(
537                                               p->webseeds ), &iter, NULL,
538                                           i );
539            gtk_list_store_set( p->webseeds, &iter,
540                                WEBSEED_COL_DOWNLOAD_RATE, speeds[i],
541                                -1 );
542        }
543        tr_free( speeds );
544    }
545
546    /**
547    ***  merge the peer diffs into the tree model.
548    ***
549    ***  this is more complicated than creating a new model,
550    ***  but is also (a) more efficient and (b) doesn't undo
551    ***  the view's visible area and sorting on every refresh.
552    **/
553
554    n_peers = 0;
555    peers = tr_torrentPeers ( tor, &n_peers );
556    qsort ( peers, n_peers, sizeof( tr_peer_stat ), compare_peers );
557
558    if( gtk_tree_model_get_iter_first ( model, &iter ) ) do
559        {
560            char *         addr = NULL;
561            tr_peer_stat * peer = NULL;
562            gtk_tree_model_get ( model, &iter, PEER_COL_ADDRESS, &addr, -1 );
563            peer = bsearch ( addr, peers, n_peers, sizeof( tr_peer_stat ),
564                             compare_addr_to_peer );
565            g_free ( addr );
566
567            if( peer ) /* update a pre-existing row */
568            {
569                const int pos = peer - peers;
570                const int n_rhs = n_peers - ( pos + 1 );
571                g_assert ( n_rhs >= 0 );
572
573                peer_row_set ( store, &iter, peer );
574
575                /* remove it from the tr_peer_stat list */
576                g_memmove ( peer, peer + 1, sizeof( tr_peer_stat ) * n_rhs );
577                --n_peers;
578            }
579            else if( !gtk_list_store_remove ( store, &iter ) )
580                break; /* we removed the model's last item */
581        }
582        while( gtk_tree_model_iter_next ( model, &iter ) );
583
584    append_peers_to_model ( store, peers, n_peers ); /* all these are new */
585
586#ifdef SHOW_PIECES
587    if( GDK_IS_DRAWABLE ( p->completeness->window ) )
588        refresh_pieces ( p->completeness, NULL, p->gtor );
589#endif
590
591    fmtpeercount ( p->seeders_lb, stat->seeders );
592    fmtpeercount ( p->leechers_lb, stat->leechers );
593    fmtpeercount ( p->completed_lb, stat->timesCompleted );
594
595    free( peers );
596
597    gtk_widget_queue_draw( p->peer_tree_view );
598}
599
600#if GTK_CHECK_VERSION( 2, 12, 0 )
601static gboolean
602onPeerViewQueryTooltip( GtkWidget *            widget,
603                        gint                   x,
604                        gint                   y,
605                        gboolean               keyboard_tip,
606                        GtkTooltip *           tooltip,
607                        gpointer     user_data UNUSED )
608{
609    gboolean       show_tip = FALSE;
610    GtkTreeModel * model;
611    GtkTreeIter    iter;
612
613    if( gtk_tree_view_get_tooltip_context( GTK_TREE_VIEW( widget ),
614                                           &x, &y, keyboard_tip,
615                                           &model, NULL, &iter ) )
616    {
617        const char * pch;
618        char *       str = NULL;
619        GString *    gstr = g_string_new( NULL );
620        gtk_tree_model_get( model, &iter, PEER_COL_STATUS, &str, -1 );
621        for( pch = str; pch && *pch; ++pch )
622        {
623            const char * txt = NULL;
624            switch( *pch )
625            {
626                case 'O':
627                    txt = _( "Optimistic unchoke" ); break;
628
629                case 'D':
630                    txt = _( "Downloading from this peer" ); break;
631
632                case 'd':
633                    txt = _(
634                        "We would download from this peer if they would let us" );
635                    break;
636
637                case 'U':
638                    txt = _( "Uploading to peer" ); break;
639
640                case 'u':
641                    txt = _( "We would upload to this peer if they asked" );
642                    break;
643
644                case 'K':
645                    txt = _(
646                        "Peer has unchoked us, but we're not interested" );
647                    break;
648
649                case '?':
650                    txt = _(
651                        "We unchoked this peer, but they're not interested" );
652                    break;
653
654                case 'E':
655                    txt = _( "Encrypted connection" ); break;
656
657                case 'X':
658                    txt = _(
659                        "Peer was discovered through Peer Exchange (PEX)" );
660                    break;
661
662                case 'I':
663                    txt = _( "Peer is an incoming connection" ); break;
664            }
665            if( txt )
666                g_string_append_printf( gstr, "%c: %s\n", *pch, txt );
667        }
668        if( gstr->len ) /* remove the last linefeed */
669            g_string_set_size( gstr, gstr->len - 1 );
670        gtk_tooltip_set_text( tooltip, gstr->str );
671        g_string_free( gstr, TRUE );
672        g_free( str );
673        show_tip = TRUE;
674    }
675
676    return show_tip;
677}
678
679#endif
680
681static GtkWidget*
682peer_page_new( TrTorrent * gtor )
683{
684    guint           i;
685    GtkTreeModel *  m;
686    GtkWidget *     v, *w, *ret, *sw, *l, *vbox, *hbox;
687    GtkWidget *     webtree = NULL;
688    tr_torrent *    tor = tr_torrent_handle ( gtor );
689    PeerData *      p = g_new ( PeerData, 1 );
690    const tr_info * inf = tr_torrent_info( gtor );
691
692    /* TODO: make this configurable? */
693    int view_columns[] = { PEER_COL_IS_ENCRYPTED,
694                           PEER_COL_UPLOAD_RATE,
695                           PEER_COL_DOWNLOAD_RATE,
696                           PEER_COL_PROGRESS,
697                           PEER_COL_STATUS,
698                           PEER_COL_ADDRESS,
699                           PEER_COL_CLIENT };
700
701
702    if( inf->webseedCount )
703    {
704        GtkTreeViewColumn * c;
705        GtkCellRenderer *   r;
706        const char *        t;
707        GtkWidget *         w;
708        GtkWidget *         v;
709
710        m = webseed_model_new( tr_torrent_handle( gtor ) );
711        v = gtk_tree_view_new_with_model( m );
712        gtk_tree_view_set_fixed_height_mode( GTK_TREE_VIEW( v ), TRUE );
713        g_signal_connect( v, "button-release-event", G_CALLBACK( on_tree_view_button_released ), NULL );
714        gtk_tree_view_set_rules_hint( GTK_TREE_VIEW( v ), TRUE );
715        p->webseeds = GTK_LIST_STORE( m );
716        g_object_unref( G_OBJECT( m ) );
717
718        t = _( webseed_column_names[WEBSEED_COL_URL] );
719        r = gtk_cell_renderer_text_new ( );
720        g_object_set( G_OBJECT( r ), "ellipsize", PANGO_ELLIPSIZE_END, NULL );
721        c = gtk_tree_view_column_new_with_attributes( t, r, "text", WEBSEED_COL_URL, NULL );
722        g_object_set( G_OBJECT( c ), "expand", TRUE, NULL );
723        gtk_tree_view_column_set_sort_column_id( c, WEBSEED_COL_URL );
724        gtk_tree_view_append_column( GTK_TREE_VIEW( v ), c );
725
726        t = _( webseed_column_names[WEBSEED_COL_DOWNLOAD_RATE] );
727        r = gtk_cell_renderer_text_new ( );
728        c = gtk_tree_view_column_new_with_attributes ( t, r, "text", WEBSEED_COL_DOWNLOAD_RATE, NULL );
729        gtk_tree_view_column_set_cell_data_func ( c, r, render_dl_rate, NULL, NULL );
730        gtk_tree_view_column_set_sort_column_id( c, WEBSEED_COL_DOWNLOAD_RATE );
731        gtk_tree_view_append_column( GTK_TREE_VIEW( v ), c );
732
733        w = gtk_scrolled_window_new ( NULL, NULL );
734        gtk_scrolled_window_set_policy ( GTK_SCROLLED_WINDOW( w ),
735                                         GTK_POLICY_AUTOMATIC,
736                                         GTK_POLICY_AUTOMATIC );
737        gtk_scrolled_window_set_shadow_type ( GTK_SCROLLED_WINDOW( w ),
738                                              GTK_SHADOW_IN );
739        gtk_container_add ( GTK_CONTAINER( w ), v );
740
741        webtree = w;
742    }
743
744    m  = peer_model_new ( tor );
745    v = GTK_WIDGET( g_object_new( GTK_TYPE_TREE_VIEW,
746                                  "model",  gtk_tree_model_sort_new_with_model( m ),
747                                  "rules-hint", TRUE,
748#if GTK_CHECK_VERSION( 2, 12, 0 )
749                                  "has-tooltip", TRUE,
750#endif
751                                  NULL ) );
752    gtk_tree_view_set_fixed_height_mode( GTK_TREE_VIEW( v ), TRUE );
753    p->peer_tree_view = v;
754
755#if GTK_CHECK_VERSION( 2, 12, 0 )
756    g_signal_connect( v, "query-tooltip",
757                      G_CALLBACK( onPeerViewQueryTooltip ), NULL );
758#endif
759    gtk_widget_set_size_request( v, 550, 0 );
760    g_object_unref ( G_OBJECT( m ) );
761    g_signal_connect( v, "button-release-event",
762                      G_CALLBACK( on_tree_view_button_released ), NULL );
763
764    for( i = 0; i < G_N_ELEMENTS( view_columns ); ++i )
765    {
766        const int           col = view_columns[i];
767        const char *        t = _( peer_column_names[col] );
768        GtkTreeViewColumn * c;
769        GtkCellRenderer *   r;
770
771        switch( col )
772        {
773            case PEER_COL_ADDRESS:
774                r = gtk_cell_renderer_text_new ( );
775                c = gtk_tree_view_column_new_with_attributes ( t, r, "text", col, NULL );
776                break;
777
778            case PEER_COL_CLIENT:
779                r = gtk_cell_renderer_text_new ( );
780                c = gtk_tree_view_column_new_with_attributes ( t, r, "text", col, NULL );
781                gtk_tree_view_column_set_cell_data_func ( c, r, render_client, NULL, NULL );
782                break;
783
784            case PEER_COL_PROGRESS:
785                r = gtk_cell_renderer_progress_new ( );
786                c = gtk_tree_view_column_new_with_attributes ( t, r, "value", PEER_COL_PROGRESS, NULL );
787                break;
788
789            case PEER_COL_IS_ENCRYPTED:
790                r = gtk_cell_renderer_pixbuf_new ( );
791                c = gtk_tree_view_column_new_with_attributes ( t, r, NULL );
792                gtk_tree_view_column_set_sizing ( c, GTK_TREE_VIEW_COLUMN_FIXED );
793                gtk_tree_view_column_set_fixed_width ( c, 20 );
794                gtk_tree_view_column_set_cell_data_func ( c, r, render_encrypted, NULL, NULL );
795                break;
796
797            case PEER_COL_DOWNLOAD_RATE:
798                r = gtk_cell_renderer_text_new ( );
799                c = gtk_tree_view_column_new_with_attributes ( t, r, "text", col, NULL );
800                gtk_tree_view_column_set_cell_data_func ( c, r, render_dl_rate, NULL, NULL );
801                break;
802
803            case PEER_COL_UPLOAD_RATE:
804                r = gtk_cell_renderer_text_new ( );
805                c = gtk_tree_view_column_new_with_attributes ( t, r, "text", col, NULL );
806                gtk_tree_view_column_set_cell_data_func ( c, r, render_ul_rate, NULL, NULL );
807                break;
808
809            case PEER_COL_STATUS:
810                r = gtk_cell_renderer_text_new( );
811                c = gtk_tree_view_column_new_with_attributes ( t, r, "text", col, NULL );
812                break;
813
814            default:
815                abort ( );
816        }
817
818        gtk_tree_view_column_set_resizable ( c, FALSE );
819        gtk_tree_view_column_set_sort_column_id ( c, col );
820        gtk_tree_view_append_column ( GTK_TREE_VIEW( v ), c );
821    }
822
823    /* the 'expander' column has a 10-pixel margin on the left
824       that doesn't look quite correct in any of these columns...
825       so create a non-visible column and assign it as the
826       'expander column. */
827    {
828        GtkTreeViewColumn *c = gtk_tree_view_column_new ( );
829        gtk_tree_view_column_set_visible ( c, FALSE );
830        gtk_tree_view_append_column ( GTK_TREE_VIEW( v ), c );
831        gtk_tree_view_set_expander_column ( GTK_TREE_VIEW( v ), c );
832    }
833
834    w = sw = gtk_scrolled_window_new ( NULL, NULL );
835    gtk_scrolled_window_set_policy ( GTK_SCROLLED_WINDOW( w ),
836                                     GTK_POLICY_AUTOMATIC,
837                                     GTK_POLICY_AUTOMATIC );
838    gtk_scrolled_window_set_shadow_type ( GTK_SCROLLED_WINDOW( w ),
839                                          GTK_SHADOW_IN );
840    gtk_container_add ( GTK_CONTAINER( w ), v );
841
842
843    vbox = gtk_vbox_new ( FALSE, GUI_PAD );
844    gtk_container_set_border_width ( GTK_CONTAINER( vbox ), GUI_PAD_BIG );
845
846    if( webtree == NULL )
847        gtk_box_pack_start( GTK_BOX( vbox ), sw, TRUE, TRUE, 0 );
848    else {
849        GtkWidget * vpaned = gtk_vpaned_new( );
850        gtk_paned_pack1( GTK_PANED( vpaned ), webtree, FALSE, TRUE );
851        gtk_paned_pack2( GTK_PANED( vpaned ), sw, TRUE, TRUE );
852        gtk_box_pack_start( GTK_BOX( vbox ), vpaned, TRUE, TRUE, 0 );
853    }
854
855    hbox = gtk_hbox_new ( FALSE, GUI_PAD );
856    l = gtk_label_new ( NULL );
857    gtk_label_set_markup ( GTK_LABEL( l ), _( "<b>Seeders:</b>" ) );
858    gtk_box_pack_start ( GTK_BOX( hbox ), l, FALSE, FALSE, 0 );
859    l = p->seeders_lb = gtk_label_new ( NULL );
860    gtk_box_pack_start ( GTK_BOX( hbox ), l, FALSE, FALSE, 0 );
861    gtk_box_pack_start( GTK_BOX( hbox ),
862                        gtk_alignment_new ( 0.0f, 0.0f, 0.0f, 0.0f ),
863                        TRUE, TRUE, 0 );
864    l = gtk_label_new ( NULL );
865    gtk_label_set_markup ( GTK_LABEL( l ), _( "<b>Leechers:</b>" ) );
866    gtk_box_pack_start ( GTK_BOX( hbox ), l, FALSE, FALSE, 0 );
867    l = p->leechers_lb = gtk_label_new ( NULL );
868    gtk_box_pack_start ( GTK_BOX( hbox ), l, FALSE, FALSE, 0 );
869    gtk_box_pack_start( GTK_BOX( hbox ),
870                        gtk_alignment_new ( 0.0f, 0.0f, 0.0f, 0.0f ),
871                        TRUE, TRUE, 0 );
872    l = gtk_label_new ( NULL );
873    gtk_label_set_markup ( GTK_LABEL( l ), _( "<b>Times Completed:</b>" ) );
874    gtk_box_pack_start ( GTK_BOX( hbox ), l, FALSE, FALSE, 0 );
875    l = p->completed_lb = gtk_label_new ( NULL );
876    gtk_box_pack_start ( GTK_BOX( hbox ), l, FALSE, FALSE, 0 );
877    gtk_box_pack_start ( GTK_BOX( vbox ), hbox, FALSE, FALSE, 0 );
878
879    ret = vbox;
880    p->gtor = gtor;
881    p->model = m;
882    p->store = GTK_LIST_STORE( m );
883    g_object_set_data_full ( G_OBJECT( ret ), "peer-data", p, g_free );
884    return ret;
885}
886
887/****
888*****  INFO TAB
889****/
890
891static void
892refresh_time_lb( GtkWidget * l,
893                 time_t      t )
894{
895    const char * never = _( "Never" );
896
897    if( !t )
898        gtk_label_set_text( GTK_LABEL( l ), never );
899    else
900    {
901        char * str = gtr_localtime( t );
902        gtk_label_set_text( GTK_LABEL( l ), str );
903        g_free( str );
904    }
905}
906
907static GtkWidget*
908info_page_new( tr_torrent * tor )
909{
910    int             row = 0;
911    GtkWidget *     t = hig_workarea_create ( );
912    GtkWidget *     l, *w, *fr;
913    char *          pch;
914    char            sizeStr[128];
915    char            countStr[128];
916    char            buf[256];
917    GtkTextBuffer * b;
918    const tr_info * info = tr_torrentInfo( tor );
919
920    hig_workarea_add_section_title ( t, &row, _( "Details" ) );
921
922    g_snprintf( countStr, sizeof( countStr ),
923                ngettext( "%'d Piece", "%'d Pieces", info->pieceCount ),
924                info->pieceCount );
925    tr_strlsize( sizeStr, info->pieceSize, sizeof( sizeStr ) );
926    g_snprintf( buf, sizeof( buf ),
927                /* %1$s is number of pieces;
928                   %2$s is how big each piece is */
929                _( "%1$s @ %2$s" ),
930                countStr, sizeStr );
931
932    l = gtk_label_new ( buf );
933    hig_workarea_add_row ( t, &row, _( "Pieces:" ), l, NULL );
934
935    l =
936        g_object_new( GTK_TYPE_LABEL, "label", info->hashString,
937                      "selectable",
938                      TRUE,
939                      "ellipsize", PANGO_ELLIPSIZE_END,
940                      NULL );
941    hig_workarea_add_row ( t, &row, _( "Hash:" ), l, NULL );
942
943    pch = ( info->isPrivate )
944          ? _( "Private to this tracker -- PEX disabled" )
945          : _( "Public torrent" );
946    l = gtk_label_new ( pch );
947    hig_workarea_add_row ( t, &row, _( "Privacy:" ), l, NULL );
948
949    b = gtk_text_buffer_new ( NULL );
950    if( info->comment )
951        gtk_text_buffer_set_text ( b, info->comment, -1 );
952    w = gtk_text_view_new_with_buffer ( b );
953    gtk_widget_set_size_request ( w, 0u, 100u );
954    gtk_text_view_set_wrap_mode ( GTK_TEXT_VIEW( w ), GTK_WRAP_WORD );
955    gtk_text_view_set_editable ( GTK_TEXT_VIEW( w ), FALSE );
956    fr = gtk_frame_new ( NULL );
957    gtk_frame_set_shadow_type ( GTK_FRAME( fr ), GTK_SHADOW_IN );
958    gtk_container_add ( GTK_CONTAINER( fr ), w );
959    w = hig_workarea_add_row ( t, &row, _( "Comment:" ), fr, NULL );
960    gtk_misc_set_alignment ( GTK_MISC( w ), 0.0f, 0.0f );
961
962    hig_workarea_add_section_divider ( t, &row );
963    hig_workarea_add_section_title ( t, &row, _( "Origins" ) );
964
965    l = gtk_label_new ( *info->creator ? info->creator : _( "Unknown" ) );
966    gtk_label_set_ellipsize( GTK_LABEL( l ), PANGO_ELLIPSIZE_END );
967    hig_workarea_add_row ( t, &row, _( "Creator:" ), l, NULL );
968
969    l = gtk_label_new( NULL );
970    refresh_time_lb( l, info->dateCreated );
971    hig_workarea_add_row ( t, &row, _( "Date:" ), l, NULL );
972
973    hig_workarea_add_section_divider ( t, &row );
974    hig_workarea_add_section_title ( t, &row, _( "Location" ) );
975
976    l =
977        g_object_new( GTK_TYPE_LABEL, "label", tr_torrentGetDownloadDir(
978                          tor ), "selectable", TRUE,
979                      "ellipsize", PANGO_ELLIPSIZE_END, NULL );
980    hig_workarea_add_row ( t, &row, _( "Destination folder:" ), l, NULL );
981
982    l =
983        g_object_new( GTK_TYPE_LABEL, "label", info->torrent, "selectable",
984                      TRUE,
985                      "ellipsize", PANGO_ELLIPSIZE_END,
986                      NULL );
987    hig_workarea_add_row ( t, &row, _( "Torrent file:" ), l, NULL );
988
989    hig_workarea_finish ( t, &row );
990    return t;
991}
992
993/****
994*****  ACTIVITY TAB
995****/
996
997typedef struct
998{
999    GtkWidget *  state_lb;
1000    GtkWidget *  progress_lb;
1001    GtkWidget *  have_lb;
1002    GtkWidget *  dl_lb;
1003    GtkWidget *  ul_lb;
1004    GtkWidget *  failed_lb;
1005    GtkWidget *  ratio_lb;
1006    GtkWidget *  err_lb;
1007    GtkWidget *  swarm_lb;
1008    GtkWidget *  date_added_lb;
1009    GtkWidget *  last_activity_lb;
1010    GtkWidget *  availability_da;
1011    TrTorrent *  gtor;
1012}
1013Activity;
1014
1015static void
1016refresh_activity( GtkWidget * top )
1017{
1018    int i;
1019    char * pch;
1020    char buf1[128];
1021    char buf2[128];
1022    Activity * a = g_object_get_data ( G_OBJECT( top ), "activity-data" );
1023    const tr_stat * stat = tr_torrent_stat( a->gtor );
1024    const tr_info * info = tr_torrent_info( a->gtor );
1025    const double complete = stat->percentComplete * 100.0;
1026    const double done = stat->percentDone * 100.0;
1027    const double verifiedPieceCount = (double)stat->haveValid / info->pieceSize;
1028
1029    pch = tr_torrent_status_str( a->gtor );
1030    gtk_label_set_text ( GTK_LABEL( a->state_lb ), pch );
1031    g_free ( pch );
1032
1033    if( (int)complete == (int)done )
1034        pch = g_strdup_printf( _( "%.1f%%" ), complete );
1035    else
1036        /* %1$.1f is percent of how much of what we want's been downloaded,
1037         * %2$.1f is percent of how much of the whole torrent we've downloaded */
1038        pch = g_strdup_printf( _( "%1$.1f%% (%2$.1f%% selected)" ),
1039                               complete, done );
1040    gtk_label_set_text ( GTK_LABEL( a->progress_lb ), pch );
1041    g_free ( pch );
1042
1043    i = (int) ceil( verifiedPieceCount );
1044    tr_strlsize( buf1,  stat->haveValid + stat->haveUnchecked, sizeof( buf1 ) );
1045    tr_strlsize( buf2, stat->haveValid, sizeof( buf2 ) );
1046    /* %1$s is total size of what we've saved to disk
1047     * %2$s is how much of it's passed the checksum test
1048     * %3$s is how many pieces are verified */
1049    pch = g_strdup_printf( ngettext( "%1$s (%2$s verified in %3$d piece)",
1050                                     "%1$s (%2$s verified in %3$d pieces)", i ),
1051                           buf1, buf2, i );
1052    gtk_label_set_text( GTK_LABEL( a->have_lb ), pch );
1053    g_free( pch );
1054
1055    tr_strlsize( buf1, stat->downloadedEver, sizeof( buf1 ) );
1056    gtk_label_set_text( GTK_LABEL( a->dl_lb ), buf1 );
1057
1058    tr_strlsize( buf1, stat->uploadedEver, sizeof( buf1 ) );
1059    gtk_label_set_text( GTK_LABEL( a->ul_lb ), buf1 );
1060
1061    tr_strlsize( buf1, stat->corruptEver, sizeof( buf1 ) );
1062    gtk_label_set_text( GTK_LABEL( a->failed_lb ), buf1 );
1063
1064    tr_strlratio( buf1, stat->ratio, sizeof( buf1 ) );
1065    gtk_label_set_text( GTK_LABEL( a->ratio_lb ), buf1 );
1066
1067    tr_strlspeed( buf1, stat->swarmSpeed, sizeof( buf1 ) );
1068    gtk_label_set_text ( GTK_LABEL( a->swarm_lb ), buf1 );
1069
1070    gtk_label_set_text ( GTK_LABEL( a->err_lb ),
1071                        *stat->errorString ? stat->errorString : _( "None" ) );
1072
1073    refresh_time_lb( a->date_added_lb, stat->addedDate );
1074
1075    refresh_time_lb( a->last_activity_lb, stat->activityDate );
1076
1077#ifdef SHOW_PIECES
1078    if( GDK_IS_DRAWABLE ( a->availability_da->window ) )
1079        refresh_pieces ( a->availability_da, NULL, a->gtor );
1080#endif
1081}
1082
1083static GtkWidget*
1084activity_page_new( TrTorrent * gtor )
1085{
1086    Activity * a = g_new ( Activity, 1 );
1087    int        row = 0;
1088    GtkWidget *t = hig_workarea_create ( );
1089    GtkWidget *l;
1090
1091    a->gtor = gtor;
1092
1093    hig_workarea_add_section_title ( t, &row, _( "Transfer" ) );
1094
1095    l = a->state_lb = gtk_label_new ( NULL );
1096    hig_workarea_add_row ( t, &row, _( "State:" ), l, NULL );
1097
1098    l = a->progress_lb = gtk_label_new ( NULL );
1099    hig_workarea_add_row ( t, &row, _( "Progress:" ), l, NULL );
1100
1101    l = a->have_lb = gtk_label_new ( NULL );
1102    /* "Have" refers to how much of the torrent we have */
1103    hig_workarea_add_row ( t, &row, _( "Have:" ), l, NULL );
1104
1105    l = a->dl_lb = gtk_label_new ( NULL );
1106    hig_workarea_add_row ( t, &row, _( "Downloaded:" ), l, NULL );
1107
1108    l = a->ul_lb = gtk_label_new ( NULL );
1109    hig_workarea_add_row ( t, &row, _( "Uploaded:" ), l, NULL );
1110
1111    /* how much downloaded data was corrupt */
1112    l = a->failed_lb = gtk_label_new ( NULL );
1113    hig_workarea_add_row ( t, &row, _( "Failed DL:" ), l, NULL );
1114
1115    l = a->ratio_lb = gtk_label_new ( NULL );
1116    hig_workarea_add_row ( t, &row, _( "Ratio:" ), l, NULL );
1117
1118    l = a->swarm_lb = gtk_label_new ( NULL );
1119    hig_workarea_add_row ( t, &row, _( "Swarm rate:" ), l, NULL );
1120
1121    l = a->err_lb = gtk_label_new ( NULL );
1122    hig_workarea_add_row ( t, &row, _( "Error:" ), l, NULL );
1123
1124#ifdef SHOW_PIECES
1125    hig_workarea_add_section_divider ( t, &row );
1126    hig_workarea_add_section_title ( t, &row, _( "Completion" ) );
1127
1128    w = a->availability_da = gtk_drawing_area_new ( );
1129    gtk_widget_set_size_request ( w, 0u, 100u );
1130    g_object_set_data ( G_OBJECT( w ), "draw-mode",
1131                       GINT_TO_POINTER( DRAW_PROG ) );
1132    g_signal_connect ( w, "expose-event", G_CALLBACK(
1133                           refresh_pieces ), gtor );
1134    hig_workarea_add_wide_control( t, &row, w );
1135#endif
1136
1137    hig_workarea_add_section_divider ( t, &row );
1138    hig_workarea_add_section_title ( t, &row, _( "Dates" ) );
1139
1140    l = a->date_added_lb = gtk_label_new ( NULL );
1141    hig_workarea_add_row ( t, &row, _( "Started at:" ), l, NULL );
1142
1143    l = a->last_activity_lb = gtk_label_new ( NULL );
1144    hig_workarea_add_row ( t, &row, _( "Last activity at:" ), l, NULL );
1145
1146    hig_workarea_add_section_divider ( t, &row );
1147    hig_workarea_finish ( t, &row );
1148    g_object_set_data_full ( G_OBJECT( t ), "activity-data", a, g_free );
1149    return t;
1150}
1151
1152/****
1153*****  OPTIONS
1154****/
1155
1156static void
1157speed_toggled_cb( GtkToggleButton * tb,
1158                  gpointer          gtor,
1159                  int               up_or_down )
1160{
1161    tr_torrent * tor = tr_torrent_handle ( gtor );
1162    gboolean     b = gtk_toggle_button_get_active( tb );
1163
1164    tr_torrentSetSpeedMode( tor, up_or_down, b ? TR_SPEEDLIMIT_SINGLE
1165                            : TR_SPEEDLIMIT_GLOBAL );
1166}
1167
1168static void
1169ul_speed_toggled_cb( GtkToggleButton *tb,
1170                     gpointer         gtor )
1171{
1172    speed_toggled_cb( tb, gtor, TR_UP );
1173}
1174
1175static void
1176dl_speed_toggled_cb( GtkToggleButton *tb,
1177                     gpointer         gtor )
1178{
1179    speed_toggled_cb( tb, gtor, TR_DOWN );
1180}
1181
1182#define RATIO_MODE_KEY "ratio-mode"
1183
1184static void
1185ratio_mode_changed_cb( GtkToggleButton * tb, gpointer gtor )
1186{
1187    if( gtk_toggle_button_get_active( tb ) )
1188    {
1189        tr_torrent * tor = tr_torrent_handle( gtor );
1190        const int mode = GPOINTER_TO_INT( g_object_get_data( G_OBJECT( tb ), RATIO_MODE_KEY ) );
1191        tr_torrentSetRatioMode( tor, mode );
1192    }
1193}
1194
1195static void
1196sensitize_from_check_cb( GtkToggleButton *toggle,
1197                         gpointer         w )
1198{
1199    gtk_widget_set_sensitive ( GTK_WIDGET( w ),
1200                              gtk_toggle_button_get_active( toggle ) );
1201}
1202
1203static void
1204setSpeedLimit( GtkSpinButton* spin,
1205               gpointer       gtor,
1206               int            up_or_down )
1207{
1208    tr_torrent * tor = tr_torrent_handle ( gtor );
1209    int          kb_sec = gtk_spin_button_get_value_as_int ( spin );
1210
1211    tr_torrentSetSpeedLimit( tor, up_or_down, kb_sec );
1212}
1213
1214static void
1215ul_speed_spun_cb( GtkSpinButton *spin,
1216                  gpointer       gtor )
1217{
1218    setSpeedLimit( spin, gtor, TR_UP );
1219}
1220
1221static void
1222dl_speed_spun_cb( GtkSpinButton *spin,
1223                  gpointer       gtor )
1224{
1225    setSpeedLimit( spin, gtor, TR_DOWN );
1226}
1227
1228static void
1229ratio_spun_cb( GtkSpinButton *spin,
1230               gpointer       gtor )
1231{
1232    tr_torrent * tor = tr_torrent_handle ( gtor );
1233    float        ratio = gtk_spin_button_get_value ( spin );
1234
1235    tr_torrentSetRatioLimit( tor, ratio );
1236}
1237
1238static void
1239max_peers_spun_cb( GtkSpinButton * spin,
1240                   gpointer        gtor )
1241{
1242    const uint16_t n = gtk_spin_button_get_value( spin );
1243
1244    tr_torrentSetPeerLimit( tr_torrent_handle( gtor ), n );
1245}
1246
1247static char*
1248get_global_ratio_radiobutton_string( void )
1249{
1250    char * s;
1251    const gboolean b = pref_flag_get( TR_PREFS_KEY_RATIO_ENABLED );
1252    const double d = pref_double_get( TR_PREFS_KEY_RATIO );
1253
1254    if( b )
1255        s = g_strdup_printf( _( "Use _Global setting  (currently: stop seeding when a torrent's ratio reaches %.2f)" ), d );
1256    else
1257        s = g_strdup( _( "Use _Global setting  (currently: seed regardless of ratio)" ) );
1258
1259    return s;
1260}
1261
1262static void
1263prefsChanged( TrCore * core UNUSED, const char *  key, gpointer rb )
1264{
1265    if( !strcmp( key, TR_PREFS_KEY_RATIO_ENABLED ) || !strcmp( key, TR_PREFS_KEY_RATIO ) )
1266    {
1267        char * s = get_global_ratio_radiobutton_string( );
1268        gtk_button_set_label( GTK_BUTTON( rb ), s );
1269        g_free( s );
1270    }
1271}
1272
1273static GtkWidget*
1274options_page_new( ResponseData * data )
1275{
1276    uint16_t     maxConnectedPeers;
1277    int          i, row;
1278    double       d;
1279    gboolean     b;
1280    char       * s;
1281    GSList     * group;
1282    GtkWidget  * t, *w, *tb, *h;
1283    tr_ratiolimit mode;
1284    TrCore     * core = data->core;
1285    TrTorrent  * gtor = data->gtor;
1286    tr_torrent * tor = tr_torrent_handle ( gtor );
1287
1288    row = 0;
1289    t = hig_workarea_create ( );
1290    hig_workarea_add_section_title ( t, &row, _( "Limits" ) );
1291
1292    tb =
1293        gtk_check_button_new_with_mnemonic ( _(
1294                                                "Limit _download speed (KB/s):" ) );
1295    b = tr_torrentGetSpeedMode( tor, TR_DOWN ) == TR_SPEEDLIMIT_SINGLE;
1296    gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON( tb ), b );
1297    g_signal_connect ( tb, "toggled", G_CALLBACK(
1298                           dl_speed_toggled_cb ), gtor );
1299
1300    i = tr_torrentGetSpeedLimit( tor, TR_DOWN );
1301    w = gtk_spin_button_new_with_range( 1, INT_MAX, 5 );
1302    gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), i );
1303
1304    g_signal_connect ( w, "value-changed", G_CALLBACK(
1305                           dl_speed_spun_cb ), gtor );
1306    g_signal_connect ( tb, "toggled", G_CALLBACK(
1307                           sensitize_from_check_cb ), w );
1308    sensitize_from_check_cb ( GTK_TOGGLE_BUTTON( tb ), w );
1309    hig_workarea_add_row_w ( t, &row, tb, w, NULL );
1310
1311    tb =
1312        gtk_check_button_new_with_mnemonic ( _(
1313                                                "Limit _upload speed (KB/s):" ) );
1314    b = tr_torrentGetSpeedMode( tor, TR_UP ) == TR_SPEEDLIMIT_SINGLE;
1315    gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON( tb ), b );
1316    g_signal_connect ( tb, "toggled", G_CALLBACK(
1317                           ul_speed_toggled_cb ), gtor );
1318
1319    i = tr_torrentGetSpeedLimit( tor, TR_UP );
1320    w = gtk_spin_button_new_with_range( 1, INT_MAX, 5 );
1321    gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), i );
1322
1323    g_signal_connect ( w, "value-changed", G_CALLBACK(
1324                           ul_speed_spun_cb ), gtor );
1325    g_signal_connect ( tb, "toggled", G_CALLBACK(
1326                           sensitize_from_check_cb ), w );
1327    sensitize_from_check_cb ( GTK_TOGGLE_BUTTON( tb ), w );
1328    hig_workarea_add_row_w ( t, &row, tb, w, NULL );
1329
1330    hig_workarea_add_section_divider ( t, &row );
1331    hig_workarea_add_section_title ( t, &row, _( "Seed-Until Ratio" ) );
1332
1333
1334        group = NULL;
1335        mode = tr_torrentGetRatioMode( tor );
1336        s = get_global_ratio_radiobutton_string( );
1337        w = gtk_radio_button_new_with_mnemonic( group, s );
1338        data->handler = g_signal_connect( core, "prefs-changed", G_CALLBACK( prefsChanged ), w );
1339        group = gtk_radio_button_get_group( GTK_RADIO_BUTTON( w ) );
1340        gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON( w ), mode == TR_RATIOLIMIT_GLOBAL);
1341        hig_workarea_add_wide_control( t, &row, w );
1342        g_free( s );
1343        g_object_set_data( G_OBJECT( w ), RATIO_MODE_KEY, GINT_TO_POINTER( TR_RATIOLIMIT_GLOBAL ) );
1344        g_signal_connect( w, "toggled", G_CALLBACK( ratio_mode_changed_cb ), gtor );
1345
1346        w = gtk_radio_button_new_with_mnemonic( group, _( "Seed _regardless of ratio" ) );
1347        group = gtk_radio_button_get_group( GTK_RADIO_BUTTON( w ) );
1348        gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON( w ), mode == TR_RATIOLIMIT_UNLIMITED);
1349        hig_workarea_add_wide_control( t, &row, w );
1350        g_object_set_data( G_OBJECT( w ), RATIO_MODE_KEY, GINT_TO_POINTER( TR_RATIOLIMIT_UNLIMITED ) );
1351        g_signal_connect( w, "toggled", G_CALLBACK( ratio_mode_changed_cb ), gtor );
1352
1353        h = gtk_hbox_new ( FALSE, GUI_PAD );
1354        w = gtk_radio_button_new_with_mnemonic( group, _( "_Stop seeding when a torrent's ratio reaches" ) );
1355        g_object_set_data( G_OBJECT( w ), RATIO_MODE_KEY, GINT_TO_POINTER( TR_RATIOLIMIT_SINGLE ) );
1356        gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON( w ), mode == TR_RATIOLIMIT_SINGLE);
1357        g_signal_connect( w, "toggled", G_CALLBACK( ratio_mode_changed_cb ), gtor );
1358        group = gtk_radio_button_get_group( GTK_RADIO_BUTTON( w ) );
1359        gtk_box_pack_start ( GTK_BOX( h ), w, FALSE, FALSE, 0 );
1360        d = tr_torrentGetRatioLimit( tor );
1361        w = gtk_spin_button_new_with_range( 0.5, INT_MAX, .05 );
1362        gtk_spin_button_set_digits( GTK_SPIN_BUTTON( w ), 2 );
1363        gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), d );
1364        g_signal_connect ( w, "value-changed", G_CALLBACK( ratio_spun_cb ), gtor );
1365        gtk_box_pack_start ( GTK_BOX( h ), w, FALSE, FALSE, 0 );
1366        hig_workarea_add_wide_control( t, &row, h );
1367   
1368    hig_workarea_add_section_divider ( t, &row );
1369    hig_workarea_add_section_title ( t, &row, _( "Peer Connections" ) );
1370
1371    maxConnectedPeers = tr_torrentGetPeerLimit( tor );
1372    w = gtk_spin_button_new_with_range( 1, 3000, 5 );
1373    gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), maxConnectedPeers );
1374    hig_workarea_add_row( t, &row, _( "_Maximum peers:" ), w, w );
1375    g_signal_connect( w, "value-changed", G_CALLBACK(
1376                          max_peers_spun_cb ), gtor );
1377
1378    hig_workarea_finish ( t, &row );
1379    return t;
1380}
1381
1382static void
1383refresh_options( GtkWidget * top UNUSED )
1384{}
1385
1386/****
1387*****  TRACKER
1388****/
1389
1390#define TRACKER_PAGE "tracker-page"
1391
1392struct tracker_page
1393{
1394    TrTorrent *         gtor;
1395
1396    GtkTreeView *       view;
1397    GtkListStore *      store;
1398    GtkTreeSelection *  sel;
1399
1400    GtkWidget *         add_button;
1401    GtkWidget *         remove_button;
1402    GtkWidget *         save_button;
1403    GtkWidget *         revert_button;
1404
1405    GtkWidget *         last_scrape_time_lb;
1406    GtkWidget *         last_scrape_response_lb;
1407    GtkWidget *         next_scrape_countdown_lb;
1408
1409    GtkWidget *         last_announce_time_lb;
1410    GtkWidget *         last_announce_response_lb;
1411    GtkWidget *         next_announce_countdown_lb;
1412    GtkWidget *         manual_announce_countdown_lb;
1413};
1414
1415static GtkWidget*
1416tracker_page_new( TrTorrent * gtor )
1417{
1418    GtkWidget *           t;
1419    GtkWidget *           l;
1420    GtkWidget *           w;
1421    int                   row = 0;
1422    const char *          s;
1423    struct tracker_page * page = g_new0( struct tracker_page, 1 );
1424    const tr_info *       info = tr_torrent_info ( gtor );
1425
1426    page->gtor = gtor;
1427
1428    t = hig_workarea_create( );
1429    hig_workarea_add_section_title( t, &row, _( "Trackers" ) );
1430
1431    w = tracker_list_new( gtor );
1432    hig_workarea_add_wide_control( t, &row, w );
1433
1434    hig_workarea_add_section_divider( t, &row );
1435    hig_workarea_add_section_title( t, &row, _( "Scrape" ) );
1436
1437    s = _( "Last scrape at:" );
1438    l = gtk_label_new( NULL );
1439    page->last_scrape_time_lb = l;
1440    hig_workarea_add_row( t, &row, s, l, NULL );
1441
1442    s = _( "Tracker responded:" );
1443    l = gtk_label_new( NULL );
1444    page->last_scrape_response_lb = l;
1445    hig_workarea_add_row( t, &row, s, l, NULL );
1446
1447    s = _( "Next scrape in:" );
1448    l = gtk_label_new( NULL );
1449    page->next_scrape_countdown_lb = l;
1450    hig_workarea_add_row( t, &row, s, l, NULL );
1451
1452    hig_workarea_add_section_divider( t, &row );
1453    hig_workarea_add_section_title( t, &row, _( "Announce" ) );
1454
1455    l = gtk_label_new( info->trackers[0].announce );
1456    gtk_label_set_ellipsize( GTK_LABEL( l ), PANGO_ELLIPSIZE_END );
1457    hig_workarea_add_row ( t, &row, _( "Tracker:" ), l, NULL );
1458
1459    s = _( "Last announce at:" );
1460    l = gtk_label_new( NULL );
1461    page->last_announce_time_lb = l;
1462    hig_workarea_add_row( t, &row, s, l, NULL );
1463
1464    s = _( "Tracker responded:" );
1465    l = gtk_label_new( NULL );
1466    page->last_announce_response_lb = l;
1467    hig_workarea_add_row( t, &row, s, l, NULL );
1468
1469    s = _( "Next announce in:" );
1470    l = gtk_label_new( NULL );
1471    page->next_announce_countdown_lb = l;
1472    hig_workarea_add_row( t, &row, s, l, NULL );
1473
1474    /* how long until the tracker will honor user
1475    * pressing the "ask for more peers" button */
1476    s = _( "Manual announce allowed in:" );
1477    l = gtk_label_new( NULL );
1478    page->manual_announce_countdown_lb = l;
1479    hig_workarea_add_row( t, &row, s, l, NULL );
1480
1481    hig_workarea_finish( t, &row );
1482    g_object_set_data_full( G_OBJECT( t ), TRACKER_PAGE, page, g_free );
1483    return t;
1484}
1485
1486static void
1487refresh_countdown_lb( GtkWidget *  w,
1488                      time_t       t,
1489                      const char * countdown_done )
1490{
1491    const time_t now = time( NULL );
1492    GtkLabel *   l = GTK_LABEL( w );
1493
1494    if( t == 1 )
1495        gtk_label_set_text( l, _( "In progress" ) );
1496    else if( t < now )
1497        gtk_label_set_text( l, countdown_done );
1498    else
1499    {
1500        char      buf[1024];
1501        const int seconds = t - now;
1502        tr_strltime( buf, seconds, sizeof( buf ) );
1503        gtk_label_set_text( l, buf );
1504    }
1505}
1506
1507static void
1508refresh_tracker( GtkWidget * w )
1509{
1510    GtkWidget *           l;
1511    time_t                t;
1512    struct tracker_page * page = g_object_get_data( G_OBJECT(
1513                                                        w ), TRACKER_PAGE );
1514    const tr_stat *       torStat = tr_torrent_stat( page->gtor );
1515
1516    l = page->last_scrape_time_lb;
1517    t = torStat->lastScrapeTime;
1518    refresh_time_lb( l, t );
1519
1520    l = page->last_scrape_response_lb;
1521    gtk_label_set_text( GTK_LABEL( l ), torStat->scrapeResponse );
1522
1523    l = page->next_scrape_countdown_lb;
1524    t = torStat->nextScrapeTime;
1525    refresh_countdown_lb( l, t, _( "Never" ) );
1526
1527    l = page->last_announce_time_lb;
1528    t = torStat->lastAnnounceTime;
1529    refresh_time_lb( l, t );
1530
1531    l = page->last_announce_response_lb;
1532    gtk_label_set_text( GTK_LABEL( l ), torStat->announceResponse );
1533
1534    l = page->next_announce_countdown_lb;
1535    t = torStat->nextAnnounceTime;
1536    refresh_countdown_lb( l, t, _( "Never" ) );
1537
1538    l = page->manual_announce_countdown_lb;
1539    t = torStat->manualAnnounceTime;
1540    refresh_countdown_lb( l, t, _( "Now" ) );
1541}
1542
1543/****
1544*****  DIALOG
1545****/
1546
1547static void
1548torrent_destroyed( gpointer               dialog,
1549                   GObject * dead_torrent UNUSED )
1550{
1551    gtk_widget_destroy ( GTK_WIDGET( dialog ) );
1552}
1553
1554static void
1555remove_tag( gpointer tag )
1556{
1557    g_source_remove ( GPOINTER_TO_UINT( tag ) ); /* stop the periodic refresh */
1558}
1559
1560static void
1561response_cb( GtkDialog *  dialog,
1562             int response UNUSED,
1563             gpointer     data )
1564{
1565    ResponseData *rd = data;
1566    TrCore * core = rd->core;
1567    gulong handler = rd-> handler;
1568
1569    g_signal_handler_disconnect( core, handler );
1570    g_object_weak_unref ( G_OBJECT( rd->gtor ), torrent_destroyed, dialog );
1571    gtk_widget_destroy ( GTK_WIDGET( dialog ) );
1572
1573    g_free ( rd );
1574}
1575
1576static gboolean
1577periodic_refresh( gpointer data )
1578{
1579    refresh_tracker   ( g_object_get_data ( G_OBJECT( data ), "tracker-top" ) );
1580    refresh_peers     ( g_object_get_data ( G_OBJECT( data ), "peers-top" ) );
1581    refresh_activity  ( g_object_get_data ( G_OBJECT( data ),
1582                                            "activity-top" ) );
1583    refresh_options   ( g_object_get_data ( G_OBJECT( data ), "options-top" ) );
1584    return TRUE;
1585}
1586
1587GtkWidget*
1588torrent_inspector_new( GtkWindow * parent,
1589                       TrCore    * core,
1590                       TrTorrent * gtor )
1591{
1592    guint           tag;
1593    GtkWidget *     d, *n, *w;
1594    tr_torrent *    tor = tr_torrent_handle ( gtor );
1595    char            title[512];
1596    const tr_info * info = tr_torrent_info ( gtor );
1597    ResponseData  * rd;
1598
1599    /* create the dialog */
1600    rd = g_new0(ResponseData, 1);
1601    rd->gtor = gtor;
1602    rd->core = core;
1603    g_snprintf( title, sizeof( title ), _( "%s Properties" ), info->name );
1604    d = gtk_dialog_new_with_buttons ( title, parent, 0,
1605                                      GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1606                                      NULL );
1607    gtk_window_set_role ( GTK_WINDOW( d ), "tr-info" );
1608    g_signal_connect ( d, "response", G_CALLBACK ( response_cb ), rd );
1609    gtk_dialog_set_has_separator( GTK_DIALOG( d ), FALSE );
1610    gtk_container_set_border_width( GTK_CONTAINER( d ), GUI_PAD );
1611    g_object_weak_ref ( G_OBJECT( gtor ), torrent_destroyed, d );
1612
1613
1614    /* add the notebook */
1615    n = gtk_notebook_new ( );
1616    gtk_container_set_border_width ( GTK_CONTAINER ( n ), GUI_PAD );
1617
1618    w = activity_page_new ( gtor );
1619    g_object_set_data ( G_OBJECT( d ), "activity-top", w );
1620    gtk_notebook_append_page ( GTK_NOTEBOOK( n ), w,
1621                              gtk_label_new ( _( "Activity" ) ) );
1622
1623    w = peer_page_new ( gtor );
1624    g_object_set_data ( G_OBJECT( d ), "peers-top", w );
1625    gtk_notebook_append_page ( GTK_NOTEBOOK( n ),  w,
1626                              gtk_label_new ( _( "Peers" ) ) );
1627
1628    w = tracker_page_new( gtor );
1629    g_object_set_data( G_OBJECT( d ), "tracker-top", w );
1630    gtk_notebook_append_page( GTK_NOTEBOOK( n ), w,
1631                             gtk_label_new( _( "Tracker" ) ) );
1632
1633    gtk_notebook_append_page ( GTK_NOTEBOOK( n ),
1634                              info_page_new ( tor ),
1635                              gtk_label_new ( _( "Information" ) ) );
1636
1637    w = file_list_new( gtor );
1638    gtk_container_set_border_width( GTK_CONTAINER( w ), GUI_PAD_BIG );
1639    g_object_set_data ( G_OBJECT( d ), "files-top", w );
1640    gtk_notebook_append_page ( GTK_NOTEBOOK( n ), w,
1641                              gtk_label_new ( _( "Files" ) ) );
1642
1643    w = options_page_new ( rd );
1644    g_object_set_data ( G_OBJECT( d ), "options-top", w );
1645    gtk_notebook_append_page ( GTK_NOTEBOOK( n ), w,
1646                               gtk_label_new ( _( "Options" ) ) );
1647
1648    gtk_box_pack_start( GTK_BOX( GTK_DIALOG( d )->vbox ), n, TRUE, TRUE, 0 );
1649
1650    tag = gtr_timeout_add_seconds( UPDATE_INTERVAL_SECONDS, periodic_refresh, d );
1651    g_object_set_data_full ( G_OBJECT( d ), "tag",
1652                             GUINT_TO_POINTER( tag ), remove_tag );
1653
1654    /* return the results */
1655    periodic_refresh ( d );
1656    gtk_widget_show_all ( GTK_DIALOG( d )->vbox );
1657    return d;
1658}
1659
Note: See TracBrowser for help on using the repository browser.