source: branches/1.5x/gtk/tr-prefs.c @ 7953

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

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

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