source: trunk/gtk/details.c @ 8258

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

(trunk gtk) tweak the encryption ui in the details dialog

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