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

Last change on this file since 12654 was 12654, checked in by jordan, 10 years ago

remove the gtr_timeout_add_seconds() portability wrapper around gdk_threads_add_timeout_seconds(); it's unnecessary now that the minimum gtk version's been bumped.

  • Property svn:keywords set to Date Rev Author Id
File size: 46.0 KB
Line 
1/*
2 * This file Copyright (C) Mnemosyne LLC
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 12654 2011-08-08 17:06:46Z jordan $
11 */
12
13#include <ctype.h> /* isspace */
14#include <limits.h> /* USHRT_MAX */
15#include <string.h> /* strcmp() */
16#include <unistd.h>
17#include <glib/gi18n.h>
18#include <gtk/gtk.h>
19#include <libtransmission/transmission.h>
20#include <libtransmission/utils.h>
21#include <libtransmission/version.h>
22#include <libtransmission/web.h>
23#include "conf.h"
24#include "hig.h"
25#include "tr-core.h"
26#include "tr-prefs.h"
27#include "util.h"
28
29/**
30***
31**/
32
33#define PREF_KEY "pref-key"
34
35static void
36response_cb( GtkDialog *     dialog,
37             int             response,
38             gpointer unused UNUSED )
39{
40    if( response == GTK_RESPONSE_HELP )
41    {
42        char * uri = g_strconcat( gtr_get_help_uri(), "/html/preferences.html", NULL );
43        gtr_open_uri( uri );
44        g_free( uri );
45    }
46
47    if( response == GTK_RESPONSE_CLOSE )
48        gtk_widget_destroy( GTK_WIDGET( dialog ) );
49}
50
51static void
52toggled_cb( GtkToggleButton * w,
53            gpointer          core )
54{
55    const char *   key = g_object_get_data( G_OBJECT( w ), PREF_KEY );
56    const gboolean flag = gtk_toggle_button_get_active( w );
57
58    gtr_core_set_pref_bool( TR_CORE( core ), key, flag );
59}
60
61static GtkWidget*
62new_check_button( const char * mnemonic,
63                  const char * key,
64                  gpointer     core )
65{
66    GtkWidget * w = gtk_check_button_new_with_mnemonic( mnemonic );
67
68    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, g_strdup(
69                                key ), g_free );
70    gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w ),
71                                 gtr_pref_flag_get( key ) );
72    g_signal_connect( w, "toggled", G_CALLBACK( toggled_cb ), core );
73    return w;
74}
75
76#define IDLE_DATA "idle-data"
77
78struct spin_idle_data
79{
80    gpointer    core;
81    GTimer *    last_change;
82    gboolean    isDouble;
83};
84
85static void
86spin_idle_data_free( gpointer gdata )
87{
88    struct spin_idle_data * data = gdata;
89
90    g_timer_destroy( data->last_change );
91    g_free( data );
92}
93
94static gboolean
95spun_cb_idle( gpointer spin )
96{
97    gboolean keep_waiting = TRUE;
98    GObject * o = G_OBJECT( spin );
99    struct spin_idle_data * data = g_object_get_data( o, IDLE_DATA );
100
101    /* has the user stopped making changes? */
102    if( g_timer_elapsed( data->last_change, NULL ) > 0.33f )
103    {
104        /* update the core */
105        const char * key = g_object_get_data( o, PREF_KEY );
106
107        if (data->isDouble)
108        {
109            const double value = gtk_spin_button_get_value( GTK_SPIN_BUTTON( spin ) );
110            gtr_core_set_pref_double( TR_CORE( data->core ), key, value );
111        }
112        else
113        {
114            const int value = gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin ) );
115            gtr_core_set_pref_int( TR_CORE( data->core ), key, value );
116        }
117
118        /* cleanup */
119        g_object_set_data( o, IDLE_DATA, NULL );
120        keep_waiting = FALSE;
121        g_object_unref( G_OBJECT( o ) );
122    }
123
124    return keep_waiting;
125}
126
127static void
128spun_cb( GtkSpinButton * w, gpointer core, gboolean isDouble )
129{
130    /* user may be spinning through many values, so let's hold off
131       for a moment to keep from flooding the core with changes */
132    GObject * o = G_OBJECT( w );
133    struct spin_idle_data * data = g_object_get_data( o, IDLE_DATA );
134
135    if( data == NULL )
136    {
137        data = g_new( struct spin_idle_data, 1 );
138        data->core = core;
139        data->last_change = g_timer_new( );
140        data->isDouble = isDouble;
141        g_object_set_data_full( o, IDLE_DATA, data, spin_idle_data_free );
142        g_object_ref( G_OBJECT( o ) );
143        gdk_threads_add_timeout_seconds( 1, spun_cb_idle, w );
144    }
145    g_timer_start( data->last_change );
146}
147
148static void
149spun_cb_int( GtkSpinButton * w, gpointer core )
150{
151    spun_cb( w, core, FALSE );
152}
153
154static void
155spun_cb_double( GtkSpinButton * w, gpointer core )
156{
157    spun_cb( w, core, TRUE );
158}
159
160static GtkWidget*
161new_spin_button( const char * key,
162                 gpointer     core,
163                 int          low,
164                 int          high,
165                 int          step )
166{
167    GtkWidget * w = gtk_spin_button_new_with_range( low, high, step );
168    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, g_strdup( key ), g_free );
169    gtk_spin_button_set_digits( GTK_SPIN_BUTTON( w ), 0 );
170    gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), gtr_pref_int_get( key ) );
171    g_signal_connect( w, "value-changed", G_CALLBACK( spun_cb_int ), core );
172    return w;
173}
174
175static GtkWidget*
176new_spin_button_double( const char * key,
177                       gpointer      core,
178                       double        low,
179                       double        high,
180                       double        step )
181{
182    GtkWidget * w = gtk_spin_button_new_with_range( low, high, step );
183    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, g_strdup( key ), g_free );
184    gtk_spin_button_set_digits( GTK_SPIN_BUTTON( w ), 2 );
185    gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), gtr_pref_double_get( key ) );
186    g_signal_connect( w, "value-changed", G_CALLBACK( spun_cb_double ), core );
187    return w;
188}
189
190static void
191entry_changed_cb( GtkEntry * w, gpointer   core )
192{
193    const char * key = g_object_get_data( G_OBJECT( w ), PREF_KEY );
194    const char * value = gtk_entry_get_text( w );
195
196    gtr_core_set_pref( TR_CORE( core ), key, value );
197}
198
199static GtkWidget*
200new_entry( const char * key,
201           gpointer     core )
202{
203    GtkWidget *  w = gtk_entry_new( );
204    const char * value = gtr_pref_string_get( key );
205
206    if( value )
207        gtk_entry_set_text( GTK_ENTRY( w ), value );
208    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, g_strdup(
209                                key ), g_free );
210    g_signal_connect( w, "changed", G_CALLBACK( entry_changed_cb ), core );
211    return w;
212}
213
214static void
215chosen_cb( GtkFileChooser * w, gpointer core )
216{
217    const char * key = g_object_get_data( G_OBJECT( w ), PREF_KEY );
218    char * value = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER( w ) );
219    gtr_core_set_pref( TR_CORE( core ), key, value );
220    g_free( value );
221}
222
223static GtkWidget*
224new_path_chooser_button( const char * key, gpointer core )
225{
226    GtkWidget *  w = gtk_file_chooser_button_new( NULL, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER );
227    const char * path = gtr_pref_string_get( key );
228    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, g_strdup( key ), g_free );
229    g_signal_connect( w, "selection-changed", G_CALLBACK( chosen_cb ), core );
230    if( path != NULL )
231        gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER( w ), path );
232    return w;
233}
234
235static GtkWidget*
236new_file_chooser_button( const char * key, gpointer core )
237{
238    GtkWidget *  w = gtk_file_chooser_button_new( NULL, GTK_FILE_CHOOSER_ACTION_OPEN );
239    const char * path = gtr_pref_string_get( key );
240    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, g_strdup( key ), g_free );
241    if( path != NULL )
242        gtk_file_chooser_set_filename( GTK_FILE_CHOOSER( w ), path );
243    g_signal_connect( w, "selection-changed", G_CALLBACK( chosen_cb ), core );
244    return w;
245}
246
247static void
248target_cb( GtkWidget * tb, gpointer target )
249{
250    const gboolean b = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( tb ) );
251
252    gtk_widget_set_sensitive( GTK_WIDGET( target ), b );
253}
254
255/****
256*****  Download Tab
257****/
258
259static GtkWidget*
260downloadPage( GObject * core )
261{
262    GtkWidget * t;
263    GtkWidget * w;
264    GtkWidget * l;
265    const char * s;
266    int row = 0;
267
268    t = hig_workarea_create( );
269
270    hig_workarea_add_section_title( t, &row, _( "Location" ) );
271
272    w = new_path_chooser_button( TR_PREFS_KEY_DOWNLOAD_DIR, core );
273    hig_workarea_add_row( t, &row, _( "Save to _Location:" ), w, NULL );
274
275    hig_workarea_add_section_divider( t, &row );
276    hig_workarea_add_section_title( t, &row, _( "Queue" ) );
277
278    s = _( "Maximum active _downloads:" );
279    w = new_spin_button( TR_PREFS_KEY_DOWNLOAD_QUEUE_SIZE, core, 0, INT_MAX, 1 );
280    hig_workarea_add_row( t, &row, s, w, NULL );
281
282    s = _( "Downloads sharing data in the last N minutes are _active:" );
283    w = new_spin_button( TR_PREFS_KEY_QUEUE_STALLED_MINUTES, core, 1, INT_MAX, 15 );
284    hig_workarea_add_row( t, &row, s, w, NULL );
285
286    hig_workarea_add_section_divider( t, &row );
287    hig_workarea_add_section_title( t, &row, _( "Incomplete" ) );
288
289    s = _( "Append \"._part\" to incomplete files' names" );
290    w = new_check_button( s, TR_PREFS_KEY_RENAME_PARTIAL_FILES, core );
291    hig_workarea_add_wide_control( t, &row, w );
292
293    s = _( "Keep _incomplete torrents in:" );
294    l = new_check_button( s, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED, core );
295    w = new_path_chooser_button( TR_PREFS_KEY_INCOMPLETE_DIR, core );
296    gtk_widget_set_sensitive( GTK_WIDGET( w ), gtr_pref_flag_get( TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED ) );
297    g_signal_connect( l, "toggled", G_CALLBACK( target_cb ), w );
298    hig_workarea_add_row_w( t, &row, l, w, NULL );
299
300    s = _( "Call _script when torrent is completed:" );
301    l = new_check_button( s, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED, core );
302    w = new_file_chooser_button( TR_PREFS_KEY_SCRIPT_TORRENT_DONE_FILENAME, core );
303    gtk_widget_set_sensitive( GTK_WIDGET( w ), gtr_pref_flag_get( TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED ) );
304    g_signal_connect( l, "toggled", G_CALLBACK( target_cb ), w );
305    hig_workarea_add_row_w( t, &row, l, w, NULL );
306
307    hig_workarea_finish( t, &row );
308    return t;
309}
310
311/****
312*****  Torrent Tab
313****/
314
315static GtkWidget*
316torrentPage( GObject * core )
317{
318    int          row = 0;
319    const char * s;
320    GtkWidget *  t;
321    GtkWidget *  w;
322    GtkWidget *  w2;
323    GtkWidget *  l;
324
325    t = hig_workarea_create( );
326    hig_workarea_add_section_title( t, &row, _( "Adding" ) );
327
328    s = _( "_Start when added" );
329    w = new_check_button( s, TR_PREFS_KEY_START, core );
330    hig_workarea_add_wide_control( t, &row, w );
331
332    s = _( "Show _options dialog" );
333    w = new_check_button( s, PREF_KEY_OPTIONS_PROMPT, core );
334    hig_workarea_add_wide_control( t, &row, w );
335
336    s = _( "Mo_ve .torrent file to the trash" );
337    w = new_check_button( s, TR_PREFS_KEY_TRASH_ORIGINAL, core );
338    hig_workarea_add_wide_control( t, &row, w );
339
340    s = _( "Automatically _add torrents from:" );
341    l = new_check_button( s, PREF_KEY_DIR_WATCH_ENABLED, core );
342    w = new_path_chooser_button( PREF_KEY_DIR_WATCH, core );
343    gtk_widget_set_sensitive( GTK_WIDGET( w ), gtr_pref_flag_get( PREF_KEY_DIR_WATCH_ENABLED ) );
344    g_signal_connect( l, "toggled", G_CALLBACK( target_cb ), w );
345    hig_workarea_add_row_w( t, &row, l, w, NULL );
346
347    hig_workarea_add_section_divider( t, &row );
348    hig_workarea_add_section_title( t, &row, _( "Seeding" ) );
349
350    s = _( "Stop seeding at _ratio:" );
351    w = new_check_button( s, TR_PREFS_KEY_RATIO_ENABLED, core );
352    w2 = new_spin_button_double( TR_PREFS_KEY_RATIO, core, 0, 1000, .05 );
353    gtk_widget_set_sensitive( GTK_WIDGET( w2 ), gtr_pref_flag_get( TR_PREFS_KEY_RATIO_ENABLED ) );
354    g_signal_connect( w, "toggled", G_CALLBACK( target_cb ), w2 );
355    hig_workarea_add_row_w( t, &row, w, w2, NULL );
356
357    s = _( "Stop seeding if idle for _N minutes:" );
358    w = new_check_button( s, TR_PREFS_KEY_IDLE_LIMIT_ENABLED, core );
359    w2 = new_spin_button( TR_PREFS_KEY_IDLE_LIMIT, core, 1, 9999, 5 );
360    gtk_widget_set_sensitive( GTK_WIDGET( w2 ), gtr_pref_flag_get( TR_PREFS_KEY_IDLE_LIMIT_ENABLED ) );
361    g_signal_connect( w, "toggled", G_CALLBACK( target_cb ), w2 );
362    hig_workarea_add_row_w( t, &row, w, w2, NULL );
363
364    hig_workarea_finish( t, &row );
365    return t;
366}
367
368/****
369*****  Desktop Tab
370****/
371
372static GtkWidget*
373desktopPage( GObject * core )
374{
375    int row = 0;
376    GtkWidget * t;
377    GtkWidget * w;
378    const char * s;
379
380    t = hig_workarea_create( );
381    hig_workarea_add_section_title( t, &row, _( "Desktop" ) );
382
383    s = _( "_Inhibit hibernation when torrents are active" );
384    w = new_check_button( s, PREF_KEY_INHIBIT_HIBERNATION, core );
385    hig_workarea_add_wide_control( t, &row, w );
386
387    s = _( "Show Transmission icon in the _notification area" );
388    w = new_check_button( s, PREF_KEY_SHOW_TRAY_ICON, core );
389    hig_workarea_add_wide_control( t, &row, w );
390
391    hig_workarea_add_section_divider( t, &row );
392    hig_workarea_add_section_title ( t, &row, _( "Notification" ) );
393
394    s = _( "Show a notification when torrents are a_dded" );
395    w = new_check_button( s, PREF_KEY_TORRENT_ADDED_NOTIFICATION_ENABLED, core );
396    hig_workarea_add_wide_control( t, &row, w );
397
398    s = _( "Show a notification when torrents _finish" );
399    w = new_check_button( s, PREF_KEY_TORRENT_COMPLETE_NOTIFICATION_ENABLED, core );
400    hig_workarea_add_wide_control( t, &row, w );
401
402    s = _( "Play a _sound when torrents finish" );
403    w = new_check_button( s, PREF_KEY_TORRENT_COMPLETE_SOUND_ENABLED, core );
404    hig_workarea_add_wide_control( t, &row, w );
405
406    hig_workarea_finish( t, &row );
407    return t;
408}
409
410/****
411*****  Peer Tab
412****/
413
414struct blocklist_data
415{
416    gulong      updateBlocklistTag;
417    GtkWidget * updateBlocklistButton;
418    GtkWidget * updateBlocklistDialog;
419    GtkWidget * label;
420    GtkWidget * check;
421    TrCore    * core;
422};
423
424static void
425updateBlocklistText( GtkWidget * w, TrCore * core )
426{
427    char buf1[512];
428    char buf2[512];
429    const int n = tr_blocklistGetRuleCount( gtr_core_session( core ) );
430    g_snprintf( buf1, sizeof( buf1 ),
431                ngettext( "Blocklist contains %'d rule",
432                          "Blocklist contains %'d rules", n ), n );
433    g_snprintf( buf2, sizeof( buf2 ), "<i>%s</i>", buf1 );
434    gtk_label_set_markup( GTK_LABEL( w ), buf2 );
435}
436
437/* prefs dialog is being destroyed, so stop listening to blocklist updates */
438static void
439privacyPageDestroyed( gpointer gdata, GObject * dead UNUSED )
440{
441    struct blocklist_data * data = gdata;
442    if( data->updateBlocklistTag > 0 )
443        g_signal_handler_disconnect( data->core, data->updateBlocklistTag );
444    g_free( data );
445}
446
447/* user hit "close" in the blocklist-update dialog */
448static void
449onBlocklistUpdateResponse( GtkDialog * dialog, gint response UNUSED, gpointer gdata )
450{
451    struct blocklist_data * data = gdata;
452    gtk_widget_destroy( GTK_WIDGET( dialog ) );
453    gtk_widget_set_sensitive( data->updateBlocklistButton, TRUE );
454    data->updateBlocklistDialog = NULL;
455    g_signal_handler_disconnect( data->core, data->updateBlocklistTag );
456}
457
458/* core says the blocklist was updated */
459static void
460onBlocklistUpdated( TrCore * core, int n, gpointer gdata )
461{
462    const bool success = n >= 0;
463    const int count = n >=0 ? n : tr_blocklistGetRuleCount( gtr_core_session( core ) );
464    const char * s = ngettext( "Blocklist has %'d rule.", "Blocklist has %'d rules.", count );
465    struct blocklist_data * data = gdata;
466    GtkMessageDialog * d = GTK_MESSAGE_DIALOG( data->updateBlocklistDialog );
467    gtk_widget_set_sensitive( data->updateBlocklistButton, TRUE );
468    gtk_message_dialog_set_markup( d, success ? _( "<b>Update succeeded!</b>" ) : _( "<b>Unable to update.</b>" ) );
469    gtk_message_dialog_format_secondary_text( d, s, count );
470    updateBlocklistText( data->label, core );
471}
472
473/* user pushed a button to update the blocklist */
474static void
475onBlocklistUpdate( GtkButton * w, gpointer gdata )
476{
477    GtkWidget * d;
478    struct blocklist_data * data = gdata;
479    d = gtk_message_dialog_new( GTK_WINDOW( gtk_widget_get_toplevel( GTK_WIDGET( w ) ) ),
480                                GTK_DIALOG_DESTROY_WITH_PARENT,
481                                GTK_MESSAGE_INFO,
482                                GTK_BUTTONS_CLOSE,
483                                "%s", _( "Update Blocklist" ) );
484    gtk_widget_set_sensitive( data->updateBlocklistButton, FALSE );
485    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( d ), "%s", _( "Getting new blocklist..." ) );
486    data->updateBlocklistDialog = d;
487    g_signal_connect( d, "response", G_CALLBACK(onBlocklistUpdateResponse), data );
488    gtk_widget_show( d );
489    gtr_core_blocklist_update( data->core );
490    data->updateBlocklistTag = g_signal_connect( data->core, "blocklist-updated", G_CALLBACK( onBlocklistUpdated ), data );
491}
492
493static void
494on_blocklist_url_changed( GtkEditable * e, gpointer gbutton )
495{
496    gchar * url = gtk_editable_get_chars( e, 0, -1 );
497    const gboolean err = tr_urlParse( url, -1, NULL, NULL, NULL, NULL );
498    gtk_widget_set_sensitive( GTK_WIDGET( gbutton ), !err );
499    g_free( url );
500}
501
502static void
503onIntComboChanged( GtkComboBox * combo_box, gpointer core )
504{
505    const int val = gtr_combo_box_get_active_enum( combo_box );
506    const char * key = g_object_get_data( G_OBJECT( combo_box ), PREF_KEY );
507    gtr_core_set_pref_int( TR_CORE( core ), key, val );
508}
509
510static GtkWidget*
511new_encryption_combo( GObject * core, const char * key )
512{
513    GtkWidget * w = gtr_combo_box_new_enum( _( "Allow encryption" ),   TR_CLEAR_PREFERRED,
514                                            _( "Prefer encryption" ),  TR_ENCRYPTION_PREFERRED,
515                                            _( "Require encryption" ), TR_ENCRYPTION_REQUIRED,
516                                            NULL );
517    gtr_combo_box_set_active_enum( GTK_COMBO_BOX( w ), gtr_pref_int_get( key ) );
518    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, tr_strdup( key ), g_free );
519    g_signal_connect( w, "changed", G_CALLBACK( onIntComboChanged ), core );
520    return w;
521}
522
523static GtkWidget*
524privacyPage( GObject * core )
525{
526    int row = 0;
527    const char * s;
528    GtkWidget * t;
529    GtkWidget * w;
530    GtkWidget * b;
531    GtkWidget * h;
532    GtkWidget * e;
533    struct blocklist_data * data;
534
535    data = g_new0( struct blocklist_data, 1 );
536    data->core = TR_CORE( core );
537
538    t = hig_workarea_create( );
539    hig_workarea_add_section_title( t, &row, _( "Blocklist" ) );
540
541    b = new_check_button( _( "Enable _blocklist:" ), TR_PREFS_KEY_BLOCKLIST_ENABLED, core );
542    e = new_entry( TR_PREFS_KEY_BLOCKLIST_URL, core );
543    gtk_widget_set_size_request( e, 300, -1 );
544    hig_workarea_add_row_w( t, &row, b, e, NULL );
545    data->check = b;
546    g_signal_connect( b, "toggled", G_CALLBACK( target_cb ), e );
547    target_cb( b, e );
548
549    w = gtk_label_new( "" );
550    gtk_misc_set_alignment( GTK_MISC( w ), 0.0f, 0.5f );
551    updateBlocklistText( w, TR_CORE( core ) );
552    data->label = w;
553    h = gtk_hbox_new( FALSE, GUI_PAD_BIG );
554    gtk_box_pack_start( GTK_BOX( h ), w, TRUE, TRUE, 0 );
555    b = data->updateBlocklistButton = gtk_button_new_with_mnemonic( _( "_Update" ) );
556    g_object_set_data( G_OBJECT( b ), "session", gtr_core_session( TR_CORE( core ) ) );
557    g_signal_connect( b, "clicked", G_CALLBACK( onBlocklistUpdate ), data );
558    g_signal_connect( data->check, "toggled", G_CALLBACK( target_cb ), b ); target_cb( data->check, b );
559    gtk_box_pack_start( GTK_BOX( h ), b, FALSE, FALSE, 0 );
560    g_signal_connect( data->check, "toggled", G_CALLBACK( target_cb ), w ); target_cb( data->check, w );
561    hig_workarea_add_wide_control( t, &row, h );
562    g_signal_connect( e, "changed", G_CALLBACK( on_blocklist_url_changed ), data->updateBlocklistButton );
563    on_blocklist_url_changed( GTK_EDITABLE( e ), data->updateBlocklistButton );
564
565    s = _( "Enable _automatic updates" );
566    w = new_check_button( s, PREF_KEY_BLOCKLIST_UPDATES_ENABLED, core );
567    hig_workarea_add_wide_control( t, &row, w );
568    g_signal_connect( data->check, "toggled", G_CALLBACK( target_cb ), w ); target_cb( data->check, w );
569
570    hig_workarea_add_section_divider( t, &row );
571    hig_workarea_add_section_title ( t, &row, _( "Privacy" ) );
572
573    s = _( "_Encryption mode:" );
574    w = new_encryption_combo( core, "encryption" );
575    hig_workarea_add_row( t, &row, s, w, NULL );
576
577    s = _( "Use PE_X to find more peers" );
578    w = new_check_button( s, TR_PREFS_KEY_PEX_ENABLED, core );
579    s = _( "PEX is a tool for exchanging peer lists with the peers you're connected to." );
580    gtk_widget_set_tooltip_text( w, s );
581    hig_workarea_add_wide_control( t, &row, w );
582
583    s = _( "Use _DHT to find more peers" );
584    w = new_check_button( s, TR_PREFS_KEY_DHT_ENABLED, core );
585    s = _( "DHT is a tool for finding peers without a tracker." );
586    gtk_widget_set_tooltip_text( w, s );
587    hig_workarea_add_wide_control( t, &row, w );
588
589    s = _( "Use _Local Peer Discovery to find more peers" );
590    w = new_check_button( s, TR_PREFS_KEY_LPD_ENABLED, core );
591    s = _( "LPD is a tool for finding peers on your local network." );
592    gtk_widget_set_tooltip_text( w, s );
593    hig_workarea_add_wide_control( t, &row, w );
594
595    hig_workarea_finish( t, &row );
596    g_object_weak_ref( G_OBJECT( t ), privacyPageDestroyed, data );
597    return t;
598}
599
600/****
601*****  Web Tab
602****/
603
604enum
605{
606    COL_ADDRESS,
607    N_COLS
608};
609
610static GtkTreeModel*
611whitelist_tree_model_new( const char * whitelist )
612{
613    int            i;
614    char **        rules;
615    GtkListStore * store = gtk_list_store_new( N_COLS,
616                                               G_TYPE_STRING,
617                                               G_TYPE_STRING );
618
619    rules = g_strsplit( whitelist, ",", 0 );
620
621    for( i = 0; rules && rules[i]; ++i )
622    {
623        GtkTreeIter iter;
624        const char * s = rules[i];
625        while( isspace( *s ) ) ++s;
626        gtk_list_store_append( store, &iter );
627        gtk_list_store_set( store, &iter, COL_ADDRESS, s, -1 );
628    }
629
630    g_strfreev( rules );
631    return GTK_TREE_MODEL( store );
632}
633
634struct remote_page
635{
636    TrCore *           core;
637    GtkTreeView *      view;
638    GtkListStore *     store;
639    GtkWidget *        remove_button;
640    GSList *           widgets;
641    GSList *           auth_widgets;
642    GSList *           whitelist_widgets;
643    GtkToggleButton *  rpc_tb;
644    GtkToggleButton *  auth_tb;
645    GtkToggleButton *  whitelist_tb;
646};
647
648static void
649refreshWhitelist( struct remote_page * page )
650{
651    GtkTreeIter iter;
652    GString * gstr = g_string_new( NULL );
653    GtkTreeModel * model = GTK_TREE_MODEL( page->store );
654
655    if( gtk_tree_model_iter_nth_child( model, &iter, NULL, 0 ) ) do
656    {
657        char * address;
658        gtk_tree_model_get( model, &iter, COL_ADDRESS, &address, -1 );
659        g_string_append( gstr, address );
660        g_string_append( gstr, "," );
661        g_free( address );
662    }
663    while( gtk_tree_model_iter_next( model, &iter ) );
664
665    g_string_truncate( gstr, gstr->len - 1 ); /* remove the trailing comma */
666
667    gtr_core_set_pref( page->core, TR_PREFS_KEY_RPC_WHITELIST, gstr->str );
668
669    g_string_free( gstr, TRUE );
670}
671
672static void
673onAddressEdited( GtkCellRendererText  * r UNUSED,
674                 gchar *                  path_string,
675                 gchar *                  address,
676                 gpointer                 gpage )
677{
678    GtkTreeIter          iter;
679    struct remote_page * page = gpage;
680    GtkTreeModel *       model = GTK_TREE_MODEL( page->store );
681    GtkTreePath *        path = gtk_tree_path_new_from_string( path_string );
682
683    if( gtk_tree_model_get_iter( model, &iter, path ) )
684        gtk_list_store_set( page->store, &iter, COL_ADDRESS, address, -1 );
685
686    gtk_tree_path_free( path );
687    refreshWhitelist( page );
688}
689
690static void
691onAddWhitelistClicked( GtkButton * b UNUSED,
692                 gpointer      gpage )
693{
694    GtkTreeIter          iter;
695    GtkTreePath *        path;
696    struct remote_page * page = gpage;
697
698    gtk_list_store_append( page->store, &iter );
699    gtk_list_store_set( page->store, &iter,
700                        COL_ADDRESS,  "0.0.0.0",
701                        -1 );
702
703    path = gtk_tree_model_get_path( GTK_TREE_MODEL( page->store ), &iter );
704    gtk_tree_view_set_cursor(
705        page->view, path,
706        gtk_tree_view_get_column( page->view, COL_ADDRESS ),
707        TRUE );
708    gtk_tree_path_free( path );
709}
710
711static void
712onRemoveWhitelistClicked( GtkButton * b UNUSED,
713                    gpointer      gpage )
714{
715    struct remote_page * page = gpage;
716    GtkTreeSelection *   sel = gtk_tree_view_get_selection( page->view );
717    GtkTreeIter          iter;
718
719    if( gtk_tree_selection_get_selected( sel, NULL, &iter ) )
720    {
721        gtk_list_store_remove( page->store, &iter );
722        refreshWhitelist( page );
723    }
724}
725
726static void
727refreshRPCSensitivity( struct remote_page * page )
728{
729    GSList *           l;
730    const int          rpc_active = gtk_toggle_button_get_active(
731        page->rpc_tb );
732    const int          auth_active = gtk_toggle_button_get_active(
733        page->auth_tb );
734    const int          whitelist_active = gtk_toggle_button_get_active(
735        page->whitelist_tb );
736    GtkTreeSelection * sel = gtk_tree_view_get_selection( page->view );
737    const int          have_addr =
738        gtk_tree_selection_get_selected( sel, NULL,
739                                         NULL );
740    const int          n_rules = gtk_tree_model_iter_n_children(
741        GTK_TREE_MODEL( page->store ), NULL );
742
743    for( l = page->widgets; l != NULL; l = l->next )
744        gtk_widget_set_sensitive( GTK_WIDGET( l->data ), rpc_active );
745
746    for( l = page->auth_widgets; l != NULL; l = l->next )
747        gtk_widget_set_sensitive( GTK_WIDGET(
748                                      l->data ), rpc_active && auth_active );
749
750    for( l = page->whitelist_widgets; l != NULL; l = l->next )
751        gtk_widget_set_sensitive( GTK_WIDGET( l->data ),
752                                  rpc_active && whitelist_active );
753
754    gtk_widget_set_sensitive( page->remove_button,
755                              rpc_active && have_addr && n_rules > 1 );
756}
757
758static void
759onRPCToggled( GtkToggleButton * tb UNUSED,
760              gpointer             page )
761{
762    refreshRPCSensitivity( page );
763}
764
765static void
766onWhitelistSelectionChanged( GtkTreeSelection * sel UNUSED,
767                       gpointer               page )
768{
769    refreshRPCSensitivity( page );
770}
771
772static void
773onLaunchClutchCB( GtkButton * w UNUSED, gpointer data UNUSED )
774{
775    const int port = gtr_pref_int_get( TR_PREFS_KEY_RPC_PORT );
776    char * uri = g_strdup_printf( "http://localhost:%d/", port );
777
778    gtr_open_uri( uri );
779    g_free( uri );
780}
781
782static void
783remotePageFree( gpointer gpage )
784{
785    struct remote_page * page = gpage;
786
787    g_slist_free( page->widgets );
788    g_slist_free( page->auth_widgets );
789    g_slist_free( page->whitelist_widgets );
790    g_free( page );
791}
792
793static GtkWidget*
794webPage( GObject * core )
795{
796    const char *         s;
797    int                  row = 0;
798    GtkWidget *          t;
799    GtkWidget *          w;
800    GtkWidget *          h;
801    struct remote_page * page = g_new0( struct remote_page, 1 );
802
803    page->core = TR_CORE( core );
804
805    t = hig_workarea_create( );
806    g_object_set_data_full( G_OBJECT( t ), "page", page, remotePageFree );
807
808    hig_workarea_add_section_title( t, &row, _( "Web Client" ) );
809
810    /* "enabled" checkbutton */
811    s = _( "_Enable web client" );
812    w = new_check_button( s, TR_PREFS_KEY_RPC_ENABLED, core );
813    page->rpc_tb = GTK_TOGGLE_BUTTON( w );
814    g_signal_connect( w, "clicked", G_CALLBACK( onRPCToggled ), page );
815    h = gtk_hbox_new( FALSE, GUI_PAD_BIG );
816    gtk_box_pack_start( GTK_BOX( h ), w, TRUE, TRUE, 0 );
817    w = gtk_button_new_with_mnemonic( _( "_Open web client" ) );
818    page->widgets = g_slist_append( page->widgets, w );
819    g_signal_connect( w, "clicked", G_CALLBACK( onLaunchClutchCB ), NULL );
820    gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 );
821    hig_workarea_add_wide_control( t, &row, h );
822
823    /* port */
824    w = new_spin_button( TR_PREFS_KEY_RPC_PORT, core, 0, USHRT_MAX, 1 );
825    page->widgets = g_slist_append( page->widgets, w );
826    w = hig_workarea_add_row( t, &row, _( "HTTP _port:" ), w, NULL );
827    page->widgets = g_slist_append( page->widgets, w );
828
829    /* require authentication */
830    s = _( "Use _authentication" );
831    w = new_check_button( s, TR_PREFS_KEY_RPC_AUTH_REQUIRED, core );
832    hig_workarea_add_wide_control( t, &row, w );
833    page->auth_tb = GTK_TOGGLE_BUTTON( w );
834    page->widgets = g_slist_append( page->widgets, w );
835    g_signal_connect( w, "clicked", G_CALLBACK( onRPCToggled ), page );
836
837    /* username */
838    s = _( "_Username:" );
839    w = new_entry( TR_PREFS_KEY_RPC_USERNAME, core );
840    page->auth_widgets = g_slist_append( page->auth_widgets, w );
841    w = hig_workarea_add_row( t, &row, s, w, NULL );
842    page->auth_widgets = g_slist_append( page->auth_widgets, w );
843
844    /* password */
845    s = _( "Pass_word:" );
846    w = new_entry( TR_PREFS_KEY_RPC_PASSWORD, core );
847    gtk_entry_set_visibility( GTK_ENTRY( w ), FALSE );
848    page->auth_widgets = g_slist_append( page->auth_widgets, w );
849    w = hig_workarea_add_row( t, &row, s, w, NULL );
850    page->auth_widgets = g_slist_append( page->auth_widgets, w );
851
852    /* require authentication */
853    s = _( "Only allow these IP a_ddresses to connect:" );
854    w = new_check_button( s, TR_PREFS_KEY_RPC_WHITELIST_ENABLED, core );
855    hig_workarea_add_wide_control( t, &row, w );
856    page->whitelist_tb = GTK_TOGGLE_BUTTON( w );
857    page->widgets = g_slist_append( page->widgets, w );
858    g_signal_connect( w, "clicked", G_CALLBACK( onRPCToggled ), page );
859
860    /* access control list */
861    {
862        const char *        val = gtr_pref_string_get( TR_PREFS_KEY_RPC_WHITELIST );
863        GtkTreeModel *      m = whitelist_tree_model_new( val );
864        GtkTreeViewColumn * c;
865        GtkCellRenderer *   r;
866        GtkTreeSelection *  sel;
867        GtkTreeView *       v;
868        GtkWidget *         w;
869        GtkWidget *         h;
870
871        page->store = GTK_LIST_STORE( m );
872        w = gtk_tree_view_new_with_model( m );
873        g_signal_connect( w, "button-release-event",
874                          G_CALLBACK( on_tree_view_button_released ), NULL );
875
876        page->whitelist_widgets = g_slist_append( page->whitelist_widgets, w );
877        v = page->view = GTK_TREE_VIEW( w );
878        gtk_widget_set_tooltip_text( w, _( "IP addresses may use wildcards, such as 192.168.*.*" ) );
879        sel = gtk_tree_view_get_selection( v );
880        g_signal_connect( sel, "changed",
881                          G_CALLBACK( onWhitelistSelectionChanged ), page );
882        g_object_unref( G_OBJECT( m ) );
883        gtk_tree_view_set_headers_visible( v, TRUE );
884        w = gtk_frame_new( NULL );
885        gtk_frame_set_shadow_type( GTK_FRAME( w ), GTK_SHADOW_IN );
886        gtk_container_add( GTK_CONTAINER( w ), GTK_WIDGET( v ) );
887
888        /* ip address column */
889        r = gtk_cell_renderer_text_new( );
890        g_signal_connect( r, "edited",
891                          G_CALLBACK( onAddressEdited ), page );
892        g_object_set( G_OBJECT( r ), "editable", TRUE, NULL );
893        c = gtk_tree_view_column_new_with_attributes( NULL, r,
894                                                      "text", COL_ADDRESS,
895                                                      NULL );
896        gtk_tree_view_column_set_expand( c, TRUE );
897        gtk_tree_view_append_column( v, c );
898        gtk_tree_view_set_headers_visible( v, FALSE );
899
900        s = _( "Addresses:" );
901        w = hig_workarea_add_row( t, &row, s, w, NULL );
902        gtk_misc_set_alignment( GTK_MISC( w ), 0.0f, 0.0f );
903        gtk_misc_set_padding( GTK_MISC( w ), 0, GUI_PAD );
904        page->whitelist_widgets = g_slist_append( page->whitelist_widgets, w );
905
906        h = gtk_hbox_new( TRUE, GUI_PAD );
907        w = gtk_button_new_from_stock( GTK_STOCK_REMOVE );
908        g_signal_connect( w, "clicked", G_CALLBACK(
909                              onRemoveWhitelistClicked ), page );
910        page->remove_button = w;
911        onWhitelistSelectionChanged( sel, page );
912        gtk_box_pack_start( GTK_BOX( h ), w, TRUE, TRUE, 0 );
913        w = gtk_button_new_from_stock( GTK_STOCK_ADD );
914        page->whitelist_widgets = g_slist_append( page->whitelist_widgets, w );
915        g_signal_connect( w, "clicked", G_CALLBACK( onAddWhitelistClicked ), page );
916        gtk_box_pack_start( GTK_BOX( h ), w, TRUE, TRUE, 0 );
917        w = gtk_hbox_new( FALSE, 0 );
918        gtk_box_pack_start( GTK_BOX( w ), gtk_alignment_new( 0, 0, 0, 0 ),
919                            TRUE, TRUE, 0 );
920        gtk_box_pack_start( GTK_BOX( w ), h, FALSE, FALSE, 0 );
921        hig_workarea_add_wide_control( t, &row, w );
922    }
923
924    refreshRPCSensitivity( page );
925    hig_workarea_finish( t, &row );
926    return t;
927}
928
929/****
930*****  Bandwidth Tab
931****/
932
933struct BandwidthPage
934{
935    TrCore *  core;
936    GSList *  sched_widgets;
937};
938
939static void
940refreshSchedSensitivity( struct BandwidthPage * p )
941{
942    GSList *       l;
943    const gboolean sched_enabled = gtr_pref_flag_get( TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED );
944
945    for( l = p->sched_widgets; l != NULL; l = l->next )
946        gtk_widget_set_sensitive( GTK_WIDGET( l->data ), sched_enabled );
947}
948
949static void
950onSchedToggled( GtkToggleButton * tb UNUSED,
951                gpointer             user_data )
952{
953    refreshSchedSensitivity( user_data );
954}
955
956static void
957onTimeComboChanged( GtkComboBox * w,
958                    gpointer      core )
959{
960    GtkTreeIter iter;
961
962    if( gtk_combo_box_get_active_iter( w, &iter ) )
963    {
964        const char * key = g_object_get_data( G_OBJECT( w ), PREF_KEY );
965        int          val = 0;
966        gtk_tree_model_get( gtk_combo_box_get_model(
967                                w ), &iter, 0, &val, -1 );
968        gtr_core_set_pref_int( TR_CORE( core ), key, val );
969    }
970}
971
972static GtkWidget*
973new_time_combo( GObject *    core,
974                const char * key )
975{
976    int               val;
977    int               i;
978    GtkWidget *       w;
979    GtkCellRenderer * r;
980    GtkListStore *    store;
981
982    /* build a store at 15 minute intervals */
983    store = gtk_list_store_new( 2, G_TYPE_INT, G_TYPE_STRING );
984    for( i = 0; i < 60 * 24; i += 15 )
985    {
986        char        buf[128];
987        GtkTreeIter iter;
988        struct tm   tm;
989        tm.tm_hour = i / 60;
990        tm.tm_min = i % 60;
991        tm.tm_sec = 0;
992        strftime( buf, sizeof( buf ), "%H:%M", &tm );
993        gtk_list_store_append( store, &iter );
994        gtk_list_store_set( store, &iter, 0, i, 1, buf, -1 );
995    }
996
997    /* build the widget */
998    w = gtk_combo_box_new_with_model( GTK_TREE_MODEL( store ) );
999    gtk_combo_box_set_wrap_width( GTK_COMBO_BOX( w ), 4 );
1000    r = gtk_cell_renderer_text_new( );
1001    gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( w ), r, TRUE );
1002    gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT(
1003                                        w ), r, "text", 1, NULL );
1004    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, tr_strdup(
1005                                key ), g_free );
1006    val = gtr_pref_int_get( key );
1007    gtk_combo_box_set_active( GTK_COMBO_BOX( w ), val / ( 15 ) );
1008    g_signal_connect( w, "changed", G_CALLBACK( onTimeComboChanged ), core );
1009
1010    /* cleanup */
1011    g_object_unref( G_OBJECT( store ) );
1012    return w;
1013}
1014
1015static GtkWidget*
1016new_week_combo( GObject * core, const char * key )
1017{
1018    GtkWidget * w = gtr_combo_box_new_enum( _( "Every Day" ), TR_SCHED_ALL,
1019                                            _( "Weekdays" ),  TR_SCHED_WEEKDAY,
1020                                            _( "Weekends" ),  TR_SCHED_WEEKEND,
1021                                            _( "Sunday" ),    TR_SCHED_SUN,
1022                                            _( "Monday" ),    TR_SCHED_MON,
1023                                            _( "Tuesday" ),   TR_SCHED_TUES,
1024                                            _( "Wednesday" ), TR_SCHED_WED,
1025                                            _( "Thursday" ),  TR_SCHED_THURS,
1026                                            _( "Friday" ),    TR_SCHED_FRI,
1027                                            _( "Saturday" ),  TR_SCHED_SAT,
1028                                            NULL );
1029    gtr_combo_box_set_active_enum( GTK_COMBO_BOX( w ), gtr_pref_int_get( key ) );
1030    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, tr_strdup( key ), g_free );
1031    g_signal_connect( w, "changed", G_CALLBACK( onIntComboChanged ), core );
1032    return w;
1033}
1034
1035static void
1036bandwidthPageFree( gpointer gpage )
1037{
1038    struct BandwidthPage * page = gpage;
1039
1040    g_slist_free( page->sched_widgets );
1041    g_free( page );
1042}
1043
1044static GtkWidget*
1045bandwidthPage( GObject * core )
1046{
1047    int row = 0;
1048    const char * s;
1049    GtkWidget * t;
1050    GtkWidget * l;
1051    GtkWidget * w, * w2, * h;
1052    char buf[512];
1053    struct BandwidthPage * page = tr_new0( struct BandwidthPage, 1 );
1054
1055    page->core = TR_CORE( core );
1056
1057    t = hig_workarea_create( );
1058    hig_workarea_add_section_title( t, &row, _( "Speed Limits" ) );
1059
1060        g_snprintf( buf, sizeof( buf ), _( "_Upload (%s):" ), _(speed_K_str) );
1061        w = new_check_button( buf, TR_PREFS_KEY_USPEED_ENABLED, core );
1062        w2 = new_spin_button( TR_PREFS_KEY_USPEED_KBps, core, 0, INT_MAX, 5 );
1063        gtk_widget_set_sensitive( GTK_WIDGET( w2 ), gtr_pref_flag_get( TR_PREFS_KEY_USPEED_ENABLED ) );
1064        g_signal_connect( w, "toggled", G_CALLBACK( target_cb ), w2 );
1065        hig_workarea_add_row_w( t, &row, w, w2, NULL );
1066
1067        g_snprintf( buf, sizeof( buf ), _( "_Download (%s):" ), _(speed_K_str) );
1068        w = new_check_button( buf, TR_PREFS_KEY_DSPEED_ENABLED, core );
1069        w2 = new_spin_button( TR_PREFS_KEY_DSPEED_KBps, core, 0, INT_MAX, 5 );
1070        gtk_widget_set_sensitive( GTK_WIDGET( w2 ), gtr_pref_flag_get( TR_PREFS_KEY_DSPEED_ENABLED ) );
1071        g_signal_connect( w, "toggled", G_CALLBACK( target_cb ), w2 );
1072        hig_workarea_add_row_w( t, &row, w, w2, NULL );
1073
1074    hig_workarea_add_section_divider( t, &row );
1075    h = gtk_hbox_new( FALSE, GUI_PAD );
1076    w = gtk_image_new_from_stock( "alt-speed-on", -1 );
1077    gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 );
1078    g_snprintf( buf, sizeof( buf ), "<b>%s</b>", _( "Alternative Speed Limits" ) );
1079    w = gtk_label_new( buf );
1080    gtk_misc_set_alignment( GTK_MISC( w ), 0.0f, 0.5f );
1081    gtk_label_set_use_markup( GTK_LABEL( w ), TRUE );
1082    gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 );
1083    hig_workarea_add_section_title_widget( t, &row, h );
1084
1085        s = _( "Override normal speed limits manually or at scheduled times" );
1086        g_snprintf( buf, sizeof( buf ), "<small>%s</small>", s );
1087        w = gtk_label_new( buf );
1088        gtk_label_set_use_markup( GTK_LABEL( w ), TRUE );
1089        gtk_misc_set_alignment( GTK_MISC( w ), 0.5f, 0.5f );
1090        hig_workarea_add_wide_control( t, &row, w );
1091
1092        g_snprintf( buf, sizeof( buf ), _( "U_pload (%s):" ), _(speed_K_str) );
1093        w = new_spin_button( TR_PREFS_KEY_ALT_SPEED_UP_KBps, core, 0, INT_MAX, 5 );
1094        hig_workarea_add_row( t, &row, buf, w, NULL );
1095
1096        g_snprintf( buf, sizeof( buf ), _( "Do_wnload (%s):" ), _(speed_K_str) );
1097        w = new_spin_button( TR_PREFS_KEY_ALT_SPEED_DOWN_KBps, core, 0, INT_MAX, 5 );
1098        hig_workarea_add_row( t, &row, buf, w, NULL );
1099
1100        s = _( "_Scheduled times:" );
1101        h = gtk_hbox_new( FALSE, 0 );
1102        w2 = new_time_combo( core, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN );
1103        page->sched_widgets = g_slist_append( page->sched_widgets, w2 );
1104        gtk_box_pack_start( GTK_BOX( h ), w2, TRUE, TRUE, 0 );
1105        w2 = l = gtk_label_new_with_mnemonic ( _( " _to " ) );
1106        page->sched_widgets = g_slist_append( page->sched_widgets, w2 );
1107        gtk_box_pack_start( GTK_BOX( h ), w2, FALSE, FALSE, 0 );
1108        w2 = new_time_combo( core, TR_PREFS_KEY_ALT_SPEED_TIME_END );
1109        gtk_label_set_mnemonic_widget( GTK_LABEL( l ), w2 );
1110        page->sched_widgets = g_slist_append( page->sched_widgets, w2 );
1111        gtk_box_pack_start( GTK_BOX( h ), w2, TRUE, TRUE, 0 );
1112        w = new_check_button( s, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, core );
1113        g_signal_connect( w, "toggled", G_CALLBACK( onSchedToggled ), page );
1114        hig_workarea_add_row_w( t, &row, w, h, NULL );
1115
1116        s = _( "_On days:" );
1117        w = new_week_combo( core, TR_PREFS_KEY_ALT_SPEED_TIME_DAY );
1118        page->sched_widgets = g_slist_append( page->sched_widgets, w );
1119        w = hig_workarea_add_row( t, &row, s, w, NULL );
1120        page->sched_widgets = g_slist_append( page->sched_widgets, w );
1121
1122    hig_workarea_finish( t, &row );
1123    g_object_set_data_full( G_OBJECT( t ), "page", page, bandwidthPageFree );
1124
1125    refreshSchedSensitivity( page );
1126    return t;
1127}
1128
1129/****
1130*****  Network Tab
1131****/
1132
1133struct network_page_data
1134{
1135    TrCore     * core;
1136    GtkWidget  * portLabel;
1137    GtkWidget  * portButton;
1138    GtkWidget  * portSpin;
1139    gulong       portTag;
1140    gulong       prefsTag;
1141};
1142
1143static void
1144onCorePrefsChanged( TrCore * core UNUSED, const char *  key, gpointer gdata )
1145{
1146    if( !strcmp( key, TR_PREFS_KEY_PEER_PORT ) )
1147    {
1148        struct network_page_data * data = gdata;
1149        gdk_threads_enter();
1150        gtr_label_set_text( GTK_LABEL( data->portLabel ), _( "Status unknown" ) );
1151        gtk_widget_set_sensitive( data->portButton, TRUE );
1152        gtk_widget_set_sensitive( data->portSpin, TRUE );
1153        gdk_threads_leave();
1154    }
1155}
1156
1157static void
1158networkPageDestroyed( gpointer gdata, GObject * dead UNUSED )
1159{
1160    struct network_page_data * data = gdata;
1161    if( data->prefsTag > 0 )
1162        g_signal_handler_disconnect( data->core, data->prefsTag );
1163    if( data->portTag > 0 )
1164        g_signal_handler_disconnect( data->core, data->portTag );
1165    g_free( data );
1166}
1167
1168static void
1169onPortTested( TrCore * core UNUSED, gboolean isOpen, gpointer vdata )
1170{
1171    struct network_page_data * data = vdata;
1172    const char * markup = isOpen ? _( "Port is <b>open</b>" ) : _( "Port is <b>closed</b>" );
1173    gdk_threads_enter();
1174    gtk_label_set_markup( GTK_LABEL( data->portLabel ), markup );
1175    gtk_widget_set_sensitive( data->portButton, TRUE );
1176    gtk_widget_set_sensitive( data->portSpin, TRUE );
1177    gdk_threads_leave();
1178}
1179
1180static void
1181onPortTest( GtkButton * button UNUSED, gpointer vdata )
1182{
1183    struct network_page_data * data = vdata;
1184    gtk_widget_set_sensitive( data->portButton, FALSE );
1185    gtk_widget_set_sensitive( data->portSpin, FALSE );
1186    gtk_label_set_markup( GTK_LABEL( data->portLabel ), _( "<i>Testing TCP port...</i>" ) );
1187    if( !data->portTag )
1188        data->portTag = g_signal_connect( data->core, "port-tested", G_CALLBACK(onPortTested), data );
1189    gtr_core_port_test( data->core );
1190}
1191
1192static GtkWidget*
1193networkPage( GObject * core )
1194{
1195    int                        row = 0;
1196    const char *               s;
1197    GtkWidget *                t;
1198    GtkWidget *                w;
1199    GtkWidget *                h;
1200    GtkWidget *                l;
1201    struct network_page_data * data;
1202
1203    /* register to stop listening to core prefs changes when the page is destroyed */
1204    data = g_new0( struct network_page_data, 1 );
1205    data->core = TR_CORE( core );
1206
1207    /* build the page */
1208    t = hig_workarea_create( );
1209    hig_workarea_add_section_title( t, &row, _( "Listening Port" ) );
1210
1211    s = _( "_Port used for incoming connections:" );
1212    w = data->portSpin = new_spin_button( TR_PREFS_KEY_PEER_PORT, core, 1, USHRT_MAX, 1 );
1213    hig_workarea_add_row( t, &row, s, w, NULL );
1214
1215    h = gtk_hbox_new( FALSE, GUI_PAD_BIG );
1216    l = data->portLabel = gtk_label_new( _( "Status unknown" ) );
1217    gtk_misc_set_alignment( GTK_MISC( l ), 0.0f, 0.5f );
1218    gtk_box_pack_start( GTK_BOX( h ), l, TRUE, TRUE, 0 );
1219    w = data->portButton = gtk_button_new_with_mnemonic( _( "Te_st Port" ) );
1220    gtk_box_pack_end( GTK_BOX( h ), w, FALSE, FALSE, 0 );
1221    g_signal_connect( w, "clicked", G_CALLBACK(onPortTest), data );
1222    hig_workarea_add_row( t, &row, NULL, h, NULL );
1223    data->prefsTag = g_signal_connect( TR_CORE( core ), "prefs-changed", G_CALLBACK( onCorePrefsChanged ), data );
1224    g_object_weak_ref( G_OBJECT( t ), networkPageDestroyed, data );
1225
1226    s = _( "Pick a _random port every time Transmission is started" );
1227    w = new_check_button( s, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START, core );
1228    hig_workarea_add_wide_control( t, &row, w );
1229
1230    s = _( "Use UPnP or NAT-PMP port _forwarding from my router" );
1231    w = new_check_button( s, TR_PREFS_KEY_PORT_FORWARDING, core );
1232    hig_workarea_add_wide_control( t, &row, w );
1233
1234    hig_workarea_add_section_divider( t, &row );
1235    hig_workarea_add_section_title( t, &row, _( "Peer Limits" ) );
1236
1237    w = new_spin_button( TR_PREFS_KEY_PEER_LIMIT_TORRENT, core, 1, FD_SETSIZE, 5 );
1238    hig_workarea_add_row( t, &row, _( "Maximum peers per _torrent:" ), w, NULL );
1239    w = new_spin_button( TR_PREFS_KEY_PEER_LIMIT_GLOBAL, core, 1, FD_SETSIZE, 5 );
1240    hig_workarea_add_row( t, &row, _( "Maximum peers _overall:" ), w, NULL );
1241
1242    hig_workarea_add_section_divider( t, &row );
1243    hig_workarea_add_section_title( t, &row, _( "Options" ) );
1244
1245#ifdef WITH_UTP
1246    s = _( "Enable _uTP for peer communication" );
1247    w = new_check_button( s, TR_PREFS_KEY_UTP_ENABLED, core );
1248    s = _( "uTP is a tool for reducing network congestion." );
1249    gtk_widget_set_tooltip_text( w, s );
1250    hig_workarea_add_wide_control( t, &row, w );
1251#endif
1252
1253    hig_workarea_finish( t, &row );
1254    return t;
1255}
1256
1257/****
1258*****
1259****/
1260
1261GtkWidget *
1262gtr_prefs_dialog_new( GtkWindow * parent, GObject * core )
1263{
1264    GtkWidget * d;
1265    GtkWidget * n;
1266
1267    d = gtk_dialog_new_with_buttons( _(
1268                                         "Transmission Preferences" ),
1269                                     parent,
1270                                     GTK_DIALOG_DESTROY_WITH_PARENT,
1271                                     GTK_STOCK_HELP, GTK_RESPONSE_HELP,
1272                                     GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1273                                     NULL );
1274    gtk_window_set_role( GTK_WINDOW( d ), "transmission-preferences-dialog" );
1275    gtk_container_set_border_width( GTK_CONTAINER( d ), GUI_PAD );
1276
1277    n = gtk_notebook_new( );
1278    gtk_container_set_border_width ( GTK_CONTAINER ( n ), GUI_PAD );
1279
1280    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1281                              torrentPage( core ),
1282                              gtk_label_new ( _( "Torrents" ) ) );
1283    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1284                              downloadPage( core ),
1285                              gtk_label_new ( _( "Downloading" ) ) );
1286    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1287                              bandwidthPage( core ),
1288                              gtk_label_new ( _( "Speed" ) ) );
1289    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1290                              privacyPage( core ),
1291                              gtk_label_new ( _( "Privacy" ) ) );
1292    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1293                              networkPage( core ),
1294                              gtk_label_new ( _( "Network" ) ) );
1295    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1296                              desktopPage( core ),
1297                              gtk_label_new ( _( "Desktop" ) ) );
1298    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1299                              webPage( core ),
1300                              gtk_label_new ( _( "Web" ) ) );
1301
1302    g_signal_connect( d, "response", G_CALLBACK( response_cb ), core );
1303    gtr_dialog_set_content( GTK_DIALOG( d ), n );
1304    return d;
1305}
1306
Note: See TracBrowser for help on using the repository browser.