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

Last change on this file since 8253 was 8253, checked in by charles, 13 years ago

(trunk gtk) make the gtk client's preferences dialog's encryption combobox match the qt client's

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