source: branches/1.4x/gtk/tr-prefs.c @ 7463

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

(1.4x gtk) #1585: use g_timeout_add_seconds() where appropriate to group timers together for fewer scheduled wakeups

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