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

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

(trunk gtk) add portability wrappers for gtk_hbox_new() and gtk_vbox_new(), which are deprecated now in GTK+ 3.2

  • 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 12963 2011-10-11 04:11:08Z 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, _( "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, _( "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 ( _( "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.