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

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

(trunk gtk, qt) "It is unobvious if test port feature tests UDP port status as well" -- fixed.

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