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

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

(trunk gtk) in the prefs dialog, swap the order of "random port" and "port forwarding"

  • Property svn:keywords set to Date Rev Author Id
File size: 49.8 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 10673 2010-05-20 16:39:23Z 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, gpointer core )
227{
228    const char * key = g_object_get_data( G_OBJECT( w ), PREF_KEY );
229    char * value = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER( w ) );
230    tr_core_set_pref( TR_CORE( core ), key, value );
231    g_free( value );
232}
233
234static GtkWidget*
235new_path_chooser_button( const char * key, gpointer core )
236{
237    GtkWidget *  w = gtk_file_chooser_button_new( NULL, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER );
238    const char * path = pref_string_get( key );
239    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, g_strdup( key ), g_free );
240    g_signal_connect( w, "selection-changed", G_CALLBACK( chosen_cb ), core );
241    if( path != NULL )
242        gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER( w ), path );
243    return w;
244}
245
246static GtkWidget*
247new_file_chooser_button( const char * key, gpointer core )
248{
249    GtkWidget *  w = gtk_file_chooser_button_new( NULL, GTK_FILE_CHOOSER_ACTION_OPEN );
250    const char * path = pref_string_get( key );
251    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, g_strdup( key ), g_free );
252    if( path != NULL )
253        gtk_file_chooser_set_filename( GTK_FILE_CHOOSER( w ), path );
254    g_signal_connect( w, "selection-changed", G_CALLBACK( chosen_cb ), core );
255    return w;
256}
257
258static void
259target_cb( GtkWidget * tb,
260           gpointer    target )
261{
262    const gboolean b = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( tb ) );
263
264    gtk_widget_set_sensitive( GTK_WIDGET( target ), b );
265}
266
267/****
268*****  Torrent Tab
269****/
270
271static GtkWidget*
272torrentPage( GObject * core )
273{
274    int          row = 0;
275    const char * s;
276    GtkWidget *  t;
277    GtkWidget *  w;
278    GtkWidget *  w2;
279    GtkWidget *  l;
280
281    t = hig_workarea_create( );
282    hig_workarea_add_section_title( t, &row, _( "Adding Torrents" ) );
283
284#ifdef HAVE_GIO
285    s = _( "Automatically _add torrents from:" );
286    l = new_check_button( s, PREF_KEY_DIR_WATCH_ENABLED, core );
287    w = new_path_chooser_button( PREF_KEY_DIR_WATCH, core );
288    gtk_widget_set_sensitive( GTK_WIDGET( w ),
289                             pref_flag_get( PREF_KEY_DIR_WATCH_ENABLED ) );
290    g_signal_connect( l, "toggled", G_CALLBACK( target_cb ), w );
291    hig_workarea_add_row_w( t, &row, l, w, NULL );
292#endif
293
294    s = _( "Show _options dialog" );
295    w = new_check_button( s, PREF_KEY_OPTIONS_PROMPT, core );
296    hig_workarea_add_wide_control( t, &row, w );
297
298    s = _( "_Start when added" );
299    w = new_check_button( s, TR_PREFS_KEY_START, core );
300    hig_workarea_add_wide_control( t, &row, w );
301
302    s = _( "Mo_ve .torrent file to the trash" );
303    w = new_check_button( s, TR_PREFS_KEY_TRASH_ORIGINAL, core );
304    hig_workarea_add_wide_control( t, &row, w );
305
306    s = _( "Append \"._part\" to incomplete files' names" );
307    w = new_check_button( s, TR_PREFS_KEY_RENAME_PARTIAL_FILES, core );
308    hig_workarea_add_wide_control( t, &row, w );
309
310    w = new_path_chooser_button( TR_PREFS_KEY_DOWNLOAD_DIR, core );
311    hig_workarea_add_row( t, &row, _( "Save to _Location:" ), w, NULL );
312
313    s = _( "Keep _incomplete torrents in:" );
314    l = new_check_button( s, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED, core );
315    w = new_path_chooser_button( TR_PREFS_KEY_INCOMPLETE_DIR, core );
316    gtk_widget_set_sensitive( GTK_WIDGET( w ), pref_flag_get( TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED ) );
317    g_signal_connect( l, "toggled", G_CALLBACK( target_cb ), w );
318    hig_workarea_add_row_w( t, &row, l, w, NULL );
319
320    s = _( "Call scrip_t when torrent is completed:" );
321    l = new_check_button( s, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED, core );
322    w = new_file_chooser_button( TR_PREFS_KEY_SCRIPT_TORRENT_DONE_FILENAME, core );
323    gtk_widget_set_sensitive( GTK_WIDGET( w ), pref_flag_get( TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED ) );
324    g_signal_connect( l, "toggled", G_CALLBACK( target_cb ), w );
325    hig_workarea_add_row_w( t, &row, l, w, NULL );
326
327    hig_workarea_add_section_divider( t, &row );
328    hig_workarea_add_section_title( t, &row, _( "Limits" ) );
329
330    s = _( "_Seed torrent until its ratio reaches:" );
331    w = new_check_button( s, TR_PREFS_KEY_RATIO_ENABLED, core );
332    w2 = new_spin_button_double( TR_PREFS_KEY_RATIO, core, 0, INT_MAX, .05 );
333    gtk_widget_set_sensitive( GTK_WIDGET( w2 ), pref_flag_get( TR_PREFS_KEY_RATIO_ENABLED ) );
334    g_signal_connect( w, "toggled", G_CALLBACK( target_cb ), w2 );
335    hig_workarea_add_row_w( t, &row, w, w2, NULL );
336
337    hig_workarea_finish( t, &row );
338    return t;
339}
340
341/****
342*****  Desktop Tab
343****/
344
345static GtkWidget*
346desktopPage( GObject * core )
347{
348    int          row = 0;
349    const char * s;
350    GtkWidget *  t;
351    GtkWidget *  w;
352
353    t = hig_workarea_create( );
354    hig_workarea_add_section_title( t, &row, _( "Desktop" ) );
355
356    s = _( "Inhibit _hibernation when torrents are active" );
357    w = new_check_button( s, PREF_KEY_INHIBIT_HIBERNATION, core );
358    hig_workarea_add_wide_control( t, &row, w );
359
360    s = _( "Show Transmission icon in the _notification area" );
361    w = new_check_button( s, PREF_KEY_SHOW_TRAY_ICON, core );
362    hig_workarea_add_wide_control( t, &row, w );
363
364    s = _( "Show _popup notifications" );
365    w = new_check_button( s, PREF_KEY_SHOW_DESKTOP_NOTIFICATION, core );
366    hig_workarea_add_wide_control( t, &row, w );
367
368#ifdef HAVE_LIBCANBERRA
369    s = _( "Play _sound when downloads are complete" );
370    w = new_check_button( s, PREF_KEY_PLAY_DOWNLOAD_COMPLETE_SOUND, core );
371    hig_workarea_add_wide_control( t, &row, w );
372#endif
373
374    hig_workarea_finish( t, &row );
375    return t;
376}
377
378/****
379*****  Peer Tab
380****/
381
382struct blocklist_data
383{
384    gulong      updateBlocklistTag;
385    GtkWidget * updateBlocklistButton;
386    GtkWidget * updateBlocklistDialog;
387    GtkWidget * check;
388    TrCore    * core;
389};
390
391static void
392updateBlocklistText( GtkWidget * w, TrCore * core )
393{
394    const int n = tr_blocklistGetRuleCount( tr_core_session( core ) );
395    char      buf[512];
396    g_snprintf( buf, sizeof( buf ),
397                ngettext( "Enable _blocklist (contains %'d rule)",
398                          "Enable _blocklist (contains %'d rules)", n ), n );
399    gtk_button_set_label( GTK_BUTTON( w ), buf );
400}
401
402/* prefs dialog is being destroyed, so stop listening to blocklist updates */
403static void
404privacyPageDestroyed( gpointer gdata, GObject * dead UNUSED )
405{
406    struct blocklist_data * data = gdata;
407    if( data->updateBlocklistTag > 0 )
408        g_signal_handler_disconnect( data->core, data->updateBlocklistTag );
409    g_free( data );
410}
411
412/* user hit "close" in the blocklist-update dialog */
413static void
414onBlocklistUpdateResponse( GtkDialog * dialog, gint response UNUSED, gpointer gdata )
415{
416    struct blocklist_data * data = gdata;
417    gtk_widget_destroy( GTK_WIDGET( dialog ) );
418    gtk_widget_set_sensitive( data->updateBlocklistButton, TRUE );
419    data->updateBlocklistDialog = NULL;
420    g_signal_handler_disconnect( data->core, data->updateBlocklistTag );
421}
422
423/* core says the blocklist was updated */
424static void
425onBlocklistUpdated( TrCore * core, int n, gpointer gdata )
426{
427    const char * s = ngettext( "Blocklist now has %'d rule.", "Blocklist now has %'d rules.", n );
428    struct blocklist_data * data = gdata;
429    GtkMessageDialog * d = GTK_MESSAGE_DIALOG( data->updateBlocklistDialog );
430    gtk_widget_set_sensitive( data->updateBlocklistButton, TRUE );
431    gtk_message_dialog_set_markup( d, _( "<b>Update succeeded!</b>" ) );
432    gtk_message_dialog_format_secondary_text( d, s, n );
433    updateBlocklistText( data->check, core );
434}
435
436/* user pushed a button to update the blocklist */
437static void
438onBlocklistUpdate( GtkButton * w, gpointer gdata )
439{
440    GtkWidget * d;
441    struct blocklist_data * data = gdata;
442    d = gtk_message_dialog_new( GTK_WINDOW( gtk_widget_get_toplevel( GTK_WIDGET( w ) ) ),
443                                GTK_DIALOG_DESTROY_WITH_PARENT,
444                                GTK_MESSAGE_INFO,
445                                GTK_BUTTONS_CLOSE,
446                                "%s", _( "Update Blocklist" ) );
447    gtk_widget_set_sensitive( data->updateBlocklistButton, FALSE );
448    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( d ), "%s", _( "Getting new blocklist..." ) );
449    data->updateBlocklistDialog = d;
450    g_signal_connect( d, "response", G_CALLBACK(onBlocklistUpdateResponse), data );
451    gtk_widget_show( d );
452    tr_core_blocklist_update( data->core );
453    data->updateBlocklistTag = g_signal_connect( data->core, "blocklist-updated", G_CALLBACK( onBlocklistUpdated ), data );
454}
455
456static void
457onIntComboChanged( GtkComboBox * w, gpointer core )
458{
459    GtkTreeIter iter;
460
461    if( gtk_combo_box_get_active_iter( w, &iter ) )
462    {
463        int val = 0;
464        const char * key = g_object_get_data( G_OBJECT( w ), PREF_KEY );
465        gtk_tree_model_get( gtk_combo_box_get_model( w ), &iter, 0, &val, -1 );
466        tr_core_set_pref_int( TR_CORE( core ), key, val );
467    }
468}
469
470static GtkWidget*
471new_encryption_combo( GObject * core, const char * key )
472{
473    int i;
474    int selIndex;
475    GtkWidget * w;
476    GtkCellRenderer * r;
477    GtkListStore * store;
478    const int currentValue = pref_int_get( key );
479    const struct {
480        int value;
481        const char * text;
482    } items[] = {
483        { TR_CLEAR_PREFERRED,      N_( "Allow encryption" )  },
484        { TR_ENCRYPTION_PREFERRED, N_( "Prefer encryption" ) },
485        { TR_ENCRYPTION_REQUIRED,  N_( "Require encryption" )  }
486    };
487
488    /* build a store for encryption */
489    selIndex = -1;
490    store = gtk_list_store_new( 2, G_TYPE_INT, G_TYPE_STRING );
491    for( i=0; i<(int)G_N_ELEMENTS(items); ++i ) {
492        GtkTreeIter iter;
493        gtk_list_store_append( store, &iter );
494        gtk_list_store_set( store, &iter, 0, items[i].value, 1, _( items[i].text ), -1 );
495        if( items[i].value == currentValue )
496            selIndex = i;
497    }
498
499    /* build the widget */
500    w = gtk_combo_box_new_with_model( GTK_TREE_MODEL( store ) );
501    r = gtk_cell_renderer_text_new( );
502    gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( w ), r, TRUE );
503    gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT( w ), r, "text", 1, NULL );
504    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, tr_strdup( key ), g_free );
505    if( selIndex >= 0 )
506        gtk_combo_box_set_active( GTK_COMBO_BOX( w ), selIndex );
507    g_signal_connect( w, "changed", G_CALLBACK( onIntComboChanged ), core );
508
509    /* cleanup */
510    g_object_unref( G_OBJECT( store ) );
511    return w;
512}
513
514static GtkWidget*
515privacyPage( GObject * core )
516{
517    int                     row = 0;
518    const char *            s;
519    GtkWidget *             t;
520    GtkWidget *             w;
521    GtkWidget *             b;
522    GtkWidget *             h;
523    struct blocklist_data * data;
524
525    data = g_new0( struct blocklist_data, 1 );
526    data->core = TR_CORE( core );
527
528    t = hig_workarea_create( );
529    hig_workarea_add_section_title( t, &row, _( "Blocklist" ) );
530
531    w = new_check_button( "", TR_PREFS_KEY_BLOCKLIST_ENABLED, core );
532    updateBlocklistText( w, TR_CORE( core ) );
533    h = gtk_hbox_new( FALSE, GUI_PAD_BIG );
534    gtk_box_pack_start( GTK_BOX( h ), w, TRUE, TRUE, 0 );
535    b = data->updateBlocklistButton = gtr_button_new_from_stock( GTK_STOCK_REFRESH, _( "_Update" ) );
536    data->check = w;
537    g_object_set_data( G_OBJECT( b ), "session",
538                      tr_core_session( TR_CORE( core ) ) );
539    g_signal_connect( b, "clicked", G_CALLBACK( onBlocklistUpdate ), data );
540    gtk_box_pack_start( GTK_BOX( h ), b, FALSE, FALSE, 0 );
541    g_signal_connect( w, "toggled", G_CALLBACK( target_cb ), b );
542    target_cb( w, b );
543    hig_workarea_add_wide_control( t, &row, h );
544
545    s = _( "Enable _automatic updates" );
546    w = new_check_button( s, PREF_KEY_BLOCKLIST_UPDATES_ENABLED, core );
547    hig_workarea_add_wide_control( t, &row, w );
548    g_signal_connect( data->check, "toggled", G_CALLBACK( target_cb ), w );
549    target_cb( data->check, w );
550
551    hig_workarea_add_section_divider( t, &row );
552    hig_workarea_add_section_title ( t, &row, _( "Privacy" ) );
553
554    s = _( "_Encryption mode:" );
555    w = new_encryption_combo( core, "encryption" );
556    hig_workarea_add_row( t, &row, s, w, NULL );
557
558    s = _( "Use PE_X to find more peers" );
559    w = new_check_button( s, TR_PREFS_KEY_PEX_ENABLED, core );
560    s = _( "PEX is a tool for exchanging peer lists with the peers you're connected to." );
561    gtr_widget_set_tooltip_text( w, s );
562    hig_workarea_add_wide_control( t, &row, w );
563
564    s = _( "Use _DHT to find more peers" );
565    w = new_check_button( s, TR_PREFS_KEY_DHT_ENABLED, core );
566    s = _( "DHT is a tool for finding peers without a tracker." );
567    gtr_widget_set_tooltip_text( w, s );
568    hig_workarea_add_wide_control( t, &row, w );
569
570    s = _( "Use Local Peer Discovery to find more peers" );
571    w = new_check_button( s, TR_PREFS_KEY_LPD_ENABLED, core );
572    s = _( "LPD is a tool for finding peers on your local network." );
573    gtr_widget_set_tooltip_text( w, s );
574    hig_workarea_add_wide_control( t, &row, w );
575
576    hig_workarea_finish( t, &row );
577    g_object_weak_ref( G_OBJECT( t ), privacyPageDestroyed, data );
578    return t;
579}
580
581/****
582*****  Web Tab
583****/
584
585enum
586{
587    COL_ADDRESS,
588    N_COLS
589};
590
591static GtkTreeModel*
592whitelist_tree_model_new( const char * whitelist )
593{
594    int            i;
595    char **        rules;
596    GtkListStore * store = gtk_list_store_new( N_COLS,
597                                               G_TYPE_STRING,
598                                               G_TYPE_STRING );
599
600    rules = g_strsplit( whitelist, ",", 0 );
601
602    for( i = 0; rules && rules[i]; ++i )
603    {
604        GtkTreeIter iter;
605        const char * s = rules[i];
606        while( isspace( *s ) ) ++s;
607        gtk_list_store_append( store, &iter );
608        gtk_list_store_set( store, &iter, COL_ADDRESS, s, -1 );
609    }
610
611    g_strfreev( rules );
612    return GTK_TREE_MODEL( store );
613}
614
615struct remote_page
616{
617    TrCore *           core;
618    GtkTreeView *      view;
619    GtkListStore *     store;
620    GtkWidget *        remove_button;
621    GSList *           widgets;
622    GSList *           auth_widgets;
623    GSList *           whitelist_widgets;
624    GtkToggleButton *  rpc_tb;
625    GtkToggleButton *  auth_tb;
626    GtkToggleButton *  whitelist_tb;
627};
628
629static void
630refreshWhitelist( struct remote_page * page )
631{
632    GtkTreeIter    iter;
633    GtkTreeModel * model = GTK_TREE_MODEL( page->store );
634    GString *      gstr = g_string_new( NULL );
635
636    if( gtk_tree_model_get_iter_first( model, &iter ) ) do
637        {
638            char * address;
639            gtk_tree_model_get( model, &iter,
640                                COL_ADDRESS, &address,
641                                -1 );
642            g_string_append( gstr, address );
643            g_string_append( gstr, "," );
644            g_free( address );
645        }
646        while( gtk_tree_model_iter_next( model, &iter ) );
647
648    g_string_truncate( gstr, gstr->len - 1 ); /* remove the trailing comma */
649
650    tr_core_set_pref( page->core, TR_PREFS_KEY_RPC_WHITELIST, gstr->str );
651
652    g_string_free( gstr, TRUE );
653}
654
655static void
656onAddressEdited( GtkCellRendererText  * r UNUSED,
657                 gchar *                  path_string,
658                 gchar *                  address,
659                 gpointer                 gpage )
660{
661    GtkTreeIter          iter;
662    struct remote_page * page = gpage;
663    GtkTreeModel *       model = GTK_TREE_MODEL( page->store );
664    GtkTreePath *        path = gtk_tree_path_new_from_string( path_string );
665
666    if( gtk_tree_model_get_iter( model, &iter, path ) )
667        gtk_list_store_set( page->store, &iter, COL_ADDRESS, address, -1 );
668
669    gtk_tree_path_free( path );
670    refreshWhitelist( page );
671}
672
673static void
674onAddWhitelistClicked( GtkButton * b UNUSED,
675                 gpointer      gpage )
676{
677    GtkTreeIter          iter;
678    GtkTreePath *        path;
679    struct remote_page * page = gpage;
680
681    gtk_list_store_append( page->store, &iter );
682    gtk_list_store_set( page->store, &iter,
683                        COL_ADDRESS,  "0.0.0.0",
684                        -1 );
685
686    path = gtk_tree_model_get_path( GTK_TREE_MODEL( page->store ), &iter );
687    gtk_tree_view_set_cursor(
688        page->view, path,
689        gtk_tree_view_get_column( page->view, COL_ADDRESS ),
690        TRUE );
691    gtk_tree_path_free( path );
692}
693
694static void
695onRemoveWhitelistClicked( GtkButton * b UNUSED,
696                    gpointer      gpage )
697{
698    struct remote_page * page = gpage;
699    GtkTreeSelection *   sel = gtk_tree_view_get_selection( page->view );
700    GtkTreeIter          iter;
701
702    if( gtk_tree_selection_get_selected( sel, NULL, &iter ) )
703    {
704        gtk_list_store_remove( page->store, &iter );
705        refreshWhitelist( page );
706    }
707}
708
709static void
710refreshRPCSensitivity( struct remote_page * page )
711{
712    GSList *           l;
713    const int          rpc_active = gtk_toggle_button_get_active(
714        page->rpc_tb );
715    const int          auth_active = gtk_toggle_button_get_active(
716        page->auth_tb );
717    const int          whitelist_active = gtk_toggle_button_get_active(
718        page->whitelist_tb );
719    GtkTreeSelection * sel = gtk_tree_view_get_selection( page->view );
720    const int          have_addr =
721        gtk_tree_selection_get_selected( sel, NULL,
722                                         NULL );
723    const int          n_rules = gtk_tree_model_iter_n_children(
724        GTK_TREE_MODEL( page->store ), NULL );
725
726    for( l = page->widgets; l != NULL; l = l->next )
727        gtk_widget_set_sensitive( GTK_WIDGET( l->data ), rpc_active );
728
729    for( l = page->auth_widgets; l != NULL; l = l->next )
730        gtk_widget_set_sensitive( GTK_WIDGET(
731                                      l->data ), rpc_active && auth_active );
732
733    for( l = page->whitelist_widgets; l != NULL; l = l->next )
734        gtk_widget_set_sensitive( GTK_WIDGET( l->data ),
735                                  rpc_active && whitelist_active );
736
737    gtk_widget_set_sensitive( page->remove_button,
738                              rpc_active && have_addr && n_rules > 1 );
739}
740
741static void
742onRPCToggled( GtkToggleButton * tb UNUSED,
743              gpointer             page )
744{
745    refreshRPCSensitivity( page );
746}
747
748static void
749onWhitelistSelectionChanged( GtkTreeSelection * sel UNUSED,
750                       gpointer               page )
751{
752    refreshRPCSensitivity( page );
753}
754
755static void
756onLaunchClutchCB( GtkButton * w UNUSED,
757                  gpointer data UNUSED )
758{
759    int    port = pref_int_get( TR_PREFS_KEY_RPC_PORT );
760    char * url = g_strdup_printf( "http://localhost:%d/transmission/web",
761                                  port );
762
763    gtr_open_file( url );
764    g_free( url );
765}
766
767static void
768remotePageFree( gpointer gpage )
769{
770    struct remote_page * page = gpage;
771
772    g_slist_free( page->widgets );
773    g_slist_free( page->auth_widgets );
774    g_slist_free( page->whitelist_widgets );
775    g_free( page );
776}
777
778static GtkWidget*
779webPage( GObject * core )
780{
781    const char *         s;
782    int                  row = 0;
783    GtkWidget *          t;
784    GtkWidget *          w;
785    GtkWidget *          h;
786    struct remote_page * page = g_new0( struct remote_page, 1 );
787
788    page->core = TR_CORE( core );
789
790    t = hig_workarea_create( );
791    g_object_set_data_full( G_OBJECT( t ), "page", page, remotePageFree );
792
793    hig_workarea_add_section_title( t, &row, _( "Web Client" ) );
794
795    /* "enabled" checkbutton */
796    s = _( "_Enable web client" );
797    w = new_check_button( s, TR_PREFS_KEY_RPC_ENABLED, core );
798    page->rpc_tb = GTK_TOGGLE_BUTTON( w );
799    g_signal_connect( w, "clicked", G_CALLBACK( onRPCToggled ), page );
800    h = gtk_hbox_new( FALSE, GUI_PAD_BIG );
801    gtk_box_pack_start( GTK_BOX( h ), w, TRUE, TRUE, 0 );
802    w = gtr_button_new_from_stock( GTK_STOCK_OPEN, _( "_Open web client" ) );
803    page->widgets = g_slist_append( page->widgets, w );
804    g_signal_connect( w, "clicked", G_CALLBACK( onLaunchClutchCB ), NULL );
805    gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 );
806    hig_workarea_add_wide_control( t, &row, h );
807
808    /* port */
809    w = new_spin_button( TR_PREFS_KEY_RPC_PORT, core, 0, USHRT_MAX, 1 );
810    page->widgets = g_slist_append( page->widgets, w );
811    w = hig_workarea_add_row( t, &row, _( "Listening _port:" ), w, NULL );
812    page->widgets = g_slist_append( page->widgets, w );
813
814    /* require authentication */
815    s = _( "Use _authentication" );
816    w = new_check_button( s, TR_PREFS_KEY_RPC_AUTH_REQUIRED, core );
817    hig_workarea_add_wide_control( t, &row, w );
818    page->auth_tb = GTK_TOGGLE_BUTTON( w );
819    page->widgets = g_slist_append( page->widgets, w );
820    g_signal_connect( w, "clicked", G_CALLBACK( onRPCToggled ), page );
821
822    /* username */
823    s = _( "_Username:" );
824    w = new_entry( TR_PREFS_KEY_RPC_USERNAME, core );
825    page->auth_widgets = g_slist_append( page->auth_widgets, w );
826    w = hig_workarea_add_row( t, &row, s, w, NULL );
827    page->auth_widgets = g_slist_append( page->auth_widgets, w );
828
829    /* password */
830    s = _( "Pass_word:" );
831    w = new_entry( TR_PREFS_KEY_RPC_PASSWORD, core );
832    gtk_entry_set_visibility( GTK_ENTRY( w ), FALSE );
833    page->auth_widgets = g_slist_append( page->auth_widgets, w );
834    w = hig_workarea_add_row( t, &row, s, w, NULL );
835    page->auth_widgets = g_slist_append( page->auth_widgets, w );
836
837    /* require authentication */
838    s = _( "Only allow these IP a_ddresses to connect:" );
839    w = new_check_button( s, TR_PREFS_KEY_RPC_WHITELIST_ENABLED, core );
840    hig_workarea_add_wide_control( t, &row, w );
841    page->whitelist_tb = GTK_TOGGLE_BUTTON( w );
842    page->widgets = g_slist_append( page->widgets, w );
843    g_signal_connect( w, "clicked", G_CALLBACK( onRPCToggled ), page );
844
845    /* access control list */
846    {
847        const char *        val = pref_string_get( TR_PREFS_KEY_RPC_WHITELIST );
848        GtkTreeModel *      m = whitelist_tree_model_new( val );
849        GtkTreeViewColumn * c;
850        GtkCellRenderer *   r;
851        GtkTreeSelection *  sel;
852        GtkTreeView *       v;
853        GtkWidget *         w;
854        GtkWidget *         h;
855
856        page->store = GTK_LIST_STORE( m );
857        w = gtk_tree_view_new_with_model( m );
858        g_signal_connect( w, "button-release-event",
859                          G_CALLBACK( on_tree_view_button_released ), NULL );
860
861        page->whitelist_widgets = g_slist_append( page->whitelist_widgets, w );
862        v = page->view = GTK_TREE_VIEW( w );
863        gtr_widget_set_tooltip_text( w, _( "IP addresses may use wildcards, such as 192.168.*.*" ) );
864        sel = gtk_tree_view_get_selection( v );
865        g_signal_connect( sel, "changed",
866                          G_CALLBACK( onWhitelistSelectionChanged ), page );
867        g_object_unref( G_OBJECT( m ) );
868        gtk_tree_view_set_headers_visible( v, TRUE );
869        w = gtk_frame_new( NULL );
870        gtk_frame_set_shadow_type( GTK_FRAME( w ), GTK_SHADOW_IN );
871        gtk_container_add( GTK_CONTAINER( w ), GTK_WIDGET( v ) );
872
873        /* ip address column */
874        r = gtk_cell_renderer_text_new( );
875        g_signal_connect( r, "edited",
876                          G_CALLBACK( onAddressEdited ), page );
877        g_object_set( G_OBJECT( r ), "editable", TRUE, NULL );
878        c = gtk_tree_view_column_new_with_attributes( NULL, r,
879                                                      "text", COL_ADDRESS,
880                                                      NULL );
881        gtk_tree_view_column_set_expand( c, TRUE );
882        gtk_tree_view_append_column( v, c );
883        gtk_tree_view_set_headers_visible( v, FALSE );
884
885        s = _( "Addresses:" );
886        w = hig_workarea_add_row( t, &row, s, w, NULL );
887        gtk_misc_set_alignment( GTK_MISC( w ), 0.0f, 0.0f );
888        gtk_misc_set_padding( GTK_MISC( w ), 0, GUI_PAD );
889        page->whitelist_widgets = g_slist_append( page->whitelist_widgets, w );
890
891        h = gtk_hbox_new( TRUE, GUI_PAD );
892        w = gtk_button_new_from_stock( GTK_STOCK_REMOVE );
893        g_signal_connect( w, "clicked", G_CALLBACK(
894                              onRemoveWhitelistClicked ), page );
895        page->remove_button = w;
896        onWhitelistSelectionChanged( sel, page );
897        gtk_box_pack_start( GTK_BOX( h ), w, TRUE, TRUE, 0 );
898        w = gtk_button_new_from_stock( GTK_STOCK_ADD );
899        page->whitelist_widgets = g_slist_append( page->whitelist_widgets, w );
900        g_signal_connect( w, "clicked", G_CALLBACK( onAddWhitelistClicked ), page );
901        gtk_box_pack_start( GTK_BOX( h ), w, TRUE, TRUE, 0 );
902        w = gtk_hbox_new( FALSE, 0 );
903        gtk_box_pack_start( GTK_BOX( w ), gtk_alignment_new( 0, 0, 0, 0 ),
904                            TRUE, TRUE, 0 );
905        gtk_box_pack_start( GTK_BOX( w ), h, FALSE, FALSE, 0 );
906        hig_workarea_add_wide_control( t, &row, w );
907    }
908
909    refreshRPCSensitivity( page );
910    hig_workarea_finish( t, &row );
911    return t;
912}
913
914/****
915*****  Proxy Tab
916****/
917
918struct ProxyPage
919{
920    TrCore *  core;
921    GSList *  proxy_widgets;
922    GSList *  proxy_auth_widgets;
923};
924
925static void
926refreshProxySensitivity( struct ProxyPage * p )
927{
928    GSList *       l;
929    const gboolean proxy_enabled = pref_flag_get( TR_PREFS_KEY_PROXY_ENABLED );
930    const gboolean proxy_auth_enabled = pref_flag_get( TR_PREFS_KEY_PROXY_AUTH_ENABLED );
931
932    for( l = p->proxy_widgets; l != NULL; l = l->next )
933        gtk_widget_set_sensitive( GTK_WIDGET( l->data ), proxy_enabled );
934
935    for( l = p->proxy_auth_widgets; l != NULL; l = l->next )
936        gtk_widget_set_sensitive( GTK_WIDGET( l->data ),
937                                  proxy_enabled && proxy_auth_enabled );
938}
939
940static void
941onProxyToggled( GtkToggleButton * tb UNUSED,
942                gpointer             user_data )
943{
944    refreshProxySensitivity( user_data );
945}
946
947static void
948proxyPageFree( gpointer gpage )
949{
950    struct ProxyPage * page = gpage;
951
952    g_slist_free( page->proxy_widgets );
953    g_slist_free( page->proxy_auth_widgets );
954    g_free( page );
955}
956
957static GtkTreeModel*
958proxyTypeModelNew( void )
959{
960    GtkTreeIter    iter;
961    GtkListStore * store = gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_INT );
962
963    gtk_list_store_append( store, &iter );
964    gtk_list_store_set( store, &iter, 0, "HTTP", 1, TR_PROXY_HTTP, -1 );
965    gtk_list_store_append( store, &iter );
966    gtk_list_store_set( store, &iter, 0, "SOCKS4", 1, TR_PROXY_SOCKS4, -1 );
967    gtk_list_store_append( store, &iter );
968    gtk_list_store_set( store, &iter, 0, "SOCKS5", 1, TR_PROXY_SOCKS5, -1 );
969    return GTK_TREE_MODEL( store );
970}
971
972static void
973onProxyTypeChanged( GtkComboBox * w,
974                    gpointer      gpage )
975{
976    GtkTreeIter iter;
977
978    if( gtk_combo_box_get_active_iter( w, &iter ) )
979    {
980        struct ProxyPage * page = gpage;
981        int type = TR_PROXY_HTTP;
982        gtk_tree_model_get( gtk_combo_box_get_model( w ), &iter, 1, &type, -1 );
983        tr_core_set_pref_int( TR_CORE( page->core ), TR_PREFS_KEY_PROXY_TYPE, type );
984    }
985}
986
987static GtkWidget*
988trackerPage( GObject * core )
989{
990    int                row = 0;
991    const char *       s;
992    GtkWidget *        t;
993    GtkWidget *        w;
994    GtkTreeModel *     m;
995    GtkCellRenderer *  r;
996    struct ProxyPage * page = tr_new0( struct ProxyPage, 1 );
997
998    page->core = TR_CORE( core );
999
1000    t = hig_workarea_create( );
1001    hig_workarea_add_section_title ( t, &row, _( "Tracker" ) );
1002
1003    s = _( "Connect to tracker via a pro_xy" );
1004    w = new_check_button( s, TR_PREFS_KEY_PROXY_ENABLED, core );
1005    g_signal_connect( w, "toggled", G_CALLBACK( onProxyToggled ), page );
1006    hig_workarea_add_wide_control( t, &row, w );
1007
1008    s = _( "Proxy _server:" );
1009    w = new_entry( TR_PREFS_KEY_PROXY, core );
1010    page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
1011    w = hig_workarea_add_row( t, &row, s, w, NULL );
1012    page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
1013
1014    w = new_spin_button( TR_PREFS_KEY_PROXY_PORT, core, 0, USHRT_MAX, 1 );
1015    page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
1016    w = hig_workarea_add_row( t, &row, _( "Proxy _port:" ), w, NULL );
1017    page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
1018
1019    s = _( "Proxy _type:" );
1020    m = proxyTypeModelNew( );
1021    w = gtk_combo_box_new_with_model( m );
1022    r = gtk_cell_renderer_text_new( );
1023    gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( w ), r, TRUE );
1024    gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT( w ), r, "text", 0, NULL );
1025    gtk_combo_box_set_active( GTK_COMBO_BOX( w ), pref_int_get( TR_PREFS_KEY_PROXY_TYPE ) );
1026    g_signal_connect( w, "changed", G_CALLBACK( onProxyTypeChanged ), page );
1027    g_object_unref( G_OBJECT( m ) );
1028    page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
1029    w = hig_workarea_add_row( t, &row, s, w, NULL );
1030    page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
1031
1032    s = _( "Use _authentication" );
1033    w = new_check_button( s, TR_PREFS_KEY_PROXY_AUTH_ENABLED, core );
1034    g_signal_connect( w, "toggled", G_CALLBACK( onProxyToggled ), page );
1035    hig_workarea_add_wide_control( t, &row, w );
1036    page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
1037
1038    s = _( "_Username:" );
1039    w = new_entry( TR_PREFS_KEY_PROXY_USERNAME, core );
1040    page->proxy_auth_widgets = g_slist_append( page->proxy_auth_widgets, w );
1041    w = hig_workarea_add_row( t, &row, s, w, NULL );
1042    page->proxy_auth_widgets = g_slist_append( page->proxy_auth_widgets, w );
1043
1044    s = _( "Pass_word:" );
1045    w = new_entry( TR_PREFS_KEY_PROXY_PASSWORD, core );
1046    gtk_entry_set_visibility( GTK_ENTRY( w ), FALSE );
1047    page->proxy_auth_widgets = g_slist_append( page->proxy_auth_widgets, w );
1048    w = hig_workarea_add_row( t, &row, s, w, NULL );
1049    page->proxy_auth_widgets = g_slist_append( page->proxy_auth_widgets, w );
1050
1051    hig_workarea_finish( t, &row );
1052    g_object_set_data_full( G_OBJECT( t ), "page", page, proxyPageFree );
1053
1054    refreshProxySensitivity( page );
1055    return t;
1056}
1057
1058/****
1059*****  Bandwidth Tab
1060****/
1061
1062struct BandwidthPage
1063{
1064    TrCore *  core;
1065    GSList *  sched_widgets;
1066};
1067
1068static void
1069refreshSchedSensitivity( struct BandwidthPage * p )
1070{
1071    GSList *       l;
1072    const gboolean sched_enabled = pref_flag_get( TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED );
1073
1074    for( l = p->sched_widgets; l != NULL; l = l->next )
1075        gtk_widget_set_sensitive( GTK_WIDGET( l->data ), sched_enabled );
1076}
1077
1078static void
1079onSchedToggled( GtkToggleButton * tb UNUSED,
1080                gpointer             user_data )
1081{
1082    refreshSchedSensitivity( user_data );
1083}
1084
1085static void
1086onTimeComboChanged( GtkComboBox * w,
1087                    gpointer      core )
1088{
1089    GtkTreeIter iter;
1090
1091    if( gtk_combo_box_get_active_iter( w, &iter ) )
1092    {
1093        const char * key = g_object_get_data( G_OBJECT( w ), PREF_KEY );
1094        int          val = 0;
1095        gtk_tree_model_get( gtk_combo_box_get_model(
1096                                w ), &iter, 0, &val, -1 );
1097        tr_core_set_pref_int( TR_CORE( core ), key, val );
1098    }
1099}
1100
1101static GtkWidget*
1102new_time_combo( GObject *    core,
1103                const char * key )
1104{
1105    int               val;
1106    int               i;
1107    GtkWidget *       w;
1108    GtkCellRenderer * r;
1109    GtkListStore *    store;
1110
1111    /* build a store at 15 minute intervals */
1112    store = gtk_list_store_new( 2, G_TYPE_INT, G_TYPE_STRING );
1113    for( i = 0; i < 60 * 24; i += 15 )
1114    {
1115        char        buf[128];
1116        GtkTreeIter iter;
1117        struct tm   tm;
1118        tm.tm_hour = i / 60;
1119        tm.tm_min = i % 60;
1120        tm.tm_sec = 0;
1121        strftime( buf, sizeof( buf ), "%H:%M", &tm );
1122        gtk_list_store_append( store, &iter );
1123        gtk_list_store_set( store, &iter, 0, i, 1, buf, -1 );
1124    }
1125
1126    /* build the widget */
1127    w = gtk_combo_box_new_with_model( GTK_TREE_MODEL( store ) );
1128    gtk_combo_box_set_wrap_width( GTK_COMBO_BOX( w ), 4 );
1129    r = gtk_cell_renderer_text_new( );
1130    gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( w ), r, TRUE );
1131    gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT(
1132                                        w ), r, "text", 1, NULL );
1133    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, tr_strdup(
1134                                key ), g_free );
1135    val = pref_int_get( key );
1136    gtk_combo_box_set_active( GTK_COMBO_BOX( w ), val / ( 15 ) );
1137    g_signal_connect( w, "changed", G_CALLBACK( onTimeComboChanged ), core );
1138
1139    /* cleanup */
1140    g_object_unref( G_OBJECT( store ) );
1141    return w;
1142}
1143
1144static GtkWidget*
1145new_week_combo( GObject * core, const char * key )
1146{
1147    int i;
1148    int selIndex;
1149    GtkWidget * w;
1150    GtkCellRenderer * r;
1151    GtkListStore * store;
1152    const int currentValue = pref_int_get( key );
1153    const struct {
1154        int value;
1155        const char * text;
1156    } items[] = {
1157        { TR_SCHED_ALL,     N_( "Every Day" ) },
1158        { TR_SCHED_WEEKDAY, N_( "Weekdays" ) },
1159        { TR_SCHED_WEEKEND, N_( "Weekends" ) },
1160        { TR_SCHED_SUN,     N_( "Sunday" ) },
1161        { TR_SCHED_MON,     N_( "Monday" ) },
1162        { TR_SCHED_TUES,    N_( "Tuesday" ) },
1163        { TR_SCHED_WED,     N_( "Wednesday" ) },
1164        { TR_SCHED_THURS,   N_( "Thursday" ) },
1165        { TR_SCHED_FRI,     N_( "Friday" ) },
1166        { TR_SCHED_SAT,     N_( "Saturday" ) }
1167    };
1168
1169    /* build a store for the days of the week */
1170    selIndex = -1;
1171    store = gtk_list_store_new( 2, G_TYPE_INT, G_TYPE_STRING );
1172    for( i=0; i<(int)G_N_ELEMENTS(items); ++i ) {
1173        GtkTreeIter iter;
1174        gtk_list_store_append( store, &iter );
1175        gtk_list_store_set( store, &iter, 0, items[i].value, 1, _( items[i].text ), -1 );
1176        if( items[i].value == currentValue )
1177            selIndex = i;
1178    }
1179
1180    /* build the widget */
1181    w = gtk_combo_box_new_with_model( GTK_TREE_MODEL( store ) );
1182    r = gtk_cell_renderer_text_new( );
1183    gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( w ), r, TRUE );
1184    gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT( w ), r, "text", 1, NULL );
1185    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, tr_strdup( key ), g_free );
1186    if( selIndex >= 0 )
1187        gtk_combo_box_set_active( GTK_COMBO_BOX( w ), selIndex );
1188    g_signal_connect( w, "changed", G_CALLBACK( onIntComboChanged ), core );
1189
1190    /* cleanup */
1191    g_object_unref( G_OBJECT( store ) );
1192    return w;
1193}
1194
1195static void
1196bandwidthPageFree( gpointer gpage )
1197{
1198    struct BandwidthPage * page = gpage;
1199
1200    g_slist_free( page->sched_widgets );
1201    g_free( page );
1202}
1203
1204static GtkWidget*
1205bandwidthPage( GObject * core )
1206{
1207    int row = 0;
1208    const char * s;
1209    GtkWidget * t;
1210    GtkWidget * l;
1211    GtkWidget * w, * w2, * h;
1212    char buf[512];
1213    struct BandwidthPage * page = tr_new0( struct BandwidthPage, 1 );
1214
1215    page->core = TR_CORE( core );
1216
1217    t = hig_workarea_create( );
1218    hig_workarea_add_section_title( t, &row, _( "Speed Limits" ) );
1219
1220        s = _( "Limit _download speed (KiB/s):" );
1221        w = new_check_button( s, TR_PREFS_KEY_DSPEED_ENABLED, core );
1222        w2 = new_spin_button( TR_PREFS_KEY_DSPEED, core, 0, INT_MAX, 5 );
1223        gtk_widget_set_sensitive( GTK_WIDGET( w2 ), pref_flag_get( TR_PREFS_KEY_DSPEED_ENABLED ) );
1224        g_signal_connect( w, "toggled", G_CALLBACK( target_cb ), w2 );
1225        hig_workarea_add_row_w( t, &row, w, w2, NULL );
1226
1227        s = _( "Limit _upload speed (KiB/s):" );
1228        w = new_check_button( s, TR_PREFS_KEY_USPEED_ENABLED, core );
1229        w2 = new_spin_button( TR_PREFS_KEY_USPEED, core, 0, INT_MAX, 5 );
1230        gtk_widget_set_sensitive( GTK_WIDGET( w2 ), pref_flag_get( TR_PREFS_KEY_USPEED_ENABLED ) );
1231        g_signal_connect( w, "toggled", G_CALLBACK( target_cb ), w2 );
1232        hig_workarea_add_row_w( t, &row, w, w2, NULL );
1233
1234    hig_workarea_add_section_divider( t, &row );
1235    h = gtk_hbox_new( FALSE, GUI_PAD );
1236    w = gtk_image_new_from_stock( "alt-speed-off", -1 );
1237    gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 );
1238    g_snprintf( buf, sizeof( buf ), "<b>%s</b>", _( "Temporary Speed Limits" ) );
1239    w = gtk_label_new( buf );
1240    gtk_misc_set_alignment( GTK_MISC( w ), 0.0f, 0.5f );
1241    gtk_label_set_use_markup( GTK_LABEL( w ), TRUE );
1242    gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 );
1243    hig_workarea_add_section_title_widget( t, &row, h );
1244
1245        s = _( "Override normal speed limits manually or at scheduled times" );
1246        g_snprintf( buf, sizeof( buf ), "<small>%s</small>", s );
1247        w = gtk_label_new( buf );
1248        gtk_label_set_use_markup( GTK_LABEL( w ), TRUE );
1249        gtk_misc_set_alignment( GTK_MISC( w ), 0.5f, 0.5f );
1250        hig_workarea_add_wide_control( t, &row, w );
1251
1252        s = _( "Limit do_wnload speed (KiB/s):" );
1253        w = new_spin_button( TR_PREFS_KEY_ALT_SPEED_DOWN, core, 0, INT_MAX, 5 );
1254        hig_workarea_add_row( t, &row, s, w, NULL );
1255
1256        s = _( "Limit u_pload speed (KiB/s):" );
1257        w = new_spin_button( TR_PREFS_KEY_ALT_SPEED_UP, core, 0, INT_MAX, 5 );
1258        hig_workarea_add_row( t, &row, s, w, NULL );
1259
1260        s = _( "_Scheduled times:" );
1261        h = gtk_hbox_new( FALSE, 0 );
1262        w2 = new_time_combo( core, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN );
1263        page->sched_widgets = g_slist_append( page->sched_widgets, w2 );
1264        gtk_box_pack_start( GTK_BOX( h ), w2, TRUE, TRUE, 0 );
1265        w2 = l = gtk_label_new_with_mnemonic ( _( " _to " ) );
1266        page->sched_widgets = g_slist_append( page->sched_widgets, w2 );
1267        gtk_box_pack_start( GTK_BOX( h ), w2, FALSE, FALSE, 0 );
1268        w2 = new_time_combo( core, TR_PREFS_KEY_ALT_SPEED_TIME_END );
1269        gtk_label_set_mnemonic_widget( GTK_LABEL( l ), w2 );
1270        page->sched_widgets = g_slist_append( page->sched_widgets, w2 );
1271        gtk_box_pack_start( GTK_BOX( h ), w2, TRUE, TRUE, 0 );
1272        w = new_check_button( s, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, core );
1273        g_signal_connect( w, "toggled", G_CALLBACK( onSchedToggled ), page );
1274        hig_workarea_add_row_w( t, &row, w, h, NULL );
1275
1276        s = _( "_On days:" );
1277        w = new_week_combo( core, TR_PREFS_KEY_ALT_SPEED_TIME_DAY );
1278        page->sched_widgets = g_slist_append( page->sched_widgets, w );
1279        w = hig_workarea_add_row( t, &row, s, w, NULL );
1280        page->sched_widgets = g_slist_append( page->sched_widgets, w );
1281
1282    hig_workarea_finish( t, &row );
1283    g_object_set_data_full( G_OBJECT( t ), "page", page, bandwidthPageFree );
1284
1285    refreshSchedSensitivity( page );
1286    return t;
1287}
1288
1289/****
1290*****  Network Tab
1291****/
1292
1293struct network_page_data
1294{
1295    TrCore     * core;
1296    GtkWidget  * portLabel;
1297    GtkWidget  * portButton;
1298    GtkWidget  * portSpin;
1299    gulong       portTag;
1300    gulong       prefsTag;
1301};
1302
1303static void
1304onCorePrefsChanged( TrCore * core UNUSED, const char *  key, gpointer gdata )
1305{
1306    if( !strcmp( key, TR_PREFS_KEY_PEER_PORT ) )
1307    {
1308        struct network_page_data * data = gdata;
1309        gdk_threads_enter();
1310        gtk_label_set_text( GTK_LABEL( data->portLabel ), _( "Status unknown" ) );
1311        gtk_widget_set_sensitive( data->portButton, TRUE );
1312        gtk_widget_set_sensitive( data->portSpin, TRUE );
1313        gdk_threads_leave();
1314    }
1315}
1316
1317static void
1318peerPageDestroyed( gpointer gdata, GObject * dead UNUSED )
1319{
1320    struct network_page_data * data = gdata;
1321    if( data->prefsTag > 0 )
1322        g_signal_handler_disconnect( data->core, data->prefsTag );
1323    if( data->portTag > 0 )
1324        g_signal_handler_disconnect( data->core, data->portTag );
1325    g_free( data );
1326}
1327
1328static void
1329onPortTested( TrCore * core UNUSED, gboolean isOpen, gpointer vdata )
1330{
1331    struct network_page_data * data = vdata;
1332    const char * markup = isOpen ? _( "Port is <b>open</b>" ) : _( "Port is <b>closed</b>" );
1333    gdk_threads_enter();
1334    gtk_label_set_markup( GTK_LABEL( data->portLabel ), markup );
1335    gtk_widget_set_sensitive( data->portButton, TRUE );
1336    gtk_widget_set_sensitive( data->portSpin, TRUE );
1337    gdk_threads_leave();
1338}
1339
1340static void
1341onPortTest( GtkButton * button UNUSED, gpointer vdata )
1342{
1343    struct network_page_data * data = vdata;
1344    gtk_widget_set_sensitive( data->portButton, FALSE );
1345    gtk_widget_set_sensitive( data->portSpin, FALSE );
1346    gtk_label_set_markup( GTK_LABEL( data->portLabel ), _( "<i>Testing...</i>" ) );
1347    if( !data->portTag )
1348        data->portTag = g_signal_connect( data->core, "port-tested", G_CALLBACK(onPortTested), data );
1349    tr_core_port_test( data->core );
1350}
1351
1352static GtkWidget*
1353peerPage( GObject * core )
1354{
1355    int                        row = 0;
1356    const char *               s;
1357    GtkWidget *                t;
1358    GtkWidget *                w;
1359    GtkWidget *                h;
1360    GtkWidget *                l;
1361    struct network_page_data * data;
1362
1363    /* register to stop listening to core prefs changes when the page is destroyed */
1364    data = g_new0( struct network_page_data, 1 );
1365    data->core = TR_CORE( core );
1366
1367    /* build the page */
1368    t = hig_workarea_create( );
1369    hig_workarea_add_section_title( t, &row, _( "Incoming Peers" ) );
1370
1371    s = _( "_Port for incoming connections:" );
1372    w = data->portSpin = new_spin_button( TR_PREFS_KEY_PEER_PORT, core, 1, USHRT_MAX, 1 );
1373    hig_workarea_add_row( t, &row, s, w, NULL );
1374
1375    h = gtk_hbox_new( FALSE, GUI_PAD_BIG );
1376    l = data->portLabel = gtk_label_new( _( "Status unknown" ) );
1377    gtk_misc_set_alignment( GTK_MISC( l ), 0.0f, 0.5f );
1378    gtk_box_pack_start( GTK_BOX( h ), l, TRUE, TRUE, 0 );
1379    w = data->portButton = gtk_button_new_with_mnemonic( _( "Te_st Port" ) );
1380    gtk_box_pack_end( GTK_BOX( h ), w, FALSE, FALSE, 0 );
1381    g_signal_connect( w, "clicked", G_CALLBACK(onPortTest), data );
1382    hig_workarea_add_row( t, &row, NULL, h, NULL );
1383    data->prefsTag = g_signal_connect( TR_CORE( core ), "prefs-changed", G_CALLBACK( onCorePrefsChanged ), data );
1384    g_object_weak_ref( G_OBJECT( t ), peerPageDestroyed, data );
1385
1386    s = _( "Pick a _random port on startup" );
1387    w = new_check_button( s, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START, core );
1388    hig_workarea_add_wide_control( t, &row, w );
1389
1390    s = _( "Use UPnP or NAT-PMP port _forwarding from my router" );
1391    w = new_check_button( s, TR_PREFS_KEY_PORT_FORWARDING, core );
1392    hig_workarea_add_wide_control( t, &row, w );
1393
1394    hig_workarea_add_section_divider( t, &row );
1395    hig_workarea_add_section_title( t, &row, _( "Limits" ) );
1396
1397    w = new_spin_button( TR_PREFS_KEY_PEER_LIMIT_TORRENT, core, 1, 300, 5 );
1398    hig_workarea_add_row( t, &row, _( "Maximum peers per _torrent:" ), w, NULL );
1399    w = new_spin_button( TR_PREFS_KEY_PEER_LIMIT_GLOBAL, core, 1, 3000, 5 );
1400    hig_workarea_add_row( t, &row, _( "Maximum peers _overall:" ), w, NULL );
1401
1402    hig_workarea_finish( t, &row );
1403    return t;
1404}
1405
1406/****
1407*****
1408****/
1409
1410GtkWidget *
1411tr_prefs_dialog_new( GObject *   core,
1412                     GtkWindow * parent )
1413{
1414    GtkWidget * d;
1415    GtkWidget * n;
1416
1417    d = gtk_dialog_new_with_buttons( _(
1418                                         "Transmission Preferences" ),
1419                                     parent,
1420                                     GTK_DIALOG_DESTROY_WITH_PARENT,
1421                                     GTK_STOCK_HELP, GTK_RESPONSE_HELP,
1422                                     GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1423                                     NULL );
1424    gtk_window_set_role( GTK_WINDOW( d ), "transmission-preferences-dialog" );
1425    gtk_dialog_set_has_separator( GTK_DIALOG( d ), FALSE );
1426    gtk_container_set_border_width( GTK_CONTAINER( d ), GUI_PAD );
1427
1428    n = gtk_notebook_new( );
1429    gtk_container_set_border_width ( GTK_CONTAINER ( n ), GUI_PAD );
1430
1431    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1432                              torrentPage( core ),
1433                              gtk_label_new ( _( "Torrents" ) ) );
1434    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1435                              bandwidthPage( core ),
1436                              gtk_label_new ( _( "Speed" ) ) );
1437    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1438                              privacyPage( core ),
1439                              gtk_label_new ( _( "Privacy" ) ) );
1440    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1441                              peerPage( core ),
1442                              gtk_label_new ( _( "Network" ) ) );
1443    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1444                              desktopPage( core ),
1445                              gtk_label_new ( _( "Desktop" ) ) );
1446    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1447                              webPage( core ),
1448                              gtk_label_new ( _( "Web" ) ) );
1449    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1450                              trackerPage( core ),
1451                              gtk_label_new ( _( "Proxy" ) ) );
1452
1453    g_signal_connect( d, "response", G_CALLBACK( response_cb ), core );
1454    gtk_box_pack_start( GTK_BOX( GTK_DIALOG( d )->vbox ), n, TRUE, TRUE, 0 );
1455    gtk_widget_show_all( GTK_DIALOG( d )->vbox );
1456    return d;
1457}
1458
Note: See TracBrowser for help on using the repository browser.