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

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

(trunk) have the gtk and qt clients use the same minimum seed ratio in the preferences dialog as the mac uses

  • Property svn:keywords set to Date Rev Author Id
File size: 46.3 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 8211 2009-04-10 19:16:12Z 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
432onEncryptionToggled( GtkToggleButton * w,
433                     gpointer          core )
434{
435    const int val = gtk_toggle_button_get_active( w )
436                  ? TR_ENCRYPTION_REQUIRED
437                  : TR_ENCRYPTION_PREFERRED;
438    tr_core_set_pref_int( TR_CORE( core ), TR_PREFS_KEY_ENCRYPTION, val );
439}
440
441static GtkWidget*
442peerPage( GObject * core )
443{
444    int                     row = 0;
445    const char *            s;
446    GtkWidget *             t;
447    GtkWidget *             w;
448    GtkWidget *             b;
449    GtkWidget *             h;
450    struct blocklist_data * data;
451
452    data = g_new0( struct blocklist_data, 1 );
453    data->core = TR_CORE( core );
454
455    t = hig_workarea_create( );
456    hig_workarea_add_section_title( t, &row, _( "Blocklist" ) );
457
458    w = new_check_button( "", TR_PREFS_KEY_BLOCKLIST_ENABLED, core );
459    updateBlocklistText( w, TR_CORE( core ) );
460    h = gtk_hbox_new( FALSE, GUI_PAD_BIG );
461    gtk_box_pack_start( GTK_BOX( h ), w, TRUE, TRUE, 0 );
462    b = data->updateBlocklistButton = gtr_button_new_from_stock( GTK_STOCK_REFRESH, _( "_Update" ) );
463    data->check = w;
464    g_object_set_data( G_OBJECT( b ), "session",
465                      tr_core_session( TR_CORE( core ) ) );
466    g_signal_connect( b, "clicked", G_CALLBACK( onBlocklistUpdate ), data );
467    gtk_box_pack_start( GTK_BOX( h ), b, FALSE, FALSE, 0 );
468    g_signal_connect( w, "toggled", G_CALLBACK( target_cb ), b );
469    target_cb( w, b );
470    hig_workarea_add_wide_control( t, &row, h );
471
472    s = _( "Enable _automatic updates" );
473    w = new_check_button( s, PREF_KEY_BLOCKLIST_UPDATES_ENABLED, core );
474    hig_workarea_add_wide_control( t, &row, w );
475    g_signal_connect( data->check, "toggled", G_CALLBACK( target_cb ), w );
476    target_cb( data->check, w );
477
478    hig_workarea_add_section_divider( t, &row );
479    hig_workarea_add_section_title( t, &row, _( "Limits" ) );
480
481    w = new_spin_button( TR_PREFS_KEY_PEER_LIMIT_GLOBAL, core, 1, 3000, 5 );
482    hig_workarea_add_row( t, &row, _( "Maximum peers _overall:" ), w, NULL );
483    w = new_spin_button( TR_PREFS_KEY_PEER_LIMIT_TORRENT, core, 1, 300, 5 );
484    hig_workarea_add_row( t, &row, _( "Maximum peers per _torrent:" ), w, NULL );
485
486    hig_workarea_add_section_divider( t, &row );
487    hig_workarea_add_section_title ( t, &row, _( "Privacy" ) );
488
489    s = _( "_Ignore unencrypted peers" );
490    w = gtk_check_button_new_with_mnemonic( s );
491    gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w ),
492                                  pref_int_get( TR_PREFS_KEY_ENCRYPTION ) ==
493                                  TR_ENCRYPTION_REQUIRED );
494    g_signal_connect( w, "toggled", G_CALLBACK( onEncryptionToggled ), core );
495    hig_workarea_add_wide_control( t, &row, w );
496
497    s = _( "Use peer e_xchange" );
498    w = new_check_button( s, TR_PREFS_KEY_PEX_ENABLED, core );
499    hig_workarea_add_wide_control( t, &row, w );
500
501    hig_workarea_finish( t, &row );
502    g_object_weak_ref( G_OBJECT( t ), peerPageDestroyed, data );
503    return t;
504}
505
506/****
507*****  Web Tab
508****/
509
510enum
511{
512    COL_ADDRESS,
513    N_COLS
514};
515
516static GtkTreeModel*
517whitelist_tree_model_new( const char * whitelist )
518{
519    int            i;
520    char **        rules;
521    GtkListStore * store = gtk_list_store_new( N_COLS,
522                                               G_TYPE_STRING,
523                                               G_TYPE_STRING );
524
525    rules = g_strsplit( whitelist, ",", 0 );
526
527    for( i = 0; rules && rules[i]; ++i )
528    {
529        GtkTreeIter iter;
530        const char * s = rules[i];
531        while( isspace( *s ) ) ++s;
532        gtk_list_store_append( store, &iter );
533        gtk_list_store_set( store, &iter, COL_ADDRESS, s, -1 );
534    }
535
536    g_strfreev( rules );
537    return GTK_TREE_MODEL( store );
538}
539
540struct remote_page
541{
542    TrCore *           core;
543    GtkTreeView *      view;
544    GtkListStore *     store;
545    GtkWidget *        remove_button;
546    GSList *           widgets;
547    GSList *           auth_widgets;
548    GSList *           whitelist_widgets;
549    GtkToggleButton *  rpc_tb;
550    GtkToggleButton *  auth_tb;
551    GtkToggleButton *  whitelist_tb;
552};
553
554static void
555refreshWhitelist( struct remote_page * page )
556{
557    GtkTreeIter    iter;
558    GtkTreeModel * model = GTK_TREE_MODEL( page->store );
559    GString *      gstr = g_string_new( NULL );
560
561    if( gtk_tree_model_get_iter_first( model, &iter ) ) do
562        {
563            char * address;
564            gtk_tree_model_get( model, &iter,
565                                COL_ADDRESS, &address,
566                                -1 );
567            g_string_append( gstr, address );
568            g_string_append( gstr, "," );
569            g_free( address );
570        }
571        while( gtk_tree_model_iter_next( model, &iter ) );
572
573    g_string_truncate( gstr, gstr->len - 1 ); /* remove the trailing comma */
574
575    tr_core_set_pref( page->core, TR_PREFS_KEY_RPC_WHITELIST, gstr->str );
576
577    g_string_free( gstr, TRUE );
578}
579
580static void
581onAddressEdited( GtkCellRendererText  * r UNUSED,
582                 gchar *                  path_string,
583                 gchar *                  address,
584                 gpointer                 gpage )
585{
586    GtkTreeIter          iter;
587    struct remote_page * page = gpage;
588    GtkTreeModel *       model = GTK_TREE_MODEL( page->store );
589    GtkTreePath *        path = gtk_tree_path_new_from_string( path_string );
590
591    if( gtk_tree_model_get_iter( model, &iter, path ) )
592        gtk_list_store_set( page->store, &iter, COL_ADDRESS, address, -1 );
593
594    gtk_tree_path_free( path );
595    refreshWhitelist( page );
596}
597
598static void
599onAddWhitelistClicked( GtkButton * b UNUSED,
600                 gpointer      gpage )
601{
602    GtkTreeIter          iter;
603    GtkTreePath *        path;
604    struct remote_page * page = gpage;
605
606    gtk_list_store_append( page->store, &iter );
607    gtk_list_store_set( page->store, &iter,
608                        COL_ADDRESS,  "0.0.0.0",
609                        -1 );
610
611    path = gtk_tree_model_get_path( GTK_TREE_MODEL( page->store ), &iter );
612    gtk_tree_view_set_cursor(
613        page->view, path,
614        gtk_tree_view_get_column( page->view, COL_ADDRESS ),
615        TRUE );
616    gtk_tree_path_free( path );
617}
618
619static void
620onRemoveWhitelistClicked( GtkButton * b UNUSED,
621                    gpointer      gpage )
622{
623    struct remote_page * page = gpage;
624    GtkTreeSelection *   sel = gtk_tree_view_get_selection( page->view );
625    GtkTreeIter          iter;
626
627    if( gtk_tree_selection_get_selected( sel, NULL, &iter ) )
628    {
629        gtk_list_store_remove( page->store, &iter );
630        refreshWhitelist( page );
631    }
632}
633
634static void
635refreshRPCSensitivity( struct remote_page * page )
636{
637    GSList *           l;
638    const int          rpc_active = gtk_toggle_button_get_active(
639        page->rpc_tb );
640    const int          auth_active = gtk_toggle_button_get_active(
641        page->auth_tb );
642    const int          whitelist_active = gtk_toggle_button_get_active(
643        page->whitelist_tb );
644    GtkTreeSelection * sel = gtk_tree_view_get_selection( page->view );
645    const int          have_addr =
646        gtk_tree_selection_get_selected( sel, NULL,
647                                         NULL );
648    const int          n_rules = gtk_tree_model_iter_n_children(
649        GTK_TREE_MODEL( page->store ), NULL );
650
651    for( l = page->widgets; l != NULL; l = l->next )
652        gtk_widget_set_sensitive( GTK_WIDGET( l->data ), rpc_active );
653
654    for( l = page->auth_widgets; l != NULL; l = l->next )
655        gtk_widget_set_sensitive( GTK_WIDGET(
656                                      l->data ), rpc_active && auth_active );
657
658    for( l = page->whitelist_widgets; l != NULL; l = l->next )
659        gtk_widget_set_sensitive( GTK_WIDGET( l->data ),
660                                  rpc_active && whitelist_active );
661
662    gtk_widget_set_sensitive( page->remove_button,
663                              rpc_active && have_addr && n_rules > 1 );
664}
665
666static void
667onRPCToggled( GtkToggleButton * tb UNUSED,
668              gpointer             page )
669{
670    refreshRPCSensitivity( page );
671}
672
673static void
674onWhitelistSelectionChanged( GtkTreeSelection * sel UNUSED,
675                       gpointer               page )
676{
677    refreshRPCSensitivity( page );
678}
679
680static void
681onLaunchClutchCB( GtkButton * w UNUSED,
682                  gpointer data UNUSED )
683{
684    int    port = pref_int_get( TR_PREFS_KEY_RPC_PORT );
685    char * url = g_strdup_printf( "http://localhost:%d/transmission/web",
686                                  port );
687
688    gtr_open_file( url );
689    g_free( url );
690}
691
692static GtkWidget*
693webPage( GObject * core )
694{
695    const char *         s;
696    int                  row = 0;
697    GtkWidget *          t;
698    GtkWidget *          w;
699    GtkWidget *          h;
700    struct remote_page * page = g_new0( struct remote_page, 1 );
701
702    page->core = TR_CORE( core );
703
704    t = hig_workarea_create( );
705    g_object_set_data_full( G_OBJECT( t ), "page", page, g_free );
706
707    hig_workarea_add_section_title( t, &row, _( "Web Interface" ) );
708
709    /* "enabled" checkbutton */
710    s = _( "_Enable web interface" );
711    w = new_check_button( s, TR_PREFS_KEY_RPC_ENABLED, core );
712    page->rpc_tb = GTK_TOGGLE_BUTTON( w );
713    g_signal_connect( w, "clicked", G_CALLBACK( onRPCToggled ), page );
714    h = gtk_hbox_new( FALSE, GUI_PAD_BIG );
715    gtk_box_pack_start( GTK_BOX( h ), w, TRUE, TRUE, 0 );
716    w = gtr_button_new_from_stock( GTK_STOCK_OPEN, _( "_Open web interface" ) );
717    page->widgets = g_slist_append( page->widgets, w );
718    g_signal_connect( w, "clicked", G_CALLBACK( onLaunchClutchCB ), NULL );
719    gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 );
720    hig_workarea_add_wide_control( t, &row, h );
721
722    /* port */
723    w = new_spin_button( TR_PREFS_KEY_RPC_PORT, core, 0, USHRT_MAX, 1 );
724    page->widgets = g_slist_append( page->widgets, w );
725    w = hig_workarea_add_row( t, &row, _( "Listening _port:" ), w, NULL );
726    page->widgets = g_slist_append( page->widgets, w );
727
728    /* require authentication */
729    s = _( "_Require username" );
730    w = new_check_button( s, TR_PREFS_KEY_RPC_AUTH_REQUIRED, core );
731    hig_workarea_add_wide_control( t, &row, w );
732    page->auth_tb = GTK_TOGGLE_BUTTON( w );
733    page->widgets = g_slist_append( page->widgets, w );
734    g_signal_connect( w, "clicked", G_CALLBACK( onRPCToggled ), page );
735
736    /* username */
737    s = _( "_Username:" );
738    w = new_entry( TR_PREFS_KEY_RPC_USERNAME, core );
739    page->auth_widgets = g_slist_append( page->auth_widgets, w );
740    w = hig_workarea_add_row( t, &row, s, w, NULL );
741    page->auth_widgets = g_slist_append( page->auth_widgets, w );
742
743    /* password */
744    s = _( "Pass_word:" );
745    w = new_entry( TR_PREFS_KEY_RPC_PASSWORD, core );
746    gtk_entry_set_visibility( GTK_ENTRY( w ), FALSE );
747    page->auth_widgets = g_slist_append( page->auth_widgets, w );
748    w = hig_workarea_add_row( t, &row, s, w, NULL );
749    page->auth_widgets = g_slist_append( page->auth_widgets, w );
750
751    /* require authentication */
752    s = _( "Only allow the following IP _addresses to connect:" );
753    w = new_check_button( s, TR_PREFS_KEY_RPC_WHITELIST_ENABLED, core );
754    hig_workarea_add_wide_control( t, &row, w );
755    page->whitelist_tb = GTK_TOGGLE_BUTTON( w );
756    page->widgets = g_slist_append( page->widgets, w );
757    g_signal_connect( w, "clicked", G_CALLBACK( onRPCToggled ), page );
758
759    /* access control list */
760    {
761        const char *        val = pref_string_get( TR_PREFS_KEY_RPC_WHITELIST );
762        GtkTreeModel *      m = whitelist_tree_model_new( val );
763        GtkTreeViewColumn * c;
764        GtkCellRenderer *   r;
765        GtkTreeSelection *  sel;
766        GtkTreeView *       v;
767        GtkWidget *         w;
768        GtkWidget *         h;
769
770        page->store = GTK_LIST_STORE( m );
771        w = gtk_tree_view_new_with_model( m );
772        g_signal_connect( w, "button-release-event",
773                          G_CALLBACK( on_tree_view_button_released ), NULL );
774
775        page->whitelist_widgets = g_slist_append( page->whitelist_widgets, w );
776        v = page->view = GTK_TREE_VIEW( w );
777        gtr_widget_set_tooltip_text( w, _( "IP addresses may use wildcards, such as 192.168.*.*" ) );
778        sel = gtk_tree_view_get_selection( v );
779        g_signal_connect( sel, "changed",
780                          G_CALLBACK( onWhitelistSelectionChanged ), page );
781        g_object_unref( G_OBJECT( m ) );
782        gtk_tree_view_set_headers_visible( v, TRUE );
783        w = gtk_frame_new( NULL );
784        gtk_frame_set_shadow_type( GTK_FRAME( w ), GTK_SHADOW_IN );
785        gtk_container_add( GTK_CONTAINER( w ), GTK_WIDGET( v ) );
786
787        /* ip address column */
788        r = gtk_cell_renderer_text_new( );
789        g_signal_connect( r, "edited",
790                          G_CALLBACK( onAddressEdited ), page );
791        g_object_set( G_OBJECT( r ), "editable", TRUE, NULL );
792        c = gtk_tree_view_column_new_with_attributes( NULL, r,
793                                                      "text", COL_ADDRESS,
794                                                      NULL );
795        gtk_tree_view_column_set_expand( c, TRUE );
796        gtk_tree_view_append_column( v, c );
797        gtk_tree_view_set_headers_visible( v, FALSE );
798
799        s = _( "Addresses:" );
800        w = hig_workarea_add_row( t, &row, s, w, NULL );
801        gtk_misc_set_alignment( GTK_MISC( w ), 0.0f, 0.0f );
802        gtk_misc_set_padding( GTK_MISC( w ), 0, GUI_PAD );
803        page->whitelist_widgets = g_slist_append( page->whitelist_widgets, w );
804
805        h = gtk_hbox_new( TRUE, GUI_PAD );
806        w = gtk_button_new_from_stock( GTK_STOCK_REMOVE );
807        g_signal_connect( w, "clicked", G_CALLBACK(
808                              onRemoveWhitelistClicked ), page );
809        page->remove_button = w;
810        onWhitelistSelectionChanged( sel, page );
811        gtk_box_pack_start( GTK_BOX( h ), w, TRUE, TRUE, 0 );
812        w = gtk_button_new_from_stock( GTK_STOCK_ADD );
813        page->whitelist_widgets = g_slist_append( page->whitelist_widgets, w );
814        g_signal_connect( w, "clicked", G_CALLBACK( onAddWhitelistClicked ), page );
815        gtk_box_pack_start( GTK_BOX( h ), w, TRUE, TRUE, 0 );
816        w = gtk_hbox_new( FALSE, 0 );
817        gtk_box_pack_start( GTK_BOX( w ), gtk_alignment_new( 0, 0, 0, 0 ),
818                            TRUE, TRUE, 0 );
819        gtk_box_pack_start( GTK_BOX( w ), h, FALSE, FALSE, 0 );
820        hig_workarea_add_wide_control( t, &row, w );
821    }
822
823    refreshRPCSensitivity( page );
824    hig_workarea_finish( t, &row );
825    return t;
826}
827
828/****
829*****  Proxy Tab
830****/
831
832struct ProxyPage
833{
834    TrCore *  core;
835    GSList *  proxy_widgets;
836    GSList *  proxy_auth_widgets;
837};
838
839static void
840refreshProxySensitivity( struct ProxyPage * p )
841{
842    GSList *       l;
843    const gboolean proxy_enabled = pref_flag_get( TR_PREFS_KEY_PROXY_ENABLED );
844    const gboolean proxy_auth_enabled = pref_flag_get( TR_PREFS_KEY_PROXY_AUTH_ENABLED );
845
846    for( l = p->proxy_widgets; l != NULL; l = l->next )
847        gtk_widget_set_sensitive( GTK_WIDGET( l->data ), proxy_enabled );
848
849    for( l = p->proxy_auth_widgets; l != NULL; l = l->next )
850        gtk_widget_set_sensitive( GTK_WIDGET( l->data ),
851                                  proxy_enabled && proxy_auth_enabled );
852}
853
854static void
855onProxyToggled( GtkToggleButton * tb UNUSED,
856                gpointer             user_data )
857{
858    refreshProxySensitivity( user_data );
859}
860
861static void
862proxyPageFree( gpointer gpage )
863{
864    struct ProxyPage * page = gpage;
865
866    g_slist_free( page->proxy_widgets );
867    g_slist_free( page->proxy_auth_widgets );
868    g_free( page );
869}
870
871static GtkTreeModel*
872proxyTypeModelNew( void )
873{
874    GtkTreeIter    iter;
875    GtkListStore * store = gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_INT );
876
877    gtk_list_store_append( store, &iter );
878    gtk_list_store_set( store, &iter, 0, "HTTP", 1, TR_PROXY_HTTP, -1 );
879    gtk_list_store_append( store, &iter );
880    gtk_list_store_set( store, &iter, 0, "SOCKS4", 1, TR_PROXY_SOCKS4, -1 );
881    gtk_list_store_append( store, &iter );
882    gtk_list_store_set( store, &iter, 0, "SOCKS5", 1, TR_PROXY_SOCKS5, -1 );
883    return GTK_TREE_MODEL( store );
884}
885
886static void
887onProxyTypeChanged( GtkComboBox * w,
888                    gpointer      gpage )
889{
890    GtkTreeIter iter;
891
892    if( gtk_combo_box_get_active_iter( w, &iter ) )
893    {
894        struct ProxyPage * page = gpage;
895        int type = TR_PROXY_HTTP;
896        gtk_tree_model_get( gtk_combo_box_get_model( w ), &iter, 1, &type, -1 );
897        tr_core_set_pref_int( TR_CORE( page->core ), TR_PREFS_KEY_PROXY_TYPE, type );
898    }
899}
900
901static GtkWidget*
902trackerPage( GObject * core )
903{
904    int                row = 0;
905    const char *       s;
906    GtkWidget *        t;
907    GtkWidget *        w;
908    GtkTreeModel *     m;
909    GtkCellRenderer *  r;
910    struct ProxyPage * page = tr_new0( struct ProxyPage, 1 );
911
912    page->core = TR_CORE( core );
913
914    t = hig_workarea_create( );
915    hig_workarea_add_section_title ( t, &row, _( "Tracker Proxy" ) );
916
917    s = _( "Connect to tracker via a pro_xy" );
918    w = new_check_button( s, TR_PREFS_KEY_PROXY_ENABLED, core );
919    g_signal_connect( w, "toggled", G_CALLBACK( onProxyToggled ), page );
920    hig_workarea_add_wide_control( t, &row, w );
921
922    s = _( "Proxy _server:" );
923    w = new_entry( TR_PREFS_KEY_PROXY, core );
924    page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
925    w = hig_workarea_add_row( t, &row, s, w, NULL );
926    page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
927
928    w = new_spin_button( TR_PREFS_KEY_PROXY_PORT, core, 0, USHRT_MAX, 1 );
929    page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
930    w = hig_workarea_add_row( t, &row, _( "Proxy _port:" ), w, NULL );
931    page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
932
933    s = _( "Proxy _type:" );
934    m = proxyTypeModelNew( );
935    w = gtk_combo_box_new_with_model( m );
936    r = gtk_cell_renderer_text_new( );
937    gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( w ), r, TRUE );
938    gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT( w ), r, "text", 0, NULL );
939    gtk_combo_box_set_active( GTK_COMBO_BOX( w ), pref_int_get( TR_PREFS_KEY_PROXY_TYPE ) );
940    g_signal_connect( w, "changed", G_CALLBACK( onProxyTypeChanged ), page );
941    g_object_unref( G_OBJECT( m ) );
942    page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
943    w = hig_workarea_add_row( t, &row, s, w, NULL );
944    page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
945
946    s = _( "_Authentication is required" );
947    w = new_check_button( s, TR_PREFS_KEY_PROXY_AUTH_ENABLED, core );
948    g_signal_connect( w, "toggled", G_CALLBACK( onProxyToggled ), page );
949    hig_workarea_add_wide_control( t, &row, w );
950    page->proxy_widgets = g_slist_append( page->proxy_widgets, w );
951
952    s = _( "_Username:" );
953    w = new_entry( TR_PREFS_KEY_PROXY_USERNAME, core );
954    page->proxy_auth_widgets = g_slist_append( page->proxy_auth_widgets, w );
955    w = hig_workarea_add_row( t, &row, s, w, NULL );
956    page->proxy_auth_widgets = g_slist_append( page->proxy_auth_widgets, w );
957
958    s = _( "Pass_word:" );
959    w = new_entry( TR_PREFS_KEY_RPC_PASSWORD, core );
960    gtk_entry_set_visibility( GTK_ENTRY( w ), FALSE );
961    page->proxy_auth_widgets = g_slist_append( page->proxy_auth_widgets, w );
962    w = hig_workarea_add_row( t, &row, s, w, NULL );
963    page->proxy_auth_widgets = g_slist_append( page->proxy_auth_widgets, w );
964
965    hig_workarea_finish( t, &row );
966    g_object_set_data_full( G_OBJECT( t ), "page", page, proxyPageFree );
967
968    refreshProxySensitivity( page );
969    return t;
970}
971
972/****
973*****  Bandwidth Tab
974****/
975
976struct BandwidthPage
977{
978    TrCore *  core;
979    GSList *  sched_widgets;
980};
981
982static void
983refreshSchedSensitivity( struct BandwidthPage * p )
984{
985    GSList *       l;
986    const gboolean sched_enabled = pref_flag_get( TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED );
987
988    for( l = p->sched_widgets; l != NULL; l = l->next )
989        gtk_widget_set_sensitive( GTK_WIDGET( l->data ), sched_enabled );
990}
991
992static void
993onSchedToggled( GtkToggleButton * tb UNUSED,
994                gpointer             user_data )
995{
996    refreshSchedSensitivity( user_data );
997}
998
999static void
1000onTimeComboChanged( GtkComboBox * w,
1001                    gpointer      core )
1002{
1003    GtkTreeIter iter;
1004
1005    if( gtk_combo_box_get_active_iter( w, &iter ) )
1006    {
1007        const char * key = g_object_get_data( G_OBJECT( w ), PREF_KEY );
1008        int          val = 0;
1009        gtk_tree_model_get( gtk_combo_box_get_model(
1010                                w ), &iter, 0, &val, -1 );
1011        tr_core_set_pref_int( TR_CORE( core ), key, val );
1012    }
1013}
1014
1015static GtkWidget*
1016new_time_combo( GObject *    core,
1017                const char * key )
1018{
1019    int               val;
1020    int               i;
1021    GtkWidget *       w;
1022    GtkCellRenderer * r;
1023    GtkListStore *    store;
1024
1025    /* build a store at 15 minute intervals */
1026    store = gtk_list_store_new( 2, G_TYPE_INT, G_TYPE_STRING );
1027    for( i = 0; i < 60 * 24; i += 15 )
1028    {
1029        char        buf[128];
1030        GtkTreeIter iter;
1031        struct tm   tm;
1032        tm.tm_hour = i / 60;
1033        tm.tm_min = i % 60;
1034        tm.tm_sec = 0;
1035        strftime( buf, sizeof( buf ), "%H:%M", &tm );
1036        gtk_list_store_append( store, &iter );
1037        gtk_list_store_set( store, &iter, 0, i, 1, buf, -1 );
1038    }
1039
1040    /* build the widget */
1041    w = gtk_combo_box_new_with_model( GTK_TREE_MODEL( store ) );
1042    gtk_combo_box_set_wrap_width( GTK_COMBO_BOX( w ), 4 );
1043    r = gtk_cell_renderer_text_new( );
1044    gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( w ), r, TRUE );
1045    gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT(
1046                                        w ), r, "text", 1, NULL );
1047    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, tr_strdup(
1048                                key ), g_free );
1049    val = pref_int_get( key );
1050    gtk_combo_box_set_active( GTK_COMBO_BOX( w ), val / ( 15 ) );
1051    g_signal_connect( w, "changed", G_CALLBACK( onTimeComboChanged ), core );
1052
1053    /* cleanup */
1054    g_object_unref( G_OBJECT( store ) );
1055    return w;
1056}
1057
1058static void
1059onWeekComboChanged( GtkComboBox * w, gpointer core )
1060{
1061    GtkTreeIter iter;
1062
1063    if( gtk_combo_box_get_active_iter( w, &iter ) )
1064    {
1065        int val = 0;
1066        const char * key = g_object_get_data( G_OBJECT( w ), PREF_KEY );
1067        gtk_tree_model_get( gtk_combo_box_get_model( w ), &iter, 0, &val, -1 );
1068        tr_core_set_pref_int( TR_CORE( core ), key, val );
1069    }
1070}
1071
1072static GtkWidget*
1073new_week_combo( GObject * core, const char * key )
1074{
1075    int i;
1076    int selIndex;
1077    GtkWidget * w;
1078    GtkCellRenderer * r;
1079    GtkListStore * store;
1080    const int currentValue = pref_int_get( key );
1081    const struct {
1082        int value;
1083        const char * text;
1084    } items[] = {
1085        { TR_SCHED_ALL,     N_( "Every Day" ) },
1086        { TR_SCHED_WEEKDAY, N_( "Weekdays" ) },
1087        { TR_SCHED_WEEKEND, N_( "Weekends" ) },
1088        { TR_SCHED_SUN,     N_( "Sunday" ) },
1089        { TR_SCHED_MON,     N_( "Monday" ) },
1090        { TR_SCHED_TUES,    N_( "Tuesday" ) },
1091        { TR_SCHED_WED,     N_( "Wednesday" ) },
1092        { TR_SCHED_THURS,   N_( "Thursday" ) },
1093        { TR_SCHED_FRI,     N_( "Friday" ) },
1094        { TR_SCHED_SAT,     N_( "Saturday" ) }
1095    };
1096
1097    /* build a store for the days of the week */
1098    selIndex = -1;
1099    store = gtk_list_store_new( 2, G_TYPE_INT, G_TYPE_STRING );
1100    for( i=0; i<(int)G_N_ELEMENTS(items); ++i ) {
1101        GtkTreeIter iter;
1102        gtk_list_store_append( store, &iter );
1103        gtk_list_store_set( store, &iter, 0, items[i].value, 1, _( items[i].text ), -1 );
1104        if( items[i].value == currentValue )
1105            selIndex = i;
1106    }
1107
1108    /* build the widget */
1109    w = gtk_combo_box_new_with_model( GTK_TREE_MODEL( store ) );
1110    r = gtk_cell_renderer_text_new( );
1111    gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( w ), r, TRUE );
1112    gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT( w ), r, "text", 1, NULL );
1113    g_object_set_data_full( G_OBJECT( w ), PREF_KEY, tr_strdup( key ), g_free );
1114    if( selIndex >= 0 )
1115        gtk_combo_box_set_active( GTK_COMBO_BOX( w ), selIndex );
1116    g_signal_connect( w, "changed", G_CALLBACK( onWeekComboChanged ), core );
1117
1118    /* cleanup */
1119    g_object_unref( G_OBJECT( store ) );
1120    return w;
1121}
1122
1123static void
1124bandwidthPageFree( gpointer gpage )
1125{
1126    struct BandwidthPage * page = gpage;
1127
1128    g_slist_free( page->sched_widgets );
1129    g_free( page );
1130}
1131
1132static GtkWidget*
1133bandwidthPage( GObject * core )
1134{
1135    int                    row = 0;
1136    const char *           s;
1137    GtkWidget *            t;
1138    GtkWidget *            w, * w2, * h;
1139    char buf[512];
1140    struct BandwidthPage * page = tr_new0( struct BandwidthPage, 1 );
1141
1142    page->core = TR_CORE( core );
1143
1144    t = hig_workarea_create( );
1145    hig_workarea_add_section_title( t, &row, _( "Global Bandwidth Limits" ) );
1146
1147        s = _( "Limit _download speed (KB/s):" );
1148        w = new_check_button( s, TR_PREFS_KEY_DSPEED_ENABLED, core );
1149        w2 = new_spin_button( TR_PREFS_KEY_DSPEED, core, 0, INT_MAX, 5 );
1150        gtk_widget_set_sensitive( GTK_WIDGET( w2 ), pref_flag_get( TR_PREFS_KEY_DSPEED_ENABLED ) );
1151        g_signal_connect( w, "toggled", G_CALLBACK( target_cb ), w2 );
1152        hig_workarea_add_row_w( t, &row, w, w2, NULL );
1153
1154        s = _( "Limit _upload speed (KB/s):" );
1155        w = new_check_button( s, TR_PREFS_KEY_USPEED_ENABLED, core );
1156        w2 = new_spin_button( TR_PREFS_KEY_USPEED, core, 0, INT_MAX, 5 );
1157        gtk_widget_set_sensitive( GTK_WIDGET( w2 ), pref_flag_get( TR_PREFS_KEY_USPEED_ENABLED ) );
1158        g_signal_connect( w, "toggled", G_CALLBACK( target_cb ), w2 );
1159        hig_workarea_add_row_w( t, &row, w, w2, NULL );
1160
1161    hig_workarea_add_section_divider( t, &row );
1162    h = gtk_hbox_new( FALSE, GUI_PAD );
1163    w = gtk_image_new_from_stock( "alt-speed-off", -1 );
1164    gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 );
1165    g_snprintf( buf, sizeof( buf ), "<b>%s</b>", _( "Speed Limit Mode" ) );
1166    w = gtk_label_new( buf );
1167    gtk_misc_set_alignment( GTK_MISC( w ), 0.0f, 0.5f );
1168    gtk_label_set_use_markup( GTK_LABEL( w ), TRUE );
1169    gtk_box_pack_start( GTK_BOX( h ), w, FALSE, FALSE, 0 );
1170    hig_workarea_add_section_title_widget( t, &row, h );
1171
1172        s = _( "Limit do_wnload speed (KB/s):" );
1173        w = new_spin_button( TR_PREFS_KEY_ALT_SPEED_DOWN, core, 0, INT_MAX, 5 );
1174        hig_workarea_add_row( t, &row, s, w, NULL );
1175
1176        s = _( "Limit u_pload speed (KB/s):" );
1177        w = new_spin_button( TR_PREFS_KEY_ALT_SPEED_UP, core, 0, INT_MAX, 5 );
1178        hig_workarea_add_row( t, &row, s, w, NULL );
1179
1180        g_snprintf( buf, sizeof( buf ), "<small>%s</small>", _( "When enabled, Speed Limit Mode overrides the Global Bandwidth Limits" ) );
1181        w = gtk_label_new( buf );
1182        gtk_label_set_use_markup( GTK_LABEL( w ), TRUE );
1183        gtk_misc_set_alignment( GTK_MISC( w ), 0.5f, 0.5f );
1184        hig_workarea_add_wide_control( t, &row, w );
1185
1186        s = _( "Use Speed Limit Mode between:" ); 
1187        h = gtk_hbox_new( FALSE, 0 );
1188        w2 = new_time_combo( core, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN );
1189        page->sched_widgets = g_slist_append( page->sched_widgets, w2 );
1190        gtk_box_pack_start( GTK_BOX( h ), w2, TRUE, TRUE, 0 );
1191        w2 = gtk_label_new ( _( " and " ) );
1192        page->sched_widgets = g_slist_append( page->sched_widgets, w2 );
1193        gtk_box_pack_start( GTK_BOX( h ), w2, FALSE, FALSE, 0 );
1194        w2 = new_time_combo( core, TR_PREFS_KEY_ALT_SPEED_TIME_END );
1195        page->sched_widgets = g_slist_append( page->sched_widgets, w2 );
1196        gtk_box_pack_start( GTK_BOX( h ), w2, TRUE, TRUE, 0 );
1197        w = new_check_button( s, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, core );
1198        g_signal_connect( w, "toggled", G_CALLBACK( onSchedToggled ), page );
1199        hig_workarea_add_row_w( t, &row, w, h, NULL );
1200
1201        s = _( "_On days:" );
1202        w = new_week_combo( core, TR_PREFS_KEY_ALT_SPEED_TIME_DAY );
1203        page->sched_widgets = g_slist_append( page->sched_widgets, w );
1204        w = hig_workarea_add_row( t, &row, s, w, NULL );
1205        page->sched_widgets = g_slist_append( page->sched_widgets, w );
1206
1207    hig_workarea_finish( t, &row );
1208    g_object_set_data_full( G_OBJECT( t ), "page", page, bandwidthPageFree );
1209
1210    refreshSchedSensitivity( page );
1211    return t;
1212}
1213
1214/****
1215*****  Network Tab
1216****/
1217
1218struct network_page_data
1219{
1220    TrCore     * core;
1221    GtkWidget  * portLabel;
1222    GtkWidget  * portButton;
1223    GtkWidget  * portSpin;
1224    gulong       portTag;
1225    gulong       prefsTag;
1226};
1227
1228static void
1229onCorePrefsChanged( TrCore * core UNUSED, const char *  key, gpointer gdata )
1230{
1231    if( !strcmp( key, TR_PREFS_KEY_PEER_PORT ) )
1232    {
1233        struct network_page_data * data = gdata;
1234        gtk_label_set_text( GTK_LABEL( data->portLabel ), _( "Status unknown" ) );
1235        gtk_widget_set_sensitive( data->portButton, TRUE );
1236        gtk_widget_set_sensitive( data->portSpin, TRUE );
1237    }
1238}
1239
1240static void
1241networkPageDestroyed( gpointer gdata, GObject * dead UNUSED )
1242{
1243    struct network_page_data * data = gdata;
1244    if( data->prefsTag > 0 )
1245        g_signal_handler_disconnect( data->core, data->prefsTag );
1246    if( data->portTag > 0 )
1247        g_signal_handler_disconnect( data->core, data->portTag );
1248    g_free( data );
1249}
1250
1251static void
1252onPortTested( TrCore * core UNUSED, gboolean isOpen, gpointer vdata )
1253{
1254    struct network_page_data * data = vdata;
1255    const char * markup = isOpen ? _( "Port is <b>open</b>" ) : _( "Port is <b>closed</b>" );
1256    gtk_label_set_markup( GTK_LABEL( data->portLabel ), markup );
1257    gtk_widget_set_sensitive( data->portButton, TRUE );
1258    gtk_widget_set_sensitive( data->portSpin, TRUE );
1259}
1260
1261static void
1262onPortTest( GtkButton * button UNUSED, gpointer vdata )
1263{
1264    struct network_page_data * data = vdata;
1265    gtk_widget_set_sensitive( data->portButton, FALSE );
1266    gtk_widget_set_sensitive( data->portSpin, FALSE );
1267    gtk_label_set_markup( GTK_LABEL( data->portLabel ), _( "<i>Testing...</i>" ) );
1268    data->portTag = g_signal_connect( data->core, "port-tested", G_CALLBACK(onPortTested), data );
1269    tr_core_port_test( data->core );
1270}
1271
1272static GtkWidget*
1273networkPage( GObject * core )
1274{
1275    int                        row = 0;
1276    const char *               s;
1277    GtkWidget *                t;
1278    GtkWidget *                w;
1279    GtkWidget *                h;
1280    GtkWidget *                l;
1281    struct network_page_data * data;
1282
1283    /* register to stop listening to core prefs changes when the page is destroyed */
1284    data = g_new0( struct network_page_data, 1 );
1285    data->core = TR_CORE( core );
1286
1287    /* build the page */
1288    t = hig_workarea_create( );
1289    hig_workarea_add_section_title( t, &row, _( "Incoming Peers" ) );
1290
1291    s = _( "_Port for incoming connections:" );
1292    w = data->portSpin = new_spin_button( TR_PREFS_KEY_PEER_PORT, core, 1, USHRT_MAX, 1 );
1293    hig_workarea_add_row( t, &row, s, w, NULL );
1294
1295    h = gtk_hbox_new( FALSE, GUI_PAD_BIG );
1296    l = data->portLabel = gtk_label_new( _( "Status unknown" ) );
1297    gtk_misc_set_alignment( GTK_MISC( l ), 0.0f, 0.5f );
1298    gtk_box_pack_start( GTK_BOX( h ), l, TRUE, TRUE, 0 );
1299    w = data->portButton = gtk_button_new_with_mnemonic( _( "_Test Port" ) );
1300    gtk_box_pack_end( GTK_BOX( h ), w, FALSE, FALSE, 0 );
1301    g_signal_connect( w, "clicked", G_CALLBACK(onPortTest), data );
1302    hig_workarea_add_row( t, &row, NULL, h, NULL );
1303    data->prefsTag = g_signal_connect( TR_CORE( core ), "prefs-changed", G_CALLBACK( onCorePrefsChanged ), data );
1304    g_object_weak_ref( G_OBJECT( t ), networkPageDestroyed, data );
1305
1306    s = _( "Randomize the port every launch" );
1307    w = new_check_button( s, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START, core );
1308    hig_workarea_add_wide_control( t, &row, w );
1309
1310    s = _( "Use UPnP or NAT-PMP port _forwarding from my router" );
1311    w = new_check_button( s, TR_PREFS_KEY_PORT_FORWARDING, core );
1312    hig_workarea_add_wide_control( t, &row, w );
1313
1314    hig_workarea_finish( t, &row );
1315    return t;
1316}
1317
1318/****
1319*****
1320****/
1321
1322GtkWidget *
1323tr_prefs_dialog_new( GObject *   core,
1324                     GtkWindow * parent )
1325{
1326    GtkWidget * d;
1327    GtkWidget * n;
1328
1329    d = gtk_dialog_new_with_buttons( _(
1330                                         "Transmission Preferences" ),
1331                                     parent,
1332                                     GTK_DIALOG_DESTROY_WITH_PARENT,
1333                                     GTK_STOCK_HELP, GTK_RESPONSE_HELP,
1334                                     GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1335                                     NULL );
1336    gtk_window_set_role( GTK_WINDOW( d ), "transmission-preferences-dialog" );
1337    gtk_dialog_set_has_separator( GTK_DIALOG( d ), FALSE );
1338    gtk_container_set_border_width( GTK_CONTAINER( d ), GUI_PAD );
1339
1340    n = gtk_notebook_new( );
1341    gtk_container_set_border_width ( GTK_CONTAINER ( n ), GUI_PAD );
1342
1343    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1344                              torrentPage( core ),
1345                              gtk_label_new ( _( "Torrents" ) ) );
1346    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1347                              peerPage( core ),
1348                              gtk_label_new ( _( "Peers" ) ) );
1349    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1350                              bandwidthPage( core ),
1351                              gtk_label_new ( _( "Speed" ) ) );
1352    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1353                              networkPage( core ),
1354                              gtk_label_new ( _( "Network" ) ) );
1355    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1356                              desktopPage( core ),
1357                              gtk_label_new ( _( "Desktop" ) ) );
1358    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1359                              webPage( core ),
1360                              gtk_label_new ( _( "Web" ) ) );
1361    gtk_notebook_append_page( GTK_NOTEBOOK( n ),
1362                              trackerPage( core ),
1363                              gtk_label_new ( _( "Trackers" ) ) );
1364
1365    g_signal_connect( d, "response", G_CALLBACK( response_cb ), core );
1366    gtk_box_pack_start( GTK_BOX( GTK_DIALOG( d )->vbox ), n, TRUE, TRUE, 0 );
1367    gtk_widget_show_all( GTK_DIALOG( d )->vbox );
1368    return d;
1369}
1370
Note: See TracBrowser for help on using the repository browser.