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

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

(trunk gtk) rearrange the prefs dialog tabs a little

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