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

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

(1.5x) backport #1757, #1777 to 1.5x branch

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