source: trunk/gtk/details.c @ 8249

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

(trunk gtk) fix minor oops from earlier today in the properties dialog's peer list

  • Property svn:keywords set to Date Rev Author Id
File size: 45.5 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 8249 2009-04-17 00:04:15Z 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 GtkWidget*
982options_page_new( struct ResponseData * data )
983{
984    uint16_t     maxConnectedPeers;
985    int          i, row;
986    double       d;
987    gboolean     b;
988    char       * s;
989    GSList     * group;
990    GtkWidget  * t, *w, *tb, *h;
991    tr_ratiolimit mode;
992    TrCore     * core = data->core;
993    TrTorrent  * gtor = data->gtor;
994    tr_torrent * tor = tr_torrent_handle( gtor );
995
996    row = 0;
997    t = hig_workarea_create( );
998    hig_workarea_add_section_title( t, &row, _( "Speed Limits" ) );
999
1000        b = tr_torrentUsesSessionLimits( tor );
1001        tb = hig_workarea_add_wide_checkbutton( t, &row, _( "Honor global _limits" ), b );
1002        g_signal_connect( tb, "toggled", G_CALLBACK( global_speed_toggled_cb ), gtor );
1003
1004        tb = gtk_check_button_new_with_mnemonic( _( "Limit _download speed (KB/s):" ) );
1005        b = tr_torrentUsesSpeedLimit( tor, TR_DOWN );
1006        gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( tb ), b );
1007        g_signal_connect( tb, "toggled", G_CALLBACK( down_speed_toggled_cb ), gtor );
1008
1009        i = tr_torrentGetSpeedLimit( tor, TR_DOWN );
1010        w = gtk_spin_button_new_with_range( 1, INT_MAX, 5 );
1011        gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), i );
1012        g_signal_connect( w, "value-changed", G_CALLBACK( down_speed_spun_cb ), gtor );
1013        g_signal_connect( tb, "toggled", G_CALLBACK( sensitize_from_check_cb ), w );
1014        sensitize_from_check_cb( GTK_TOGGLE_BUTTON( tb ), w );
1015        hig_workarea_add_row_w( t, &row, tb, w, NULL );
1016
1017        tb = gtk_check_button_new_with_mnemonic( _( "Limit _upload speed (KB/s):" ) );
1018        b = tr_torrentUsesSpeedLimit( tor, TR_UP );
1019        gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( tb ), b );
1020        g_signal_connect( tb, "toggled", G_CALLBACK( up_speed_toggled_cb ), gtor );
1021
1022        i = tr_torrentGetSpeedLimit( tor, TR_UP );
1023        w = gtk_spin_button_new_with_range( 1, INT_MAX, 5 );
1024        gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), i );
1025        g_signal_connect( w, "value-changed", G_CALLBACK( up_speed_spun_cb ), gtor );
1026        g_signal_connect( tb, "toggled", G_CALLBACK( sensitize_from_check_cb ), w );
1027        sensitize_from_check_cb( GTK_TOGGLE_BUTTON( tb ), w );
1028        hig_workarea_add_row_w( t, &row, tb, w, NULL );
1029
1030    hig_workarea_add_section_divider( t, &row );
1031    hig_workarea_add_section_title( t, &row, _( "Seed-Until Ratio" ) );
1032
1033        group = NULL;
1034        mode = tr_torrentGetRatioMode( tor );
1035        s = get_global_ratio_radiobutton_string( );
1036        w = gtk_radio_button_new_with_mnemonic( group, s );
1037        data->handler = g_signal_connect( core, "prefs-changed", G_CALLBACK( prefsChanged ), w );
1038        group = gtk_radio_button_get_group( GTK_RADIO_BUTTON( w ) );
1039        gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w ), mode == TR_RATIOLIMIT_GLOBAL);
1040        hig_workarea_add_wide_control( t, &row, w );
1041        g_free( s );
1042        g_object_set_data( G_OBJECT( w ), RATIO_MODE_KEY, GINT_TO_POINTER( TR_RATIOLIMIT_GLOBAL ) );
1043        g_signal_connect( w, "toggled", G_CALLBACK( ratio_mode_changed_cb ), gtor );
1044
1045        w = gtk_radio_button_new_with_mnemonic( group, _( "Seed _regardless of ratio" ) );
1046        group = gtk_radio_button_get_group( GTK_RADIO_BUTTON( w ) );
1047        gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w ), mode == TR_RATIOLIMIT_UNLIMITED);
1048        hig_workarea_add_wide_control( t, &row, w );
1049        g_object_set_data( G_OBJECT( w ), RATIO_MODE_KEY, GINT_TO_POINTER( TR_RATIOLIMIT_UNLIMITED ) );
1050        g_signal_connect( w, "toggled", G_CALLBACK( ratio_mode_changed_cb ), gtor );
1051
1052        h = gtk_hbox_new( FALSE, GUI_PAD );
1053        w = gtk_radio_button_new_with_mnemonic( group, _( "_Stop seeding when a torrent's ratio reaches" ) );
1054        g_object_set_data( G_OBJECT( w ), RATIO_MODE_KEY, GINT_TO_POINTER( TR_RATIOLIMIT_SINGLE ) );
1055        gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w ), mode == TR_RATIOLIMIT_SINGLE);
1056        g_signal_connect( w, "toggled", G_CALLBACK( ratio_mode_changed_cb ), gtor );
1057        group = gtk_radio_button_get_group( GTK_RADIO_BUTTON( w ) );
1058        gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 );
1059        d = tr_torrentGetRatioLimit( tor );
1060        w = gtk_spin_button_new_with_range( 0.5, INT_MAX, .05 );
1061        gtk_spin_button_set_digits( GTK_SPIN_BUTTON( w ), 2 );
1062        gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), d );
1063        g_signal_connect( w, "value-changed", G_CALLBACK( ratio_spun_cb ), gtor );
1064        gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 );
1065        hig_workarea_add_wide_control( t, &row, h );
1066   
1067    hig_workarea_add_section_divider( t, &row );
1068    hig_workarea_add_section_title( t, &row, _( "Peer Connections" ) );
1069
1070    maxConnectedPeers = tr_torrentGetPeerLimit( tor );
1071    w = gtk_spin_button_new_with_range( 1, 3000, 5 );
1072    gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), maxConnectedPeers );
1073    hig_workarea_add_row( t, &row, _( "_Maximum peers:" ), w, w );
1074    g_signal_connect( w, "value-changed", G_CALLBACK(
1075                          max_peers_spun_cb ), gtor );
1076
1077    hig_workarea_finish( t, &row );
1078    return t;
1079}
1080
1081/****
1082*****  TRACKER
1083****/
1084
1085#define TRACKER_PAGE "tracker-page"
1086
1087struct tracker_page
1088{
1089    TrTorrent *         gtor;
1090
1091    GtkTreeView *       view;
1092    GtkListStore *      store;
1093    GtkTreeSelection *  sel;
1094
1095    GtkWidget *         add_button;
1096    GtkWidget *         remove_button;
1097    GtkWidget *         save_button;
1098    GtkWidget *         revert_button;
1099
1100    GtkWidget *         last_scrape_time_lb;
1101    GtkWidget *         last_scrape_response_lb;
1102    GtkWidget *         next_scrape_countdown_lb;
1103
1104    GtkWidget *         last_announce_time_lb;
1105    GtkWidget *         last_announce_response_lb;
1106    GtkWidget *         next_announce_countdown_lb;
1107    GtkWidget *         manual_announce_countdown_lb;
1108};
1109
1110static GtkWidget*
1111tracker_page_new( TrTorrent * gtor )
1112{
1113    GtkWidget *           t;
1114    GtkWidget *           l;
1115    GtkWidget *           w;
1116    int                   row = 0;
1117    const char *          s;
1118    struct tracker_page * page = g_new0( struct tracker_page, 1 );
1119    const tr_info *       info = tr_torrent_info( gtor );
1120
1121    page->gtor = gtor;
1122
1123    t = hig_workarea_create( );
1124    hig_workarea_add_section_title( t, &row, _( "Trackers" ) );
1125
1126    w = tracker_list_new( gtor );
1127    hig_workarea_add_wide_control( t, &row, w );
1128
1129    hig_workarea_add_section_divider( t, &row );
1130    hig_workarea_add_section_title( t, &row, _( "Scrape" ) );
1131
1132    s = _( "Last scrape at:" );
1133    l = gtk_label_new( NULL );
1134    page->last_scrape_time_lb = l;
1135    hig_workarea_add_row( t, &row, s, l, NULL );
1136
1137    s = _( "Tracker responded:" );
1138    l = gtk_label_new( NULL );
1139    page->last_scrape_response_lb = l;
1140    hig_workarea_add_row( t, &row, s, l, NULL );
1141
1142    s = _( "Next scrape in:" );
1143    l = gtk_label_new( NULL );
1144    page->next_scrape_countdown_lb = l;
1145    hig_workarea_add_row( t, &row, s, l, NULL );
1146
1147    hig_workarea_add_section_divider( t, &row );
1148    hig_workarea_add_section_title( t, &row, _( "Announce" ) );
1149
1150    l = gtk_label_new( info->trackers[0].announce );
1151    gtk_label_set_ellipsize( GTK_LABEL( l ), PANGO_ELLIPSIZE_END );
1152    hig_workarea_add_row( t, &row, _( "Tracker:" ), l, NULL );
1153
1154    s = _( "Last announce at:" );
1155    l = gtk_label_new( NULL );
1156    page->last_announce_time_lb = l;
1157    hig_workarea_add_row( t, &row, s, l, NULL );
1158
1159    s = _( "Tracker responded:" );
1160    l = gtk_label_new( NULL );
1161    page->last_announce_response_lb = l;
1162    hig_workarea_add_row( t, &row, s, l, NULL );
1163
1164    s = _( "Next announce in:" );
1165    l = gtk_label_new( NULL );
1166    page->next_announce_countdown_lb = l;
1167    hig_workarea_add_row( t, &row, s, l, NULL );
1168
1169    /* how long until the tracker will honor user
1170    * pressing the "ask for more peers" button */
1171    s = _( "Manual announce allowed in:" );
1172    l = gtk_label_new( NULL );
1173    page->manual_announce_countdown_lb = l;
1174    hig_workarea_add_row( t, &row, s, l, NULL );
1175
1176    hig_workarea_finish( t, &row );
1177    g_object_set_data_full( G_OBJECT( t ), TRACKER_PAGE, page, g_free );
1178    return t;
1179}
1180
1181static void
1182refresh_countdown_lb( GtkWidget *  w,
1183                      time_t       t,
1184                      const char * countdown_done )
1185{
1186    const time_t now = time( NULL );
1187    GtkLabel *   l = GTK_LABEL( w );
1188
1189    if( t == 1 )
1190        gtk_label_set_text( l, _( "In progress" ) );
1191    else if( t < now )
1192        gtk_label_set_text( l, countdown_done );
1193    else {
1194        char buf[512];
1195        const int seconds = t - now;
1196        tr_strltime( buf, seconds, sizeof( buf ) );
1197        gtk_label_set_text( l, buf );
1198    }
1199}
1200
1201static void
1202refresh_tracker( GtkWidget * w )
1203{
1204    GtkWidget *           l;
1205    time_t                t;
1206    struct tracker_page * page = g_object_get_data( G_OBJECT(
1207                                                        w ), TRACKER_PAGE );
1208    const tr_stat *       torStat = tr_torrent_stat( page->gtor );
1209
1210    l = page->last_scrape_time_lb;
1211    t = torStat->lastScrapeTime;
1212    refresh_time_lb( l, t );
1213
1214    l = page->last_scrape_response_lb;
1215    gtk_label_set_text( GTK_LABEL( l ), torStat->scrapeResponse );
1216
1217    l = page->next_scrape_countdown_lb;
1218    t = torStat->nextScrapeTime;
1219    refresh_countdown_lb( l, t, _( "Never" ) );
1220
1221    l = page->last_announce_time_lb;
1222    t = torStat->lastAnnounceTime;
1223    refresh_time_lb( l, t );
1224
1225    l = page->last_announce_response_lb;
1226    gtk_label_set_text( GTK_LABEL( l ), torStat->announceResponse );
1227
1228    l = page->next_announce_countdown_lb;
1229    t = torStat->nextAnnounceTime;
1230    refresh_countdown_lb( l, t, _( "Never" ) );
1231
1232    l = page->manual_announce_countdown_lb;
1233    t = torStat->manualAnnounceTime;
1234    refresh_countdown_lb( l, t, _( "Now" ) );
1235}
1236
1237/****
1238*****  DIALOG
1239****/
1240
1241static void
1242torrent_destroyed( gpointer dialog, GObject * dead_torrent UNUSED )
1243{
1244    gtk_widget_destroy( GTK_WIDGET( dialog ) );
1245}
1246
1247static void
1248remove_tag( gpointer tag )
1249{
1250    g_source_remove( GPOINTER_TO_UINT( tag ) ); /* stop the periodic refresh */
1251}
1252
1253static void
1254response_cb( GtkDialog *  dialog,
1255             int response UNUSED,
1256             gpointer     data )
1257{
1258    struct ResponseData *rd = data;
1259    TrCore * core = rd->core;
1260    gulong handler = rd-> handler;
1261
1262    g_signal_handler_disconnect( core, handler );
1263    g_object_weak_unref( G_OBJECT( rd->gtor ), torrent_destroyed, dialog );
1264    gtk_widget_destroy( GTK_WIDGET( dialog ) );
1265
1266    g_free( rd );
1267}
1268
1269static gboolean
1270periodic_refresh( gpointer data )
1271{
1272    refresh_tracker   ( g_object_get_data( G_OBJECT( data ), "tracker-top" ) );
1273    refresh_peers     ( g_object_get_data( G_OBJECT( data ), "peers-top" ) );
1274    refresh_activity  ( g_object_get_data( G_OBJECT( data ), "activity-top" ) );
1275    return TRUE;
1276}
1277
1278GtkWidget*
1279torrent_inspector_new( GtkWindow * parent,
1280                       TrCore    * core,
1281                       TrTorrent * gtor )
1282{
1283    guint           tag;
1284    GtkWidget *     d, *n, *w, *lb;
1285    char            title[512];
1286    struct ResponseData  * rd;
1287    tr_torrent *    tor = tr_torrent_handle( gtor );
1288    const tr_info * info = tr_torrent_info( gtor );
1289
1290    /* create the dialog */
1291    rd = g_new0(struct ResponseData, 1);
1292    rd->gtor = gtor;
1293    rd->core = core;
1294    g_snprintf( title, sizeof( title ), _( "%s Properties" ), info->name );
1295    d = gtk_dialog_new_with_buttons( title, parent, 0,
1296                                     GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1297                                     NULL );
1298    gtk_window_set_role( GTK_WINDOW( d ), "tr-info" );
1299    g_signal_connect( d, "response", G_CALLBACK( response_cb ), rd );
1300    gtk_dialog_set_has_separator( GTK_DIALOG( d ), FALSE );
1301    gtk_container_set_border_width( GTK_CONTAINER( d ), GUI_PAD );
1302    g_object_weak_ref( G_OBJECT( gtor ), torrent_destroyed, d );
1303
1304
1305    /* add the notebook */
1306    n = gtk_notebook_new( );
1307    gtk_container_set_border_width( GTK_CONTAINER( n ), GUI_PAD );
1308
1309    w = activity_page_new( gtor );
1310    lb = gtk_label_new( _( "Activity" ) );
1311    gtk_notebook_append_page( GTK_NOTEBOOK( n ), w, lb );
1312    g_object_set_data( G_OBJECT( d ), "activity-top", w );
1313
1314    w = peer_page_new( gtor );
1315    lb = gtk_label_new( _( "Peers" ) );
1316    gtk_notebook_append_page( GTK_NOTEBOOK( n ),  w, lb );
1317    g_object_set_data( G_OBJECT( d ), "peers-top", w );
1318
1319    w = tracker_page_new( gtor );
1320    lb = gtk_label_new( _( "Tracker" ) );
1321    gtk_notebook_append_page( GTK_NOTEBOOK( n ), w, lb );
1322    g_object_set_data( G_OBJECT( d ), "tracker-top", w );
1323
1324    w = info_page_new( tor );
1325    lb = gtk_label_new( _( "Information" ) );
1326    gtk_notebook_append_page( GTK_NOTEBOOK( n ), w, lb );
1327
1328    w = file_list_new( gtor );
1329    gtk_container_set_border_width( GTK_CONTAINER( w ), GUI_PAD_BIG );
1330    lb = gtk_label_new( _( "Files" ) );
1331    gtk_notebook_append_page( GTK_NOTEBOOK( n ), w, lb );
1332    g_object_set_data( G_OBJECT( d ), "files-top", w );
1333
1334    w = options_page_new( rd );
1335    lb = gtk_label_new( _( "Options" ) );
1336    gtk_notebook_append_page( GTK_NOTEBOOK( n ), w, lb );
1337
1338    gtk_box_pack_start( GTK_BOX( GTK_DIALOG( d )->vbox ), n, TRUE, TRUE, 0 );
1339
1340    tag = gtr_timeout_add_seconds( UPDATE_INTERVAL_SECONDS, periodic_refresh, d );
1341    g_object_set_data_full( G_OBJECT( d ), "tag", GUINT_TO_POINTER( tag ), remove_tag );
1342    periodic_refresh( d );
1343    gtk_widget_show_all( GTK_DIALOG( d )->vbox );
1344    return d;
1345}
1346
Note: See TracBrowser for help on using the repository browser.