source: trunk/gtk/details.c @ 6998

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

(gtk) don't use code marked as deprecated in gtk 2.14

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