source: trunk/gtk/tr-prefs.c @ 6338

Last change on this file since 6338 was 6338, checked in by charles, 14 years ago

(gtk) add "Launch Clutch" button to the web tab in the preferences dialog. that way users won't have to dig through documentation to find the "http://localhost:port/transmission/clutch" URL.

  • Property svn:keywords set to Date Rev Author Id
File size: 39.5 KB
Line 
1/*
2 * This file Copyright (C) 2007-2008 Charles Kerr <charles@rebelbase.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: tr-prefs.c 6338 2008-07-16 02:31:08Z charles $
11 */
12
13#include <ctype.h> /* isspace */
14#include <errno.h>
15#include <stdarg.h>
16#include <stdlib.h> /* free() */
17#include <unistd.h>
18#include <glib/gi18n.h>
19#include <gtk/gtk.h>
20#include <libtransmission/transmission.h>
21#include <libtransmission/utils.h>
22#include <libtransmission/version.h>
23#include <libtransmission/web.h>
24#include "conf.h"
25#include "hig.h"
26#include "tr-core.h"
27#include "tr-prefs.h"
28#include "util.h"
29
30/**
31 * This is where we initialize the preferences file with the default values.
32 * If you add a new preferences key, you /must/ add a default value here.
33 */
34void
35tr_prefs_init_global( void )
36{
37    int i;
38    char pw[32];
39    const char * str;
40    const char * pool = "abcdefghijklmnopqrstuvwxyz"
41                        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
42                        "1234567890";
43
44    cf_check_older_configs( );
45
46#if HAVE_GIO
47    str = NULL;
48    if( !str ) str = g_get_user_special_dir( G_USER_DIRECTORY_DESKTOP );
49    if( !str ) str = g_get_home_dir( );
50    pref_string_set_default ( PREF_KEY_DIR_WATCH, str );
51    pref_flag_set_default   ( PREF_KEY_DIR_WATCH_ENABLED, FALSE );
52#endif
53
54    pref_int_set_default    ( PREF_KEY_PEER_SOCKET_TOS, TR_DEFAULT_PEER_SOCKET_TOS );
55    pref_flag_set_default   ( PREF_KEY_ALLOW_HIBERNATION, FALSE );
56    pref_flag_set_default   ( PREF_KEY_BLOCKLIST_ENABLED, TR_DEFAULT_BLOCKLIST_ENABLED );
57
58    pref_string_set_default ( PREF_KEY_OPEN_DIALOG_FOLDER, g_get_home_dir( ) );
59
60    pref_int_set_default    ( PREF_KEY_MAX_PEERS_GLOBAL, TR_DEFAULT_GLOBAL_PEER_LIMIT );
61    pref_int_set_default    ( PREF_KEY_MAX_PEERS_PER_TORRENT, 50 );
62
63    pref_flag_set_default   ( PREF_KEY_TOOLBAR, TRUE );
64    pref_flag_set_default   ( PREF_KEY_FILTERBAR, TRUE );
65    pref_flag_set_default   ( PREF_KEY_STATUSBAR, TRUE );
66    pref_flag_set_default   ( PREF_KEY_SHOW_TRAY_ICON, FALSE );
67    pref_string_set_default ( PREF_KEY_STATUSBAR_STATS, "total-ratio" );
68
69    pref_flag_set_default   ( PREF_KEY_DL_LIMIT_ENABLED, FALSE );
70    pref_int_set_default    ( PREF_KEY_DL_LIMIT, 100 );
71    pref_flag_set_default   ( PREF_KEY_UL_LIMIT_ENABLED, FALSE );
72    pref_int_set_default    ( PREF_KEY_UL_LIMIT, 50 );
73    pref_flag_set_default   ( PREF_KEY_OPTIONS_PROMPT, TRUE );
74
75    pref_int_set_default    ( PREF_KEY_MAIN_WINDOW_HEIGHT, 500 );
76    pref_int_set_default    ( PREF_KEY_MAIN_WINDOW_WIDTH, 300 );
77    pref_int_set_default    ( PREF_KEY_MAIN_WINDOW_X, 50 );
78    pref_int_set_default    ( PREF_KEY_MAIN_WINDOW_Y, 50 );
79
80    pref_string_set_default ( PREF_KEY_PROXY_SERVER, "" );
81    pref_int_set_default    ( PREF_KEY_PROXY_PORT, TR_DEFAULT_PROXY_PORT );
82    pref_int_set_default    ( PREF_KEY_PROXY_TYPE, TR_DEFAULT_PROXY_TYPE );
83    pref_flag_set_default   ( PREF_KEY_PROXY_SERVER_ENABLED, TR_DEFAULT_PROXY_ENABLED );
84    pref_flag_set_default   ( PREF_KEY_PROXY_AUTH_ENABLED, TR_DEFAULT_PROXY_AUTH_ENABLED );
85    pref_string_set_default ( PREF_KEY_PROXY_USERNAME, "" );
86    pref_string_set_default ( PREF_KEY_PROXY_PASSWORD, "" );
87
88    str = NULL;
89#if GLIB_CHECK_VERSION(2,14,0)
90    if( !str ) str = g_get_user_special_dir( G_USER_DIRECTORY_DOWNLOAD );
91#endif
92    if( !str ) str = g_get_home_dir( );
93    pref_string_set_default ( PREF_KEY_DOWNLOAD_DIR, str );
94
95    pref_int_set_default    ( PREF_KEY_PORT, TR_DEFAULT_PORT );
96
97    pref_flag_set_default   ( PREF_KEY_PORT_FORWARDING, TRUE );
98    pref_flag_set_default   ( PREF_KEY_PEX, TR_DEFAULT_PEX_ENABLED );
99    pref_flag_set_default   ( PREF_KEY_ASKQUIT, TRUE );
100    pref_flag_set_default   ( PREF_KEY_ENCRYPTION, TR_ENCRYPTION_PREFERRED );
101
102    pref_int_set_default    ( PREF_KEY_MSGLEVEL, TR_MSG_INF );
103
104    pref_string_set_default ( PREF_KEY_SORT_MODE, "sort-by-name" );
105    pref_flag_set_default   ( PREF_KEY_SORT_REVERSED, FALSE );
106    pref_flag_set_default   ( PREF_KEY_MINIMAL_VIEW, FALSE );
107
108    pref_flag_set_default   ( PREF_KEY_START, TRUE );
109    pref_flag_set_default   ( PREF_KEY_TRASH_ORIGINAL, FALSE );
110
111    pref_flag_set_default   ( PREF_KEY_RPC_ENABLED, TR_DEFAULT_RPC_ENABLED );
112    pref_int_set_default    ( PREF_KEY_RPC_PORT, TR_DEFAULT_RPC_PORT );
113    pref_string_set_default ( PREF_KEY_RPC_ACL, TR_DEFAULT_RPC_ACL );
114
115    for( i=0; i<16; ++i )
116        pw[i] = pool[ tr_rand( strlen( pool ) ) ];
117    pw[16] = '\0';
118    pref_string_set_default( PREF_KEY_RPC_USERNAME, "transmission" );
119    pref_string_set_default( PREF_KEY_RPC_PASSWORD, pw );
120    pref_flag_set_default  ( PREF_KEY_RPC_AUTH_ENABLED, FALSE );
121
122    pref_save( );
123}
124
125/**
126***
127**/
128
129#define PREF_KEY "pref-key"
130
131static void
132response_cb( GtkDialog * dialog, int response, gpointer unused UNUSED )
133{
134    if( response == GTK_RESPONSE_HELP ) {
135        char * base = gtr_get_help_url( );
136        char * url = g_strdup_printf( "%s/html/preferences.html", base );
137        gtr_open_file( url );
138        g_free( url );
139        g_free( base );
140    }
141
142    if( response == GTK_RESPONSE_CLOSE )
143        gtk_widget_destroy( GTK_WIDGET(dialog) );
144}
145
146static void
147toggled_cb( GtkToggleButton * w, gpointer core )
148{
149    const char * key = g_object_get_data( G_OBJECT(w), PREF_KEY );
150    const gboolean flag = gtk_toggle_button_get_active( w );
151    tr_core_set_pref_bool( TR_CORE(core), key, flag );
152}
153
154static GtkWidget*
155new_check_button( const char * mnemonic, const char * key, gpointer core )
156{
157    GtkWidget * w = gtk_check_button_new_with_mnemonic( mnemonic );
158    g_object_set_data_full( G_OBJECT(w), PREF_KEY, g_strdup(key), g_free );
159    gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(w), pref_flag_get(key) );
160    g_signal_connect( w, "toggled", G_CALLBACK(toggled_cb), core );
161    return w;
162}
163
164static void
165spun_cb( GtkSpinButton * w, gpointer core )
166{
167    const char * key = g_object_get_data( G_OBJECT(w), PREF_KEY );
168    const int value = gtk_spin_button_get_value_as_int( w );
169    tr_core_set_pref_int( TR_CORE(core), key, value );
170}
171
172static GtkWidget*
173new_spin_button( const char * key, gpointer core, int low, int high, int step )
174{
175    GtkWidget * w = gtk_spin_button_new_with_range( low, high, step );
176    g_object_set_data_full( G_OBJECT(w), PREF_KEY, g_strdup(key), g_free );
177    gtk_spin_button_set_digits( GTK_SPIN_BUTTON(w), 0 );
178    gtk_spin_button_set_value( GTK_SPIN_BUTTON(w), pref_int_get(key) );
179    g_signal_connect( w, "value-changed", G_CALLBACK(spun_cb), core );
180    return w;
181}
182
183static void
184entry_changed_cb( GtkEntry * w, gpointer core )
185{
186    const char * key = g_object_get_data( G_OBJECT( w ), PREF_KEY );
187    const char * value = gtk_entry_get_text( w );
188    tr_core_set_pref( TR_CORE( core ), key, value );
189}
190
191static GtkWidget*
192new_entry( const char * key, gpointer core )
193{
194    GtkWidget * w = gtk_entry_new( );
195    const char * value = pref_string_get( key );
196    if( value )
197        gtk_entry_set_text( GTK_ENTRY( w ), value );
198    g_object_set_data_full( G_OBJECT(w), PREF_KEY, g_strdup(key), g_free );
199    g_signal_connect( w, "changed", G_CALLBACK(entry_changed_cb), core );
200    return w;
201}
202
203static void
204chosen_cb( GtkFileChooser * w, gpointer core )
205{
206    const char * key = g_object_get_data( G_OBJECT(w), PREF_KEY );
207    char * value = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(w) );
208    tr_core_set_pref( TR_CORE(core), key, value );
209    g_free( value );
210}
211
212static GtkWidget*
213new_path_chooser_button( const char * key, gpointer core )
214{
215    GtkWidget * w = gtk_file_chooser_button_new( NULL,
216                                    GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER );
217    const char * path = pref_string_get( key );
218    g_object_set_data_full( G_OBJECT(w), PREF_KEY, g_strdup(key), g_free );
219    g_signal_connect( w, "selection-changed", G_CALLBACK(chosen_cb), core );
220    gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(w), path );
221    return w;
222}
223
224static void
225target_cb( GtkWidget * tb, gpointer target )
226{
227    const gboolean b = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( tb ) );
228    gtk_widget_set_sensitive( GTK_WIDGET(target), b );
229}
230
231struct test_port_data
232{
233    GtkWidget * label;
234    gboolean * alive;
235};
236
237static void
238testing_port_done( tr_handle   * handle         UNUSED,
239                   long          response_code  UNUSED,
240                   const void  * response,
241                   size_t        response_len,
242                   void        * gdata )
243{
244    struct test_port_data * data = gdata;
245    if( *data->alive )
246    {
247        const int isOpen = response_len && *(char*)response=='1';
248        gtk_label_set_markup( GTK_LABEL( data->label ), isOpen
249                              ? _("Port is <b>open</b>")
250                              : _("Port is <b>closed</b>") );
251    }
252}
253
254static gboolean
255testing_port_begin( gpointer gdata )
256{
257    struct test_port_data * data = gdata;
258    if( *data->alive )
259    {
260        GtkSpinButton * spin = g_object_get_data( G_OBJECT( data->label ), "tr-port-spin" );
261        tr_handle * handle = g_object_get_data( G_OBJECT( data->label ), "handle" );
262        const int port = gtk_spin_button_get_value_as_int( spin );
263        char url[256];
264        g_snprintf( url, sizeof(url), "http://portcheck.transmissionbt.com/%d", port );
265        tr_webRun( handle, url, NULL, testing_port_done, data );
266    }
267    return FALSE;
268}
269
270static void
271testing_port_cb( GtkWidget * unused UNUSED, gpointer l )
272{
273    gtk_label_set_markup( GTK_LABEL( l ), _( "<i>Testing port...</i>" ) );
274    /* wait three seconds to give the port forwarding time to kick in */
275    struct test_port_data * data = g_new0( struct test_port_data, 1 );
276    data->label = l;
277    data->alive = g_object_get_data( G_OBJECT( l ), "alive" );
278    g_timeout_add( 3000, testing_port_begin, data );
279}
280
281static void
282dialogDestroyed( gpointer alive, GObject * dialog UNUSED )
283{
284    *(gboolean*)alive = FALSE;
285}
286
287static GtkWidget*
288torrentPage( GObject * core )
289{
290    int row = 0;
291    const char * s;
292    GtkWidget * t;
293    GtkWidget * w;
294#ifdef HAVE_GIO
295    GtkWidget * l;
296#endif
297
298    t = hig_workarea_create( );
299    hig_workarea_add_section_title( t, &row, _( "Adding Torrents" ) );
300
301#ifdef HAVE_GIO
302        s = _( "Automatically _add torrents from:" );
303        l = new_check_button( s, PREF_KEY_DIR_WATCH_ENABLED, core );
304        w = new_path_chooser_button( PREF_KEY_DIR_WATCH, core );
305        gtk_widget_set_sensitive( GTK_WIDGET(w), pref_flag_get( PREF_KEY_DIR_WATCH_ENABLED ) );
306        g_signal_connect( l, "toggled", G_CALLBACK(target_cb), w );
307        hig_workarea_add_row_w( t, &row, l, w, NULL );
308#endif
309
310        s = _( "Display _options dialog" );
311        w = new_check_button( s, PREF_KEY_OPTIONS_PROMPT, core );
312        hig_workarea_add_wide_control( t, &row, w );
313
314        s = _( "_Start when added" );
315        w = new_check_button( s, PREF_KEY_START, core );
316        hig_workarea_add_wide_control( t, &row, w );
317
318        s = _( "Mo_ve source files to Trash" );
319        w = new_check_button( s, PREF_KEY_TRASH_ORIGINAL, core ); 
320        hig_workarea_add_wide_control( t, &row, w );
321
322        w = new_path_chooser_button( PREF_KEY_DOWNLOAD_DIR, core );
323        hig_workarea_add_row( t, &row, _( "_Destination folder:" ), w, NULL );
324
325    hig_workarea_finish( t, &row );
326    return t;
327}
328
329/***
330****
331***/
332
333struct blocklist_data
334{
335    GtkWidget * check;
336    GtkWidget * dialog;
337    TrCore * core;
338    int abortFlag;
339    char secondary[256];
340};
341
342static void
343updateBlocklistText( GtkWidget * w, TrCore * core )
344{
345    const int n = tr_blocklistGetRuleCount( tr_core_handle( core ) );
346    char buf[512];
347    g_snprintf( buf, sizeof( buf ),
348                ngettext( "Ignore the %'d _blocklisted peer",
349                          "Ignore the %'d _blocklisted peers", n ), n );
350    gtk_button_set_label( GTK_BUTTON( w ), buf );
351}
352static gboolean
353updateBlocklistTextFromData( gpointer gdata )
354{
355    struct blocklist_data * data = gdata;
356    updateBlocklistText( data->check, data->core );
357    return FALSE;
358}
359
360static gboolean
361blocklistDialogSetSecondary( gpointer gdata )
362{
363    struct blocklist_data * data = gdata;
364    GtkMessageDialog * md = GTK_MESSAGE_DIALOG( data->dialog );
365    gtk_message_dialog_format_secondary_text( md, data->secondary );
366    return FALSE;
367}
368
369static gboolean
370blocklistDialogAllowClose( gpointer dialog )
371{
372    GtkDialog * d = GTK_DIALOG( dialog );
373    gtk_dialog_set_response_sensitive( GTK_DIALOG( d ), GTK_RESPONSE_CANCEL, FALSE );
374    gtk_dialog_set_response_sensitive( GTK_DIALOG( d ), GTK_RESPONSE_CLOSE, TRUE );
375    return FALSE;
376}
377
378static void
379got_blocklist( tr_handle   * handle         UNUSED,
380               long          response_code  UNUSED,
381               const void  * response,
382               size_t        response_len,
383               void        * gdata )
384{
385    struct blocklist_data * data = gdata;
386    const char * text = response;
387    int size = response_len;
388    int rules = 0;
389    gchar * filename = NULL;
390    gchar * filename2 = NULL;
391    int fd = -1;
392    int ok = 1;
393
394    if( !data->abortFlag && ( !text || !size ) )
395    {
396        ok = FALSE;
397        g_snprintf( data->secondary, sizeof( data->secondary ),
398                    _( "Unable to get blocklist." ) );
399        g_message( data->secondary );
400        g_idle_add( blocklistDialogSetSecondary, data );
401    }     
402
403    if( ok && !data->abortFlag )
404    {
405        GError * err = NULL;
406        fd = g_file_open_tmp( "transmission-blockfile-XXXXXX", &filename, &err );
407        if( err ) {
408            g_snprintf( data->secondary, sizeof( data->secondary ),
409                        _( "Unable to get blocklist: %s" ), err->message );
410            g_warning( data->secondary );
411            g_idle_add( blocklistDialogSetSecondary, data );
412            g_clear_error( &err );
413            ok = FALSE;
414        } else {
415            write( fd, text, size );
416            close( fd );
417        }
418    }
419    if( ok && !data->abortFlag )
420    {
421        filename2 = g_strdup_printf( "%s.txt", filename );
422        g_snprintf( data->secondary, sizeof( data->secondary ),
423                    _( "Uncompressing blocklist..." ) );
424        g_idle_add( blocklistDialogSetSecondary, data );
425        char * cmd = g_strdup_printf( "zcat %s > %s ", filename, filename2 );
426        tr_dbg( "%s", cmd );
427        system( cmd );
428        g_free( cmd );
429    }
430    if( ok && !data->abortFlag )
431    {
432        g_snprintf( data->secondary, sizeof( data->secondary ),
433                    _( "Parsing blocklist..." ) );
434        g_idle_add( blocklistDialogSetSecondary, data );
435        rules = tr_blocklistSetContent( tr_core_handle( data->core ), filename2 );
436    }
437    if( ok && !data->abortFlag )
438    {
439        g_snprintf( data->secondary, sizeof( data->secondary ),
440                    _( "Blocklist updated with %'d entries" ), rules );
441        g_idle_add( blocklistDialogSetSecondary, data );
442        g_idle_add( blocklistDialogAllowClose, data->dialog );
443        g_idle_add( updateBlocklistTextFromData, data );
444    }
445
446    /* g_free( data ); */
447    if( filename2 ) {
448        unlink( filename2 );
449        g_free( filename2 );
450    }
451    if( filename ) {
452        unlink( filename );
453        g_free( filename );
454    }
455}
456
457static void
458onUpdateBlocklistResponseCB( GtkDialog * dialog, int response, gpointer vdata )
459{
460    struct blocklist_data * data = vdata;
461
462    if( response == GTK_RESPONSE_CANCEL )
463        data->abortFlag = 1;
464
465    data->dialog = NULL;
466    gtk_widget_destroy( GTK_WIDGET( dialog ) );
467}
468
469static void
470onUpdateBlocklistCB( GtkButton * w, gpointer gdata )
471{
472    GtkWidget * d;
473    struct blocklist_data * data = gdata;
474    tr_handle * handle = g_object_get_data( G_OBJECT( w ), "handle" );
475    const char * url = "http://download.m0k.org/transmission/files/level1.gz";
476   
477    d = gtk_message_dialog_new( GTK_WINDOW( gtk_widget_get_toplevel( GTK_WIDGET( w ) ) ),
478                                GTK_DIALOG_DESTROY_WITH_PARENT,
479                                GTK_MESSAGE_INFO,
480                                GTK_BUTTONS_NONE,
481                                _( "Updating Blocklist" ) );
482    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( d ),
483                                              _( "Retrieving blocklist..." ) );
484    gtk_dialog_add_buttons( GTK_DIALOG( d ),
485                            GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
486                            GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
487                            NULL );
488    gtk_dialog_set_response_sensitive( GTK_DIALOG( d ), GTK_RESPONSE_CLOSE, FALSE );
489
490    data->dialog = d;
491
492    g_signal_connect( d, "response", G_CALLBACK(onUpdateBlocklistResponseCB), data );
493    gtk_widget_show( d );
494
495    tr_webRun( handle, url, NULL, got_blocklist, data );
496}
497
498static void
499onEncryptionToggled( GtkToggleButton * w, gpointer core )
500{
501    const int val = gtk_toggle_button_get_active( w )
502                  ? TR_ENCRYPTION_REQUIRED
503                  : TR_ENCRYPTION_PREFERRED;
504    tr_core_set_pref_int( TR_CORE( core ), PREF_KEY_ENCRYPTION, val );
505}
506
507static GtkWidget*
508peerPage( GObject * core, gboolean * alive )
509{
510    int row = 0;
511    const char * s;
512    GtkWidget * t;
513    GtkWidget * w;
514    GtkWidget * w2;
515    GtkWidget * b;
516    GtkWidget * h;
517    GtkWidget * l;
518    struct blocklist_data * data;
519
520    t = hig_workarea_create( );
521    hig_workarea_add_section_title (t, &row, _("Options"));
522
523        w = new_check_button( "", PREF_KEY_BLOCKLIST_ENABLED, core );
524        updateBlocklistText( w, TR_CORE( core ) );
525        h = gtk_hbox_new( FALSE, GUI_PAD_BIG );
526        gtk_box_pack_start_defaults( GTK_BOX(h), w );
527        b = gtk_button_new_with_mnemonic( _( "_Update Blocklist" ) );
528
529        data = g_new0( struct blocklist_data, 1 );
530        data->core = TR_CORE( core );
531        data->check = w;
532
533        g_object_set_data( G_OBJECT( b ), "handle", tr_core_handle( TR_CORE( core ) ) );
534        g_signal_connect( b, "clicked", G_CALLBACK(onUpdateBlocklistCB), data );
535        gtk_box_pack_start( GTK_BOX(h), b, FALSE, FALSE, 0 );
536        g_signal_connect( w, "toggled", G_CALLBACK(target_cb), b );
537        target_cb( w, b );
538        hig_workarea_add_wide_control( t, &row, h );
539       
540        s = _("_Ignore unencrypted peers");
541        w = gtk_check_button_new_with_mnemonic( s );
542        gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(w),
543                                      pref_int_get(PREF_KEY_ENCRYPTION)==TR_ENCRYPTION_REQUIRED );
544        g_signal_connect( w, "toggled", G_CALLBACK(onEncryptionToggled), core );
545        hig_workarea_add_wide_control( t, &row, w );
546
547        s = _("Use peer e_xchange");
548        w = new_check_button( s, PREF_KEY_PEX, core );
549        hig_workarea_add_wide_control( t, &row, w );
550
551        h = gtk_hbox_new( FALSE, GUI_PAD_BIG );
552        w2 = new_spin_button( PREF_KEY_PORT, core, 1, INT_MAX, 1 );
553        gtk_box_pack_start( GTK_BOX(h), w2, FALSE, FALSE, 0 );
554        l = gtk_label_new( NULL );
555        gtk_misc_set_alignment( GTK_MISC(l), 0.0f, 0.5f );
556        gtk_box_pack_start( GTK_BOX(h), l, FALSE, FALSE, 0 );
557        hig_workarea_add_row( t, &row, _("Listening _port:"), h, w2 );
558
559        g_object_set_data( G_OBJECT(l), "tr-port-spin", w2 );
560        g_object_set_data( G_OBJECT(l), "alive", alive );
561        g_object_set_data( G_OBJECT(l), "handle", tr_core_handle( TR_CORE( core ) ) );
562        testing_port_cb( NULL, l );
563        g_signal_connect( w2, "value-changed", G_CALLBACK(testing_port_cb), l );
564       
565    hig_workarea_add_section_divider( t, &row );
566    hig_workarea_add_section_title( t, &row, _( "Limits" ) );
567 
568        w = new_spin_button( PREF_KEY_MAX_PEERS_GLOBAL, core, 1, 3000, 5 );
569        hig_workarea_add_row( t, &row, _( "Maximum peers _overall:" ), w, NULL );
570        w = new_spin_button( PREF_KEY_MAX_PEERS_PER_TORRENT, core, 1, 300, 5 );
571        hig_workarea_add_row( t, &row, _( "Maximum peers per _torrent:" ), w, NULL );
572
573    hig_workarea_finish( t, &row );
574    return t;
575}
576
577static GtkTreeModel*
578allow_deny_model_new( void )
579{
580    GtkTreeIter iter;
581    GtkListStore * store = gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_CHAR );
582    gtk_list_store_append( store, &iter );
583    gtk_list_store_set( store, &iter, 0, _( "Allow" ), 1, '+', -1 );
584    gtk_list_store_append( store, &iter );
585    gtk_list_store_set( store, &iter, 0, _( "Deny" ), 1, '-', -1 );
586    return GTK_TREE_MODEL( store );
587}
588
589enum
590{
591    COL_ADDRESS,
592    COL_PERMISSION,
593    N_COLS
594};
595
596static GtkTreeModel*
597acl_tree_model_new( const char * acl )
598{
599    int i;
600    char ** rules;
601    GtkListStore * store = gtk_list_store_new( N_COLS,
602                                               G_TYPE_STRING,
603                                               G_TYPE_STRING );
604    rules = g_strsplit( acl, ",", 0 );
605
606    for( i=0; rules && rules[i]; ++i )
607    {
608        const char * s = rules[i];
609        while( isspace( *s ) ) ++s;
610        if( *s=='+' || *s=='-' )
611        {
612            GtkTreeIter iter;
613            gtk_list_store_append( store, &iter );
614            gtk_list_store_set( store, &iter,
615                COL_PERMISSION, *s=='+' ? _( "Allow" ) : _( "Deny" ) ,
616                COL_ADDRESS, s+1,
617                -1 );
618        }
619    }
620
621    g_strfreev( rules );
622    return GTK_TREE_MODEL( store );
623}
624
625struct remote_page
626{
627    TrCore * core;
628    GtkTreeView * view;
629    GtkListStore * store;
630    GtkWidget * remove_button;
631    GSList * widgets;
632    GSList * auth_widgets;
633    GtkToggleButton * rpc_tb;
634    GtkToggleButton * auth_tb;
635};
636
637static void
638refreshACL( struct remote_page * page )
639{
640    GtkTreeIter iter;
641    GtkTreeModel * model = GTK_TREE_MODEL( page->store );
642    GString * gstr = g_string_new( NULL );
643
644    if( gtk_tree_model_get_iter_first( model, &iter ) ) do
645    {
646        char * permission;
647        char * address;
648        gtk_tree_model_get( model, &iter, COL_PERMISSION, &permission,
649                                          COL_ADDRESS, &address,
650                                          -1 );
651        g_string_append_c( gstr, strcmp(permission,_("Allow")) ? '-' : '+' );
652        g_string_append( gstr, address );
653        g_string_append( gstr, ", " );
654        g_free( address );
655        g_free( permission );
656    }
657    while( gtk_tree_model_iter_next( model, &iter ) );
658
659    g_string_truncate( gstr, gstr->len-2 ); /* remove the trailing ", " */
660
661    tr_core_set_pref( page->core, PREF_KEY_RPC_ACL, gstr->str );
662
663    g_string_free( gstr, TRUE );
664}
665
666static void
667onPermissionEdited( GtkCellRendererText  * renderer UNUSED,
668                    gchar                * path_string,
669                    gchar                * new_text,
670                    gpointer               gpage )
671{
672    GtkTreeIter iter;
673    GtkTreePath * path = gtk_tree_path_new_from_string( path_string );
674    struct remote_page * page = gpage;
675    GtkTreeModel * model = GTK_TREE_MODEL( page->store );
676    if( gtk_tree_model_get_iter( model, &iter, path ) )
677        gtk_list_store_set( page->store, &iter, COL_PERMISSION, new_text, -1 );
678    gtk_tree_path_free( path );
679    refreshACL( page );
680}
681
682static void
683onAddressEdited( GtkCellRendererText  * r UNUSED,
684                 gchar                * path_string,
685                 gchar                * new_text,
686                 gpointer               gpage )
687{
688    char * acl;
689    GtkTreeIter iter;
690    struct remote_page * page = gpage;
691    GtkTreeModel * model = GTK_TREE_MODEL( page->store );
692    tr_handle * session = tr_core_handle( page->core );
693    GtkTreePath * path = gtk_tree_path_new_from_string( path_string );
694
695    acl = g_strdup_printf( "+%s", new_text );
696    if( !tr_sessionTestRPCACL( session, acl, NULL ) )
697        if( gtk_tree_model_get_iter( model, &iter, path ) )
698            gtk_list_store_set( page->store, &iter, COL_ADDRESS, new_text, -1 );
699
700    g_free( acl );
701    gtk_tree_path_free( path );
702    refreshACL( page );
703}
704
705static void
706onAddACLClicked( GtkButton * b UNUSED, gpointer gpage )
707{
708    GtkTreeIter iter;
709    GtkTreePath * path;
710    struct remote_page * page = gpage;
711    gtk_list_store_append( page->store, &iter );
712    gtk_list_store_set( page->store, &iter,
713                        COL_PERMISSION, _( "Allow" ),
714                        COL_ADDRESS,  "0.0.0.0",
715                        -1 );
716
717    path = gtk_tree_model_get_path( GTK_TREE_MODEL( page->store ), &iter );
718    gtk_tree_view_set_cursor(
719        page->view, path,
720        gtk_tree_view_get_column( page->view, COL_ADDRESS ),
721        TRUE );
722    gtk_tree_path_free( path );
723}
724
725static void
726onRemoveACLClicked( GtkButton * b UNUSED, gpointer gpage )
727{
728    struct remote_page * page = gpage;
729    GtkTreeSelection * sel = gtk_tree_view_get_selection( page->view );
730    GtkTreeIter iter;
731    if( gtk_tree_selection_get_selected( sel, NULL, &iter ) )
732    {
733        gtk_list_store_remove( page->store, &iter );
734        refreshACL( page );
735    }
736}
737
738static void
739refreshRPCSensitivity( struct remote_page * page )
740{
741    GSList * l;
742    const int rpc_active = gtk_toggle_button_get_active( page->rpc_tb );
743    const int auth_active = gtk_toggle_button_get_active( page->auth_tb );
744    GtkTreeSelection * sel = gtk_tree_view_get_selection( page->view );
745    const int have_addr = gtk_tree_selection_get_selected( sel, NULL, NULL );
746    const int n_rules = gtk_tree_model_iter_n_children(
747                                       GTK_TREE_MODEL( page->store ), NULL );
748
749    for( l=page->widgets; l!=NULL; l=l->next )
750        gtk_widget_set_sensitive( GTK_WIDGET( l->data ), rpc_active );
751
752    for( l=page->auth_widgets; l!=NULL; l=l->next )
753        gtk_widget_set_sensitive( GTK_WIDGET( l->data ), rpc_active && auth_active);
754
755    gtk_widget_set_sensitive( page->remove_button,
756                              rpc_active && have_addr && n_rules>1 );
757}
758
759static void
760onRPCToggled( GtkToggleButton * tb UNUSED, gpointer page )
761{
762    refreshRPCSensitivity( page );
763}
764static void
765onACLSelectionChanged( GtkTreeSelection * sel UNUSED, gpointer page )
766{
767    refreshRPCSensitivity( page );
768}
769
770static void
771onLaunchClutchCB( GtkButton * w UNUSED, gpointer data UNUSED )
772{
773    int port = pref_int_get( PREF_KEY_RPC_PORT );
774    char * url = g_strdup_printf( "http://localhost:%d/transmission/web", port );
775    gtr_open_file( url );
776    g_free( url );
777}
778
779static GtkWidget*
780webPage( GObject * core )
781{
782    const char  * s;
783    int row = 0;
784    GtkWidget * t;
785    GtkWidget * w;
786    GtkWidget * h;
787    struct remote_page * page = g_new0( struct remote_page, 1 );
788
789    page->core = TR_CORE( core );
790
791    t = hig_workarea_create( );
792    g_object_set_data_full( G_OBJECT( t ), "page", page, g_free );
793
794    hig_workarea_add_section_title( t, &row, _( "Web Interface" ) );
795
796        /* "enabled" checkbutton */
797        s = _( "_Allow remote access" );
798        w = new_check_button( s, PREF_KEY_RPC_ENABLED, core );
799        page->rpc_tb = GTK_TOGGLE_BUTTON( w );
800        g_signal_connect( w, "clicked", G_CALLBACK(onRPCToggled), page );
801        h = gtk_hbox_new( FALSE, GUI_PAD_BIG );
802        gtk_box_pack_start_defaults( GTK_BOX(h), w );
803        w = gtk_button_new_with_mnemonic( _( "_Launch Web GUI" ) );
804        page->widgets = g_slist_append( page->widgets, w );
805        g_signal_connect( w, "clicked", G_CALLBACK(onLaunchClutchCB), NULL );
806        gtk_box_pack_start( GTK_BOX(h), w, FALSE, FALSE, 0 );
807        hig_workarea_add_wide_control( t, &row, h );
808
809        /* require authentication */
810        s = _( "Require _username" );
811        w = new_check_button( s, PREF_KEY_RPC_AUTH_ENABLED, core );
812        hig_workarea_add_wide_control( t, &row, w );
813        page->auth_tb = GTK_TOGGLE_BUTTON( w );
814        page->widgets = g_slist_append( page->widgets, w );
815        g_signal_connect( w, "clicked", G_CALLBACK(onRPCToggled), page );
816
817        /* username */
818        s = _( "_Username:" );
819        w = new_entry( PREF_KEY_RPC_USERNAME, core );
820        page->auth_widgets = g_slist_append( page->auth_widgets, w );
821        w = hig_workarea_add_row( t, &row, s, w, NULL );
822        page->auth_widgets = g_slist_append( page->auth_widgets, w );
823
824        /* password */
825        s = _( "_Password:" );
826        w = new_entry( PREF_KEY_RPC_PASSWORD, core );
827        gtk_entry_set_visibility( GTK_ENTRY( w ), FALSE );
828        page->auth_widgets = g_slist_append( page->auth_widgets, w );
829        w = hig_workarea_add_row( t, &row, s, w, NULL );
830        page->auth_widgets = g_slist_append( page->auth_widgets, w );
831
832        /* port */
833        w = new_spin_button( PREF_KEY_RPC_PORT, core, 0, 65535, 1 );
834        page->widgets = g_slist_append( page->widgets, w );
835        w = hig_workarea_add_row( t, &row, _( "Listening _port:" ), w, NULL );
836        page->widgets = g_slist_append( page->widgets, w );
837
838        /* access control list */
839        {
840        const char * val = pref_string_get( PREF_KEY_RPC_ACL );
841        GtkTreeModel * m = acl_tree_model_new( val );
842        GtkTreeViewColumn * c;
843        GtkCellRenderer * r;
844        GtkTreeSelection * sel;
845        GtkTreeView * v;
846        GtkWidget * w;
847        GtkWidget * h;
848        GtkTooltips * tips = gtk_tooltips_new( );
849
850        s = _( "Access control list:" );
851        page->store = GTK_LIST_STORE( m );
852        w = gtk_tree_view_new_with_model( m );
853
854        page->widgets = g_slist_append( page->widgets, w );
855        v = page->view = GTK_TREE_VIEW( w );
856        gtk_tooltips_set_tip( tips, w,
857            _( "IP addresses may use wildcards, such as 192.168.*.*" ),
858            NULL );
859        sel = gtk_tree_view_get_selection( v );
860        g_signal_connect( sel, "changed",
861                          G_CALLBACK( onACLSelectionChanged ), page );
862        g_object_unref( G_OBJECT( m ) );
863        gtk_tree_view_set_headers_visible( v, TRUE );
864        w = gtk_frame_new( NULL );
865        gtk_frame_set_shadow_type( GTK_FRAME( w ), GTK_SHADOW_IN );
866        gtk_container_add( GTK_CONTAINER( w ), GTK_WIDGET( v ) );
867
868        /* ip address column */
869        r = gtk_cell_renderer_text_new( );
870        g_signal_connect( r, "edited",
871                          G_CALLBACK( onAddressEdited ), page );
872        g_object_set( G_OBJECT( r ), "editable", TRUE, NULL );
873        c = gtk_tree_view_column_new_with_attributes( _( "IP Address" ), r,
874                "text", COL_ADDRESS,
875                NULL );
876        gtk_tree_view_column_set_expand( c, TRUE );
877        gtk_tree_view_append_column( v, c );
878
879        w = hig_workarea_add_row( t, &row, s, w, NULL );
880        gtk_misc_set_alignment( GTK_MISC( w ), 0.0f, 0.1f );
881        page->widgets = g_slist_append( page->widgets, w );
882
883        /* permission column */
884        m = allow_deny_model_new( );
885        r = gtk_cell_renderer_combo_new( );
886        g_object_set( G_OBJECT( r ), "model", m,
887                                     "editable", TRUE,
888                                     "has-entry", FALSE,
889                                     "text-column", 0,
890                                     NULL );
891        c = gtk_tree_view_column_new_with_attributes( _( "Permission" ), r,
892                "text", COL_PERMISSION,
893                NULL );
894        g_signal_connect( r, "edited",
895                          G_CALLBACK( onPermissionEdited ), page );
896        gtk_tree_view_append_column( v, c );
897
898        h = gtk_hbox_new( TRUE, GUI_PAD );
899        w = gtk_button_new_from_stock( GTK_STOCK_REMOVE );
900        g_signal_connect( w, "clicked", G_CALLBACK(onRemoveACLClicked), page );
901        page->remove_button = w;
902        onACLSelectionChanged( sel, page );
903        gtk_box_pack_start_defaults( GTK_BOX( h ), w );
904        w = gtk_button_new_from_stock( GTK_STOCK_ADD );
905        page->widgets = g_slist_append( page->widgets, w );
906        g_signal_connect( w, "clicked", G_CALLBACK(onAddACLClicked), page );
907        gtk_box_pack_start_defaults( GTK_BOX( h ), w );
908        w = gtk_hbox_new( FALSE, 0 );
909        gtk_box_pack_start_defaults( GTK_BOX( w ), gtk_alignment_new( 0, 0, 0, 0 ) );
910        gtk_box_pack_start( GTK_BOX( w ), h, FALSE, FALSE, 0 );
911        hig_workarea_add_wide_control( t, &row, w );
912        }
913
914    refreshRPCSensitivity( page );
915    hig_workarea_finish( t, &row );
916    return t;
917}
918
919struct ProxyPage
920{
921    TrCore * core;
922    GSList * proxy_widgets;
923    GSList * proxy_auth_widgets;
924};
925
926static void
927refreshProxySensitivity( struct ProxyPage * p )
928{
929    GSList * l;
930    const gboolean proxy_enabled = pref_flag_get( PREF_KEY_PROXY_SERVER_ENABLED );
931    const gboolean proxy_auth_enabled = pref_flag_get( PREF_KEY_PROXY_AUTH_ENABLED );
932
933    for( l=p->proxy_widgets; l!=NULL; l=l->next )
934        gtk_widget_set_sensitive( GTK_WIDGET( l->data ), proxy_enabled );
935
936    for( l=p->proxy_auth_widgets; l!=NULL; l=l->next )
937        gtk_widget_set_sensitive( GTK_WIDGET( l->data ),
938                                  proxy_enabled && proxy_auth_enabled);
939}
940
941static void
942onProxyToggled( GtkToggleButton * tb UNUSED, gpointer user_data )
943{
944    refreshProxySensitivity( user_data );
945}
946
947static void
948proxyPageFree( gpointer gpage )
949{
950    struct ProxyPage * page = gpage;
951    g_slist_free( page->proxy_widgets );
952    g_slist_free( page->proxy_auth_widgets );
953    g_free( page );
954}
955
956static GtkTreeModel*
957proxyTypeModelNew( void )
958{
959    GtkTreeIter iter;
960    GtkListStore * store = gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_INT );
961    gtk_list_store_append( store, &iter );
962    gtk_list_store_set( store, &iter, 0, _( "HTTP" ), 1, TR_PROXY_HTTP, -1 );
963    gtk_list_store_append( store, &iter );
964    gtk_list_store_set( store, &iter, 0, _( "SOCKS4" ), 1, TR_PROXY_SOCKS4, -1 );
965    gtk_list_store_append( store, &iter );
966    gtk_list_store_set( store, &iter, 0, _( "SOCKS5" ), 1, TR_PROXY_SOCKS5, -1 );
967    return GTK_TREE_MODEL( store );
968}
969
970static void
971onProxyTypeChanged( GtkComboBox * w, gpointer gpage )
972{
973    GtkTreeIter iter;
974    if( gtk_combo_box_get_active_iter( w, &iter ) )
975    {
976        struct ProxyPage * page = gpage;
977        int type = TR_PROXY_HTTP;
978        gtk_tree_model_get( gtk_combo_box_get_model( w ), &iter, 1, &type, -1 );
979        tr_core_set_pref_int( TR_CORE( page->core ), PREF_KEY_PROXY_TYPE, type );
980    }
981}
982
983static GtkWidget*
984trackerPage( GObject * core )
985{
986    int row = 0;
987    const char * s;
988    GtkWidget * t;
989    GtkWidget * w;
990    GtkTreeModel * m;
991    GtkCellRenderer * r;
992    struct ProxyPage * page = tr_new0( struct ProxyPage, 1 );
993
994    page->core = TR_CORE( core );
995
996    t = hig_workarea_create( );
997    hig_workarea_add_section_title (t, &row, _( "Tracker Proxy" ) );
998
999        s = _( "Connect to tracker with HTTP proxy" );
1000        w = new_check_button( s, PREF_KEY_PROXY_SERVER_ENABLED, core );
1001        g_signal_connect( w, "toggled", G_CALLBACK(onProxyToggled), page );
1002        hig_workarea_add_wide_control( t, &row, w );
1003
1004        s = _( "Proxy server:" );
1005        w = new_entry( PREF_KEY_PROXY_SERVER, core );
1006        page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
1007        w = hig_workarea_add_row( t, &row, s, w, NULL );
1008        page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
1009
1010        w = new_spin_button( PREF_KEY_PROXY_PORT, core, 0, 65536, 1 );
1011        page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
1012        w = hig_workarea_add_row( t, &row, _( "Proxy port:" ), w, NULL );
1013        page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
1014
1015        s = _( "Proxy type:" );
1016        m = proxyTypeModelNew( );
1017        w = gtk_combo_box_new_with_model( m );
1018        r = gtk_cell_renderer_text_new( );
1019        gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( w ), r, TRUE );
1020        gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT( w ), r, "text", 0, NULL );
1021        gtk_combo_box_set_active( GTK_COMBO_BOX( w ), pref_int_get( PREF_KEY_PROXY_TYPE ) );
1022        g_signal_connect( w, "changed", G_CALLBACK(onProxyTypeChanged), page );
1023        g_object_unref( G_OBJECT( m ) );
1024        page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
1025        w = hig_workarea_add_row( t, &row, s, w, NULL );
1026        page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
1027
1028        s = _( "_Authentication is required" );
1029        w = new_check_button( s, PREF_KEY_PROXY_AUTH_ENABLED, core );
1030        g_signal_connect( w, "toggled", G_CALLBACK(onProxyToggled), page );
1031        hig_workarea_add_wide_control( t, &row, w );
1032        page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
1033
1034        s = _( "_Username:" );
1035        w = new_entry( PREF_KEY_PROXY_USERNAME, core );
1036        page->proxy_auth_widgets = g_slist_append( page->proxy_auth_widgets, w );
1037        w = hig_workarea_add_row( t, &row, s, w, NULL );
1038        page->proxy_auth_widgets = g_slist_append( page->proxy_auth_widgets, w );
1039
1040        s = _( "_Password:" );
1041        w = new_entry( PREF_KEY_PROXY_PASSWORD, core );
1042        gtk_entry_set_visibility( GTK_ENTRY( w ), FALSE );
1043        page->proxy_auth_widgets = g_slist_append( page->proxy_auth_widgets, w );
1044        w = hig_workarea_add_row( t, &row, s, w, NULL );
1045        page->proxy_auth_widgets = g_slist_append( page->proxy_auth_widgets, w );
1046
1047    hig_workarea_finish( t, &row );
1048    g_object_set_data_full( G_OBJECT( t ), "page", page, proxyPageFree );
1049
1050    refreshProxySensitivity( page );
1051    return t;
1052}
1053
1054static GtkWidget*
1055networkPage( GObject * core )
1056{
1057    int row = 0;
1058    const char * s;
1059    GtkWidget * t;
1060    GtkWidget * w, * w2;
1061
1062    t = hig_workarea_create( );
1063    hig_workarea_add_section_title (t, &row, _( "Router" ) );
1064
1065        s = _("Use UPnP or NAT-PMP port _forwarding from my router" );
1066        w = new_check_button( s, PREF_KEY_PORT_FORWARDING, core );
1067        hig_workarea_add_wide_control( t, &row, w );
1068
1069    hig_workarea_add_section_divider( t, &row );
1070    hig_workarea_add_section_title (t, &row, _("Bandwidth"));
1071
1072        s = _("Limit _download speed (KB/s):");
1073        w = new_check_button( s, PREF_KEY_DL_LIMIT_ENABLED, core );
1074        w2 = new_spin_button( PREF_KEY_DL_LIMIT, core, 0, INT_MAX, 5 );
1075        gtk_widget_set_sensitive( GTK_WIDGET(w2), pref_flag_get( PREF_KEY_DL_LIMIT_ENABLED ) );
1076        g_signal_connect( w, "toggled", G_CALLBACK(target_cb), w2 );
1077        hig_workarea_add_row_w( t, &row, w, w2, NULL );
1078
1079        s = _("Limit _upload speed (KB/s):");
1080        w = new_check_button( s, PREF_KEY_UL_LIMIT_ENABLED, core );
1081        w2 = new_spin_button( PREF_KEY_UL_LIMIT, core, 0, INT_MAX, 5 );
1082        gtk_widget_set_sensitive( GTK_WIDGET(w2), pref_flag_get( PREF_KEY_UL_LIMIT_ENABLED ) );
1083        g_signal_connect( w, "toggled", G_CALLBACK(target_cb), w2 );
1084        hig_workarea_add_row_w( t, &row, w, w2, NULL );
1085
1086    hig_workarea_finish( t, &row );
1087    return t;
1088}
1089
1090GtkWidget *
1091tr_prefs_dialog_new( GObject * core, GtkWindow * parent )
1092{
1093    GtkWidget * d;
1094    GtkWidget * n;
1095    gboolean * alive;
1096
1097    alive = g_new( gboolean, 1 );
1098    *alive = TRUE;
1099
1100    d = gtk_dialog_new_with_buttons( _( "Transmission Preferences" ), parent,
1101                                     GTK_DIALOG_DESTROY_WITH_PARENT,
1102                                     GTK_STOCK_HELP, GTK_RESPONSE_HELP,
1103                                     GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1104                                     NULL );
1105    gtk_window_set_role( GTK_WINDOW(d), "transmission-preferences-dialog" );
1106    gtk_dialog_set_has_separator( GTK_DIALOG( d ), FALSE );
1107    gtk_container_set_border_width( GTK_CONTAINER( d ), GUI_PAD );
1108    g_object_weak_ref( G_OBJECT( d ), dialogDestroyed, alive );
1109
1110    n = gtk_notebook_new( );
1111    gtk_container_set_border_width ( GTK_CONTAINER ( n ), GUI_PAD );
1112
1113    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1114                              torrentPage( core ),
1115                              gtk_label_new (_("Torrents")) );
1116    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1117                              peerPage( core, alive ),
1118                              gtk_label_new (_("Peers")) );
1119    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1120                              trackerPage( core ),
1121                              gtk_label_new (_("Trackers")) );
1122    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1123                              networkPage( core ),
1124                              gtk_label_new (_("Network")) );
1125    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1126                              webPage( core ),
1127                              gtk_label_new (_("Web")) );
1128
1129    g_signal_connect( d, "response", G_CALLBACK(response_cb), core );
1130    gtk_box_pack_start_defaults( GTK_BOX(GTK_DIALOG(d)->vbox), n );
1131    gtk_widget_show_all( GTK_DIALOG(d)->vbox );
1132    return d;
1133}
Note: See TracBrowser for help on using the repository browser.