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

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

(trunk gtk) #1773: First run - error in Properties

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