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

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

(trunk gtk) #1873: Fallback path for the default download directory could be better

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