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

Last change on this file since 10391 was 10391, checked in by charles, 12 years ago

(trunk) #2898 "add pausing and deletion to daemon" -- committed patch from Longinus00 to trunk for 2.00

  • Property svn:keywords set to Date Rev Author Id
File size: 48.7 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 10391 2010-03-17 19:23:03Z 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 * base = gtr_get_help_url( );
43        char * url = g_strdup_printf( "%s/html/preferences.html", base );
44        gtr_open_file( url );
45        g_free( url );
46        g_free( base );
47    }
48
49    if( response == GTK_RESPONSE_CLOSE )
50        gtk_widget_destroy( GTK_WIDGET( dialog ) );
51}
52
53static void
54toggled_cb( GtkToggleButton * w,
55            gpointer          core )
56{
57    const char *   key = g_object_get_data( G_OBJECT( w ), PREF_KEY );
58    const gboolean flag = gtk_toggle_button_get_active( w );
59
60    tr_core_set_pref_bool( TR_CORE( core ), key, flag );
61}
62
63static GtkWidget*
64new_check_button( const char * mnemonic,
65                  const char * key,
66                  gpointer     core )
67{
68    GtkWidget * w = gtk_check_button_new_with_mnemonic( mnemonic );
69
70    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, g_strdup(
71                                key ), g_free );
72    gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w ),
73                                 pref_flag_get( key ) );
74    g_signal_connect( w, "toggled", G_CALLBACK( toggled_cb ), core );
75    return w;
76}
77
78#define IDLE_DATA "idle-data"
79
80struct spin_idle_data
81{
82    gpointer    core;
83    GTimer *    last_change;
84    gboolean    isDouble;
85};
86
87static void
88spin_idle_data_free( gpointer gdata )
89{
90    struct spin_idle_data * data = gdata;
91
92    g_timer_destroy( data->last_change );
93    g_free( data );
94}
95
96static gboolean
97spun_cb_idle( gpointer spin )
98{
99    gboolean                keep_waiting = TRUE;
100    GObject *               o = G_OBJECT( spin );
101    struct spin_idle_data * data = g_object_get_data( o, IDLE_DATA );
102
103    /* has the user stopped making changes? */
104    if( g_timer_elapsed( data->last_change, NULL ) > 0.33f )
105    {
106        /* update the core */
107        const char * key = g_object_get_data( o, PREF_KEY );
108        if (data->isDouble)
109        {
110            const double value = gtk_spin_button_get_value( GTK_SPIN_BUTTON( spin ) );
111            tr_core_set_pref_double( TR_CORE( data->core ), key, value );
112        }
113        else
114        {
115            const int    value = gtk_spin_button_get_value_as_int(
116                                 GTK_SPIN_BUTTON( spin ) );
117            tr_core_set_pref_int( TR_CORE( data->core ), key, value );
118        }
119
120        /* cleanup */
121        g_object_set_data( o, IDLE_DATA, NULL );
122        keep_waiting = FALSE;
123        g_object_unref( G_OBJECT( o ) );
124    }
125
126    return keep_waiting;
127}
128
129static void
130spun_cb( GtkSpinButton * w,
131         gpointer        core,
132         gboolean        isDouble )
133{
134    /* user may be spinning through many values, so let's hold off
135       for a moment to keep from flooding the core with changes */
136    GObject *               o = G_OBJECT( w );
137    struct spin_idle_data * data = g_object_get_data( o, IDLE_DATA );
138
139    if( data == NULL )
140    {
141        data = g_new( struct spin_idle_data, 1 );
142        data->core = core;
143        data->last_change = g_timer_new( );
144        data->isDouble = isDouble;
145        g_object_set_data_full( o, IDLE_DATA, data, spin_idle_data_free );
146        g_object_ref( G_OBJECT( o ) );
147        gtr_timeout_add_seconds( 1, spun_cb_idle, w );
148    }
149    g_timer_start( data->last_change );
150}
151
152static void
153spun_cb_int( GtkSpinButton * w,
154             gpointer        core )
155{
156    spun_cb( w, core, FALSE );
157}
158
159static void
160spun_cb_double( GtkSpinButton * w,
161                gpointer        core )
162{
163    spun_cb( w, core, TRUE );
164}
165
166static GtkWidget*
167new_spin_button( const char * key,
168                 gpointer     core,
169                 int          low,
170                 int          high,
171                 int          step )
172{
173    GtkWidget * w = gtk_spin_button_new_with_range( low, high, step );
174
175    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, g_strdup(
176                                key ), g_free );
177    gtk_spin_button_set_digits( GTK_SPIN_BUTTON( w ), 0 );
178    gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), pref_int_get( key ) );
179    g_signal_connect( w, "value-changed", G_CALLBACK( spun_cb_int ), core );
180    return w;
181}
182
183static GtkWidget*
184new_spin_button_double( const char * key,
185                       gpointer      core,
186                       double        low,
187                       double        high,
188                       double        step )
189{
190    GtkWidget * w = gtk_spin_button_new_with_range( low, high, step );
191
192    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, g_strdup(
193                                key ), g_free );
194    gtk_spin_button_set_digits( GTK_SPIN_BUTTON( w ), 2 );
195    gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), pref_double_get( key ) );
196    g_signal_connect( w, "value-changed", G_CALLBACK( spun_cb_double ), core );
197    return w;
198}
199
200static void
201entry_changed_cb( GtkEntry * w,
202                  gpointer   core )
203{
204    const char * key = g_object_get_data( G_OBJECT( w ), PREF_KEY );
205    const char * value = gtk_entry_get_text( w );
206
207    tr_core_set_pref( TR_CORE( core ), key, value );
208}
209
210static GtkWidget*
211new_entry( const char * key,
212           gpointer     core )
213{
214    GtkWidget *  w = gtk_entry_new( );
215    const char * value = pref_string_get( key );
216
217    if( value )
218        gtk_entry_set_text( GTK_ENTRY( w ), value );
219    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, g_strdup(
220                                key ), g_free );
221    g_signal_connect( w, "changed", G_CALLBACK( entry_changed_cb ), core );
222    return w;
223}
224
225static void
226chosen_cb( GtkFileChooser * w,
227           gpointer         core )
228{
229    const char * key = g_object_get_data( G_OBJECT( w ), PREF_KEY );
230    char *       value = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER( w ) );
231
232    tr_core_set_pref( TR_CORE( core ), key, value );
233    g_free( value );
234}
235
236static GtkWidget*
237new_path_chooser_button( const char * key,
238                         gpointer     core )
239{
240    GtkWidget *  w = gtk_file_chooser_button_new(
241        NULL,
242        GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER );
243    const char * path = pref_string_get( key );
244
245    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, g_strdup(
246                                key ), g_free );
247    g_signal_connect( w, "selection-changed", G_CALLBACK( chosen_cb ), core );
248    gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER( w ), path );
249    return w;
250}
251
252static void
253target_cb( GtkWidget * tb,
254           gpointer    target )
255{
256    const gboolean b = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( tb ) );
257
258    gtk_widget_set_sensitive( GTK_WIDGET( target ), b );
259}
260
261/****
262*****  Torrent Tab
263****/
264
265static GtkWidget*
266torrentPage( GObject * core )
267{
268    int          row = 0;
269    const char * s;
270    GtkWidget *  t;
271    GtkWidget *  w;
272    GtkWidget *  w2;
273    GtkWidget *  l;
274
275    t = hig_workarea_create( );
276    hig_workarea_add_section_title( t, &row, _( "Adding Torrents" ) );
277
278#ifdef HAVE_GIO
279    s = _( "Automatically _add torrents from:" );
280    l = new_check_button( s, PREF_KEY_DIR_WATCH_ENABLED, core );
281    w = new_path_chooser_button( PREF_KEY_DIR_WATCH, core );
282    gtk_widget_set_sensitive( GTK_WIDGET( w ),
283                             pref_flag_get( PREF_KEY_DIR_WATCH_ENABLED ) );
284    g_signal_connect( l, "toggled", G_CALLBACK( target_cb ), w );
285    hig_workarea_add_row_w( t, &row, l, w, NULL );
286#endif
287
288    s = _( "Show _options dialog" );
289    w = new_check_button( s, PREF_KEY_OPTIONS_PROMPT, core );
290    hig_workarea_add_wide_control( t, &row, w );
291
292    s = _( "_Start when added" );
293    w = new_check_button( s, TR_PREFS_KEY_START, core );
294    hig_workarea_add_wide_control( t, &row, w );
295
296    s = _( "Mo_ve .torrent file to the trash" );
297    w = new_check_button( s, TR_PREFS_KEY_TRASH_ORIGINAL, core );
298    hig_workarea_add_wide_control( t, &row, w );
299
300    s = _( "Append \"._part\" to incomplete files' names" );
301    w = new_check_button( s, TR_PREFS_KEY_RENAME_PARTIAL_FILES, core );
302    hig_workarea_add_wide_control( t, &row, w );
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    w = new_path_chooser_button( TR_PREFS_KEY_DOWNLOAD_DIR, core );
312    hig_workarea_add_row( t, &row, _( "Save to _Location:" ), w, NULL );
313
314    hig_workarea_add_section_divider( t, &row );
315    hig_workarea_add_section_title( t, &row, _( "Limits" ) );
316
317    s = _( "_Seed torrent until its ratio reaches:" );
318    w = new_check_button( s, TR_PREFS_KEY_RATIO_ENABLED, core );
319    w2 = new_spin_button_double( TR_PREFS_KEY_RATIO, core, 0, INT_MAX, .05 );
320    gtk_widget_set_sensitive( GTK_WIDGET( w2 ), pref_flag_get( TR_PREFS_KEY_RATIO_ENABLED ) );
321    g_signal_connect( w, "toggled", G_CALLBACK( target_cb ), w2 );
322    hig_workarea_add_row_w( t, &row, w, w2, NULL );
323
324    hig_workarea_finish( t, &row );
325    return t;
326}
327
328/****
329*****  Desktop Tab
330****/
331
332static GtkWidget*
333desktopPage( GObject * core )
334{
335    int          row = 0;
336    const char * s;
337    GtkWidget *  t;
338    GtkWidget *  w;
339
340    t = hig_workarea_create( );
341    hig_workarea_add_section_title( t, &row, _( "Desktop" ) );
342
343    s = _( "Inhibit _hibernation when torrents are active" );
344    w = new_check_button( s, PREF_KEY_INHIBIT_HIBERNATION, core );
345    hig_workarea_add_wide_control( t, &row, w );
346
347    s = _( "Show Transmission icon in the _notification area" );
348    w = new_check_button( s, PREF_KEY_SHOW_TRAY_ICON, core );
349    hig_workarea_add_wide_control( t, &row, w );
350
351    s = _( "Show _popup notifications" );
352    w = new_check_button( s, PREF_KEY_SHOW_DESKTOP_NOTIFICATION, core );
353    hig_workarea_add_wide_control( t, &row, w );
354
355#ifdef HAVE_LIBCANBERRA
356    s = _( "Play _sound when downloads are complete" );
357    w = new_check_button( s, PREF_KEY_PLAY_DOWNLOAD_COMPLETE_SOUND, core );
358    hig_workarea_add_wide_control( t, &row, w );
359#endif
360
361    hig_workarea_finish( t, &row );
362    return t;
363}
364
365/****
366*****  Peer Tab
367****/
368
369struct blocklist_data
370{
371    gulong      updateBlocklistTag;
372    GtkWidget * updateBlocklistButton;
373    GtkWidget * updateBlocklistDialog;
374    GtkWidget * check;
375    TrCore    * core;
376};
377
378static void
379updateBlocklistText( GtkWidget * w, TrCore * core )
380{
381    const int n = tr_blocklistGetRuleCount( tr_core_session( core ) );
382    char      buf[512];
383    g_snprintf( buf, sizeof( buf ),
384                ngettext( "Enable _blocklist (contains %'d rule)",
385                          "Enable _blocklist (contains %'d rules)", n ), n );
386    gtk_button_set_label( GTK_BUTTON( w ), buf );
387}
388
389/* prefs dialog is being destroyed, so stop listening to blocklist updates */
390static void
391privacyPageDestroyed( gpointer gdata, GObject * dead UNUSED )
392{
393    struct blocklist_data * data = gdata;
394    if( data->updateBlocklistTag > 0 )
395        g_signal_handler_disconnect( data->core, data->updateBlocklistTag );
396    g_free( data );
397}
398
399/* user hit "close" in the blocklist-update dialog */
400static void
401onBlocklistUpdateResponse( GtkDialog * dialog, gint response UNUSED, gpointer gdata )
402{
403    struct blocklist_data * data = gdata;
404    gtk_widget_destroy( GTK_WIDGET( dialog ) );
405    gtk_widget_set_sensitive( data->updateBlocklistButton, TRUE );
406    data->updateBlocklistDialog = NULL;
407    g_signal_handler_disconnect( data->core, data->updateBlocklistTag );
408}
409
410/* core says the blocklist was updated */
411static void
412onBlocklistUpdated( TrCore * core, int n, gpointer gdata )
413{
414    const char * s = ngettext( "Blocklist now has %'d rule.", "Blocklist now has %'d rules.", n );
415    struct blocklist_data * data = gdata;
416    GtkMessageDialog * d = GTK_MESSAGE_DIALOG( data->updateBlocklistDialog );
417    gtk_widget_set_sensitive( data->updateBlocklistButton, TRUE );
418    gtk_message_dialog_set_markup( d, _( "<b>Update succeeded!</b>" ) );
419    gtk_message_dialog_format_secondary_text( d, s, n );
420    updateBlocklistText( data->check, core );
421}
422
423/* user pushed a button to update the blocklist */
424static void
425onBlocklistUpdate( GtkButton * w, gpointer gdata )
426{
427    GtkWidget * d;
428    struct blocklist_data * data = gdata;
429    d = gtk_message_dialog_new( GTK_WINDOW( gtk_widget_get_toplevel( GTK_WIDGET( w ) ) ),
430                                GTK_DIALOG_DESTROY_WITH_PARENT,
431                                GTK_MESSAGE_INFO,
432                                GTK_BUTTONS_CLOSE,
433                                "%s", _( "Update Blocklist" ) );
434    gtk_widget_set_sensitive( data->updateBlocklistButton, FALSE );
435    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( d ), "%s", _( "Getting new blocklist..." ) );
436    data->updateBlocklistDialog = d;
437    g_signal_connect( d, "response", G_CALLBACK(onBlocklistUpdateResponse), data );
438    gtk_widget_show( d );
439    tr_core_blocklist_update( data->core );
440    data->updateBlocklistTag = g_signal_connect( data->core, "blocklist-updated", G_CALLBACK( onBlocklistUpdated ), data );
441}
442
443static void
444onIntComboChanged( GtkComboBox * w, gpointer core )
445{
446    GtkTreeIter iter;
447
448    if( gtk_combo_box_get_active_iter( w, &iter ) )
449    {
450        int val = 0;
451        const char * key = g_object_get_data( G_OBJECT( w ), PREF_KEY );
452        gtk_tree_model_get( gtk_combo_box_get_model( w ), &iter, 0, &val, -1 );
453        tr_core_set_pref_int( TR_CORE( core ), key, val );
454    }
455}
456
457static GtkWidget*
458new_encryption_combo( GObject * core, const char * key )
459{
460    int i;
461    int selIndex;
462    GtkWidget * w;
463    GtkCellRenderer * r;
464    GtkListStore * store;
465    const int currentValue = pref_int_get( key );
466    const struct {
467        int value;
468        const char * text;
469    } items[] = {
470        { TR_CLEAR_PREFERRED,      N_( "Allow encryption" )  },
471        { TR_ENCRYPTION_PREFERRED, N_( "Prefer encryption" ) },
472        { TR_ENCRYPTION_REQUIRED,  N_( "Require encryption" )  }
473    };
474
475    /* build a store for encryption */
476    selIndex = -1;
477    store = gtk_list_store_new( 2, G_TYPE_INT, G_TYPE_STRING );
478    for( i=0; i<(int)G_N_ELEMENTS(items); ++i ) {
479        GtkTreeIter iter;
480        gtk_list_store_append( store, &iter );
481        gtk_list_store_set( store, &iter, 0, items[i].value, 1, _( items[i].text ), -1 );
482        if( items[i].value == currentValue )
483            selIndex = i;
484    }
485
486    /* build the widget */
487    w = gtk_combo_box_new_with_model( GTK_TREE_MODEL( store ) );
488    r = gtk_cell_renderer_text_new( );
489    gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( w ), r, TRUE );
490    gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT( w ), r, "text", 1, NULL );
491    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, tr_strdup( key ), g_free );
492    if( selIndex >= 0 )
493        gtk_combo_box_set_active( GTK_COMBO_BOX( w ), selIndex );
494    g_signal_connect( w, "changed", G_CALLBACK( onIntComboChanged ), core );
495
496    /* cleanup */
497    g_object_unref( G_OBJECT( store ) );
498    return w;
499}
500
501static GtkWidget*
502privacyPage( GObject * core )
503{
504    int                     row = 0;
505    const char *            s;
506    GtkWidget *             t;
507    GtkWidget *             w;
508    GtkWidget *             b;
509    GtkWidget *             h;
510    struct blocklist_data * data;
511
512    data = g_new0( struct blocklist_data, 1 );
513    data->core = TR_CORE( core );
514
515    t = hig_workarea_create( );
516    hig_workarea_add_section_title( t, &row, _( "Blocklist" ) );
517
518    w = new_check_button( "", TR_PREFS_KEY_BLOCKLIST_ENABLED, core );
519    updateBlocklistText( w, TR_CORE( core ) );
520    h = gtk_hbox_new( FALSE, GUI_PAD_BIG );
521    gtk_box_pack_start( GTK_BOX( h ), w, TRUE, TRUE, 0 );
522    b = data->updateBlocklistButton = gtr_button_new_from_stock( GTK_STOCK_REFRESH, _( "_Update" ) );
523    data->check = w;
524    g_object_set_data( G_OBJECT( b ), "session",
525                      tr_core_session( TR_CORE( core ) ) );
526    g_signal_connect( b, "clicked", G_CALLBACK( onBlocklistUpdate ), data );
527    gtk_box_pack_start( GTK_BOX( h ), b, FALSE, FALSE, 0 );
528    g_signal_connect( w, "toggled", G_CALLBACK( target_cb ), b );
529    target_cb( w, b );
530    hig_workarea_add_wide_control( t, &row, h );
531
532    s = _( "Enable _automatic updates" );
533    w = new_check_button( s, PREF_KEY_BLOCKLIST_UPDATES_ENABLED, core );
534    hig_workarea_add_wide_control( t, &row, w );
535    g_signal_connect( data->check, "toggled", G_CALLBACK( target_cb ), w );
536    target_cb( data->check, w );
537
538    hig_workarea_add_section_divider( t, &row );
539    hig_workarea_add_section_title ( t, &row, _( "Privacy" ) );
540
541    s = _( "_Encryption mode:" );
542    w = new_encryption_combo( core, "encryption" );
543    hig_workarea_add_row( t, &row, s, w, NULL );
544
545    s = _( "Use PE_X to find more peers" );
546    w = new_check_button( s, TR_PREFS_KEY_PEX_ENABLED, core );
547    s = _( "PEX is a tool for exchanging peer lists with the peers you're connected to." );
548    gtr_widget_set_tooltip_text( w, s );
549    hig_workarea_add_wide_control( t, &row, w );
550
551    s = _( "Use _DHT to find more peers" );
552    w = new_check_button( s, TR_PREFS_KEY_DHT_ENABLED, core );
553    s = _( "DHT is a tool for finding peers without a tracker." );
554    gtr_widget_set_tooltip_text( w, s );
555    hig_workarea_add_wide_control( t, &row, w );
556
557    hig_workarea_finish( t, &row );
558    g_object_weak_ref( G_OBJECT( t ), privacyPageDestroyed, data );
559    return t;
560}
561
562/****
563*****  Web Tab
564****/
565
566enum
567{
568    COL_ADDRESS,
569    N_COLS
570};
571
572static GtkTreeModel*
573whitelist_tree_model_new( const char * whitelist )
574{
575    int            i;
576    char **        rules;
577    GtkListStore * store = gtk_list_store_new( N_COLS,
578                                               G_TYPE_STRING,
579                                               G_TYPE_STRING );
580
581    rules = g_strsplit( whitelist, ",", 0 );
582
583    for( i = 0; rules && rules[i]; ++i )
584    {
585        GtkTreeIter iter;
586        const char * s = rules[i];
587        while( isspace( *s ) ) ++s;
588        gtk_list_store_append( store, &iter );
589        gtk_list_store_set( store, &iter, COL_ADDRESS, s, -1 );
590    }
591
592    g_strfreev( rules );
593    return GTK_TREE_MODEL( store );
594}
595
596struct remote_page
597{
598    TrCore *           core;
599    GtkTreeView *      view;
600    GtkListStore *     store;
601    GtkWidget *        remove_button;
602    GSList *           widgets;
603    GSList *           auth_widgets;
604    GSList *           whitelist_widgets;
605    GtkToggleButton *  rpc_tb;
606    GtkToggleButton *  auth_tb;
607    GtkToggleButton *  whitelist_tb;
608};
609
610static void
611refreshWhitelist( struct remote_page * page )
612{
613    GtkTreeIter    iter;
614    GtkTreeModel * model = GTK_TREE_MODEL( page->store );
615    GString *      gstr = g_string_new( NULL );
616
617    if( gtk_tree_model_get_iter_first( model, &iter ) ) do
618        {
619            char * address;
620            gtk_tree_model_get( model, &iter,
621                                COL_ADDRESS, &address,
622                                -1 );
623            g_string_append( gstr, address );
624            g_string_append( gstr, "," );
625            g_free( address );
626        }
627        while( gtk_tree_model_iter_next( model, &iter ) );
628
629    g_string_truncate( gstr, gstr->len - 1 ); /* remove the trailing comma */
630
631    tr_core_set_pref( page->core, TR_PREFS_KEY_RPC_WHITELIST, gstr->str );
632
633    g_string_free( gstr, TRUE );
634}
635
636static void
637onAddressEdited( GtkCellRendererText  * r UNUSED,
638                 gchar *                  path_string,
639                 gchar *                  address,
640                 gpointer                 gpage )
641{
642    GtkTreeIter          iter;
643    struct remote_page * page = gpage;
644    GtkTreeModel *       model = GTK_TREE_MODEL( page->store );
645    GtkTreePath *        path = gtk_tree_path_new_from_string( path_string );
646
647    if( gtk_tree_model_get_iter( model, &iter, path ) )
648        gtk_list_store_set( page->store, &iter, COL_ADDRESS, address, -1 );
649
650    gtk_tree_path_free( path );
651    refreshWhitelist( page );
652}
653
654static void
655onAddWhitelistClicked( GtkButton * b UNUSED,
656                 gpointer      gpage )
657{
658    GtkTreeIter          iter;
659    GtkTreePath *        path;
660    struct remote_page * page = gpage;
661
662    gtk_list_store_append( page->store, &iter );
663    gtk_list_store_set( page->store, &iter,
664                        COL_ADDRESS,  "0.0.0.0",
665                        -1 );
666
667    path = gtk_tree_model_get_path( GTK_TREE_MODEL( page->store ), &iter );
668    gtk_tree_view_set_cursor(
669        page->view, path,
670        gtk_tree_view_get_column( page->view, COL_ADDRESS ),
671        TRUE );
672    gtk_tree_path_free( path );
673}
674
675static void
676onRemoveWhitelistClicked( GtkButton * b UNUSED,
677                    gpointer      gpage )
678{
679    struct remote_page * page = gpage;
680    GtkTreeSelection *   sel = gtk_tree_view_get_selection( page->view );
681    GtkTreeIter          iter;
682
683    if( gtk_tree_selection_get_selected( sel, NULL, &iter ) )
684    {
685        gtk_list_store_remove( page->store, &iter );
686        refreshWhitelist( page );
687    }
688}
689
690static void
691refreshRPCSensitivity( struct remote_page * page )
692{
693    GSList *           l;
694    const int          rpc_active = gtk_toggle_button_get_active(
695        page->rpc_tb );
696    const int          auth_active = gtk_toggle_button_get_active(
697        page->auth_tb );
698    const int          whitelist_active = gtk_toggle_button_get_active(
699        page->whitelist_tb );
700    GtkTreeSelection * sel = gtk_tree_view_get_selection( page->view );
701    const int          have_addr =
702        gtk_tree_selection_get_selected( sel, NULL,
703                                         NULL );
704    const int          n_rules = gtk_tree_model_iter_n_children(
705        GTK_TREE_MODEL( page->store ), NULL );
706
707    for( l = page->widgets; l != NULL; l = l->next )
708        gtk_widget_set_sensitive( GTK_WIDGET( l->data ), rpc_active );
709
710    for( l = page->auth_widgets; l != NULL; l = l->next )
711        gtk_widget_set_sensitive( GTK_WIDGET(
712                                      l->data ), rpc_active && auth_active );
713
714    for( l = page->whitelist_widgets; l != NULL; l = l->next )
715        gtk_widget_set_sensitive( GTK_WIDGET( l->data ),
716                                  rpc_active && whitelist_active );
717
718    gtk_widget_set_sensitive( page->remove_button,
719                              rpc_active && have_addr && n_rules > 1 );
720}
721
722static void
723onRPCToggled( GtkToggleButton * tb UNUSED,
724              gpointer             page )
725{
726    refreshRPCSensitivity( page );
727}
728
729static void
730onWhitelistSelectionChanged( GtkTreeSelection * sel UNUSED,
731                       gpointer               page )
732{
733    refreshRPCSensitivity( page );
734}
735
736static void
737onLaunchClutchCB( GtkButton * w UNUSED,
738                  gpointer data UNUSED )
739{
740    int    port = pref_int_get( TR_PREFS_KEY_RPC_PORT );
741    char * url = g_strdup_printf( "http://localhost:%d/transmission/web",
742                                  port );
743
744    gtr_open_file( url );
745    g_free( url );
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 = gtr_button_new_from_stock( GTK_STOCK_OPEN, _( "_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, _( "Listening _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 = 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*****  Proxy Tab
897****/
898
899struct ProxyPage
900{
901    TrCore *  core;
902    GSList *  proxy_widgets;
903    GSList *  proxy_auth_widgets;
904};
905
906static void
907refreshProxySensitivity( struct ProxyPage * p )
908{
909    GSList *       l;
910    const gboolean proxy_enabled = pref_flag_get( TR_PREFS_KEY_PROXY_ENABLED );
911    const gboolean proxy_auth_enabled = pref_flag_get( TR_PREFS_KEY_PROXY_AUTH_ENABLED );
912
913    for( l = p->proxy_widgets; l != NULL; l = l->next )
914        gtk_widget_set_sensitive( GTK_WIDGET( l->data ), proxy_enabled );
915
916    for( l = p->proxy_auth_widgets; l != NULL; l = l->next )
917        gtk_widget_set_sensitive( GTK_WIDGET( l->data ),
918                                  proxy_enabled && proxy_auth_enabled );
919}
920
921static void
922onProxyToggled( GtkToggleButton * tb UNUSED,
923                gpointer             user_data )
924{
925    refreshProxySensitivity( user_data );
926}
927
928static void
929proxyPageFree( gpointer gpage )
930{
931    struct ProxyPage * page = gpage;
932
933    g_slist_free( page->proxy_widgets );
934    g_slist_free( page->proxy_auth_widgets );
935    g_free( page );
936}
937
938static GtkTreeModel*
939proxyTypeModelNew( void )
940{
941    GtkTreeIter    iter;
942    GtkListStore * store = gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_INT );
943
944    gtk_list_store_append( store, &iter );
945    gtk_list_store_set( store, &iter, 0, "HTTP", 1, TR_PROXY_HTTP, -1 );
946    gtk_list_store_append( store, &iter );
947    gtk_list_store_set( store, &iter, 0, "SOCKS4", 1, TR_PROXY_SOCKS4, -1 );
948    gtk_list_store_append( store, &iter );
949    gtk_list_store_set( store, &iter, 0, "SOCKS5", 1, TR_PROXY_SOCKS5, -1 );
950    return GTK_TREE_MODEL( store );
951}
952
953static void
954onProxyTypeChanged( GtkComboBox * w,
955                    gpointer      gpage )
956{
957    GtkTreeIter iter;
958
959    if( gtk_combo_box_get_active_iter( w, &iter ) )
960    {
961        struct ProxyPage * page = gpage;
962        int type = TR_PROXY_HTTP;
963        gtk_tree_model_get( gtk_combo_box_get_model( w ), &iter, 1, &type, -1 );
964        tr_core_set_pref_int( TR_CORE( page->core ), TR_PREFS_KEY_PROXY_TYPE, type );
965    }
966}
967
968static GtkWidget*
969trackerPage( GObject * core )
970{
971    int                row = 0;
972    const char *       s;
973    GtkWidget *        t;
974    GtkWidget *        w;
975    GtkTreeModel *     m;
976    GtkCellRenderer *  r;
977    struct ProxyPage * page = tr_new0( struct ProxyPage, 1 );
978
979    page->core = TR_CORE( core );
980
981    t = hig_workarea_create( );
982    hig_workarea_add_section_title ( t, &row, _( "Tracker" ) );
983
984    s = _( "Connect to tracker via a pro_xy" );
985    w = new_check_button( s, TR_PREFS_KEY_PROXY_ENABLED, core );
986    g_signal_connect( w, "toggled", G_CALLBACK( onProxyToggled ), page );
987    hig_workarea_add_wide_control( t, &row, w );
988
989    s = _( "Proxy _server:" );
990    w = new_entry( TR_PREFS_KEY_PROXY, core );
991    page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
992    w = hig_workarea_add_row( t, &row, s, w, NULL );
993    page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
994
995    w = new_spin_button( TR_PREFS_KEY_PROXY_PORT, core, 0, USHRT_MAX, 1 );
996    page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
997    w = hig_workarea_add_row( t, &row, _( "Proxy _port:" ), w, NULL );
998    page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
999
1000    s = _( "Proxy _type:" );
1001    m = proxyTypeModelNew( );
1002    w = gtk_combo_box_new_with_model( m );
1003    r = gtk_cell_renderer_text_new( );
1004    gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( w ), r, TRUE );
1005    gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT( w ), r, "text", 0, NULL );
1006    gtk_combo_box_set_active( GTK_COMBO_BOX( w ), pref_int_get( TR_PREFS_KEY_PROXY_TYPE ) );
1007    g_signal_connect( w, "changed", G_CALLBACK( onProxyTypeChanged ), page );
1008    g_object_unref( G_OBJECT( m ) );
1009    page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
1010    w = hig_workarea_add_row( t, &row, s, w, NULL );
1011    page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
1012
1013    s = _( "Use _authentication" );
1014    w = new_check_button( s, TR_PREFS_KEY_PROXY_AUTH_ENABLED, core );
1015    g_signal_connect( w, "toggled", G_CALLBACK( onProxyToggled ), page );
1016    hig_workarea_add_wide_control( t, &row, w );
1017    page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
1018
1019    s = _( "_Username:" );
1020    w = new_entry( TR_PREFS_KEY_PROXY_USERNAME, core );
1021    page->proxy_auth_widgets = g_slist_append( page->proxy_auth_widgets, w );
1022    w = hig_workarea_add_row( t, &row, s, w, NULL );
1023    page->proxy_auth_widgets = g_slist_append( page->proxy_auth_widgets, w );
1024
1025    s = _( "Pass_word:" );
1026    w = new_entry( TR_PREFS_KEY_PROXY_PASSWORD, core );
1027    gtk_entry_set_visibility( GTK_ENTRY( w ), FALSE );
1028    page->proxy_auth_widgets = g_slist_append( page->proxy_auth_widgets, w );
1029    w = hig_workarea_add_row( t, &row, s, w, NULL );
1030    page->proxy_auth_widgets = g_slist_append( page->proxy_auth_widgets, w );
1031
1032    hig_workarea_finish( t, &row );
1033    g_object_set_data_full( G_OBJECT( t ), "page", page, proxyPageFree );
1034
1035    refreshProxySensitivity( page );
1036    return t;
1037}
1038
1039/****
1040*****  Bandwidth Tab
1041****/
1042
1043struct BandwidthPage
1044{
1045    TrCore *  core;
1046    GSList *  sched_widgets;
1047};
1048
1049static void
1050refreshSchedSensitivity( struct BandwidthPage * p )
1051{
1052    GSList *       l;
1053    const gboolean sched_enabled = pref_flag_get( TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED );
1054
1055    for( l = p->sched_widgets; l != NULL; l = l->next )
1056        gtk_widget_set_sensitive( GTK_WIDGET( l->data ), sched_enabled );
1057}
1058
1059static void
1060onSchedToggled( GtkToggleButton * tb UNUSED,
1061                gpointer             user_data )
1062{
1063    refreshSchedSensitivity( user_data );
1064}
1065
1066static void
1067onTimeComboChanged( GtkComboBox * w,
1068                    gpointer      core )
1069{
1070    GtkTreeIter iter;
1071
1072    if( gtk_combo_box_get_active_iter( w, &iter ) )
1073    {
1074        const char * key = g_object_get_data( G_OBJECT( w ), PREF_KEY );
1075        int          val = 0;
1076        gtk_tree_model_get( gtk_combo_box_get_model(
1077                                w ), &iter, 0, &val, -1 );
1078        tr_core_set_pref_int( TR_CORE( core ), key, val );
1079    }
1080}
1081
1082static GtkWidget*
1083new_time_combo( GObject *    core,
1084                const char * key )
1085{
1086    int               val;
1087    int               i;
1088    GtkWidget *       w;
1089    GtkCellRenderer * r;
1090    GtkListStore *    store;
1091
1092    /* build a store at 15 minute intervals */
1093    store = gtk_list_store_new( 2, G_TYPE_INT, G_TYPE_STRING );
1094    for( i = 0; i < 60 * 24; i += 15 )
1095    {
1096        char        buf[128];
1097        GtkTreeIter iter;
1098        struct tm   tm;
1099        tm.tm_hour = i / 60;
1100        tm.tm_min = i % 60;
1101        tm.tm_sec = 0;
1102        strftime( buf, sizeof( buf ), "%H:%M", &tm );
1103        gtk_list_store_append( store, &iter );
1104        gtk_list_store_set( store, &iter, 0, i, 1, buf, -1 );
1105    }
1106
1107    /* build the widget */
1108    w = gtk_combo_box_new_with_model( GTK_TREE_MODEL( store ) );
1109    gtk_combo_box_set_wrap_width( GTK_COMBO_BOX( w ), 4 );
1110    r = gtk_cell_renderer_text_new( );
1111    gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( w ), r, TRUE );
1112    gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT(
1113                                        w ), r, "text", 1, NULL );
1114    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, tr_strdup(
1115                                key ), g_free );
1116    val = pref_int_get( key );
1117    gtk_combo_box_set_active( GTK_COMBO_BOX( w ), val / ( 15 ) );
1118    g_signal_connect( w, "changed", G_CALLBACK( onTimeComboChanged ), core );
1119
1120    /* cleanup */
1121    g_object_unref( G_OBJECT( store ) );
1122    return w;
1123}
1124
1125static GtkWidget*
1126new_week_combo( GObject * core, const char * key )
1127{
1128    int i;
1129    int selIndex;
1130    GtkWidget * w;
1131    GtkCellRenderer * r;
1132    GtkListStore * store;
1133    const int currentValue = pref_int_get( key );
1134    const struct {
1135        int value;
1136        const char * text;
1137    } items[] = {
1138        { TR_SCHED_ALL,     N_( "Every Day" ) },
1139        { TR_SCHED_WEEKDAY, N_( "Weekdays" ) },
1140        { TR_SCHED_WEEKEND, N_( "Weekends" ) },
1141        { TR_SCHED_SUN,     N_( "Sunday" ) },
1142        { TR_SCHED_MON,     N_( "Monday" ) },
1143        { TR_SCHED_TUES,    N_( "Tuesday" ) },
1144        { TR_SCHED_WED,     N_( "Wednesday" ) },
1145        { TR_SCHED_THURS,   N_( "Thursday" ) },
1146        { TR_SCHED_FRI,     N_( "Friday" ) },
1147        { TR_SCHED_SAT,     N_( "Saturday" ) }
1148    };
1149
1150    /* build a store for the days of the week */
1151    selIndex = -1;
1152    store = gtk_list_store_new( 2, G_TYPE_INT, G_TYPE_STRING );
1153    for( i=0; i<(int)G_N_ELEMENTS(items); ++i ) {
1154        GtkTreeIter iter;
1155        gtk_list_store_append( store, &iter );
1156        gtk_list_store_set( store, &iter, 0, items[i].value, 1, _( items[i].text ), -1 );
1157        if( items[i].value == currentValue )
1158            selIndex = i;
1159    }
1160
1161    /* build the widget */
1162    w = gtk_combo_box_new_with_model( GTK_TREE_MODEL( store ) );
1163    r = gtk_cell_renderer_text_new( );
1164    gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( w ), r, TRUE );
1165    gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT( w ), r, "text", 1, NULL );
1166    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, tr_strdup( key ), g_free );
1167    if( selIndex >= 0 )
1168        gtk_combo_box_set_active( GTK_COMBO_BOX( w ), selIndex );
1169    g_signal_connect( w, "changed", G_CALLBACK( onIntComboChanged ), core );
1170
1171    /* cleanup */
1172    g_object_unref( G_OBJECT( store ) );
1173    return w;
1174}
1175
1176static void
1177bandwidthPageFree( gpointer gpage )
1178{
1179    struct BandwidthPage * page = gpage;
1180
1181    g_slist_free( page->sched_widgets );
1182    g_free( page );
1183}
1184
1185static GtkWidget*
1186bandwidthPage( GObject * core )
1187{
1188    int row = 0;
1189    const char * s;
1190    GtkWidget * t;
1191    GtkWidget * l;
1192    GtkWidget * w, * w2, * h;
1193    char buf[512];
1194    struct BandwidthPage * page = tr_new0( struct BandwidthPage, 1 );
1195
1196    page->core = TR_CORE( core );
1197
1198    t = hig_workarea_create( );
1199    hig_workarea_add_section_title( t, &row, _( "Speed Limits" ) );
1200
1201        s = _( "Limit _download speed (KB/s):" );
1202        w = new_check_button( s, TR_PREFS_KEY_DSPEED_ENABLED, core );
1203        w2 = new_spin_button( TR_PREFS_KEY_DSPEED, core, 0, INT_MAX, 5 );
1204        gtk_widget_set_sensitive( GTK_WIDGET( w2 ), pref_flag_get( TR_PREFS_KEY_DSPEED_ENABLED ) );
1205        g_signal_connect( w, "toggled", G_CALLBACK( target_cb ), w2 );
1206        hig_workarea_add_row_w( t, &row, w, w2, NULL );
1207
1208        s = _( "Limit _upload speed (KB/s):" );
1209        w = new_check_button( s, TR_PREFS_KEY_USPEED_ENABLED, core );
1210        w2 = new_spin_button( TR_PREFS_KEY_USPEED, core, 0, INT_MAX, 5 );
1211        gtk_widget_set_sensitive( GTK_WIDGET( w2 ), pref_flag_get( TR_PREFS_KEY_USPEED_ENABLED ) );
1212        g_signal_connect( w, "toggled", G_CALLBACK( target_cb ), w2 );
1213        hig_workarea_add_row_w( t, &row, w, w2, NULL );
1214
1215    hig_workarea_add_section_divider( t, &row );
1216    h = gtk_hbox_new( FALSE, GUI_PAD );
1217    w = gtk_image_new_from_stock( "alt-speed-off", -1 );
1218    gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 );
1219    g_snprintf( buf, sizeof( buf ), "<b>%s</b>", _( "Temporary Speed Limits" ) );
1220    w = gtk_label_new( buf );
1221    gtk_misc_set_alignment( GTK_MISC( w ), 0.0f, 0.5f );
1222    gtk_label_set_use_markup( GTK_LABEL( w ), TRUE );
1223    gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 );
1224    hig_workarea_add_section_title_widget( t, &row, h );
1225
1226        s = _( "Override normal speed limits manually or at scheduled times" );
1227        g_snprintf( buf, sizeof( buf ), "<small>%s</small>", s );
1228        w = gtk_label_new( buf );
1229        gtk_label_set_use_markup( GTK_LABEL( w ), TRUE );
1230        gtk_misc_set_alignment( GTK_MISC( w ), 0.5f, 0.5f );
1231        hig_workarea_add_wide_control( t, &row, w );
1232
1233        s = _( "Limit do_wnload speed (KB/s):" );
1234        w = new_spin_button( TR_PREFS_KEY_ALT_SPEED_DOWN, core, 0, INT_MAX, 5 );
1235        hig_workarea_add_row( t, &row, s, w, NULL );
1236
1237        s = _( "Limit u_pload speed (KB/s):" );
1238        w = new_spin_button( TR_PREFS_KEY_ALT_SPEED_UP, core, 0, INT_MAX, 5 );
1239        hig_workarea_add_row( t, &row, s, w, NULL );
1240
1241        s = _( "_Scheduled times:" );
1242        h = gtk_hbox_new( FALSE, 0 );
1243        w2 = new_time_combo( core, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN );
1244        page->sched_widgets = g_slist_append( page->sched_widgets, w2 );
1245        gtk_box_pack_start( GTK_BOX( h ), w2, TRUE, TRUE, 0 );
1246        w2 = l = gtk_label_new_with_mnemonic ( _( " _to " ) );
1247        page->sched_widgets = g_slist_append( page->sched_widgets, w2 );
1248        gtk_box_pack_start( GTK_BOX( h ), w2, FALSE, FALSE, 0 );
1249        w2 = new_time_combo( core, TR_PREFS_KEY_ALT_SPEED_TIME_END );
1250        gtk_label_set_mnemonic_widget( GTK_LABEL( l ), w2 );
1251        page->sched_widgets = g_slist_append( page->sched_widgets, w2 );
1252        gtk_box_pack_start( GTK_BOX( h ), w2, TRUE, TRUE, 0 );
1253        w = new_check_button( s, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, core );
1254        g_signal_connect( w, "toggled", G_CALLBACK( onSchedToggled ), page );
1255        hig_workarea_add_row_w( t, &row, w, h, NULL );
1256
1257        s = _( "_On days:" );
1258        w = new_week_combo( core, TR_PREFS_KEY_ALT_SPEED_TIME_DAY );
1259        page->sched_widgets = g_slist_append( page->sched_widgets, w );
1260        w = hig_workarea_add_row( t, &row, s, w, NULL );
1261        page->sched_widgets = g_slist_append( page->sched_widgets, w );
1262
1263    hig_workarea_finish( t, &row );
1264    g_object_set_data_full( G_OBJECT( t ), "page", page, bandwidthPageFree );
1265
1266    refreshSchedSensitivity( page );
1267    return t;
1268}
1269
1270/****
1271*****  Network Tab
1272****/
1273
1274struct network_page_data
1275{
1276    TrCore     * core;
1277    GtkWidget  * portLabel;
1278    GtkWidget  * portButton;
1279    GtkWidget  * portSpin;
1280    gulong       portTag;
1281    gulong       prefsTag;
1282};
1283
1284static void
1285onCorePrefsChanged( TrCore * core UNUSED, const char *  key, gpointer gdata )
1286{
1287    if( !strcmp( key, TR_PREFS_KEY_PEER_PORT ) )
1288    {
1289        struct network_page_data * data = gdata;
1290        gdk_threads_enter();
1291        gtk_label_set_text( GTK_LABEL( data->portLabel ), _( "Status unknown" ) );
1292        gtk_widget_set_sensitive( data->portButton, TRUE );
1293        gtk_widget_set_sensitive( data->portSpin, TRUE );
1294        gdk_threads_leave();
1295    }
1296}
1297
1298static void
1299peerPageDestroyed( gpointer gdata, GObject * dead UNUSED )
1300{
1301    struct network_page_data * data = gdata;
1302    if( data->prefsTag > 0 )
1303        g_signal_handler_disconnect( data->core, data->prefsTag );
1304    if( data->portTag > 0 )
1305        g_signal_handler_disconnect( data->core, data->portTag );
1306    g_free( data );
1307}
1308
1309static void
1310onPortTested( TrCore * core UNUSED, gboolean isOpen, gpointer vdata )
1311{
1312    struct network_page_data * data = vdata;
1313    const char * markup = isOpen ? _( "Port is <b>open</b>" ) : _( "Port is <b>closed</b>" );
1314    gdk_threads_enter();
1315    gtk_label_set_markup( GTK_LABEL( data->portLabel ), markup );
1316    gtk_widget_set_sensitive( data->portButton, TRUE );
1317    gtk_widget_set_sensitive( data->portSpin, TRUE );
1318    gdk_threads_leave();
1319}
1320
1321static void
1322onPortTest( GtkButton * button UNUSED, gpointer vdata )
1323{
1324    struct network_page_data * data = vdata;
1325    gtk_widget_set_sensitive( data->portButton, FALSE );
1326    gtk_widget_set_sensitive( data->portSpin, FALSE );
1327    gtk_label_set_markup( GTK_LABEL( data->portLabel ), _( "<i>Testing...</i>" ) );
1328    if( !data->portTag )
1329        data->portTag = g_signal_connect( data->core, "port-tested", G_CALLBACK(onPortTested), data );
1330    tr_core_port_test( data->core );
1331}
1332
1333static GtkWidget*
1334peerPage( GObject * core )
1335{
1336    int                        row = 0;
1337    const char *               s;
1338    GtkWidget *                t;
1339    GtkWidget *                w;
1340    GtkWidget *                h;
1341    GtkWidget *                l;
1342    struct network_page_data * data;
1343
1344    /* register to stop listening to core prefs changes when the page is destroyed */
1345    data = g_new0( struct network_page_data, 1 );
1346    data->core = TR_CORE( core );
1347
1348    /* build the page */
1349    t = hig_workarea_create( );
1350    hig_workarea_add_section_title( t, &row, _( "Incoming Peers" ) );
1351
1352    s = _( "_Port for incoming connections:" );
1353    w = data->portSpin = new_spin_button( TR_PREFS_KEY_PEER_PORT, core, 1, USHRT_MAX, 1 );
1354    hig_workarea_add_row( t, &row, s, w, NULL );
1355
1356    h = gtk_hbox_new( FALSE, GUI_PAD_BIG );
1357    l = data->portLabel = gtk_label_new( _( "Status unknown" ) );
1358    gtk_misc_set_alignment( GTK_MISC( l ), 0.0f, 0.5f );
1359    gtk_box_pack_start( GTK_BOX( h ), l, TRUE, TRUE, 0 );
1360    w = data->portButton = gtk_button_new_with_mnemonic( _( "Te_st Port" ) );
1361    gtk_box_pack_end( GTK_BOX( h ), w, FALSE, FALSE, 0 );
1362    g_signal_connect( w, "clicked", G_CALLBACK(onPortTest), data );
1363    hig_workarea_add_row( t, &row, NULL, h, NULL );
1364    data->prefsTag = g_signal_connect( TR_CORE( core ), "prefs-changed", G_CALLBACK( onCorePrefsChanged ), data );
1365    g_object_weak_ref( G_OBJECT( t ), peerPageDestroyed, data );
1366
1367    s = _( "Use UPnP or NAT-PMP port _forwarding from my router" );
1368    w = new_check_button( s, TR_PREFS_KEY_PORT_FORWARDING, core );
1369    hig_workarea_add_wide_control( t, &row, w );
1370
1371    s = _( "Pick a _random port every time Transmission is started" );
1372    w = new_check_button( s, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START, core );
1373    hig_workarea_add_wide_control( t, &row, w );
1374
1375    hig_workarea_add_section_divider( t, &row );
1376    hig_workarea_add_section_title( t, &row, _( "Limits" ) );
1377
1378    w = new_spin_button( TR_PREFS_KEY_PEER_LIMIT_TORRENT, core, 1, 300, 5 );
1379    hig_workarea_add_row( t, &row, _( "Maximum peers per _torrent:" ), w, NULL );
1380    w = new_spin_button( TR_PREFS_KEY_PEER_LIMIT_GLOBAL, core, 1, 3000, 5 );
1381    hig_workarea_add_row( t, &row, _( "Maximum peers _overall:" ), w, NULL );
1382
1383    hig_workarea_finish( t, &row );
1384    return t;
1385}
1386
1387/****
1388*****
1389****/
1390
1391GtkWidget *
1392tr_prefs_dialog_new( GObject *   core,
1393                     GtkWindow * parent )
1394{
1395    GtkWidget * d;
1396    GtkWidget * n;
1397
1398    d = gtk_dialog_new_with_buttons( _(
1399                                         "Transmission Preferences" ),
1400                                     parent,
1401                                     GTK_DIALOG_DESTROY_WITH_PARENT,
1402                                     GTK_STOCK_HELP, GTK_RESPONSE_HELP,
1403                                     GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1404                                     NULL );
1405    gtk_window_set_role( GTK_WINDOW( d ), "transmission-preferences-dialog" );
1406    gtk_dialog_set_has_separator( GTK_DIALOG( d ), FALSE );
1407    gtk_container_set_border_width( GTK_CONTAINER( d ), GUI_PAD );
1408
1409    n = gtk_notebook_new( );
1410    gtk_container_set_border_width ( GTK_CONTAINER ( n ), GUI_PAD );
1411
1412    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1413                              torrentPage( core ),
1414                              gtk_label_new ( _( "Torrents" ) ) );
1415    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1416                              bandwidthPage( core ),
1417                              gtk_label_new ( _( "Speed" ) ) );
1418    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1419                              privacyPage( core ),
1420                              gtk_label_new ( _( "Privacy" ) ) );
1421    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1422                              peerPage( core ),
1423                              gtk_label_new ( _( "Network" ) ) );
1424    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1425                              desktopPage( core ),
1426                              gtk_label_new ( _( "Desktop" ) ) );
1427    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1428                              webPage( core ),
1429                              gtk_label_new ( _( "Web" ) ) );
1430    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1431                              trackerPage( core ),
1432                              gtk_label_new ( _( "Proxy" ) ) );
1433
1434    g_signal_connect( d, "response", G_CALLBACK( response_cb ), core );
1435    gtk_box_pack_start( GTK_BOX( GTK_DIALOG( d )->vbox ), n, TRUE, TRUE, 0 );
1436    gtk_widget_show_all( GTK_DIALOG( d )->vbox );
1437    return d;
1438}
1439
Note: See TracBrowser for help on using the repository browser.