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

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

(gtk trunk) tweak one of the strings in the prefs dialog.

  • 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 8187 2009-04-09 18:46:16Z 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, .5, 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.