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

Last change on this file since 6798 was 6798, checked in by charles, 14 years ago

rpc-server cleanups. add true wildmat control. break the Mac build a little harder.

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