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

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

(1.5x gtk) when running for the first time and deciding on a default watch directory, check G_USER_DIRECTORY_DOWNLOAD *before* G_USER
_DIRECTORY_DESKTOP. Suggested by Chris Coulson @ https://bugs.launchpad.net/ubuntu/+source/transmission/+bug/338046

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