source: branches/1.5x/gtk/details.c @ 8205

Last change on this file since 8205 was 8205, checked in by charles, 12 years ago

(1.5x gtk) minor backports:
(1) remove dead code
(2) minor improvements to the filterbar buttons
(3) various minor formatting changes to reduce the diffs between 1.52 and trunk

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