source: trunk/gtk/details.c @ 8097

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

(trunk) more speedlimit work

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