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

Last change on this file since 11512 was 11512, checked in by charles, 11 years ago

(trunk) #3817 "use the OS' proxy support" -- implemented for libtransmission, transmission-gtk

  • Property svn:keywords set to Date Rev Author Id
File size: 45.1 KB
Line 
1/*
2 * This file Copyright (C) 2007-2010 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 11512 2010-12-10 18:51:05Z charles $
11 */
12
13#include <ctype.h> /* isspace */
14#include <limits.h> /* USHRT_MAX */
15#include <stdlib.h> /* free() */
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    tr_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                                 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            tr_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            tr_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 ), 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 ), 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    tr_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 = 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    tr_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 = 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 = 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#ifdef HAVE_GIO
273    s = _( "Automatically _add torrents from:" );
274    l = new_check_button( s, PREF_KEY_DIR_WATCH_ENABLED, core );
275    w = new_path_chooser_button( PREF_KEY_DIR_WATCH, core );
276    gtk_widget_set_sensitive( GTK_WIDGET( w ),
277                             pref_flag_get( PREF_KEY_DIR_WATCH_ENABLED ) );
278    g_signal_connect( l, "toggled", G_CALLBACK( target_cb ), w );
279    hig_workarea_add_row_w( t, &row, l, w, NULL );
280#endif
281
282    s = _( "Show _options dialog" );
283    w = new_check_button( s, PREF_KEY_OPTIONS_PROMPT, core );
284    hig_workarea_add_wide_control( t, &row, w );
285
286    s = _( "_Start when added" );
287    w = new_check_button( s, TR_PREFS_KEY_START, core );
288    hig_workarea_add_wide_control( t, &row, w );
289
290    s = _( "Mo_ve .torrent file to the trash" );
291    w = new_check_button( s, TR_PREFS_KEY_TRASH_ORIGINAL, core );
292    hig_workarea_add_wide_control( t, &row, w );
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 ), 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 ), 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 Limits" ) );
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 ), 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 ), 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( tr_core_session( core ) );
396    g_snprintf( buf1, sizeof( buf1 ),
397                gtr_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 tr_bool success = n >= 0;
429    const int count = n >=0 ? n : tr_blocklistGetRuleCount( tr_core_session( core ) );
430    const char * s = gtr_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    tr_core_blocklist_update( data->core );
456    data->updateBlocklistTag = g_signal_connect( data->core, "blocklist-updated", G_CALLBACK( onBlocklistUpdated ), data );
457}
458
459static void
460onIntComboChanged( GtkComboBox * combo_box, gpointer core )
461{
462    const int val = gtr_combo_box_get_active_enum( combo_box );
463    const char * key = g_object_get_data( G_OBJECT( combo_box ), PREF_KEY );
464    tr_core_set_pref_int( TR_CORE( core ), key, val );
465}
466
467static GtkWidget*
468new_encryption_combo( GObject * core, const char * key )
469{
470    GtkWidget * w = gtr_combo_box_new_enum( _( "Allow encryption" ),   TR_CLEAR_PREFERRED,
471                                            _( "Prefer encryption" ),  TR_ENCRYPTION_PREFERRED,
472                                            _( "Require encryption" ), TR_ENCRYPTION_REQUIRED,
473                                            NULL );
474    gtr_combo_box_set_active_enum( GTK_COMBO_BOX( w ), pref_int_get( key ) );
475    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, tr_strdup( key ), g_free );
476    g_signal_connect( w, "changed", G_CALLBACK( onIntComboChanged ), core );
477    return w;
478}
479
480static GtkWidget*
481privacyPage( GObject * core )
482{
483    int row = 0;
484    const char * s;
485    GtkWidget * t;
486    GtkWidget * w;
487    GtkWidget * b;
488    GtkWidget * h;
489    GtkWidget * e;
490    struct blocklist_data * data;
491
492    data = g_new0( struct blocklist_data, 1 );
493    data->core = TR_CORE( core );
494
495    t = hig_workarea_create( );
496    hig_workarea_add_section_title( t, &row, _( "Blocklist" ) );
497
498    b = new_check_button( _( "Enable _blocklist:" ), TR_PREFS_KEY_BLOCKLIST_ENABLED, core );
499    e = new_entry( TR_PREFS_KEY_BLOCKLIST_URL, core );
500    gtk_widget_set_size_request( e, 300, -1 );
501    hig_workarea_add_row_w( t, &row, b, e, NULL );
502    data->check = b;
503    g_signal_connect( b, "toggled", G_CALLBACK( target_cb ), e );
504    target_cb( b, e );
505
506    w = gtk_label_new( "" );
507    gtk_misc_set_alignment( GTK_MISC( w ), 0.0f, 0.5f );
508    updateBlocklistText( w, TR_CORE( core ) );
509    data->label = w;
510    h = gtk_hbox_new( FALSE, GUI_PAD_BIG );
511    gtk_box_pack_start( GTK_BOX( h ), w, TRUE, TRUE, 0 );
512    b = data->updateBlocklistButton = gtk_button_new_with_mnemonic( _( "_Update" ) );
513    g_object_set_data( G_OBJECT( b ), "session", tr_core_session( TR_CORE( core ) ) );
514    g_signal_connect( b, "clicked", G_CALLBACK( onBlocklistUpdate ), data );
515    g_signal_connect( data->check, "toggled", G_CALLBACK( target_cb ), b ); target_cb( data->check, b );
516    gtk_box_pack_start( GTK_BOX( h ), b, FALSE, FALSE, 0 );
517    g_signal_connect( data->check, "toggled", G_CALLBACK( target_cb ), w ); target_cb( data->check, w );
518    hig_workarea_add_wide_control( t, &row, h );
519
520    s = _( "Enable _automatic updates" );
521    w = new_check_button( s, PREF_KEY_BLOCKLIST_UPDATES_ENABLED, core );
522    hig_workarea_add_wide_control( t, &row, w );
523    g_signal_connect( data->check, "toggled", G_CALLBACK( target_cb ), w ); target_cb( data->check, w );
524
525    hig_workarea_add_section_divider( t, &row );
526    hig_workarea_add_section_title ( t, &row, _( "Privacy" ) );
527
528    s = _( "_Encryption mode:" );
529    w = new_encryption_combo( core, "encryption" );
530    hig_workarea_add_row( t, &row, s, w, NULL );
531
532    s = _( "Use PE_X to find more peers" );
533    w = new_check_button( s, TR_PREFS_KEY_PEX_ENABLED, core );
534    s = _( "PEX is a tool for exchanging peer lists with the peers you're connected to." );
535    gtr_widget_set_tooltip_text( w, s );
536    hig_workarea_add_wide_control( t, &row, w );
537
538    s = _( "Use _DHT to find more peers" );
539    w = new_check_button( s, TR_PREFS_KEY_DHT_ENABLED, core );
540    s = _( "DHT is a tool for finding peers without a tracker." );
541    gtr_widget_set_tooltip_text( w, s );
542    hig_workarea_add_wide_control( t, &row, w );
543
544    s = _( "Use _Local Peer Discovery to find more peers" );
545    w = new_check_button( s, TR_PREFS_KEY_LPD_ENABLED, core );
546    s = _( "LPD is a tool for finding peers on your local network." );
547    gtr_widget_set_tooltip_text( w, s );
548    hig_workarea_add_wide_control( t, &row, w );
549
550    hig_workarea_finish( t, &row );
551    g_object_weak_ref( G_OBJECT( t ), privacyPageDestroyed, data );
552    return t;
553}
554
555/****
556*****  Web Tab
557****/
558
559enum
560{
561    COL_ADDRESS,
562    N_COLS
563};
564
565static GtkTreeModel*
566whitelist_tree_model_new( const char * whitelist )
567{
568    int            i;
569    char **        rules;
570    GtkListStore * store = gtk_list_store_new( N_COLS,
571                                               G_TYPE_STRING,
572                                               G_TYPE_STRING );
573
574    rules = g_strsplit( whitelist, ",", 0 );
575
576    for( i = 0; rules && rules[i]; ++i )
577    {
578        GtkTreeIter iter;
579        const char * s = rules[i];
580        while( isspace( *s ) ) ++s;
581        gtk_list_store_append( store, &iter );
582        gtk_list_store_set( store, &iter, COL_ADDRESS, s, -1 );
583    }
584
585    g_strfreev( rules );
586    return GTK_TREE_MODEL( store );
587}
588
589struct remote_page
590{
591    TrCore *           core;
592    GtkTreeView *      view;
593    GtkListStore *     store;
594    GtkWidget *        remove_button;
595    GSList *           widgets;
596    GSList *           auth_widgets;
597    GSList *           whitelist_widgets;
598    GtkToggleButton *  rpc_tb;
599    GtkToggleButton *  auth_tb;
600    GtkToggleButton *  whitelist_tb;
601};
602
603static void
604refreshWhitelist( struct remote_page * page )
605{
606    GtkTreeIter    iter;
607    GtkTreeModel * model = GTK_TREE_MODEL( page->store );
608    GString *      gstr = g_string_new( NULL );
609
610    if( gtk_tree_model_get_iter_first( model, &iter ) ) do
611        {
612            char * address;
613            gtk_tree_model_get( model, &iter,
614                                COL_ADDRESS, &address,
615                                -1 );
616            g_string_append( gstr, address );
617            g_string_append( gstr, "," );
618            g_free( address );
619        }
620        while( gtk_tree_model_iter_next( model, &iter ) );
621
622    g_string_truncate( gstr, gstr->len - 1 ); /* remove the trailing comma */
623
624    tr_core_set_pref( page->core, TR_PREFS_KEY_RPC_WHITELIST, gstr->str );
625
626    g_string_free( gstr, TRUE );
627}
628
629static void
630onAddressEdited( GtkCellRendererText  * r UNUSED,
631                 gchar *                  path_string,
632                 gchar *                  address,
633                 gpointer                 gpage )
634{
635    GtkTreeIter          iter;
636    struct remote_page * page = gpage;
637    GtkTreeModel *       model = GTK_TREE_MODEL( page->store );
638    GtkTreePath *        path = gtk_tree_path_new_from_string( path_string );
639
640    if( gtk_tree_model_get_iter( model, &iter, path ) )
641        gtk_list_store_set( page->store, &iter, COL_ADDRESS, address, -1 );
642
643    gtk_tree_path_free( path );
644    refreshWhitelist( page );
645}
646
647static void
648onAddWhitelistClicked( GtkButton * b UNUSED,
649                 gpointer      gpage )
650{
651    GtkTreeIter          iter;
652    GtkTreePath *        path;
653    struct remote_page * page = gpage;
654
655    gtk_list_store_append( page->store, &iter );
656    gtk_list_store_set( page->store, &iter,
657                        COL_ADDRESS,  "0.0.0.0",
658                        -1 );
659
660    path = gtk_tree_model_get_path( GTK_TREE_MODEL( page->store ), &iter );
661    gtk_tree_view_set_cursor(
662        page->view, path,
663        gtk_tree_view_get_column( page->view, COL_ADDRESS ),
664        TRUE );
665    gtk_tree_path_free( path );
666}
667
668static void
669onRemoveWhitelistClicked( GtkButton * b UNUSED,
670                    gpointer      gpage )
671{
672    struct remote_page * page = gpage;
673    GtkTreeSelection *   sel = gtk_tree_view_get_selection( page->view );
674    GtkTreeIter          iter;
675
676    if( gtk_tree_selection_get_selected( sel, NULL, &iter ) )
677    {
678        gtk_list_store_remove( page->store, &iter );
679        refreshWhitelist( page );
680    }
681}
682
683static void
684refreshRPCSensitivity( struct remote_page * page )
685{
686    GSList *           l;
687    const int          rpc_active = gtk_toggle_button_get_active(
688        page->rpc_tb );
689    const int          auth_active = gtk_toggle_button_get_active(
690        page->auth_tb );
691    const int          whitelist_active = gtk_toggle_button_get_active(
692        page->whitelist_tb );
693    GtkTreeSelection * sel = gtk_tree_view_get_selection( page->view );
694    const int          have_addr =
695        gtk_tree_selection_get_selected( sel, NULL,
696                                         NULL );
697    const int          n_rules = gtk_tree_model_iter_n_children(
698        GTK_TREE_MODEL( page->store ), NULL );
699
700    for( l = page->widgets; l != NULL; l = l->next )
701        gtk_widget_set_sensitive( GTK_WIDGET( l->data ), rpc_active );
702
703    for( l = page->auth_widgets; l != NULL; l = l->next )
704        gtk_widget_set_sensitive( GTK_WIDGET(
705                                      l->data ), rpc_active && auth_active );
706
707    for( l = page->whitelist_widgets; l != NULL; l = l->next )
708        gtk_widget_set_sensitive( GTK_WIDGET( l->data ),
709                                  rpc_active && whitelist_active );
710
711    gtk_widget_set_sensitive( page->remove_button,
712                              rpc_active && have_addr && n_rules > 1 );
713}
714
715static void
716onRPCToggled( GtkToggleButton * tb UNUSED,
717              gpointer             page )
718{
719    refreshRPCSensitivity( page );
720}
721
722static void
723onWhitelistSelectionChanged( GtkTreeSelection * sel UNUSED,
724                       gpointer               page )
725{
726    refreshRPCSensitivity( page );
727}
728
729static void
730onLaunchClutchCB( GtkButton * w UNUSED, gpointer data UNUSED )
731{
732    const int port = pref_int_get( TR_PREFS_KEY_RPC_PORT );
733    char * uri = g_strdup_printf( "http://localhost:%d/transmission/web", port );
734
735    gtr_open_uri( uri );
736    g_free( uri );
737}
738
739static void
740remotePageFree( gpointer gpage )
741{
742    struct remote_page * page = gpage;
743
744    g_slist_free( page->widgets );
745    g_slist_free( page->auth_widgets );
746    g_slist_free( page->whitelist_widgets );
747    g_free( page );
748}
749
750static GtkWidget*
751webPage( GObject * core )
752{
753    const char *         s;
754    int                  row = 0;
755    GtkWidget *          t;
756    GtkWidget *          w;
757    GtkWidget *          h;
758    struct remote_page * page = g_new0( struct remote_page, 1 );
759
760    page->core = TR_CORE( core );
761
762    t = hig_workarea_create( );
763    g_object_set_data_full( G_OBJECT( t ), "page", page, remotePageFree );
764
765    hig_workarea_add_section_title( t, &row, _( "Web Client" ) );
766
767    /* "enabled" checkbutton */
768    s = _( "_Enable web client" );
769    w = new_check_button( s, TR_PREFS_KEY_RPC_ENABLED, core );
770    page->rpc_tb = GTK_TOGGLE_BUTTON( w );
771    g_signal_connect( w, "clicked", G_CALLBACK( onRPCToggled ), page );
772    h = gtk_hbox_new( FALSE, GUI_PAD_BIG );
773    gtk_box_pack_start( GTK_BOX( h ), w, TRUE, TRUE, 0 );
774    w = gtk_button_new_with_mnemonic( _( "_Open web client" ) );
775    page->widgets = g_slist_append( page->widgets, w );
776    g_signal_connect( w, "clicked", G_CALLBACK( onLaunchClutchCB ), NULL );
777    gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 );
778    hig_workarea_add_wide_control( t, &row, h );
779
780    /* port */
781    w = new_spin_button( TR_PREFS_KEY_RPC_PORT, core, 0, USHRT_MAX, 1 );
782    page->widgets = g_slist_append( page->widgets, w );
783    w = hig_workarea_add_row( t, &row, _( "Listening _port:" ), w, NULL );
784    page->widgets = g_slist_append( page->widgets, w );
785
786    /* require authentication */
787    s = _( "Use _authentication" );
788    w = new_check_button( s, TR_PREFS_KEY_RPC_AUTH_REQUIRED, core );
789    hig_workarea_add_wide_control( t, &row, w );
790    page->auth_tb = GTK_TOGGLE_BUTTON( w );
791    page->widgets = g_slist_append( page->widgets, w );
792    g_signal_connect( w, "clicked", G_CALLBACK( onRPCToggled ), page );
793
794    /* username */
795    s = _( "_Username:" );
796    w = new_entry( TR_PREFS_KEY_RPC_USERNAME, core );
797    page->auth_widgets = g_slist_append( page->auth_widgets, w );
798    w = hig_workarea_add_row( t, &row, s, w, NULL );
799    page->auth_widgets = g_slist_append( page->auth_widgets, w );
800
801    /* password */
802    s = _( "Pass_word:" );
803    w = new_entry( TR_PREFS_KEY_RPC_PASSWORD, core );
804    gtk_entry_set_visibility( GTK_ENTRY( w ), FALSE );
805    page->auth_widgets = g_slist_append( page->auth_widgets, w );
806    w = hig_workarea_add_row( t, &row, s, w, NULL );
807    page->auth_widgets = g_slist_append( page->auth_widgets, w );
808
809    /* require authentication */
810    s = _( "Only allow these IP a_ddresses to connect:" );
811    w = new_check_button( s, TR_PREFS_KEY_RPC_WHITELIST_ENABLED, core );
812    hig_workarea_add_wide_control( t, &row, w );
813    page->whitelist_tb = GTK_TOGGLE_BUTTON( w );
814    page->widgets = g_slist_append( page->widgets, w );
815    g_signal_connect( w, "clicked", G_CALLBACK( onRPCToggled ), page );
816
817    /* access control list */
818    {
819        const char *        val = pref_string_get( TR_PREFS_KEY_RPC_WHITELIST );
820        GtkTreeModel *      m = whitelist_tree_model_new( val );
821        GtkTreeViewColumn * c;
822        GtkCellRenderer *   r;
823        GtkTreeSelection *  sel;
824        GtkTreeView *       v;
825        GtkWidget *         w;
826        GtkWidget *         h;
827
828        page->store = GTK_LIST_STORE( m );
829        w = gtk_tree_view_new_with_model( m );
830        g_signal_connect( w, "button-release-event",
831                          G_CALLBACK( on_tree_view_button_released ), NULL );
832
833        page->whitelist_widgets = g_slist_append( page->whitelist_widgets, w );
834        v = page->view = GTK_TREE_VIEW( w );
835        gtr_widget_set_tooltip_text( w, _( "IP addresses may use wildcards, such as 192.168.*.*" ) );
836        sel = gtk_tree_view_get_selection( v );
837        g_signal_connect( sel, "changed",
838                          G_CALLBACK( onWhitelistSelectionChanged ), page );
839        g_object_unref( G_OBJECT( m ) );
840        gtk_tree_view_set_headers_visible( v, TRUE );
841        w = gtk_frame_new( NULL );
842        gtk_frame_set_shadow_type( GTK_FRAME( w ), GTK_SHADOW_IN );
843        gtk_container_add( GTK_CONTAINER( w ), GTK_WIDGET( v ) );
844
845        /* ip address column */
846        r = gtk_cell_renderer_text_new( );
847        g_signal_connect( r, "edited",
848                          G_CALLBACK( onAddressEdited ), page );
849        g_object_set( G_OBJECT( r ), "editable", TRUE, NULL );
850        c = gtk_tree_view_column_new_with_attributes( NULL, r,
851                                                      "text", COL_ADDRESS,
852                                                      NULL );
853        gtk_tree_view_column_set_expand( c, TRUE );
854        gtk_tree_view_append_column( v, c );
855        gtk_tree_view_set_headers_visible( v, FALSE );
856
857        s = _( "Addresses:" );
858        w = hig_workarea_add_row( t, &row, s, w, NULL );
859        gtk_misc_set_alignment( GTK_MISC( w ), 0.0f, 0.0f );
860        gtk_misc_set_padding( GTK_MISC( w ), 0, GUI_PAD );
861        page->whitelist_widgets = g_slist_append( page->whitelist_widgets, w );
862
863        h = gtk_hbox_new( TRUE, GUI_PAD );
864        w = gtk_button_new_from_stock( GTK_STOCK_REMOVE );
865        g_signal_connect( w, "clicked", G_CALLBACK(
866                              onRemoveWhitelistClicked ), page );
867        page->remove_button = w;
868        onWhitelistSelectionChanged( sel, page );
869        gtk_box_pack_start( GTK_BOX( h ), w, TRUE, TRUE, 0 );
870        w = gtk_button_new_from_stock( GTK_STOCK_ADD );
871        page->whitelist_widgets = g_slist_append( page->whitelist_widgets, w );
872        g_signal_connect( w, "clicked", G_CALLBACK( onAddWhitelistClicked ), page );
873        gtk_box_pack_start( GTK_BOX( h ), w, TRUE, TRUE, 0 );
874        w = gtk_hbox_new( FALSE, 0 );
875        gtk_box_pack_start( GTK_BOX( w ), gtk_alignment_new( 0, 0, 0, 0 ),
876                            TRUE, TRUE, 0 );
877        gtk_box_pack_start( GTK_BOX( w ), h, FALSE, FALSE, 0 );
878        hig_workarea_add_wide_control( t, &row, w );
879    }
880
881    refreshRPCSensitivity( page );
882    hig_workarea_finish( t, &row );
883    return t;
884}
885
886/****
887*****  Bandwidth Tab
888****/
889
890struct BandwidthPage
891{
892    TrCore *  core;
893    GSList *  sched_widgets;
894};
895
896static void
897refreshSchedSensitivity( struct BandwidthPage * p )
898{
899    GSList *       l;
900    const gboolean sched_enabled = pref_flag_get( TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED );
901
902    for( l = p->sched_widgets; l != NULL; l = l->next )
903        gtk_widget_set_sensitive( GTK_WIDGET( l->data ), sched_enabled );
904}
905
906static void
907onSchedToggled( GtkToggleButton * tb UNUSED,
908                gpointer             user_data )
909{
910    refreshSchedSensitivity( user_data );
911}
912
913static void
914onTimeComboChanged( GtkComboBox * w,
915                    gpointer      core )
916{
917    GtkTreeIter iter;
918
919    if( gtk_combo_box_get_active_iter( w, &iter ) )
920    {
921        const char * key = g_object_get_data( G_OBJECT( w ), PREF_KEY );
922        int          val = 0;
923        gtk_tree_model_get( gtk_combo_box_get_model(
924                                w ), &iter, 0, &val, -1 );
925        tr_core_set_pref_int( TR_CORE( core ), key, val );
926    }
927}
928
929static GtkWidget*
930new_time_combo( GObject *    core,
931                const char * key )
932{
933    int               val;
934    int               i;
935    GtkWidget *       w;
936    GtkCellRenderer * r;
937    GtkListStore *    store;
938
939    /* build a store at 15 minute intervals */
940    store = gtk_list_store_new( 2, G_TYPE_INT, G_TYPE_STRING );
941    for( i = 0; i < 60 * 24; i += 15 )
942    {
943        char        buf[128];
944        GtkTreeIter iter;
945        struct tm   tm;
946        tm.tm_hour = i / 60;
947        tm.tm_min = i % 60;
948        tm.tm_sec = 0;
949        strftime( buf, sizeof( buf ), "%H:%M", &tm );
950        gtk_list_store_append( store, &iter );
951        gtk_list_store_set( store, &iter, 0, i, 1, buf, -1 );
952    }
953
954    /* build the widget */
955    w = gtk_combo_box_new_with_model( GTK_TREE_MODEL( store ) );
956    gtk_combo_box_set_wrap_width( GTK_COMBO_BOX( w ), 4 );
957    r = gtk_cell_renderer_text_new( );
958    gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( w ), r, TRUE );
959    gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT(
960                                        w ), r, "text", 1, NULL );
961    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, tr_strdup(
962                                key ), g_free );
963    val = pref_int_get( key );
964    gtk_combo_box_set_active( GTK_COMBO_BOX( w ), val / ( 15 ) );
965    g_signal_connect( w, "changed", G_CALLBACK( onTimeComboChanged ), core );
966
967    /* cleanup */
968    g_object_unref( G_OBJECT( store ) );
969    return w;
970}
971
972static GtkWidget*
973new_week_combo( GObject * core, const char * key )
974{
975    GtkWidget * w = gtr_combo_box_new_enum( _( "Every Day" ), TR_SCHED_ALL,
976                                            _( "Weekdays" ),  TR_SCHED_WEEKDAY,
977                                            _( "Weekends" ),  TR_SCHED_WEEKEND,
978                                            _( "Sunday" ),    TR_SCHED_SUN,
979                                            _( "Monday" ),    TR_SCHED_MON,
980                                            _( "Tuesday" ),   TR_SCHED_TUES,
981                                            _( "Wednesday" ), TR_SCHED_WED,
982                                            _( "Thursday" ),  TR_SCHED_THURS,
983                                            _( "Friday" ),    TR_SCHED_FRI,
984                                            _( "Saturday" ),  TR_SCHED_SAT,
985                                            NULL );
986    gtr_combo_box_set_active_enum( GTK_COMBO_BOX( w ), pref_int_get( key ) );
987    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, tr_strdup( key ), g_free );
988    g_signal_connect( w, "changed", G_CALLBACK( onIntComboChanged ), core );
989    return w;
990}
991
992static void
993bandwidthPageFree( gpointer gpage )
994{
995    struct BandwidthPage * page = gpage;
996
997    g_slist_free( page->sched_widgets );
998    g_free( page );
999}
1000
1001static GtkWidget*
1002bandwidthPage( GObject * core )
1003{
1004    int row = 0;
1005    const char * s;
1006    GtkWidget * t;
1007    GtkWidget * l;
1008    GtkWidget * w, * w2, * h;
1009    char buf[512];
1010    struct BandwidthPage * page = tr_new0( struct BandwidthPage, 1 );
1011
1012    page->core = TR_CORE( core );
1013
1014    t = hig_workarea_create( );
1015    hig_workarea_add_section_title( t, &row, _( "Speed Limits" ) );
1016
1017        g_snprintf( buf, sizeof( buf ), _( "Limit _download speed (%s):" ), _(speed_K_str) );
1018        w = new_check_button( buf, TR_PREFS_KEY_DSPEED_ENABLED, core );
1019        w2 = new_spin_button( TR_PREFS_KEY_DSPEED_KBps, core, 0, INT_MAX, 5 );
1020        gtk_widget_set_sensitive( GTK_WIDGET( w2 ), pref_flag_get( TR_PREFS_KEY_DSPEED_ENABLED ) );
1021        g_signal_connect( w, "toggled", G_CALLBACK( target_cb ), w2 );
1022        hig_workarea_add_row_w( t, &row, w, w2, NULL );
1023
1024        g_snprintf( buf, sizeof( buf ), _( "Limit _upload speed (%s):" ), _(speed_K_str) );
1025        w = new_check_button( buf, TR_PREFS_KEY_USPEED_ENABLED, core );
1026        w2 = new_spin_button( TR_PREFS_KEY_USPEED_KBps, core, 0, INT_MAX, 5 );
1027        gtk_widget_set_sensitive( GTK_WIDGET( w2 ), pref_flag_get( TR_PREFS_KEY_USPEED_ENABLED ) );
1028        g_signal_connect( w, "toggled", G_CALLBACK( target_cb ), w2 );
1029        hig_workarea_add_row_w( t, &row, w, w2, NULL );
1030
1031    hig_workarea_add_section_divider( t, &row );
1032    h = gtk_hbox_new( FALSE, GUI_PAD );
1033    w = gtk_image_new_from_stock( "alt-speed-off", -1 );
1034    gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 );
1035    g_snprintf( buf, sizeof( buf ), "<b>%s</b>", _( "Temporary Speed Limits" ) );
1036    w = gtk_label_new( buf );
1037    gtk_misc_set_alignment( GTK_MISC( w ), 0.0f, 0.5f );
1038    gtk_label_set_use_markup( GTK_LABEL( w ), TRUE );
1039    gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 );
1040    hig_workarea_add_section_title_widget( t, &row, h );
1041
1042        s = _( "Override normal speed limits manually or at scheduled times" );
1043        g_snprintf( buf, sizeof( buf ), "<small>%s</small>", s );
1044        w = gtk_label_new( buf );
1045        gtk_label_set_use_markup( GTK_LABEL( w ), TRUE );
1046        gtk_misc_set_alignment( GTK_MISC( w ), 0.5f, 0.5f );
1047        hig_workarea_add_wide_control( t, &row, w );
1048
1049        g_snprintf( buf, sizeof( buf ), _( "Limit do_wnload speed (%s):" ), _(speed_K_str) );
1050        w = new_spin_button( TR_PREFS_KEY_ALT_SPEED_DOWN_KBps, core, 0, INT_MAX, 5 );
1051        hig_workarea_add_row( t, &row, buf, w, NULL );
1052
1053        g_snprintf( buf, sizeof( buf ), _( "Limit u_pload speed (%s):" ), _(speed_K_str) );
1054        w = new_spin_button( TR_PREFS_KEY_ALT_SPEED_UP_KBps, core, 0, INT_MAX, 5 );
1055        hig_workarea_add_row( t, &row, buf, w, NULL );
1056
1057        s = _( "_Scheduled times:" );
1058        h = gtk_hbox_new( FALSE, 0 );
1059        w2 = new_time_combo( core, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN );
1060        page->sched_widgets = g_slist_append( page->sched_widgets, w2 );
1061        gtk_box_pack_start( GTK_BOX( h ), w2, TRUE, TRUE, 0 );
1062        w2 = l = gtk_label_new_with_mnemonic ( _( " _to " ) );
1063        page->sched_widgets = g_slist_append( page->sched_widgets, w2 );
1064        gtk_box_pack_start( GTK_BOX( h ), w2, FALSE, FALSE, 0 );
1065        w2 = new_time_combo( core, TR_PREFS_KEY_ALT_SPEED_TIME_END );
1066        gtk_label_set_mnemonic_widget( GTK_LABEL( l ), w2 );
1067        page->sched_widgets = g_slist_append( page->sched_widgets, w2 );
1068        gtk_box_pack_start( GTK_BOX( h ), w2, TRUE, TRUE, 0 );
1069        w = new_check_button( s, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, core );
1070        g_signal_connect( w, "toggled", G_CALLBACK( onSchedToggled ), page );
1071        hig_workarea_add_row_w( t, &row, w, h, NULL );
1072
1073        s = _( "_On days:" );
1074        w = new_week_combo( core, TR_PREFS_KEY_ALT_SPEED_TIME_DAY );
1075        page->sched_widgets = g_slist_append( page->sched_widgets, w );
1076        w = hig_workarea_add_row( t, &row, s, w, NULL );
1077        page->sched_widgets = g_slist_append( page->sched_widgets, w );
1078
1079    hig_workarea_finish( t, &row );
1080    g_object_set_data_full( G_OBJECT( t ), "page", page, bandwidthPageFree );
1081
1082    refreshSchedSensitivity( page );
1083    return t;
1084}
1085
1086/****
1087*****  Network Tab
1088****/
1089
1090struct network_page_data
1091{
1092    TrCore     * core;
1093    GtkWidget  * portLabel;
1094    GtkWidget  * portButton;
1095    GtkWidget  * portSpin;
1096    gulong       portTag;
1097    gulong       prefsTag;
1098};
1099
1100static void
1101onCorePrefsChanged( TrCore * core UNUSED, const char *  key, gpointer gdata )
1102{
1103    if( !strcmp( key, TR_PREFS_KEY_PEER_PORT ) )
1104    {
1105        struct network_page_data * data = gdata;
1106        gdk_threads_enter();
1107        gtk_label_set_text( GTK_LABEL( data->portLabel ), _( "Status unknown" ) );
1108        gtk_widget_set_sensitive( data->portButton, TRUE );
1109        gtk_widget_set_sensitive( data->portSpin, TRUE );
1110        gdk_threads_leave();
1111    }
1112}
1113
1114static void
1115networkPageDestroyed( gpointer gdata, GObject * dead UNUSED )
1116{
1117    struct network_page_data * data = gdata;
1118    if( data->prefsTag > 0 )
1119        g_signal_handler_disconnect( data->core, data->prefsTag );
1120    if( data->portTag > 0 )
1121        g_signal_handler_disconnect( data->core, data->portTag );
1122    g_free( data );
1123}
1124
1125static void
1126onPortTested( TrCore * core UNUSED, gboolean isOpen, gpointer vdata )
1127{
1128    struct network_page_data * data = vdata;
1129    const char * markup = isOpen ? _( "Port is <b>open</b>" ) : _( "Port is <b>closed</b>" );
1130    gdk_threads_enter();
1131    gtk_label_set_markup( GTK_LABEL( data->portLabel ), markup );
1132    gtk_widget_set_sensitive( data->portButton, TRUE );
1133    gtk_widget_set_sensitive( data->portSpin, TRUE );
1134    gdk_threads_leave();
1135}
1136
1137static void
1138onPortTest( GtkButton * button UNUSED, gpointer vdata )
1139{
1140    struct network_page_data * data = vdata;
1141    gtk_widget_set_sensitive( data->portButton, FALSE );
1142    gtk_widget_set_sensitive( data->portSpin, FALSE );
1143    gtk_label_set_markup( GTK_LABEL( data->portLabel ), _( "<i>Testing...</i>" ) );
1144    if( !data->portTag )
1145        data->portTag = g_signal_connect( data->core, "port-tested", G_CALLBACK(onPortTested), data );
1146    tr_core_port_test( data->core );
1147}
1148
1149static void
1150onGNOMEClicked( GtkButton * button, gpointer vdata UNUSED )
1151{
1152    GError * err = NULL;
1153
1154    if( !g_spawn_command_line_async( "gnome-network-properties", &err ) )
1155    {
1156        GtkWidget * d = gtk_message_dialog_new( GTK_WINDOW( gtk_widget_get_toplevel( GTK_WIDGET( button ) ) ),
1157                                                GTK_DIALOG_DESTROY_WITH_PARENT,
1158                                                GTK_MESSAGE_ERROR,
1159                                                GTK_BUTTONS_CLOSE,
1160                                                "%s", err->message );
1161        g_signal_connect_swapped( d, "response", G_CALLBACK( gtk_widget_destroy ), d );
1162        gtk_widget_show( d );
1163        g_clear_error( &err );
1164    }
1165}
1166
1167static GtkWidget*
1168networkPage( GObject * core )
1169{
1170    int                        row = 0;
1171    const char *               s;
1172    GtkWidget *                t;
1173    GtkWidget *                w;
1174    GtkWidget *                h;
1175    GtkWidget *                l;
1176    struct network_page_data * data;
1177
1178    /* register to stop listening to core prefs changes when the page is destroyed */
1179    data = g_new0( struct network_page_data, 1 );
1180    data->core = TR_CORE( core );
1181
1182    /* build the page */
1183    t = hig_workarea_create( );
1184    hig_workarea_add_section_title( t, &row, _( "Incoming Peers" ) );
1185
1186    s = _( "_Port for incoming connections:" );
1187    w = data->portSpin = new_spin_button( TR_PREFS_KEY_PEER_PORT, core, 1, USHRT_MAX, 1 );
1188    hig_workarea_add_row( t, &row, s, w, NULL );
1189
1190    h = gtk_hbox_new( FALSE, GUI_PAD_BIG );
1191    l = data->portLabel = gtk_label_new( _( "Status unknown" ) );
1192    gtk_misc_set_alignment( GTK_MISC( l ), 0.0f, 0.5f );
1193    gtk_box_pack_start( GTK_BOX( h ), l, TRUE, TRUE, 0 );
1194    w = data->portButton = gtk_button_new_with_mnemonic( _( "Te_st Port" ) );
1195    gtk_box_pack_end( GTK_BOX( h ), w, FALSE, FALSE, 0 );
1196    g_signal_connect( w, "clicked", G_CALLBACK(onPortTest), data );
1197    hig_workarea_add_row( t, &row, NULL, h, NULL );
1198    data->prefsTag = g_signal_connect( TR_CORE( core ), "prefs-changed", G_CALLBACK( onCorePrefsChanged ), data );
1199    g_object_weak_ref( G_OBJECT( t ), networkPageDestroyed, data );
1200
1201    s = _( "Pick a _random port every time Transmission is started" );
1202    w = new_check_button( s, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START, core );
1203    hig_workarea_add_wide_control( t, &row, w );
1204
1205    s = _( "Use UPnP or NAT-PMP port _forwarding from my router" );
1206    w = new_check_button( s, TR_PREFS_KEY_PORT_FORWARDING, core );
1207    hig_workarea_add_wide_control( t, &row, w );
1208
1209    hig_workarea_add_section_divider( t, &row );
1210    hig_workarea_add_section_title( t, &row, _( "Limits" ) );
1211
1212    w = new_spin_button( TR_PREFS_KEY_PEER_LIMIT_TORRENT, core, 1, 300, 5 );
1213    hig_workarea_add_row( t, &row, _( "Maximum peers per _torrent:" ), w, NULL );
1214    w = new_spin_button( TR_PREFS_KEY_PEER_LIMIT_GLOBAL, core, 1, 3000, 5 );
1215    hig_workarea_add_row( t, &row, _( "Maximum peers _overall:" ), w, NULL );
1216
1217    hig_workarea_add_section_divider( t, &row );
1218    hig_workarea_add_section_title( t, &row, _( "Options" ) );
1219
1220    w = gtk_button_new_with_mnemonic( _( "Launch GNOME Network Preferences" ) );
1221    g_signal_connect( w, "clicked", G_CALLBACK( onGNOMEClicked ), data );
1222    hig_workarea_add_row( t, &row, _( "Pro_xies:" ), w, NULL );
1223
1224    hig_workarea_finish( t, &row );
1225    return t;
1226}
1227
1228/****
1229*****
1230****/
1231
1232GtkWidget *
1233tr_prefs_dialog_new( GObject *   core,
1234                     GtkWindow * parent )
1235{
1236    GtkWidget * d;
1237    GtkWidget * n;
1238
1239    d = gtk_dialog_new_with_buttons( _(
1240                                         "Transmission Preferences" ),
1241                                     parent,
1242                                     GTK_DIALOG_DESTROY_WITH_PARENT,
1243                                     GTK_STOCK_HELP, GTK_RESPONSE_HELP,
1244                                     GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1245                                     NULL );
1246    gtk_window_set_role( GTK_WINDOW( d ), "transmission-preferences-dialog" );
1247    gtk_container_set_border_width( GTK_CONTAINER( d ), GUI_PAD );
1248
1249    n = gtk_notebook_new( );
1250    gtk_container_set_border_width ( GTK_CONTAINER ( n ), GUI_PAD );
1251
1252    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1253                              torrentPage( core ),
1254                              gtk_label_new ( _( "Torrents" ) ) );
1255    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1256                              bandwidthPage( core ),
1257                              gtk_label_new ( _( "Speed" ) ) );
1258    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1259                              privacyPage( core ),
1260                              gtk_label_new ( _( "Privacy" ) ) );
1261    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1262                              networkPage( core ),
1263                              gtk_label_new ( _( "Network" ) ) );
1264    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1265                              desktopPage( core ),
1266                              gtk_label_new ( _( "Desktop" ) ) );
1267    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1268                              webPage( core ),
1269                              gtk_label_new ( _( "Web" ) ) );
1270
1271    g_signal_connect( d, "response", G_CALLBACK( response_cb ), core );
1272    gtk_box_pack_start( GTK_BOX( GTK_DIALOG( d )->vbox ), n, TRUE, TRUE, 0 );
1273    gtk_widget_show_all( GTK_DIALOG( d )->vbox );
1274    return d;
1275}
1276
Note: See TracBrowser for help on using the repository browser.