source: trunk/gtk/details.c @ 8211

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

(trunk) have the gtk and qt clients use the same minimum seed ratio in the preferences dialog as the mac uses

  • 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 8211 2009-04-10 19:16:12Z 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.