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

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

(trunk gtk) To improve translations, help gettext to differentiate between the gerund and verb forms of some -ing words like "Seeding" and "Downloading" -- fixed.

I wasn't sure how to do this, so for the benefit of my future self or anyone else who's interested, here are some breadcrumbs I found: https://trac.transmissionbt.com/ticket/4717#comment:6

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