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

Last change on this file since 12412 was 12412, checked in by jordan, 11 years ago

(trunk gtk) use ngettext() instead of gtr_dngettext() to address https://bugs.launchpad.net/ubuntu/+source/transmission/+bug/760761

  • 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 12412 2011-05-02 17:58:27Z 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/transmission/web", 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...</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 void
1159onGNOMEClicked( GtkButton * button, gpointer vdata UNUSED )
1160{
1161    GError * err = NULL;
1162
1163    if( !g_spawn_command_line_async( "gnome-network-properties", &err ) &&
1164        !g_spawn_command_line_async( "gnome-control-center network", &err ) )
1165    {
1166        GtkWidget * d = gtk_message_dialog_new( GTK_WINDOW( gtk_widget_get_toplevel( GTK_WIDGET( button ) ) ),
1167                                                GTK_DIALOG_DESTROY_WITH_PARENT,
1168                                                GTK_MESSAGE_ERROR,
1169                                                GTK_BUTTONS_CLOSE,
1170                                                "%s", err->message );
1171        g_signal_connect_swapped( d, "response", G_CALLBACK( gtk_widget_destroy ), d );
1172        gtk_widget_show( d );
1173        g_clear_error( &err );
1174    }
1175}
1176
1177static GtkWidget*
1178networkPage( GObject * core )
1179{
1180    int                        row = 0;
1181    const char *               s;
1182    GtkWidget *                t;
1183    GtkWidget *                w;
1184    GtkWidget *                h;
1185    GtkWidget *                l;
1186    struct network_page_data * data;
1187
1188    /* register to stop listening to core prefs changes when the page is destroyed */
1189    data = g_new0( struct network_page_data, 1 );
1190    data->core = TR_CORE( core );
1191
1192    /* build the page */
1193    t = hig_workarea_create( );
1194    hig_workarea_add_section_title( t, &row, _( "Listening Port" ) );
1195
1196    s = _( "_Port used for incoming connections:" );
1197    w = data->portSpin = new_spin_button( TR_PREFS_KEY_PEER_PORT, core, 1, USHRT_MAX, 1 );
1198    hig_workarea_add_row( t, &row, s, w, NULL );
1199
1200    h = gtk_hbox_new( FALSE, GUI_PAD_BIG );
1201    l = data->portLabel = gtk_label_new( _( "Status unknown" ) );
1202    gtk_misc_set_alignment( GTK_MISC( l ), 0.0f, 0.5f );
1203    gtk_box_pack_start( GTK_BOX( h ), l, TRUE, TRUE, 0 );
1204    w = data->portButton = gtk_button_new_with_mnemonic( _( "Te_st Port" ) );
1205    gtk_box_pack_end( GTK_BOX( h ), w, FALSE, FALSE, 0 );
1206    g_signal_connect( w, "clicked", G_CALLBACK(onPortTest), data );
1207    hig_workarea_add_row( t, &row, NULL, h, NULL );
1208    data->prefsTag = g_signal_connect( TR_CORE( core ), "prefs-changed", G_CALLBACK( onCorePrefsChanged ), data );
1209    g_object_weak_ref( G_OBJECT( t ), networkPageDestroyed, data );
1210
1211    s = _( "Pick a _random port every time Transmission is started" );
1212    w = new_check_button( s, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START, core );
1213    hig_workarea_add_wide_control( t, &row, w );
1214
1215    s = _( "Use UPnP or NAT-PMP port _forwarding from my router" );
1216    w = new_check_button( s, TR_PREFS_KEY_PORT_FORWARDING, core );
1217    hig_workarea_add_wide_control( t, &row, w );
1218
1219    hig_workarea_add_section_divider( t, &row );
1220    hig_workarea_add_section_title( t, &row, _( "Peer Limits" ) );
1221
1222    w = new_spin_button( TR_PREFS_KEY_PEER_LIMIT_TORRENT, core, 1, 300, 5 );
1223    hig_workarea_add_row( t, &row, _( "Maximum peers per _torrent:" ), w, NULL );
1224    w = new_spin_button( TR_PREFS_KEY_PEER_LIMIT_GLOBAL, core, 1, 3000, 5 );
1225    hig_workarea_add_row( t, &row, _( "Maximum peers _overall:" ), w, NULL );
1226
1227    hig_workarea_add_section_divider( t, &row );
1228    hig_workarea_add_section_title( t, &row, _( "Options" ) );
1229
1230#ifdef WITH_UTP
1231    s = _( "Enable _uTP for peer communication" );
1232    w = new_check_button( s, TR_PREFS_KEY_UTP_ENABLED, core );
1233    s = _( "uTP is a tool for reducing network congestion." );
1234    gtr_widget_set_tooltip_text( w, s );
1235    hig_workarea_add_wide_control( t, &row, w );
1236#endif
1237
1238    w = gtk_button_new_with_mnemonic( _( "Edit GNOME Proxy Settings" ) );
1239    g_signal_connect( w, "clicked", G_CALLBACK( onGNOMEClicked ), data );
1240    h = gtk_hbox_new( FALSE, 0 );
1241    gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 );
1242    hig_workarea_add_wide_control( t, &row, h );
1243
1244    hig_workarea_finish( t, &row );
1245    return t;
1246}
1247
1248/****
1249*****
1250****/
1251
1252GtkWidget *
1253gtr_prefs_dialog_new( GtkWindow * parent, GObject * core )
1254{
1255    GtkWidget * d;
1256    GtkWidget * n;
1257
1258    d = gtk_dialog_new_with_buttons( _(
1259                                         "Transmission Preferences" ),
1260                                     parent,
1261                                     GTK_DIALOG_DESTROY_WITH_PARENT,
1262                                     GTK_STOCK_HELP, GTK_RESPONSE_HELP,
1263                                     GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1264                                     NULL );
1265    gtk_window_set_role( GTK_WINDOW( d ), "transmission-preferences-dialog" );
1266    gtk_container_set_border_width( GTK_CONTAINER( d ), GUI_PAD );
1267
1268    n = gtk_notebook_new( );
1269    gtk_container_set_border_width ( GTK_CONTAINER ( n ), GUI_PAD );
1270
1271    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1272                              torrentPage( core ),
1273                              gtk_label_new ( _( "Torrents" ) ) );
1274    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1275                              bandwidthPage( core ),
1276                              gtk_label_new ( _( "Speed" ) ) );
1277    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1278                              privacyPage( core ),
1279                              gtk_label_new ( _( "Privacy" ) ) );
1280    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1281                              networkPage( core ),
1282                              gtk_label_new ( _( "Network" ) ) );
1283    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1284                              desktopPage( core ),
1285                              gtk_label_new ( _( "Desktop" ) ) );
1286    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1287                              webPage( core ),
1288                              gtk_label_new ( _( "Web" ) ) );
1289
1290    g_signal_connect( d, "response", G_CALLBACK( response_cb ), core );
1291    gtr_dialog_set_content( GTK_DIALOG( d ), n );
1292    return d;
1293}
1294
Note: See TracBrowser for help on using the repository browser.