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

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

janitorial: tr_handle -> tr_session

  • Property svn:keywords set to Date Rev Author Id
File size: 47.4 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 6978 2008-10-28 19:49:33Z 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 "blocklist.h"
25#include "conf.h"
26#include "hig.h"
27#include "tr-core.h"
28#include "tr-prefs.h"
29#include "util.h"
30
31/**
32 * This is where we initialize the preferences file with the default values.
33 * If you add a new preferences key, you /must/ add a default value here.
34 */
35void
36tr_prefs_init_global( void )
37{
38    int          i;
39    char         pw[32];
40    const char * str;
41    const char * pool = "abcdefghijklmnopqrstuvwxyz"
42                        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
43                        "1234567890";
44    GRand *      rand;
45
46    cf_check_older_configs( );
47
48#if HAVE_GIO
49    str = NULL;
50    if( !str ) str = g_get_user_special_dir( G_USER_DIRECTORY_DESKTOP );
51    if( !str ) str = g_get_home_dir( );
52    pref_string_set_default ( PREF_KEY_DIR_WATCH, str );
53    pref_flag_set_default   ( PREF_KEY_DIR_WATCH_ENABLED, FALSE );
54#endif
55
56    pref_int_set_default    ( PREF_KEY_PEER_SOCKET_TOS,
57                              TR_DEFAULT_PEER_SOCKET_TOS );
58    pref_flag_set_default   ( PREF_KEY_INHIBIT_HIBERNATION, FALSE );
59    pref_flag_set_default   ( PREF_KEY_BLOCKLIST_ENABLED, TRUE );
60    pref_flag_set_default   ( PREF_KEY_BLOCKLIST_UPDATES_ENABLED, TRUE );
61
62    pref_string_set_default ( PREF_KEY_OPEN_DIALOG_FOLDER, g_get_home_dir( ) );
63
64    pref_int_set_default    ( PREF_KEY_MAX_PEERS_GLOBAL,
65                              TR_DEFAULT_GLOBAL_PEER_LIMIT );
66    pref_int_set_default    ( PREF_KEY_MAX_PEERS_PER_TORRENT, 50 );
67
68    pref_flag_set_default   ( PREF_KEY_TOOLBAR, TRUE );
69    pref_flag_set_default   ( PREF_KEY_FILTERBAR, TRUE );
70    pref_flag_set_default   ( PREF_KEY_STATUSBAR, TRUE );
71    pref_flag_set_default   ( PREF_KEY_SHOW_TRAY_ICON, FALSE );
72    pref_string_set_default ( PREF_KEY_STATUSBAR_STATS, "total-ratio" );
73
74    pref_flag_set_default   ( PREF_KEY_DL_LIMIT_ENABLED, FALSE );
75    pref_int_set_default    ( PREF_KEY_DL_LIMIT, 100 );
76    pref_flag_set_default   ( PREF_KEY_UL_LIMIT_ENABLED, FALSE );
77    pref_int_set_default    ( PREF_KEY_UL_LIMIT, 50 );
78    pref_flag_set_default   ( PREF_KEY_SCHED_LIMIT_ENABLED, FALSE );
79    pref_int_set_default    ( PREF_KEY_SCHED_BEGIN,    60 * 23 ); /* 11pm */
80    pref_int_set_default    ( PREF_KEY_SCHED_END,      60 * 7 );  /* 7am */
81    pref_int_set_default    ( PREF_KEY_SCHED_DL_LIMIT, 200 );   /* 2x the other
82                                                                  limit */
83    pref_int_set_default    ( PREF_KEY_SCHED_UL_LIMIT, 100 );   /* 2x the other
84                                                                  limit */
85
86    pref_flag_set_default   ( PREF_KEY_OPTIONS_PROMPT, TRUE );
87
88    pref_int_set_default    ( PREF_KEY_MAIN_WINDOW_HEIGHT, 500 );
89    pref_int_set_default    ( PREF_KEY_MAIN_WINDOW_WIDTH, 300 );
90    pref_int_set_default    ( PREF_KEY_MAIN_WINDOW_X, 50 );
91    pref_int_set_default    ( PREF_KEY_MAIN_WINDOW_Y, 50 );
92
93    pref_string_set_default ( PREF_KEY_PROXY_SERVER, "" );
94    pref_int_set_default    ( PREF_KEY_PROXY_PORT, TR_DEFAULT_PROXY_PORT );
95    pref_int_set_default    ( PREF_KEY_PROXY_TYPE, TR_DEFAULT_PROXY_TYPE );
96    pref_flag_set_default   ( PREF_KEY_PROXY_SERVER_ENABLED,
97                              TR_DEFAULT_PROXY_ENABLED );
98    pref_flag_set_default   ( PREF_KEY_PROXY_AUTH_ENABLED,
99                              TR_DEFAULT_PROXY_AUTH_ENABLED );
100    pref_string_set_default ( PREF_KEY_PROXY_USERNAME, "" );
101    pref_string_set_default ( PREF_KEY_PROXY_PASSWORD, "" );
102
103    str = NULL;
104#if GLIB_CHECK_VERSION( 2, 14, 0 )
105    if( !str ) str = g_get_user_special_dir( G_USER_DIRECTORY_DOWNLOAD );
106#endif
107    if( !str ) str = g_get_home_dir( );
108    pref_string_set_default ( PREF_KEY_DOWNLOAD_DIR, str );
109
110    pref_int_set_default    ( PREF_KEY_PORT, TR_DEFAULT_PORT );
111
112    pref_flag_set_default   ( PREF_KEY_PORT_FORWARDING, TRUE );
113    pref_flag_set_default   ( PREF_KEY_PEX, TR_DEFAULT_PEX_ENABLED );
114    pref_flag_set_default   ( PREF_KEY_ASKQUIT, TRUE );
115    pref_flag_set_default   ( PREF_KEY_ENCRYPTION, TR_DEFAULT_ENCRYPTION );
116    pref_flag_set_default   ( PREF_KEY_LAZY_BITFIELD,
117                              TR_DEFAULT_LAZY_BITFIELD_ENABLED );
118
119    pref_int_set_default    ( PREF_KEY_MSGLEVEL, TR_MSG_INF );
120
121    pref_string_set_default ( PREF_KEY_SORT_MODE, "sort-by-name" );
122    pref_flag_set_default   ( PREF_KEY_SORT_REVERSED, FALSE );
123    pref_flag_set_default   ( PREF_KEY_MINIMAL_VIEW, FALSE );
124
125    pref_flag_set_default   ( PREF_KEY_START, TRUE );
126    pref_flag_set_default   ( PREF_KEY_TRASH_ORIGINAL, FALSE );
127
128    pref_flag_set_default   ( PREF_KEY_RPC_ENABLED, TR_DEFAULT_RPC_ENABLED );
129    pref_int_set_default    ( PREF_KEY_RPC_PORT, TR_DEFAULT_RPC_PORT );
130    pref_string_set_default ( PREF_KEY_RPC_WHITELIST, TR_DEFAULT_RPC_WHITELIST );
131    pref_flag_set_default   ( PREF_KEY_RPC_WHITELIST_ENABLED,
132                              TR_DEFAULT_RPC_WHITELIST_ENABLED  );
133
134    rand = g_rand_new ( );
135    for( i = 0; i < 16; ++i )
136        pw[i] = pool[g_rand_int_range ( rand, 0, strlen( pool ) )];
137    g_rand_free ( rand );
138
139    pw[16] = '\0';
140    pref_string_set_default( PREF_KEY_RPC_USERNAME, "transmission" );
141    pref_string_set_default( PREF_KEY_RPC_PASSWORD, pw );
142    pref_flag_set_default  ( PREF_KEY_RPC_AUTH_ENABLED, FALSE );
143
144    pref_save( );
145}
146
147/**
148***
149**/
150
151#define PREF_KEY "pref-key"
152
153static void
154response_cb( GtkDialog *     dialog,
155             int             response,
156             gpointer unused UNUSED )
157{
158    if( response == GTK_RESPONSE_HELP )
159    {
160        char * base = gtr_get_help_url( );
161        char * url = g_strdup_printf( "%s/html/preferences.html", base );
162        gtr_open_file( url );
163        g_free( url );
164        g_free( base );
165    }
166
167    if( response == GTK_RESPONSE_CLOSE )
168        gtk_widget_destroy( GTK_WIDGET( dialog ) );
169}
170
171static void
172toggled_cb( GtkToggleButton * w,
173            gpointer          core )
174{
175    const char *   key = g_object_get_data( G_OBJECT( w ), PREF_KEY );
176    const gboolean flag = gtk_toggle_button_get_active( w );
177
178    tr_core_set_pref_bool( TR_CORE( core ), key, flag );
179}
180
181static GtkWidget*
182new_check_button( const char * mnemonic,
183                  const char * key,
184                  gpointer     core )
185{
186    GtkWidget * w = gtk_check_button_new_with_mnemonic( mnemonic );
187
188    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, g_strdup(
189                                key ), g_free );
190    gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w ),
191                                 pref_flag_get( key ) );
192    g_signal_connect( w, "toggled", G_CALLBACK( toggled_cb ), core );
193    return w;
194}
195
196#define IDLE_DATA "idle-data"
197
198struct spin_idle_data
199{
200    gpointer    core;
201    GTimer *    last_change;
202};
203
204static void
205spin_idle_data_free( gpointer gdata )
206{
207    struct spin_idle_data * data = gdata;
208
209    g_timer_destroy( data->last_change );
210    g_free( data );
211}
212
213static gboolean
214spun_cb_idle( gpointer spin )
215{
216    gboolean                keep_waiting = TRUE;
217    GObject *               o = G_OBJECT( spin );
218    struct spin_idle_data * data = g_object_get_data( o, IDLE_DATA );
219
220    /* has the user stopped making changes? */
221    if( g_timer_elapsed( data->last_change, NULL ) > 0.33f )
222    {
223        /* update the core */
224        const char * key = g_object_get_data( o, PREF_KEY );
225        const int    value = gtk_spin_button_get_value_as_int(
226             GTK_SPIN_BUTTON( spin ) );
227        tr_core_set_pref_int( TR_CORE( data->core ), key, value );
228
229        /* cleanup */
230        g_object_set_data( o, IDLE_DATA, NULL );
231        keep_waiting = FALSE;
232        g_object_unref( G_OBJECT( o ) );
233    }
234
235    return keep_waiting;
236}
237
238static void
239spun_cb( GtkSpinButton * w,
240         gpointer        core )
241{
242    /* user may be spinning through many values, so let's hold off
243       for a moment to keep from flooding the core with changes */
244    GObject *               o = G_OBJECT( w );
245    struct spin_idle_data * data = g_object_get_data( o, IDLE_DATA );
246
247    if( data == NULL )
248    {
249        data = g_new( struct spin_idle_data, 1 );
250        data->core = core;
251        data->last_change = g_timer_new( );
252        g_object_set_data_full( o, IDLE_DATA, data, spin_idle_data_free );
253        g_object_ref( G_OBJECT( o ) );
254        g_timeout_add( 100, spun_cb_idle, w );
255    }
256    g_timer_start( data->last_change );
257}
258
259static GtkWidget*
260new_spin_button( const char * key,
261                 gpointer     core,
262                 int          low,
263                 int          high,
264                 int          step )
265{
266    GtkWidget * w = gtk_spin_button_new_with_range( low, high, step );
267
268    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, g_strdup(
269                                key ), g_free );
270    gtk_spin_button_set_digits( GTK_SPIN_BUTTON( w ), 0 );
271    gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), pref_int_get( key ) );
272    g_signal_connect( w, "value-changed", G_CALLBACK( spun_cb ), core );
273    return w;
274}
275
276static void
277entry_changed_cb( GtkEntry * w,
278                  gpointer   core )
279{
280    const char * key = g_object_get_data( G_OBJECT( w ), PREF_KEY );
281    const char * value = gtk_entry_get_text( w );
282
283    tr_core_set_pref( TR_CORE( core ), key, value );
284}
285
286static GtkWidget*
287new_entry( const char * key,
288           gpointer     core )
289{
290    GtkWidget *  w = gtk_entry_new( );
291    const char * value = pref_string_get( key );
292
293    if( value )
294        gtk_entry_set_text( GTK_ENTRY( w ), value );
295    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, g_strdup(
296                                key ), g_free );
297    g_signal_connect( w, "changed", G_CALLBACK( entry_changed_cb ), core );
298    return w;
299}
300
301static void
302chosen_cb( GtkFileChooser * w,
303           gpointer         core )
304{
305    const char * key = g_object_get_data( G_OBJECT( w ), PREF_KEY );
306    char *       value = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER( w ) );
307
308    tr_core_set_pref( TR_CORE( core ), key, value );
309    g_free( value );
310}
311
312static GtkWidget*
313new_path_chooser_button( const char * key,
314                         gpointer     core )
315{
316    GtkWidget *  w = gtk_file_chooser_button_new(
317        NULL,
318        GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER );
319    const char * path = pref_string_get( key );
320
321    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, g_strdup(
322                                key ), g_free );
323    g_signal_connect( w, "selection-changed", G_CALLBACK( chosen_cb ), core );
324    gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER( w ), path );
325    return w;
326}
327
328static void
329target_cb( GtkWidget * tb,
330           gpointer    target )
331{
332    const gboolean b = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( tb ) );
333
334    gtk_widget_set_sensitive( GTK_WIDGET( target ), b );
335}
336
337/****
338*****  Torrent Tab
339****/
340
341static GtkWidget*
342torrentPage( GObject * core )
343{
344    int          row = 0;
345    const char * s;
346    GtkWidget *  t;
347    GtkWidget *  w;
348
349#ifdef HAVE_GIO
350    GtkWidget *  l;
351#endif
352
353    t = hig_workarea_create( );
354    hig_workarea_add_section_title( t, &row, _( "Adding Torrents" ) );
355
356#ifdef HAVE_GIO
357    s = _( "Automatically _add torrents from:" );
358    l = new_check_button( s, PREF_KEY_DIR_WATCH_ENABLED, core );
359    w = new_path_chooser_button( PREF_KEY_DIR_WATCH, core );
360    gtk_widget_set_sensitive( GTK_WIDGET( w ),
361                             pref_flag_get( PREF_KEY_DIR_WATCH_ENABLED ) );
362    g_signal_connect( l, "toggled", G_CALLBACK( target_cb ), w );
363    hig_workarea_add_row_w( t, &row, l, w, NULL );
364#endif
365
366    s = _( "Display _options dialog" );
367    w = new_check_button( s, PREF_KEY_OPTIONS_PROMPT, core );
368    hig_workarea_add_wide_control( t, &row, w );
369
370    s = _( "_Start when added" );
371    w = new_check_button( s, PREF_KEY_START, core );
372    hig_workarea_add_wide_control( t, &row, w );
373
374    s = _( "Mo_ve source files to Trash" );
375    w = new_check_button( s, PREF_KEY_TRASH_ORIGINAL, core );
376    hig_workarea_add_wide_control( t, &row, w );
377
378    w = new_path_chooser_button( PREF_KEY_DOWNLOAD_DIR, core );
379    hig_workarea_add_row( t, &row, _( "_Destination folder:" ), w, NULL );
380
381    hig_workarea_finish( t, &row );
382    return t;
383}
384
385/****
386*****  Desktop Tab
387****/
388
389static GtkWidget*
390desktopPage( GObject * core )
391{
392    int          row = 0;
393    const char * s;
394    GtkWidget *  t;
395    GtkWidget *  w;
396
397    t = hig_workarea_create( );
398    hig_workarea_add_section_title( t, &row, _( "Options" ) );
399
400    s = _( "_Inhibit desktop hibernation when torrents are active" );
401    w = new_check_button( s, PREF_KEY_INHIBIT_HIBERNATION, core );
402    hig_workarea_add_wide_control( t, &row, w );
403
404    s = _( "Show _icon in the desktop Notification Area" );
405    w = new_check_button( s, PREF_KEY_SHOW_TRAY_ICON, core );
406    hig_workarea_add_wide_control( t, &row, w );
407
408    hig_workarea_finish( t, &row );
409    return t;
410}
411
412/****
413*****  Peer Tab
414****/
415
416struct blocklist_data
417{
418    GtkWidget *  check;
419    GtkWidget *  dialog;
420    TrCore *     core;
421    gulong       id;
422    int          abortFlag;
423    char         secondary[256];
424};
425
426static void
427updateBlocklistText( GtkWidget * w,
428                     TrCore *    core )
429{
430    const int n = tr_blocklistGetRuleCount( tr_core_session( core ) );
431    char      buf[512];
432
433    g_snprintf( buf, sizeof( buf ),
434                ngettext( "Enable _blocklist (contains %'d rule)",
435                          "Enable _blocklist (contains %'d rules)", n ), n );
436    gtk_button_set_label( GTK_BUTTON( w ), buf );
437}
438
439static void
440onBlocklistDialogResponse( GtkDialog *  d,
441                           int response UNUSED,
442                           gpointer     gdata )
443{
444    struct blocklist_data * data = gdata;
445
446    g_signal_handler_disconnect( data->core, data->id );
447    gtk_widget_destroy( GTK_WIDGET( d ) );
448}
449
450static void
451onBlocklistStatus( TrCore * core UNUSED,
452                   gboolean      isDone,
453                   const char *  status,
454                   gpointer      gdata )
455{
456    struct blocklist_data * data = gdata;
457
458    gdk_threads_enter( );
459    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( data-> dialog ),
460                                              "%s", status );
461    gtk_dialog_set_response_sensitive( GTK_DIALOG( data->dialog ),
462                                       GTK_RESPONSE_CANCEL, !isDone );
463    gtk_dialog_set_response_sensitive( GTK_DIALOG( data->dialog ),
464                                       GTK_RESPONSE_CLOSE, isDone );
465    if( isDone )
466        updateBlocklistText( data->check, core );
467    gdk_threads_leave( );
468}
469
470static void
471onUpdateBlocklistCB( GtkButton * w,
472                     gpointer    gdata )
473{
474    GtkWidget *             d;
475    struct blocklist_data * data = gdata;
476
477    d =
478        gtk_message_dialog_new( GTK_WINDOW( gtk_widget_get_toplevel( 
479                                                                    GTK_WIDGET(
480                                                                        w ) ) ),
481                               GTK_DIALOG_DESTROY_WITH_PARENT,
482                               GTK_MESSAGE_INFO,
483                               GTK_BUTTONS_NONE,
484                               _( "Updating Blocklist" ) );
485
486    data->dialog = d;
487    data->id =
488        g_signal_connect( data->core, "blocklist-status", G_CALLBACK(
489                              onBlocklistStatus ), data );
490
491    gtk_dialog_add_buttons( GTK_DIALOG( d ),
492                            GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
493                            GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
494                            NULL );
495    gtk_dialog_set_response_sensitive( GTK_DIALOG(
496                                           d ), GTK_RESPONSE_CLOSE, FALSE );
497
498    g_signal_connect( d, "response", G_CALLBACK(
499                          onBlocklistDialogResponse ), data );
500    gtk_widget_show( d );
501
502    gtr_blocklist_update( data->core );
503}
504
505static void
506onEncryptionToggled( GtkToggleButton * w,
507                     gpointer          core )
508{
509    const int val = gtk_toggle_button_get_active( w )
510                    ? TR_ENCRYPTION_REQUIRED
511                    : TR_ENCRYPTION_PREFERRED;
512
513    tr_core_set_pref_int( TR_CORE( core ), PREF_KEY_ENCRYPTION, val );
514}
515
516static GtkWidget*
517peerPage( GObject * core )
518{
519    int                     row = 0;
520    const char *            s;
521    GtkWidget *             t;
522    GtkWidget *             w;
523    GtkWidget *             b;
524    GtkWidget *             h;
525    struct blocklist_data * data;
526
527    data = g_new0( struct blocklist_data, 1 );
528    data->core = TR_CORE( core );
529
530    t = hig_workarea_create( );
531    hig_workarea_add_section_title( t, &row, _( "Blocklist" ) );
532
533    w = new_check_button( "", PREF_KEY_BLOCKLIST_ENABLED, core );
534    updateBlocklistText( w, TR_CORE( core ) );
535    h = gtk_hbox_new( FALSE, GUI_PAD_BIG );
536    gtk_box_pack_start_defaults( GTK_BOX( h ), w );
537    b = gtr_button_new_from_stock( GTK_STOCK_REFRESH, _( "_Update" ) );
538    data->check = w;
539    g_object_set_data( G_OBJECT( b ), "session",
540                      tr_core_session( TR_CORE( core ) ) );
541    g_signal_connect( b, "clicked", G_CALLBACK( onUpdateBlocklistCB ), data );
542    gtk_box_pack_start( GTK_BOX( h ), b, FALSE, FALSE, 0 );
543    g_signal_connect( w, "toggled", G_CALLBACK( target_cb ), b );
544    target_cb( w, b );
545    hig_workarea_add_wide_control( t, &row, h );
546
547    s = _( "Enable _automatic updates" );
548    w = new_check_button( s, PREF_KEY_BLOCKLIST_UPDATES_ENABLED, core );
549    hig_workarea_add_wide_control( t, &row, w );
550    g_signal_connect( data->check, "toggled", G_CALLBACK( target_cb ), w );
551    target_cb( data->check, w );
552
553    hig_workarea_add_section_divider( t, &row );
554    hig_workarea_add_section_title( t, &row, _( "Limits" ) );
555
556    w = new_spin_button( PREF_KEY_MAX_PEERS_GLOBAL, core, 1, 3000, 5 );
557    hig_workarea_add_row( t, &row, _( "Maximum peers _overall:" ), w, NULL );
558    w = new_spin_button( PREF_KEY_MAX_PEERS_PER_TORRENT, core, 1, 300, 5 );
559    hig_workarea_add_row( t, &row, _(
560                              "Maximum peers per _torrent:" ), w, NULL );
561
562    hig_workarea_add_section_divider( t, &row );
563    hig_workarea_add_section_title ( t, &row, _( "Options" ) );
564
565    s = _( "_Ignore unencrypted peers" );
566    w = gtk_check_button_new_with_mnemonic( s );
567    gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w ),
568                                  pref_int_get(
569                                      PREF_KEY_ENCRYPTION ) ==
570                                  TR_ENCRYPTION_REQUIRED );
571    g_signal_connect( w, "toggled", G_CALLBACK( onEncryptionToggled ), core );
572    hig_workarea_add_wide_control( t, &row, w );
573
574    s = _( "Use peer e_xchange" );
575    w = new_check_button( s, PREF_KEY_PEX, core );
576    hig_workarea_add_wide_control( t, &row, w );
577
578    hig_workarea_finish( t, &row );
579    return t;
580}
581
582/****
583*****  Web Tab
584****/
585
586enum
587{
588    COL_ADDRESS,
589    N_COLS
590};
591
592static GtkTreeModel*
593whitelist_tree_model_new( const char * whitelist )
594{
595    int            i;
596    char **        rules;
597    GtkListStore * store = gtk_list_store_new( N_COLS,
598                                               G_TYPE_STRING,
599                                               G_TYPE_STRING );
600
601    rules = g_strsplit( whitelist, ",", 0 );
602
603    for( i = 0; rules && rules[i]; ++i )
604    {
605        GtkTreeIter iter;
606        const char * s = rules[i];
607        while( isspace( *s ) ) ++s;
608        gtk_list_store_append( store, &iter );
609        gtk_list_store_set( store, &iter, COL_ADDRESS, s, -1 );
610    }
611
612    g_strfreev( rules );
613    return GTK_TREE_MODEL( store );
614}
615
616struct remote_page
617{
618    TrCore *           core;
619    GtkTreeView *      view;
620    GtkListStore *     store;
621    GtkWidget *        remove_button;
622    GSList *           widgets;
623    GSList *           auth_widgets;
624    GSList *           whitelist_widgets;
625    GtkToggleButton *  rpc_tb;
626    GtkToggleButton *  auth_tb;
627    GtkToggleButton *  whitelist_tb;
628};
629
630static void
631refreshWhitelist( struct remote_page * page )
632{
633    GtkTreeIter    iter;
634    GtkTreeModel * model = GTK_TREE_MODEL( page->store );
635    GString *      gstr = g_string_new( NULL );
636
637    if( gtk_tree_model_get_iter_first( model, &iter ) ) do
638        {
639            char * address;
640            gtk_tree_model_get( model, &iter,
641                                COL_ADDRESS, &address,
642                                -1 );
643            g_string_append( gstr, address );
644            g_string_append( gstr, "," );
645            g_free( address );
646        }
647        while( gtk_tree_model_iter_next( model, &iter ) );
648
649    g_string_truncate( gstr, gstr->len - 1 ); /* remove the trailing comma */
650
651    tr_core_set_pref( page->core, PREF_KEY_RPC_WHITELIST, gstr->str );
652
653    g_string_free( gstr, TRUE );
654}
655
656static void
657onAddressEdited( GtkCellRendererText  * r UNUSED,
658                 gchar *                  path_string,
659                 gchar *                  address,
660                 gpointer                 gpage )
661{
662    GtkTreeIter          iter;
663    struct remote_page * page = gpage;
664    GtkTreeModel *       model = GTK_TREE_MODEL( page->store );
665    GtkTreePath *        path = gtk_tree_path_new_from_string( path_string );
666
667    if( gtk_tree_model_get_iter( model, &iter, path ) )
668        gtk_list_store_set( page->store, &iter, COL_ADDRESS, address, -1 );
669
670    gtk_tree_path_free( path );
671    refreshWhitelist( page );
672}
673
674static void
675onAddWhitelistClicked( GtkButton * b UNUSED,
676                 gpointer      gpage )
677{
678    GtkTreeIter          iter;
679    GtkTreePath *        path;
680    struct remote_page * page = gpage;
681
682    gtk_list_store_append( page->store, &iter );
683    gtk_list_store_set( page->store, &iter,
684                        COL_ADDRESS,  "0.0.0.0",
685                        -1 );
686
687    path = gtk_tree_model_get_path( GTK_TREE_MODEL( page->store ), &iter );
688    gtk_tree_view_set_cursor(
689        page->view, path,
690        gtk_tree_view_get_column( page->view, COL_ADDRESS ),
691        TRUE );
692    gtk_tree_path_free( path );
693}
694
695static void
696onRemoveWhitelistClicked( GtkButton * b UNUSED,
697                    gpointer      gpage )
698{
699    struct remote_page * page = gpage;
700    GtkTreeSelection *   sel = gtk_tree_view_get_selection( page->view );
701    GtkTreeIter          iter;
702
703    if( gtk_tree_selection_get_selected( sel, NULL, &iter ) )
704    {
705        gtk_list_store_remove( page->store, &iter );
706        refreshWhitelist( page );
707    }
708}
709
710static void
711refreshRPCSensitivity( struct remote_page * page )
712{
713    GSList *           l;
714    const int          rpc_active = gtk_toggle_button_get_active(
715        page->rpc_tb );
716    const int          auth_active = gtk_toggle_button_get_active(
717        page->auth_tb );
718    const int          whitelist_active = gtk_toggle_button_get_active(
719        page->whitelist_tb );
720    GtkTreeSelection * sel = gtk_tree_view_get_selection( page->view );
721    const int          have_addr =
722        gtk_tree_selection_get_selected( sel, NULL,
723                                         NULL );
724    const int          n_rules = gtk_tree_model_iter_n_children(
725        GTK_TREE_MODEL( page->store ), NULL );
726
727    for( l = page->widgets; l != NULL; l = l->next )
728        gtk_widget_set_sensitive( GTK_WIDGET( l->data ), rpc_active );
729
730    for( l = page->auth_widgets; l != NULL; l = l->next )
731        gtk_widget_set_sensitive( GTK_WIDGET(
732                                      l->data ), rpc_active && auth_active );
733
734    for( l = page->whitelist_widgets; l != NULL; l = l->next )
735        gtk_widget_set_sensitive( GTK_WIDGET( l->data ),
736                                  rpc_active && whitelist_active );
737
738    gtk_widget_set_sensitive( page->remove_button,
739                              rpc_active && have_addr && n_rules > 1 );
740}
741
742static void
743onRPCToggled( GtkToggleButton * tb UNUSED,
744              gpointer             page )
745{
746    refreshRPCSensitivity( page );
747}
748
749static void
750onWhitelistSelectionChanged( GtkTreeSelection * sel UNUSED,
751                       gpointer               page )
752{
753    refreshRPCSensitivity( page );
754}
755
756static void
757onLaunchClutchCB( GtkButton * w UNUSED,
758                  gpointer data UNUSED )
759{
760    int    port = pref_int_get( PREF_KEY_RPC_PORT );
761    char * url = g_strdup_printf( "http://localhost:%d/transmission/web",
762                                  port );
763
764    gtr_open_file( url );
765    g_free( url );
766}
767
768static GtkWidget*
769webPage( GObject * core )
770{
771    const char *         s;
772    int                  row = 0;
773    GtkWidget *          t;
774    GtkWidget *          w;
775    GtkWidget *          h;
776    struct remote_page * page = g_new0( struct remote_page, 1 );
777
778    page->core = TR_CORE( core );
779
780    t = hig_workarea_create( );
781    g_object_set_data_full( G_OBJECT( t ), "page", page, g_free );
782
783    hig_workarea_add_section_title( t, &row, _( "Web Interface" ) );
784
785    /* "enabled" checkbutton */
786    s = _( "_Enable web interface" );
787    w = new_check_button( s, PREF_KEY_RPC_ENABLED, core );
788    page->rpc_tb = GTK_TOGGLE_BUTTON( w );
789    g_signal_connect( w, "clicked", G_CALLBACK( onRPCToggled ), page );
790    h = gtk_hbox_new( FALSE, GUI_PAD_BIG );
791    gtk_box_pack_start_defaults( GTK_BOX( h ), w );
792    w = gtk_button_new_from_stock( GTK_STOCK_OPEN );
793    page->widgets = g_slist_append( page->widgets, w );
794    g_signal_connect( w, "clicked", G_CALLBACK( onLaunchClutchCB ), NULL );
795    gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 );
796    hig_workarea_add_wide_control( t, &row, h );
797
798    /* port */
799    w = new_spin_button( PREF_KEY_RPC_PORT, core, 0, 65535, 1 );
800    page->widgets = g_slist_append( page->widgets, w );
801    w = hig_workarea_add_row( t, &row, _( "Listening _port:" ), w, NULL );
802    page->widgets = g_slist_append( page->widgets, w );
803
804    /* require authentication */
805    s = _( "_Require username" );
806    w = new_check_button( s, PREF_KEY_RPC_AUTH_ENABLED, core );
807    hig_workarea_add_wide_control( t, &row, w );
808    page->auth_tb = GTK_TOGGLE_BUTTON( w );
809    page->widgets = g_slist_append( page->widgets, w );
810    g_signal_connect( w, "clicked", G_CALLBACK( onRPCToggled ), page );
811
812    /* username */
813    s = _( "_Username:" );
814    w = new_entry( PREF_KEY_RPC_USERNAME, core );
815    page->auth_widgets = g_slist_append( page->auth_widgets, w );
816    w = hig_workarea_add_row( t, &row, s, w, NULL );
817    page->auth_widgets = g_slist_append( page->auth_widgets, w );
818
819    /* password */
820    s = _( "Pass_word:" );
821    w = new_entry( PREF_KEY_RPC_PASSWORD, core );
822    gtk_entry_set_visibility( GTK_ENTRY( w ), FALSE );
823    page->auth_widgets = g_slist_append( page->auth_widgets, w );
824    w = hig_workarea_add_row( t, &row, s, w, NULL );
825    page->auth_widgets = g_slist_append( page->auth_widgets, w );
826
827    /* require authentication */
828    s = _( "Only allow the following IP _addresses to connect:" );
829    w = new_check_button( s, PREF_KEY_RPC_WHITELIST_ENABLED, core );
830    hig_workarea_add_wide_control( t, &row, w );
831    page->whitelist_tb = GTK_TOGGLE_BUTTON( w );
832    page->widgets = g_slist_append( page->widgets, w );
833    g_signal_connect( w, "clicked", G_CALLBACK( onRPCToggled ), page );
834
835    /* access control list */
836    {
837        const char *        val = pref_string_get( PREF_KEY_RPC_WHITELIST );
838        GtkTreeModel *      m = whitelist_tree_model_new( val );
839        GtkTreeViewColumn * c;
840        GtkCellRenderer *   r;
841        GtkTreeSelection *  sel;
842        GtkTreeView *       v;
843        GtkWidget *         w;
844        GtkWidget *         h;
845        GtkTooltips *       tips = gtk_tooltips_new( );
846
847        page->store = GTK_LIST_STORE( m );
848        w = gtk_tree_view_new_with_model( m );
849        g_signal_connect( w, "button-release-event",
850                          G_CALLBACK( on_tree_view_button_released ), NULL );
851
852        page->whitelist_widgets = g_slist_append( page->whitelist_widgets, w );
853        v = page->view = GTK_TREE_VIEW( w );
854        gtk_tooltips_set_tip( tips, w,
855            _( "IP addresses may use wildcards, such as 192.168.*.*" ), NULL );
856        sel = gtk_tree_view_get_selection( v );
857        g_signal_connect( sel, "changed",
858                          G_CALLBACK( onWhitelistSelectionChanged ), page );
859        g_object_unref( G_OBJECT( m ) );
860        gtk_tree_view_set_headers_visible( v, TRUE );
861        w = gtk_frame_new( NULL );
862        gtk_frame_set_shadow_type( GTK_FRAME( w ), GTK_SHADOW_IN );
863        gtk_container_add( GTK_CONTAINER( w ), GTK_WIDGET( v ) );
864
865        /* ip address column */
866        r = gtk_cell_renderer_text_new( );
867        g_signal_connect( r, "edited",
868                          G_CALLBACK( onAddressEdited ), page );
869        g_object_set( G_OBJECT( r ), "editable", TRUE, NULL );
870        c = gtk_tree_view_column_new_with_attributes( NULL, r,
871                                                      "text", COL_ADDRESS,
872                                                      NULL );
873        gtk_tree_view_column_set_expand( c, TRUE );
874        gtk_tree_view_append_column( v, c );
875        gtk_tree_view_set_headers_visible( v, FALSE );
876
877        s = _( "Addresses:" );
878        w = hig_workarea_add_row( t, &row, s, w, NULL );
879        gtk_misc_set_alignment( GTK_MISC( w ), 0.0f, 0.0f );
880        gtk_misc_set_padding( GTK_MISC( w ), 0, GUI_PAD );
881        page->whitelist_widgets = g_slist_append( page->whitelist_widgets, w );
882
883        h = gtk_hbox_new( TRUE, GUI_PAD );
884        w = gtk_button_new_from_stock( GTK_STOCK_REMOVE );
885        g_signal_connect( w, "clicked", G_CALLBACK(
886                              onRemoveWhitelistClicked ), page );
887        page->remove_button = w;
888        onWhitelistSelectionChanged( sel, page );
889        gtk_box_pack_start_defaults( GTK_BOX( h ), w );
890        w = gtk_button_new_from_stock( GTK_STOCK_ADD );
891        page->whitelist_widgets = g_slist_append( page->whitelist_widgets, w );
892        g_signal_connect( w, "clicked", G_CALLBACK( onAddWhitelistClicked ), page );
893        gtk_box_pack_start_defaults( GTK_BOX( h ), w );
894        w = gtk_hbox_new( FALSE, 0 );
895        gtk_box_pack_start_defaults( GTK_BOX( w ),
896                                    gtk_alignment_new( 0, 0, 0, 0 ) );
897        gtk_box_pack_start( GTK_BOX( w ), h, FALSE, FALSE, 0 );
898        hig_workarea_add_wide_control( t, &row, w );
899    }
900
901    refreshRPCSensitivity( page );
902    hig_workarea_finish( t, &row );
903    return t;
904}
905
906/****
907*****  Proxy Tab
908****/
909
910struct ProxyPage
911{
912    TrCore *  core;
913    GSList *  proxy_widgets;
914    GSList *  proxy_auth_widgets;
915};
916
917static void
918refreshProxySensitivity( struct ProxyPage * p )
919{
920    GSList *       l;
921    const gboolean proxy_enabled = pref_flag_get(
922        PREF_KEY_PROXY_SERVER_ENABLED );
923    const gboolean proxy_auth_enabled = pref_flag_get(
924        PREF_KEY_PROXY_AUTH_ENABLED );
925
926    for( l = p->proxy_widgets; l != NULL; l = l->next )
927        gtk_widget_set_sensitive( GTK_WIDGET( l->data ), proxy_enabled );
928
929    for( l = p->proxy_auth_widgets; l != NULL; l = l->next )
930        gtk_widget_set_sensitive( GTK_WIDGET( l->data ),
931                                  proxy_enabled && proxy_auth_enabled );
932}
933
934static void
935onProxyToggled( GtkToggleButton * tb UNUSED,
936                gpointer             user_data )
937{
938    refreshProxySensitivity( user_data );
939}
940
941static void
942proxyPageFree( gpointer gpage )
943{
944    struct ProxyPage * page = gpage;
945
946    g_slist_free( page->proxy_widgets );
947    g_slist_free( page->proxy_auth_widgets );
948    g_free( page );
949}
950
951static GtkTreeModel*
952proxyTypeModelNew( void )
953{
954    GtkTreeIter    iter;
955    GtkListStore * store = gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_INT );
956
957    gtk_list_store_append( store, &iter );
958    gtk_list_store_set( store, &iter, 0, "HTTP", 1, TR_PROXY_HTTP, -1 );
959    gtk_list_store_append( store, &iter );
960    gtk_list_store_set( store, &iter, 0, "SOCKS4", 1, TR_PROXY_SOCKS4, -1 );
961    gtk_list_store_append( store, &iter );
962    gtk_list_store_set( store, &iter, 0, "SOCKS5", 1, TR_PROXY_SOCKS5, -1 );
963    return GTK_TREE_MODEL( store );
964}
965
966static void
967onProxyTypeChanged( GtkComboBox * w,
968                    gpointer      gpage )
969{
970    GtkTreeIter iter;
971
972    if( gtk_combo_box_get_active_iter( w, &iter ) )
973    {
974        struct ProxyPage * page = gpage;
975        int                type = TR_PROXY_HTTP;
976        gtk_tree_model_get( gtk_combo_box_get_model(
977                                w ), &iter, 1, &type, -1 );
978        tr_core_set_pref_int( TR_CORE(
979                                  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 via a pro_xy" );
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, 65535, 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(
1021                                        w ), r, "text", 0, NULL );
1022    gtk_combo_box_set_active( GTK_COMBO_BOX( w ),
1023                             pref_int_get( PREF_KEY_PROXY_TYPE ) );
1024    g_signal_connect( w, "changed", G_CALLBACK( onProxyTypeChanged ), page );
1025    g_object_unref( G_OBJECT( m ) );
1026    page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
1027    w = hig_workarea_add_row( t, &row, s, w, NULL );
1028    page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
1029
1030    s = _( "_Authentication is required" );
1031    w = new_check_button( s, PREF_KEY_PROXY_AUTH_ENABLED, core );
1032    g_signal_connect( w, "toggled", G_CALLBACK( onProxyToggled ), page );
1033    hig_workarea_add_wide_control( t, &row, w );
1034    page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
1035
1036    s = _( "_Username:" );
1037    w = new_entry( PREF_KEY_PROXY_USERNAME, core );
1038    page->proxy_auth_widgets = g_slist_append( page->proxy_auth_widgets, w );
1039    w = hig_workarea_add_row( t, &row, s, w, NULL );
1040    page->proxy_auth_widgets = g_slist_append( page->proxy_auth_widgets, w );
1041
1042    s = _( "Pass_word:" );
1043    w = new_entry( PREF_KEY_PROXY_PASSWORD, core );
1044    gtk_entry_set_visibility( GTK_ENTRY( w ), FALSE );
1045    page->proxy_auth_widgets = g_slist_append( page->proxy_auth_widgets, w );
1046    w = hig_workarea_add_row( t, &row, s, w, NULL );
1047    page->proxy_auth_widgets = g_slist_append( page->proxy_auth_widgets, w );
1048
1049    hig_workarea_finish( t, &row );
1050    g_object_set_data_full( G_OBJECT( t ), "page", page, proxyPageFree );
1051
1052    refreshProxySensitivity( page );
1053    return t;
1054}
1055
1056/****
1057*****  Bandwidth Tab
1058****/
1059
1060struct BandwidthPage
1061{
1062    TrCore *  core;
1063    GSList *  sched_widgets;
1064};
1065
1066static void
1067refreshSchedSensitivity( struct BandwidthPage * p )
1068{
1069    GSList *       l;
1070    const gboolean sched_enabled = pref_flag_get(
1071        PREF_KEY_SCHED_LIMIT_ENABLED );
1072
1073    for( l = p->sched_widgets; l != NULL; l = l->next )
1074        gtk_widget_set_sensitive( GTK_WIDGET( l->data ), sched_enabled );
1075}
1076
1077static void
1078onSchedToggled( GtkToggleButton * tb UNUSED,
1079                gpointer             user_data )
1080{
1081    refreshSchedSensitivity( user_data );
1082}
1083
1084static void
1085onTimeComboChanged( GtkComboBox * w,
1086                    gpointer      core )
1087{
1088    GtkTreeIter iter;
1089
1090    if( gtk_combo_box_get_active_iter( w, &iter ) )
1091    {
1092        const char * key = g_object_get_data( G_OBJECT( w ), PREF_KEY );
1093        int          val = 0;
1094        gtk_tree_model_get( gtk_combo_box_get_model(
1095                                w ), &iter, 0, &val, -1 );
1096        tr_core_set_pref_int( TR_CORE( core ), key, val );
1097    }
1098}
1099
1100static GtkWidget*
1101new_time_combo( GObject *    core,
1102                const char * key )
1103{
1104    int               val;
1105    int               i;
1106    GtkWidget *       w;
1107    GtkCellRenderer * r;
1108    GtkListStore *    store;
1109
1110    /* build a store at 15 minute intervals */
1111    store = gtk_list_store_new( 2, G_TYPE_INT, G_TYPE_STRING );
1112    for( i = 0; i < 60 * 24; i += 15 )
1113    {
1114        char        buf[128];
1115        GtkTreeIter iter;
1116        struct tm   tm;
1117        tm.tm_hour = i / 60;
1118        tm.tm_min = i % 60;
1119        strftime( buf, sizeof( buf ), "%I:%M %p", &tm );
1120        gtk_list_store_append( store, &iter );
1121        gtk_list_store_set( store, &iter, 0, i, 1, buf, -1 );
1122    }
1123
1124    /* build the widget */
1125    w = gtk_combo_box_new_with_model( GTK_TREE_MODEL( store ) );
1126    gtk_combo_box_set_wrap_width( GTK_COMBO_BOX( w ), 4 );
1127    r = gtk_cell_renderer_text_new( );
1128    gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( w ), r, TRUE );
1129    gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT(
1130                                        w ), r, "text", 1, NULL );
1131    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, tr_strdup(
1132                                key ), g_free );
1133    val = pref_int_get( key );
1134    gtk_combo_box_set_active( GTK_COMBO_BOX( w ), val / ( 15 ) );
1135    g_signal_connect( w, "changed", G_CALLBACK( onTimeComboChanged ), core );
1136
1137    /* cleanup */
1138    g_object_unref( G_OBJECT( store ) );
1139    return w;
1140}
1141
1142static void
1143bandwidthPageFree( gpointer gpage )
1144{
1145    struct BandwidthPage * page = gpage;
1146
1147    g_slist_free( page->sched_widgets );
1148    g_free( page );
1149}
1150
1151static GtkWidget*
1152bandwidthPage( GObject * core )
1153{
1154    int                    row = 0;
1155    const char *           s;
1156    GtkWidget *            t;
1157    GtkWidget *            w, * w2, * h, * l;
1158    struct BandwidthPage * page = tr_new0( struct BandwidthPage, 1 );
1159
1160    page->core = TR_CORE( core );
1161
1162    t = hig_workarea_create( );
1163    hig_workarea_add_section_title( t, &row, _( "Limits" ) );
1164
1165    s = _( "Limit _download speed (KB/s):" );
1166    w = new_check_button( s, PREF_KEY_DL_LIMIT_ENABLED, core );
1167    w2 = new_spin_button( PREF_KEY_DL_LIMIT, core, 0, INT_MAX, 5 );
1168    gtk_widget_set_sensitive( GTK_WIDGET( w2 ),
1169                             pref_flag_get( PREF_KEY_DL_LIMIT_ENABLED ) );
1170    g_signal_connect( w, "toggled", G_CALLBACK( target_cb ), w2 );
1171    hig_workarea_add_row_w( t, &row, w, w2, NULL );
1172
1173    s = _( "Limit _upload speed (KB/s):" );
1174    w = new_check_button( s, PREF_KEY_UL_LIMIT_ENABLED, core );
1175    w2 = new_spin_button( PREF_KEY_UL_LIMIT, core, 0, INT_MAX, 5 );
1176    gtk_widget_set_sensitive( GTK_WIDGET( w2 ),
1177                             pref_flag_get( PREF_KEY_UL_LIMIT_ENABLED ) );
1178    g_signal_connect( w, "toggled", G_CALLBACK( target_cb ), w2 );
1179    hig_workarea_add_row_w( t, &row, w, w2, NULL );
1180
1181    hig_workarea_add_section_divider( t, &row );
1182    hig_workarea_add_section_title( t, &row, _( "Scheduled Limits" ) );
1183
1184    h = gtk_hbox_new( FALSE, 0 );
1185    w2 = new_time_combo( core, PREF_KEY_SCHED_BEGIN );
1186    page->sched_widgets = g_slist_append( page->sched_widgets, w2 );
1187    gtk_box_pack_start( GTK_BOX( h ), w2, FALSE, FALSE, 0 );
1188    w2 = gtk_label_new ( _( " and " ) );
1189    page->sched_widgets = g_slist_append( page->sched_widgets, w2 );
1190    gtk_box_pack_start( GTK_BOX( h ), w2, FALSE, FALSE, 0 );
1191    w2 = new_time_combo( core, PREF_KEY_SCHED_END );
1192    page->sched_widgets = g_slist_append( page->sched_widgets, w2 );
1193    gtk_box_pack_start( GTK_BOX( h ), w2, FALSE, FALSE, 0 );
1194
1195    s = _( "_Limit bandwidth between" );
1196    w = new_check_button( s, PREF_KEY_SCHED_LIMIT_ENABLED, core );
1197    g_signal_connect( w, "toggled", G_CALLBACK( onSchedToggled ), page );
1198    hig_workarea_add_row_w( t, &row, w, h, NULL );
1199
1200    w = new_spin_button( PREF_KEY_SCHED_DL_LIMIT, core, 0, INT_MAX, 5 );
1201    page->sched_widgets = g_slist_append( page->sched_widgets, w );
1202    l = hig_workarea_add_row( t, &row, _(
1203                                  "Limit d_ownload speed (KB/s):" ), w,
1204                              NULL );
1205    page->sched_widgets = g_slist_append( page->sched_widgets, l );
1206
1207    w = new_spin_button( PREF_KEY_SCHED_UL_LIMIT, core, 0, INT_MAX, 5 );
1208    page->sched_widgets = g_slist_append( page->sched_widgets, w );
1209    l = hig_workarea_add_row( t, &row, _(
1210                                  "Limit u_pload speed (KB/s):" ), w, NULL );
1211    page->sched_widgets = g_slist_append( page->sched_widgets, l );
1212
1213    hig_workarea_finish( t, &row );
1214    g_object_set_data_full( G_OBJECT( t ), "page", page, bandwidthPageFree );
1215
1216    refreshSchedSensitivity( page );
1217    return t;
1218}
1219
1220/****
1221*****  Network Tab
1222****/
1223
1224struct test_port_data
1225{
1226    GtkWidget *  label;
1227    gboolean *   alive;
1228};
1229
1230static void
1231testing_port_done( tr_session * session        UNUSED,
1232                   long          response_code UNUSED,
1233                   const void *                response,
1234                   size_t                      response_len,
1235                   void *                      gdata )
1236{
1237    struct test_port_data * data = gdata;
1238
1239    if( *data->alive )
1240    {
1241        const int isOpen = response_len && *(char*)response == '1';
1242        gdk_threads_enter( );
1243        gtk_label_set_markup( GTK_LABEL( data->label ), isOpen
1244                             ? _( "Port is <b>open</b>" )
1245                             : _( "Port is <b>closed</b>" ) );
1246        gdk_threads_leave( );
1247    }
1248}
1249
1250static gboolean
1251testing_port_begin( gpointer gdata )
1252{
1253    struct test_port_data * data = gdata;
1254
1255    if( *data->alive )
1256    {
1257        GtkSpinButton * spin = g_object_get_data( G_OBJECT(
1258                                                      data->label ),
1259                                                  "tr-port-spin" );
1260        tr_session *     session = g_object_get_data( G_OBJECT(
1261                                                        data->label ),
1262                                                    "session" );
1263        const int       port = gtk_spin_button_get_value_as_int( spin );
1264        char            url[256];
1265        g_snprintf( url, sizeof( url ),
1266                    "http://portcheck.transmissionbt.com/%d",
1267                    port );
1268        tr_webRun( session, url, NULL, testing_port_done, data );
1269    }
1270    return FALSE;
1271}
1272
1273struct network_page_data
1274{
1275    gboolean *   alive;
1276    GtkWidget *  label;
1277    guint        id;
1278    TrCore *     core;
1279};
1280
1281static void
1282onCorePrefsChanged( TrCore * core UNUSED,
1283                    const char *  key,
1284                    gpointer      gdata )
1285{
1286    if( !strcmp( key, PREF_KEY_PORT ) )
1287    {
1288        struct network_page_data * ndata = gdata;
1289        struct test_port_data *    data;
1290
1291        gtk_label_set_markup( GTK_LABEL( ndata->label ),
1292                             _( "<i>Testing port...</i>" ) );
1293
1294        /* wait three seconds to give the port forwarding time to kick in */
1295        data = g_new0( struct test_port_data, 1 );
1296        data->label = ndata->label;
1297        data->alive = ndata->alive;
1298        g_timeout_add( 3000, testing_port_begin, data );
1299    }
1300}
1301
1302static void
1303networkPageDestroyed( gpointer       gdata,
1304                      GObject * dead UNUSED )
1305{
1306    struct network_page_data * data = gdata;
1307
1308    *data->alive = FALSE;
1309    g_signal_handler_disconnect( data->core, data->id );
1310    g_free( data );
1311}
1312
1313static GtkWidget*
1314networkPage( GObject * core )
1315{
1316    int                        row = 0;
1317    const char *               s;
1318    GtkWidget *                t;
1319    GtkWidget *                w;
1320    GtkWidget *                w2;
1321    GtkWidget *                h;
1322    GtkWidget *                l;
1323    struct network_page_data * data;
1324
1325    /* register to stop listening to core prefs changes when the page is
1326      destroyed */
1327    data = g_new0( struct network_page_data, 1 );
1328    data->core = TR_CORE( core );
1329
1330    /* we leak this gboolean* s.t. we know it will still be alive when the port
1331       check is done, whether the dialog was destroyed or not.  kind of
1332       clumsy... */
1333    data->alive = g_new( gboolean, 1 );
1334    *data->alive = TRUE;
1335
1336    /* build the page */
1337    t = hig_workarea_create( );
1338    hig_workarea_add_section_title( t, &row, _( "Incoming Peers" ) );
1339
1340    h = gtk_hbox_new( FALSE, GUI_PAD_BIG );
1341    w2 = new_spin_button( PREF_KEY_PORT, core, 1, 65535, 1 );
1342    gtk_box_pack_start( GTK_BOX( h ), w2, FALSE, FALSE, 0 );
1343    data->label = l = gtk_label_new( NULL );
1344    gtk_misc_set_alignment( GTK_MISC( l ), 0.0f, 0.5f );
1345    gtk_box_pack_start( GTK_BOX( h ), l, FALSE, FALSE, 0 );
1346    hig_workarea_add_row( t, &row, _( "Listening _port:" ), h, w2 );
1347
1348    g_object_set_data( G_OBJECT( l ), "tr-port-spin", w2 );
1349    g_object_set_data( G_OBJECT( l ), "session",
1350                      tr_core_session( TR_CORE( core ) ) );
1351    data->id = g_signal_connect( TR_CORE(
1352                                     core ), "prefs-changed",
1353                                 G_CALLBACK( onCorePrefsChanged ), data );
1354    onCorePrefsChanged( NULL, PREF_KEY_PORT, data );
1355
1356    s = _( "Use UPnP or NAT-PMP port _forwarding from my router" );
1357    w = new_check_button( s, PREF_KEY_PORT_FORWARDING, core );
1358    hig_workarea_add_wide_control( t, &row, w );
1359
1360    hig_workarea_finish( t, &row );
1361    g_object_weak_ref( G_OBJECT( t ), networkPageDestroyed, data );
1362
1363    return t;
1364}
1365
1366/****
1367*****
1368****/
1369
1370GtkWidget *
1371tr_prefs_dialog_new( GObject *   core,
1372                     GtkWindow * parent )
1373{
1374    GtkWidget * d;
1375    GtkWidget * n;
1376
1377    d = gtk_dialog_new_with_buttons( _(
1378                                         "Transmission Preferences" ),
1379                                     parent,
1380                                     GTK_DIALOG_DESTROY_WITH_PARENT,
1381                                     GTK_STOCK_HELP, GTK_RESPONSE_HELP,
1382                                     GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1383                                     NULL );
1384    gtk_window_set_role( GTK_WINDOW( d ), "transmission-preferences-dialog" );
1385    gtk_dialog_set_has_separator( GTK_DIALOG( d ), FALSE );
1386    gtk_container_set_border_width( GTK_CONTAINER( d ), GUI_PAD );
1387
1388    n = gtk_notebook_new( );
1389    gtk_container_set_border_width ( GTK_CONTAINER ( n ), GUI_PAD );
1390
1391    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1392                              torrentPage( core ),
1393                              gtk_label_new ( _( "Torrents" ) ) );
1394    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1395                              peerPage( core ),
1396                              gtk_label_new ( _( "Peers" ) ) );
1397    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1398                              networkPage( core ),
1399                              gtk_label_new ( _( "Network" ) ) );
1400    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1401                              desktopPage( core ),
1402                              gtk_label_new ( _( "Desktop" ) ) );
1403    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1404                              bandwidthPage( core ),
1405                              gtk_label_new ( _( "Bandwidth" ) ) );
1406    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1407                              webPage( core ),
1408                              gtk_label_new ( _( "Web" ) ) );
1409    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1410                              trackerPage( core ),
1411                              gtk_label_new ( _( "Trackers" ) ) );
1412
1413    g_signal_connect( d, "response", G_CALLBACK( response_cb ), core );
1414    gtk_box_pack_start_defaults( GTK_BOX( GTK_DIALOG( d )->vbox ), n );
1415    gtk_widget_show_all( GTK_DIALOG( d )->vbox );
1416    return d;
1417}
1418
Note: See TracBrowser for help on using the repository browser.