2 * This file Copyright (C) Mnemosyne LLC
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.
10 * $Id: tr-prefs.c 12027 2011-02-24 15:10:18Z jordan $
13 #include <ctype.h> /* isspace */
14 #include <limits.h> /* USHRT_MAX */
15 #include <stdlib.h> /* free() */
17 #include <glib/gi18n.h>
19 #include <libtransmission/transmission.h>
20 #include <libtransmission/utils.h>
21 #include <libtransmission/version.h>
22 #include <libtransmission/web.h>
33 #define PREF_KEY "pref-key"
36 response_cb( GtkDialog
* dialog
,
38 gpointer unused UNUSED
)
40 if( response
== GTK_RESPONSE_HELP
)
42 char * uri
= g_strconcat( gtr_get_help_uri(), "/html/preferences.html", NULL
);
47 if( response
== GTK_RESPONSE_CLOSE
)
48 gtk_widget_destroy( GTK_WIDGET( dialog
) );
52 toggled_cb( GtkToggleButton
* w
,
55 const char * key
= g_object_get_data( G_OBJECT( w
), PREF_KEY
);
56 const gboolean flag
= gtk_toggle_button_get_active( w
);
58 tr_core_set_pref_bool( TR_CORE( core
), key
, flag
);
62 new_check_button( const char * mnemonic
,
66 GtkWidget
* w
= gtk_check_button_new_with_mnemonic( mnemonic
);
68 g_object_set_data_full( G_OBJECT( w
), PREF_KEY
, g_strdup(
70 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( w
),
71 gtr_pref_flag_get( key
) );
72 g_signal_connect( w
, "toggled", G_CALLBACK( toggled_cb
), core
);
76 #define IDLE_DATA "idle-data"
86 spin_idle_data_free( gpointer gdata
)
88 struct spin_idle_data
* data
= gdata
;
90 g_timer_destroy( data
->last_change
);
95 spun_cb_idle( gpointer spin
)
97 gboolean keep_waiting
= TRUE
;
98 GObject
* o
= G_OBJECT( spin
);
99 struct spin_idle_data
* data
= g_object_get_data( o
, IDLE_DATA
);
101 /* has the user stopped making changes? */
102 if( g_timer_elapsed( data
->last_change
, NULL
) > 0.33f
)
104 /* update the core */
105 const char * key
= g_object_get_data( o
, PREF_KEY
);
109 const double value
= gtk_spin_button_get_value( GTK_SPIN_BUTTON( spin
) );
110 tr_core_set_pref_double( TR_CORE( data
->core
), key
, value
);
114 const int value
= gtk_spin_button_get_value_as_int( GTK_SPIN_BUTTON( spin
) );
115 tr_core_set_pref_int( TR_CORE( data
->core
), key
, value
);
119 g_object_set_data( o
, IDLE_DATA
, NULL
);
120 keep_waiting
= FALSE
;
121 g_object_unref( G_OBJECT( o
) );
128 spun_cb( GtkSpinButton
* w
, gpointer core
, gboolean isDouble
)
130 /* user may be spinning through many values, so let's hold off
131 for a moment to keep from flooding the core with changes */
132 GObject
* o
= G_OBJECT( w
);
133 struct spin_idle_data
* data
= g_object_get_data( o
, IDLE_DATA
);
137 data
= g_new( struct spin_idle_data
, 1 );
139 data
->last_change
= g_timer_new( );
140 data
->isDouble
= isDouble
;
141 g_object_set_data_full( o
, IDLE_DATA
, data
, spin_idle_data_free
);
142 g_object_ref( G_OBJECT( o
) );
143 gtr_timeout_add_seconds( 1, spun_cb_idle
, w
);
145 g_timer_start( data
->last_change
);
149 spun_cb_int( GtkSpinButton
* w
, gpointer core
)
151 spun_cb( w
, core
, FALSE
);
155 spun_cb_double( GtkSpinButton
* w
, gpointer core
)
157 spun_cb( w
, core
, TRUE
);
161 new_spin_button( const char * key
,
167 GtkWidget
* w
= gtk_spin_button_new_with_range( low
, high
, step
);
168 g_object_set_data_full( G_OBJECT( w
), PREF_KEY
, g_strdup( key
), g_free
);
169 gtk_spin_button_set_digits( GTK_SPIN_BUTTON( w
), 0 );
170 gtk_spin_button_set_value( GTK_SPIN_BUTTON( w
), gtr_pref_int_get( key
) );
171 g_signal_connect( w
, "value-changed", G_CALLBACK( spun_cb_int
), core
);
176 new_spin_button_double( const char * key
,
182 GtkWidget
* w
= gtk_spin_button_new_with_range( low
, high
, step
);
183 g_object_set_data_full( G_OBJECT( w
), PREF_KEY
, g_strdup( key
), g_free
);
184 gtk_spin_button_set_digits( GTK_SPIN_BUTTON( w
), 2 );
185 gtk_spin_button_set_value( GTK_SPIN_BUTTON( w
), gtr_pref_double_get( key
) );
186 g_signal_connect( w
, "value-changed", G_CALLBACK( spun_cb_double
), core
);
191 entry_changed_cb( GtkEntry
* w
, gpointer core
)
193 const char * key
= g_object_get_data( G_OBJECT( w
), PREF_KEY
);
194 const char * value
= gtk_entry_get_text( w
);
196 tr_core_set_pref( TR_CORE( core
), key
, value
);
200 new_entry( const char * key
,
203 GtkWidget
* w
= gtk_entry_new( );
204 const char * value
= gtr_pref_string_get( key
);
207 gtk_entry_set_text( GTK_ENTRY( w
), value
);
208 g_object_set_data_full( G_OBJECT( w
), PREF_KEY
, g_strdup(
210 g_signal_connect( w
, "changed", G_CALLBACK( entry_changed_cb
), core
);
215 chosen_cb( GtkFileChooser
* w
, gpointer core
)
217 const char * key
= g_object_get_data( G_OBJECT( w
), PREF_KEY
);
218 char * value
= gtk_file_chooser_get_filename( GTK_FILE_CHOOSER( w
) );
219 tr_core_set_pref( TR_CORE( core
), key
, value
);
224 new_path_chooser_button( const char * key
, gpointer core
)
226 GtkWidget
* w
= gtk_file_chooser_button_new( NULL
, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
);
227 const char * path
= gtr_pref_string_get( key
);
228 g_object_set_data_full( G_OBJECT( w
), PREF_KEY
, g_strdup( key
), g_free
);
229 g_signal_connect( w
, "selection-changed", G_CALLBACK( chosen_cb
), core
);
231 gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER( w
), path
);
236 new_file_chooser_button( const char * key
, gpointer core
)
238 GtkWidget
* w
= gtk_file_chooser_button_new( NULL
, GTK_FILE_CHOOSER_ACTION_OPEN
);
239 const char * path
= gtr_pref_string_get( key
);
240 g_object_set_data_full( G_OBJECT( w
), PREF_KEY
, g_strdup( key
), g_free
);
242 gtk_file_chooser_set_filename( GTK_FILE_CHOOSER( w
), path
);
243 g_signal_connect( w
, "selection-changed", G_CALLBACK( chosen_cb
), core
);
248 target_cb( GtkWidget
* tb
, gpointer target
)
250 const gboolean b
= gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( tb
) );
252 gtk_widget_set_sensitive( GTK_WIDGET( target
), b
);
260 torrentPage( GObject
* core
)
269 t
= hig_workarea_create( );
270 hig_workarea_add_section_title( t
, &row
, _( "Adding" ) );
272 s
= _( "_Start when added" );
273 w
= new_check_button( s
, TR_PREFS_KEY_START
, core
);
274 hig_workarea_add_wide_control( t
, &row
, w
);
276 s
= _( "Show _options dialog" );
277 w
= new_check_button( s
, PREF_KEY_OPTIONS_PROMPT
, core
);
278 hig_workarea_add_wide_control( t
, &row
, w
);
280 s
= _( "Mo_ve .torrent file to the trash" );
281 w
= new_check_button( s
, TR_PREFS_KEY_TRASH_ORIGINAL
, core
);
282 hig_workarea_add_wide_control( t
, &row
, w
);
285 s
= _( "Automatically _add torrents from:" );
286 l
= new_check_button( s
, PREF_KEY_DIR_WATCH_ENABLED
, core
);
287 w
= new_path_chooser_button( PREF_KEY_DIR_WATCH
, core
);
288 gtk_widget_set_sensitive( GTK_WIDGET( w
),
289 gtr_pref_flag_get( PREF_KEY_DIR_WATCH_ENABLED
) );
290 g_signal_connect( l
, "toggled", G_CALLBACK( target_cb
), w
);
291 hig_workarea_add_row_w( t
, &row
, l
, w
, NULL
);
294 hig_workarea_add_section_divider( t
, &row
);
295 hig_workarea_add_section_title( t
, &row
, _( "Downloading" ) );
297 s
= _( "Append \"._part\" to incomplete files' names" );
298 w
= new_check_button( s
, TR_PREFS_KEY_RENAME_PARTIAL_FILES
, core
);
299 hig_workarea_add_wide_control( t
, &row
, w
);
301 w
= new_path_chooser_button( TR_PREFS_KEY_DOWNLOAD_DIR
, core
);
302 hig_workarea_add_row( t
, &row
, _( "Save to _Location:" ), w
, NULL
);
304 s
= _( "Keep _incomplete torrents in:" );
305 l
= new_check_button( s
, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED
, core
);
306 w
= new_path_chooser_button( TR_PREFS_KEY_INCOMPLETE_DIR
, core
);
307 gtk_widget_set_sensitive( GTK_WIDGET( w
), gtr_pref_flag_get( TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED
) );
308 g_signal_connect( l
, "toggled", G_CALLBACK( target_cb
), w
);
309 hig_workarea_add_row_w( t
, &row
, l
, w
, NULL
);
311 s
= _( "Call scrip_t when torrent is completed:" );
312 l
= new_check_button( s
, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED
, core
);
313 w
= new_file_chooser_button( TR_PREFS_KEY_SCRIPT_TORRENT_DONE_FILENAME
, core
);
314 gtk_widget_set_sensitive( GTK_WIDGET( w
), gtr_pref_flag_get( TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED
) );
315 g_signal_connect( l
, "toggled", G_CALLBACK( target_cb
), w
);
316 hig_workarea_add_row_w( t
, &row
, l
, w
, NULL
);
318 hig_workarea_add_section_divider( t
, &row
);
319 hig_workarea_add_section_title( t
, &row
, _( "Seeding" ) );
321 s
= _( "Stop seeding at _ratio:" );
322 w
= new_check_button( s
, TR_PREFS_KEY_RATIO_ENABLED
, core
);
323 w2
= new_spin_button_double( TR_PREFS_KEY_RATIO
, core
, 0, 1000, .05 );
324 gtk_widget_set_sensitive( GTK_WIDGET( w2
), gtr_pref_flag_get( TR_PREFS_KEY_RATIO_ENABLED
) );
325 g_signal_connect( w
, "toggled", G_CALLBACK( target_cb
), w2
);
326 hig_workarea_add_row_w( t
, &row
, w
, w2
, NULL
);
328 s
= _( "Stop seeding if idle for _N minutes:" );
329 w
= new_check_button( s
, TR_PREFS_KEY_IDLE_LIMIT_ENABLED
, core
);
330 w2
= new_spin_button( TR_PREFS_KEY_IDLE_LIMIT
, core
, 1, 9999, 5 );
331 gtk_widget_set_sensitive( GTK_WIDGET( w2
), gtr_pref_flag_get( TR_PREFS_KEY_IDLE_LIMIT_ENABLED
) );
332 g_signal_connect( w
, "toggled", G_CALLBACK( target_cb
), w2
);
333 hig_workarea_add_row_w( t
, &row
, w
, w2
, NULL
);
335 hig_workarea_finish( t
, &row
);
344 desktopPage( GObject
* core
)
351 t
= hig_workarea_create( );
352 hig_workarea_add_section_title( t
, &row
, _( "Desktop" ) );
354 s
= _( "Inhibit _hibernation when torrents are active" );
355 w
= new_check_button( s
, PREF_KEY_INHIBIT_HIBERNATION
, core
);
356 hig_workarea_add_wide_control( t
, &row
, w
);
358 s
= _( "Show Transmission icon in the _notification area" );
359 w
= new_check_button( s
, PREF_KEY_SHOW_TRAY_ICON
, core
);
360 hig_workarea_add_wide_control( t
, &row
, w
);
362 s
= _( "Show _popup notifications" );
363 w
= new_check_button( s
, PREF_KEY_SHOW_DESKTOP_NOTIFICATION
, core
);
364 hig_workarea_add_wide_control( t
, &row
, w
);
366 #ifdef HAVE_LIBCANBERRA
367 s
= _( "Play _sound when downloads are complete" );
368 w
= new_check_button( s
, PREF_KEY_PLAY_DOWNLOAD_COMPLETE_SOUND
, core
);
369 hig_workarea_add_wide_control( t
, &row
, w
);
372 hig_workarea_finish( t
, &row
);
380 struct blocklist_data
382 gulong updateBlocklistTag
;
383 GtkWidget
* updateBlocklistButton
;
384 GtkWidget
* updateBlocklistDialog
;
391 updateBlocklistText( GtkWidget
* w
, TrCore
* core
)
395 const int n
= tr_blocklistGetRuleCount( tr_core_session( core
) );
396 g_snprintf( buf1
, sizeof( buf1
),
397 gtr_ngettext( "Blocklist contains %'d rule",
398 "Blocklist contains %'d rules", n
), n
);
399 g_snprintf( buf2
, sizeof( buf2
), "<i>%s</i>", buf1
);
400 gtk_label_set_markup( GTK_LABEL( w
), buf2
);
403 /* prefs dialog is being destroyed, so stop listening to blocklist updates */
405 privacyPageDestroyed( gpointer gdata
, GObject
* dead UNUSED
)
407 struct blocklist_data
* data
= gdata
;
408 if( data
->updateBlocklistTag
> 0 )
409 g_signal_handler_disconnect( data
->core
, data
->updateBlocklistTag
);
413 /* user hit "close" in the blocklist-update dialog */
415 onBlocklistUpdateResponse( GtkDialog
* dialog
, gint response UNUSED
, gpointer gdata
)
417 struct blocklist_data
* data
= gdata
;
418 gtk_widget_destroy( GTK_WIDGET( dialog
) );
419 gtk_widget_set_sensitive( data
->updateBlocklistButton
, TRUE
);
420 data
->updateBlocklistDialog
= NULL
;
421 g_signal_handler_disconnect( data
->core
, data
->updateBlocklistTag
);
424 /* core says the blocklist was updated */
426 onBlocklistUpdated( TrCore
* core
, int n
, gpointer gdata
)
428 const tr_bool success
= n
>= 0;
429 const int count
= n
>=0 ? n
: tr_blocklistGetRuleCount( tr_core_session( core
) );
430 const char * s
= gtr_ngettext( "Blocklist has %'d rule.", "Blocklist has %'d rules.", count
);
431 struct blocklist_data
* data
= gdata
;
432 GtkMessageDialog
* d
= GTK_MESSAGE_DIALOG( data
->updateBlocklistDialog
);
433 gtk_widget_set_sensitive( data
->updateBlocklistButton
, TRUE
);
434 gtk_message_dialog_set_markup( d
, success
? _( "<b>Update succeeded!</b>" ) : _( "<b>Unable to update.</b>" ) );
435 gtk_message_dialog_format_secondary_text( d
, s
, count
);
436 updateBlocklistText( data
->label
, core
);
439 /* user pushed a button to update the blocklist */
441 onBlocklistUpdate( GtkButton
* w
, gpointer gdata
)
444 struct blocklist_data
* data
= gdata
;
445 d
= gtk_message_dialog_new( GTK_WINDOW( gtk_widget_get_toplevel( GTK_WIDGET( w
) ) ),
446 GTK_DIALOG_DESTROY_WITH_PARENT
,
449 "%s", _( "Update Blocklist" ) );
450 gtk_widget_set_sensitive( data
->updateBlocklistButton
, FALSE
);
451 gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( d
), "%s", _( "Getting new blocklist..." ) );
452 data
->updateBlocklistDialog
= d
;
453 g_signal_connect( d
, "response", G_CALLBACK(onBlocklistUpdateResponse
), data
);
454 gtk_widget_show( d
);
455 tr_core_blocklist_update( data
->core
);
456 data
->updateBlocklistTag
= g_signal_connect( data
->core
, "blocklist-updated", G_CALLBACK( onBlocklistUpdated
), data
);
460 on_blocklist_url_changed( GtkEditable
* e
, gpointer gbutton
)
462 gchar
* url
= gtk_editable_get_chars( e
, 0, -1 );
463 const gboolean err
= tr_urlParse( url
, -1, NULL
, NULL
, NULL
, NULL
);
464 gtk_widget_set_sensitive( GTK_WIDGET( gbutton
), !err
);
469 onIntComboChanged( GtkComboBox
* combo_box
, gpointer core
)
471 const int val
= gtr_combo_box_get_active_enum( combo_box
);
472 const char * key
= g_object_get_data( G_OBJECT( combo_box
), PREF_KEY
);
473 tr_core_set_pref_int( TR_CORE( core
), key
, val
);
477 new_encryption_combo( GObject
* core
, const char * key
)
479 GtkWidget
* w
= gtr_combo_box_new_enum( _( "Allow encryption" ), TR_CLEAR_PREFERRED
,
480 _( "Prefer encryption" ), TR_ENCRYPTION_PREFERRED
,
481 _( "Require encryption" ), TR_ENCRYPTION_REQUIRED
,
483 gtr_combo_box_set_active_enum( GTK_COMBO_BOX( w
), gtr_pref_int_get( key
) );
484 g_object_set_data_full( G_OBJECT( w
), PREF_KEY
, tr_strdup( key
), g_free
);
485 g_signal_connect( w
, "changed", G_CALLBACK( onIntComboChanged
), core
);
490 privacyPage( GObject
* core
)
499 struct blocklist_data
* data
;
501 data
= g_new0( struct blocklist_data
, 1 );
502 data
->core
= TR_CORE( core
);
504 t
= hig_workarea_create( );
505 hig_workarea_add_section_title( t
, &row
, _( "Blocklist" ) );
507 b
= new_check_button( _( "Enable _blocklist:" ), TR_PREFS_KEY_BLOCKLIST_ENABLED
, core
);
508 e
= new_entry( TR_PREFS_KEY_BLOCKLIST_URL
, core
);
509 gtk_widget_set_size_request( e
, 300, -1 );
510 hig_workarea_add_row_w( t
, &row
, b
, e
, NULL
);
512 g_signal_connect( b
, "toggled", G_CALLBACK( target_cb
), e
);
515 w
= gtk_label_new( "" );
516 gtk_misc_set_alignment( GTK_MISC( w
), 0.0f
, 0.5f
);
517 updateBlocklistText( w
, TR_CORE( core
) );
519 h
= gtk_hbox_new( FALSE
, GUI_PAD_BIG
);
520 gtk_box_pack_start( GTK_BOX( h
), w
, TRUE
, TRUE
, 0 );
521 b
= data
->updateBlocklistButton
= gtk_button_new_with_mnemonic( _( "_Update" ) );
522 g_object_set_data( G_OBJECT( b
), "session", tr_core_session( TR_CORE( core
) ) );
523 g_signal_connect( b
, "clicked", G_CALLBACK( onBlocklistUpdate
), data
);
524 g_signal_connect( data
->check
, "toggled", G_CALLBACK( target_cb
), b
); target_cb( data
->check
, b
);
525 gtk_box_pack_start( GTK_BOX( h
), b
, FALSE
, FALSE
, 0 );
526 g_signal_connect( data
->check
, "toggled", G_CALLBACK( target_cb
), w
); target_cb( data
->check
, w
);
527 hig_workarea_add_wide_control( t
, &row
, h
);
528 g_signal_connect( e
, "changed", G_CALLBACK( on_blocklist_url_changed
), data
->updateBlocklistButton
);
529 on_blocklist_url_changed( GTK_EDITABLE( e
), data
->updateBlocklistButton
);
531 s
= _( "Enable _automatic updates" );
532 w
= new_check_button( s
, PREF_KEY_BLOCKLIST_UPDATES_ENABLED
, core
);
533 hig_workarea_add_wide_control( t
, &row
, w
);
534 g_signal_connect( data
->check
, "toggled", G_CALLBACK( target_cb
), w
); target_cb( data
->check
, w
);
536 hig_workarea_add_section_divider( t
, &row
);
537 hig_workarea_add_section_title ( t
, &row
, _( "Privacy" ) );
539 s
= _( "_Encryption mode:" );
540 w
= new_encryption_combo( core
, "encryption" );
541 hig_workarea_add_row( t
, &row
, s
, w
, NULL
);
543 s
= _( "Use PE_X to find more peers" );
544 w
= new_check_button( s
, TR_PREFS_KEY_PEX_ENABLED
, core
);
545 s
= _( "PEX is a tool for exchanging peer lists with the peers you're connected to." );
546 gtr_widget_set_tooltip_text( w
, s
);
547 hig_workarea_add_wide_control( t
, &row
, w
);
549 s
= _( "Use _DHT to find more peers" );
550 w
= new_check_button( s
, TR_PREFS_KEY_DHT_ENABLED
, core
);
551 s
= _( "DHT is a tool for finding peers without a tracker." );
552 gtr_widget_set_tooltip_text( w
, s
);
553 hig_workarea_add_wide_control( t
, &row
, w
);
555 s
= _( "Use _Local Peer Discovery to find more peers" );
556 w
= new_check_button( s
, TR_PREFS_KEY_LPD_ENABLED
, core
);
557 s
= _( "LPD is a tool for finding peers on your local network." );
558 gtr_widget_set_tooltip_text( w
, s
);
559 hig_workarea_add_wide_control( t
, &row
, w
);
561 hig_workarea_finish( t
, &row
);
562 g_object_weak_ref( G_OBJECT( t
), privacyPageDestroyed
, data
);
577 whitelist_tree_model_new( const char * whitelist
)
581 GtkListStore
* store
= gtk_list_store_new( N_COLS
,
585 rules
= g_strsplit( whitelist
, ",", 0 );
587 for( i
= 0; rules
&& rules
[i
]; ++i
)
590 const char * s
= rules
[i
];
591 while( isspace( *s
) ) ++s
;
592 gtk_list_store_append( store
, &iter
);
593 gtk_list_store_set( store
, &iter
, COL_ADDRESS
, s
, -1 );
597 return GTK_TREE_MODEL( store
);
604 GtkListStore
* store
;
605 GtkWidget
* remove_button
;
607 GSList
* auth_widgets
;
608 GSList
* whitelist_widgets
;
609 GtkToggleButton
* rpc_tb
;
610 GtkToggleButton
* auth_tb
;
611 GtkToggleButton
* whitelist_tb
;
615 refreshWhitelist( struct remote_page
* page
)
618 GtkTreeModel
* model
= GTK_TREE_MODEL( page
->store
);
619 GString
* gstr
= g_string_new( NULL
);
621 if( gtk_tree_model_get_iter_first( model
, &iter
) ) do
624 gtk_tree_model_get( model
, &iter
,
625 COL_ADDRESS
, &address
,
627 g_string_append( gstr
, address
);
628 g_string_append( gstr
, "," );
631 while( gtk_tree_model_iter_next( model
, &iter
) );
633 g_string_truncate( gstr
, gstr
->len
- 1 ); /* remove the trailing comma */
635 tr_core_set_pref( page
->core
, TR_PREFS_KEY_RPC_WHITELIST
, gstr
->str
);
637 g_string_free( gstr
, TRUE
);
641 onAddressEdited( GtkCellRendererText
* r UNUSED
,
647 struct remote_page
* page
= gpage
;
648 GtkTreeModel
* model
= GTK_TREE_MODEL( page
->store
);
649 GtkTreePath
* path
= gtk_tree_path_new_from_string( path_string
);
651 if( gtk_tree_model_get_iter( model
, &iter
, path
) )
652 gtk_list_store_set( page
->store
, &iter
, COL_ADDRESS
, address
, -1 );
654 gtk_tree_path_free( path
);
655 refreshWhitelist( page
);
659 onAddWhitelistClicked( GtkButton
* b UNUSED
,
664 struct remote_page
* page
= gpage
;
666 gtk_list_store_append( page
->store
, &iter
);
667 gtk_list_store_set( page
->store
, &iter
,
668 COL_ADDRESS
, "0.0.0.0",
671 path
= gtk_tree_model_get_path( GTK_TREE_MODEL( page
->store
), &iter
);
672 gtk_tree_view_set_cursor(
674 gtk_tree_view_get_column( page
->view
, COL_ADDRESS
),
676 gtk_tree_path_free( path
);
680 onRemoveWhitelistClicked( GtkButton
* b UNUSED
,
683 struct remote_page
* page
= gpage
;
684 GtkTreeSelection
* sel
= gtk_tree_view_get_selection( page
->view
);
687 if( gtk_tree_selection_get_selected( sel
, NULL
, &iter
) )
689 gtk_list_store_remove( page
->store
, &iter
);
690 refreshWhitelist( page
);
695 refreshRPCSensitivity( struct remote_page
* page
)
698 const int rpc_active
= gtk_toggle_button_get_active(
700 const int auth_active
= gtk_toggle_button_get_active(
702 const int whitelist_active
= gtk_toggle_button_get_active(
703 page
->whitelist_tb
);
704 GtkTreeSelection
* sel
= gtk_tree_view_get_selection( page
->view
);
705 const int have_addr
=
706 gtk_tree_selection_get_selected( sel
, NULL
,
708 const int n_rules
= gtk_tree_model_iter_n_children(
709 GTK_TREE_MODEL( page
->store
), NULL
);
711 for( l
= page
->widgets
; l
!= NULL
; l
= l
->next
)
712 gtk_widget_set_sensitive( GTK_WIDGET( l
->data
), rpc_active
);
714 for( l
= page
->auth_widgets
; l
!= NULL
; l
= l
->next
)
715 gtk_widget_set_sensitive( GTK_WIDGET(
716 l
->data
), rpc_active
&& auth_active
);
718 for( l
= page
->whitelist_widgets
; l
!= NULL
; l
= l
->next
)
719 gtk_widget_set_sensitive( GTK_WIDGET( l
->data
),
720 rpc_active
&& whitelist_active
);
722 gtk_widget_set_sensitive( page
->remove_button
,
723 rpc_active
&& have_addr
&& n_rules
> 1 );
727 onRPCToggled( GtkToggleButton
* tb UNUSED
,
730 refreshRPCSensitivity( page
);
734 onWhitelistSelectionChanged( GtkTreeSelection
* sel UNUSED
,
737 refreshRPCSensitivity( page
);
741 onLaunchClutchCB( GtkButton
* w UNUSED
, gpointer data UNUSED
)
743 const int port
= gtr_pref_int_get( TR_PREFS_KEY_RPC_PORT
);
744 char * uri
= g_strdup_printf( "http://localhost:%d/transmission/web", port
);
751 remotePageFree( gpointer gpage
)
753 struct remote_page
* page
= gpage
;
755 g_slist_free( page
->widgets
);
756 g_slist_free( page
->auth_widgets
);
757 g_slist_free( page
->whitelist_widgets
);
762 webPage( GObject
* core
)
769 struct remote_page
* page
= g_new0( struct remote_page
, 1 );
771 page
->core
= TR_CORE( core
);
773 t
= hig_workarea_create( );
774 g_object_set_data_full( G_OBJECT( t
), "page", page
, remotePageFree
);
776 hig_workarea_add_section_title( t
, &row
, _( "Web Client" ) );
778 /* "enabled" checkbutton */
779 s
= _( "_Enable web client" );
780 w
= new_check_button( s
, TR_PREFS_KEY_RPC_ENABLED
, core
);
781 page
->rpc_tb
= GTK_TOGGLE_BUTTON( w
);
782 g_signal_connect( w
, "clicked", G_CALLBACK( onRPCToggled
), page
);
783 h
= gtk_hbox_new( FALSE
, GUI_PAD_BIG
);
784 gtk_box_pack_start( GTK_BOX( h
), w
, TRUE
, TRUE
, 0 );
785 w
= gtk_button_new_with_mnemonic( _( "_Open web client" ) );
786 page
->widgets
= g_slist_append( page
->widgets
, w
);
787 g_signal_connect( w
, "clicked", G_CALLBACK( onLaunchClutchCB
), NULL
);
788 gtk_box_pack_start( GTK_BOX( h
), w
, FALSE
, FALSE
, 0 );
789 hig_workarea_add_wide_control( t
, &row
, h
);
792 w
= new_spin_button( TR_PREFS_KEY_RPC_PORT
, core
, 0, USHRT_MAX
, 1 );
793 page
->widgets
= g_slist_append( page
->widgets
, w
);
794 w
= hig_workarea_add_row( t
, &row
, _( "HTTP _port:" ), w
, NULL
);
795 page
->widgets
= g_slist_append( page
->widgets
, w
);
797 /* require authentication */
798 s
= _( "Use _authentication" );
799 w
= new_check_button( s
, TR_PREFS_KEY_RPC_AUTH_REQUIRED
, core
);
800 hig_workarea_add_wide_control( t
, &row
, w
);
801 page
->auth_tb
= GTK_TOGGLE_BUTTON( w
);
802 page
->widgets
= g_slist_append( page
->widgets
, w
);
803 g_signal_connect( w
, "clicked", G_CALLBACK( onRPCToggled
), page
);
806 s
= _( "_Username:" );
807 w
= new_entry( TR_PREFS_KEY_RPC_USERNAME
, core
);
808 page
->auth_widgets
= g_slist_append( page
->auth_widgets
, w
);
809 w
= hig_workarea_add_row( t
, &row
, s
, w
, NULL
);
810 page
->auth_widgets
= g_slist_append( page
->auth_widgets
, w
);
813 s
= _( "Pass_word:" );
814 w
= new_entry( TR_PREFS_KEY_RPC_PASSWORD
, core
);
815 gtk_entry_set_visibility( GTK_ENTRY( w
), FALSE
);
816 page
->auth_widgets
= g_slist_append( page
->auth_widgets
, w
);
817 w
= hig_workarea_add_row( t
, &row
, s
, w
, NULL
);
818 page
->auth_widgets
= g_slist_append( page
->auth_widgets
, w
);
820 /* require authentication */
821 s
= _( "Only allow these IP a_ddresses to connect:" );
822 w
= new_check_button( s
, TR_PREFS_KEY_RPC_WHITELIST_ENABLED
, core
);
823 hig_workarea_add_wide_control( t
, &row
, w
);
824 page
->whitelist_tb
= GTK_TOGGLE_BUTTON( w
);
825 page
->widgets
= g_slist_append( page
->widgets
, w
);
826 g_signal_connect( w
, "clicked", G_CALLBACK( onRPCToggled
), page
);
828 /* access control list */
830 const char * val
= gtr_pref_string_get( TR_PREFS_KEY_RPC_WHITELIST
);
831 GtkTreeModel
* m
= whitelist_tree_model_new( val
);
832 GtkTreeViewColumn
* c
;
834 GtkTreeSelection
* sel
;
839 page
->store
= GTK_LIST_STORE( m
);
840 w
= gtk_tree_view_new_with_model( m
);
841 g_signal_connect( w
, "button-release-event",
842 G_CALLBACK( on_tree_view_button_released
), NULL
);
844 page
->whitelist_widgets
= g_slist_append( page
->whitelist_widgets
, w
);
845 v
= page
->view
= GTK_TREE_VIEW( w
);
846 gtr_widget_set_tooltip_text( w
, _( "IP addresses may use wildcards, such as 192.168.*.*" ) );
847 sel
= gtk_tree_view_get_selection( v
);
848 g_signal_connect( sel
, "changed",
849 G_CALLBACK( onWhitelistSelectionChanged
), page
);
850 g_object_unref( G_OBJECT( m
) );
851 gtk_tree_view_set_headers_visible( v
, TRUE
);
852 w
= gtk_frame_new( NULL
);
853 gtk_frame_set_shadow_type( GTK_FRAME( w
), GTK_SHADOW_IN
);
854 gtk_container_add( GTK_CONTAINER( w
), GTK_WIDGET( v
) );
856 /* ip address column */
857 r
= gtk_cell_renderer_text_new( );
858 g_signal_connect( r
, "edited",
859 G_CALLBACK( onAddressEdited
), page
);
860 g_object_set( G_OBJECT( r
), "editable", TRUE
, NULL
);
861 c
= gtk_tree_view_column_new_with_attributes( NULL
, r
,
864 gtk_tree_view_column_set_expand( c
, TRUE
);
865 gtk_tree_view_append_column( v
, c
);
866 gtk_tree_view_set_headers_visible( v
, FALSE
);
868 s
= _( "Addresses:" );
869 w
= hig_workarea_add_row( t
, &row
, s
, w
, NULL
);
870 gtk_misc_set_alignment( GTK_MISC( w
), 0.0f
, 0.0f
);
871 gtk_misc_set_padding( GTK_MISC( w
), 0, GUI_PAD
);
872 page
->whitelist_widgets
= g_slist_append( page
->whitelist_widgets
, w
);
874 h
= gtk_hbox_new( TRUE
, GUI_PAD
);
875 w
= gtk_button_new_from_stock( GTK_STOCK_REMOVE
);
876 g_signal_connect( w
, "clicked", G_CALLBACK(
877 onRemoveWhitelistClicked
), page
);
878 page
->remove_button
= w
;
879 onWhitelistSelectionChanged( sel
, page
);
880 gtk_box_pack_start( GTK_BOX( h
), w
, TRUE
, TRUE
, 0 );
881 w
= gtk_button_new_from_stock( GTK_STOCK_ADD
);
882 page
->whitelist_widgets
= g_slist_append( page
->whitelist_widgets
, w
);
883 g_signal_connect( w
, "clicked", G_CALLBACK( onAddWhitelistClicked
), page
);
884 gtk_box_pack_start( GTK_BOX( h
), w
, TRUE
, TRUE
, 0 );
885 w
= gtk_hbox_new( FALSE
, 0 );
886 gtk_box_pack_start( GTK_BOX( w
), gtk_alignment_new( 0, 0, 0, 0 ),
888 gtk_box_pack_start( GTK_BOX( w
), h
, FALSE
, FALSE
, 0 );
889 hig_workarea_add_wide_control( t
, &row
, w
);
892 refreshRPCSensitivity( page
);
893 hig_workarea_finish( t
, &row
);
904 GSList
* sched_widgets
;
908 refreshSchedSensitivity( struct BandwidthPage
* p
)
911 const gboolean sched_enabled
= gtr_pref_flag_get( TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED
);
913 for( l
= p
->sched_widgets
; l
!= NULL
; l
= l
->next
)
914 gtk_widget_set_sensitive( GTK_WIDGET( l
->data
), sched_enabled
);
918 onSchedToggled( GtkToggleButton
* tb UNUSED
,
921 refreshSchedSensitivity( user_data
);
925 onTimeComboChanged( GtkComboBox
* w
,
930 if( gtk_combo_box_get_active_iter( w
, &iter
) )
932 const char * key
= g_object_get_data( G_OBJECT( w
), PREF_KEY
);
934 gtk_tree_model_get( gtk_combo_box_get_model(
935 w
), &iter
, 0, &val
, -1 );
936 tr_core_set_pref_int( TR_CORE( core
), key
, val
);
941 new_time_combo( GObject
* core
,
948 GtkListStore
* store
;
950 /* build a store at 15 minute intervals */
951 store
= gtk_list_store_new( 2, G_TYPE_INT
, G_TYPE_STRING
);
952 for( i
= 0; i
< 60 * 24; i
+= 15 )
960 strftime( buf
, sizeof( buf
), "%H:%M", &tm
);
961 gtk_list_store_append( store
, &iter
);
962 gtk_list_store_set( store
, &iter
, 0, i
, 1, buf
, -1 );
965 /* build the widget */
966 w
= gtk_combo_box_new_with_model( GTK_TREE_MODEL( store
) );
967 gtk_combo_box_set_wrap_width( GTK_COMBO_BOX( w
), 4 );
968 r
= gtk_cell_renderer_text_new( );
969 gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( w
), r
, TRUE
);
970 gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT(
971 w
), r
, "text", 1, NULL
);
972 g_object_set_data_full( G_OBJECT( w
), PREF_KEY
, tr_strdup(
974 val
= gtr_pref_int_get( key
);
975 gtk_combo_box_set_active( GTK_COMBO_BOX( w
), val
/ ( 15 ) );
976 g_signal_connect( w
, "changed", G_CALLBACK( onTimeComboChanged
), core
);
979 g_object_unref( G_OBJECT( store
) );
984 new_week_combo( GObject
* core
, const char * key
)
986 GtkWidget
* w
= gtr_combo_box_new_enum( _( "Every Day" ), TR_SCHED_ALL
,
987 _( "Weekdays" ), TR_SCHED_WEEKDAY
,
988 _( "Weekends" ), TR_SCHED_WEEKEND
,
989 _( "Sunday" ), TR_SCHED_SUN
,
990 _( "Monday" ), TR_SCHED_MON
,
991 _( "Tuesday" ), TR_SCHED_TUES
,
992 _( "Wednesday" ), TR_SCHED_WED
,
993 _( "Thursday" ), TR_SCHED_THURS
,
994 _( "Friday" ), TR_SCHED_FRI
,
995 _( "Saturday" ), TR_SCHED_SAT
,
997 gtr_combo_box_set_active_enum( GTK_COMBO_BOX( w
), gtr_pref_int_get( key
) );
998 g_object_set_data_full( G_OBJECT( w
), PREF_KEY
, tr_strdup( key
), g_free
);
999 g_signal_connect( w
, "changed", G_CALLBACK( onIntComboChanged
), core
);
1004 bandwidthPageFree( gpointer gpage
)
1006 struct BandwidthPage
* page
= gpage
;
1008 g_slist_free( page
->sched_widgets
);
1013 bandwidthPage( GObject
* core
)
1019 GtkWidget
* w
, * w2
, * h
;
1021 struct BandwidthPage
* page
= tr_new0( struct BandwidthPage
, 1 );
1023 page
->core
= TR_CORE( core
);
1025 t
= hig_workarea_create( );
1026 hig_workarea_add_section_title( t
, &row
, _( "Speed Limits" ) );
1028 g_snprintf( buf
, sizeof( buf
), _( "_Upload (%s):" ), _(speed_K_str
) );
1029 w
= new_check_button( buf
, TR_PREFS_KEY_USPEED_ENABLED
, core
);
1030 w2
= new_spin_button( TR_PREFS_KEY_USPEED_KBps
, core
, 0, INT_MAX
, 5 );
1031 gtk_widget_set_sensitive( GTK_WIDGET( w2
), gtr_pref_flag_get( TR_PREFS_KEY_USPEED_ENABLED
) );
1032 g_signal_connect( w
, "toggled", G_CALLBACK( target_cb
), w2
);
1033 hig_workarea_add_row_w( t
, &row
, w
, w2
, NULL
);
1035 g_snprintf( buf
, sizeof( buf
), _( "_Download (%s):" ), _(speed_K_str
) );
1036 w
= new_check_button( buf
, TR_PREFS_KEY_DSPEED_ENABLED
, core
);
1037 w2
= new_spin_button( TR_PREFS_KEY_DSPEED_KBps
, core
, 0, INT_MAX
, 5 );
1038 gtk_widget_set_sensitive( GTK_WIDGET( w2
), gtr_pref_flag_get( TR_PREFS_KEY_DSPEED_ENABLED
) );
1039 g_signal_connect( w
, "toggled", G_CALLBACK( target_cb
), w2
);
1040 hig_workarea_add_row_w( t
, &row
, w
, w2
, NULL
);
1042 hig_workarea_add_section_divider( t
, &row
);
1043 h
= gtk_hbox_new( FALSE
, GUI_PAD
);
1044 w
= gtk_image_new_from_stock( "alt-speed-on", -1 );
1045 gtk_box_pack_start( GTK_BOX( h
), w
, FALSE
, FALSE
, 0 );
1046 g_snprintf( buf
, sizeof( buf
), "<b>%s</b>", _( "Alternative Speed Limits" ) );
1047 w
= gtk_label_new( buf
);
1048 gtk_misc_set_alignment( GTK_MISC( w
), 0.0f
, 0.5f
);
1049 gtk_label_set_use_markup( GTK_LABEL( w
), TRUE
);
1050 gtk_box_pack_start( GTK_BOX( h
), w
, FALSE
, FALSE
, 0 );
1051 hig_workarea_add_section_title_widget( t
, &row
, h
);
1053 s
= _( "Override normal speed limits manually or at scheduled times" );
1054 g_snprintf( buf
, sizeof( buf
), "<small>%s</small>", s
);
1055 w
= gtk_label_new( buf
);
1056 gtk_label_set_use_markup( GTK_LABEL( w
), TRUE
);
1057 gtk_misc_set_alignment( GTK_MISC( w
), 0.5f
, 0.5f
);
1058 hig_workarea_add_wide_control( t
, &row
, w
);
1060 g_snprintf( buf
, sizeof( buf
), _( "U_pload (%s):" ), _(speed_K_str
) );
1061 w
= new_spin_button( TR_PREFS_KEY_ALT_SPEED_UP_KBps
, core
, 0, INT_MAX
, 5 );
1062 hig_workarea_add_row( t
, &row
, buf
, w
, NULL
);
1064 g_snprintf( buf
, sizeof( buf
), _( "Do_wnload (%s):" ), _(speed_K_str
) );
1065 w
= new_spin_button( TR_PREFS_KEY_ALT_SPEED_DOWN_KBps
, core
, 0, INT_MAX
, 5 );
1066 hig_workarea_add_row( t
, &row
, buf
, w
, NULL
);
1068 s
= _( "_Scheduled times:" );
1069 h
= gtk_hbox_new( FALSE
, 0 );
1070 w2
= new_time_combo( core
, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN
);
1071 page
->sched_widgets
= g_slist_append( page
->sched_widgets
, w2
);
1072 gtk_box_pack_start( GTK_BOX( h
), w2
, TRUE
, TRUE
, 0 );
1073 w2
= l
= gtk_label_new_with_mnemonic ( _( " _to " ) );
1074 page
->sched_widgets
= g_slist_append( page
->sched_widgets
, w2
);
1075 gtk_box_pack_start( GTK_BOX( h
), w2
, FALSE
, FALSE
, 0 );
1076 w2
= new_time_combo( core
, TR_PREFS_KEY_ALT_SPEED_TIME_END
);
1077 gtk_label_set_mnemonic_widget( GTK_LABEL( l
), w2
);
1078 page
->sched_widgets
= g_slist_append( page
->sched_widgets
, w2
);
1079 gtk_box_pack_start( GTK_BOX( h
), w2
, TRUE
, TRUE
, 0 );
1080 w
= new_check_button( s
, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED
, core
);
1081 g_signal_connect( w
, "toggled", G_CALLBACK( onSchedToggled
), page
);
1082 hig_workarea_add_row_w( t
, &row
, w
, h
, NULL
);
1084 s
= _( "_On days:" );
1085 w
= new_week_combo( core
, TR_PREFS_KEY_ALT_SPEED_TIME_DAY
);
1086 page
->sched_widgets
= g_slist_append( page
->sched_widgets
, w
);
1087 w
= hig_workarea_add_row( t
, &row
, s
, w
, NULL
);
1088 page
->sched_widgets
= g_slist_append( page
->sched_widgets
, w
);
1090 hig_workarea_finish( t
, &row
);
1091 g_object_set_data_full( G_OBJECT( t
), "page", page
, bandwidthPageFree
);
1093 refreshSchedSensitivity( page
);
1101 struct network_page_data
1104 GtkWidget
* portLabel
;
1105 GtkWidget
* portButton
;
1106 GtkWidget
* portSpin
;
1112 onCorePrefsChanged( TrCore
* core UNUSED
, const char * key
, gpointer gdata
)
1114 if( !strcmp( key
, TR_PREFS_KEY_PEER_PORT
) )
1116 struct network_page_data
* data
= gdata
;
1117 gdk_threads_enter();
1118 gtr_label_set_text( GTK_LABEL( data
->portLabel
), _( "Status unknown" ) );
1119 gtk_widget_set_sensitive( data
->portButton
, TRUE
);
1120 gtk_widget_set_sensitive( data
->portSpin
, TRUE
);
1121 gdk_threads_leave();
1126 networkPageDestroyed( gpointer gdata
, GObject
* dead UNUSED
)
1128 struct network_page_data
* data
= gdata
;
1129 if( data
->prefsTag
> 0 )
1130 g_signal_handler_disconnect( data
->core
, data
->prefsTag
);
1131 if( data
->portTag
> 0 )
1132 g_signal_handler_disconnect( data
->core
, data
->portTag
);
1137 onPortTested( TrCore
* core UNUSED
, gboolean isOpen
, gpointer vdata
)
1139 struct network_page_data
* data
= vdata
;
1140 const char * markup
= isOpen
? _( "Port is <b>open</b>" ) : _( "Port is <b>closed</b>" );
1141 gdk_threads_enter();
1142 gtk_label_set_markup( GTK_LABEL( data
->portLabel
), markup
);
1143 gtk_widget_set_sensitive( data
->portButton
, TRUE
);
1144 gtk_widget_set_sensitive( data
->portSpin
, TRUE
);
1145 gdk_threads_leave();
1149 onPortTest( GtkButton
* button UNUSED
, gpointer vdata
)
1151 struct network_page_data
* data
= vdata
;
1152 gtk_widget_set_sensitive( data
->portButton
, FALSE
);
1153 gtk_widget_set_sensitive( data
->portSpin
, FALSE
);
1154 gtk_label_set_markup( GTK_LABEL( data
->portLabel
), _( "<i>Testing...</i>" ) );
1155 if( !data
->portTag
)
1156 data
->portTag
= g_signal_connect( data
->core
, "port-tested", G_CALLBACK(onPortTested
), data
);
1157 tr_core_port_test( data
->core
);
1161 onGNOMEClicked( GtkButton
* button
, gpointer vdata UNUSED
)
1163 GError
* err
= NULL
;
1165 if( !g_spawn_command_line_async( "gnome-network-properties", &err
) )
1167 GtkWidget
* d
= gtk_message_dialog_new( GTK_WINDOW( gtk_widget_get_toplevel( GTK_WIDGET( button
) ) ),
1168 GTK_DIALOG_DESTROY_WITH_PARENT
,
1171 "%s", err
->message
);
1172 g_signal_connect_swapped( d
, "response", G_CALLBACK( gtk_widget_destroy
), d
);
1173 gtk_widget_show( d
);
1174 g_clear_error( &err
);
1179 networkPage( GObject
* core
)
1187 struct network_page_data
* data
;
1189 /* register to stop listening to core prefs changes when the page is destroyed */
1190 data
= g_new0( struct network_page_data
, 1 );
1191 data
->core
= TR_CORE( core
);
1193 /* build the page */
1194 t
= hig_workarea_create( );
1195 hig_workarea_add_section_title( t
, &row
, _( "Listening Port" ) );
1197 s
= _( "_Port used for incoming connections:" );
1198 w
= data
->portSpin
= new_spin_button( TR_PREFS_KEY_PEER_PORT
, core
, 1, USHRT_MAX
, 1 );
1199 hig_workarea_add_row( t
, &row
, s
, w
, NULL
);
1201 h
= gtk_hbox_new( FALSE
, GUI_PAD_BIG
);
1202 l
= data
->portLabel
= gtk_label_new( _( "Status unknown" ) );
1203 gtk_misc_set_alignment( GTK_MISC( l
), 0.0f
, 0.5f
);
1204 gtk_box_pack_start( GTK_BOX( h
), l
, TRUE
, TRUE
, 0 );
1205 w
= data
->portButton
= gtk_button_new_with_mnemonic( _( "Te_st Port" ) );
1206 gtk_box_pack_end( GTK_BOX( h
), w
, FALSE
, FALSE
, 0 );
1207 g_signal_connect( w
, "clicked", G_CALLBACK(onPortTest
), data
);
1208 hig_workarea_add_row( t
, &row
, NULL
, h
, NULL
);
1209 data
->prefsTag
= g_signal_connect( TR_CORE( core
), "prefs-changed", G_CALLBACK( onCorePrefsChanged
), data
);
1210 g_object_weak_ref( G_OBJECT( t
), networkPageDestroyed
, data
);
1212 s
= _( "Pick a _random port every time Transmission is started" );
1213 w
= new_check_button( s
, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START
, core
);
1214 hig_workarea_add_wide_control( t
, &row
, w
);
1216 s
= _( "Use UPnP or NAT-PMP port _forwarding from my router" );
1217 w
= new_check_button( s
, TR_PREFS_KEY_PORT_FORWARDING
, core
);
1218 hig_workarea_add_wide_control( t
, &row
, w
);
1220 hig_workarea_add_section_divider( t
, &row
);
1221 hig_workarea_add_section_title( t
, &row
, _( "Peer Limits" ) );
1223 w
= new_spin_button( TR_PREFS_KEY_PEER_LIMIT_TORRENT
, core
, 1, 300, 5 );
1224 hig_workarea_add_row( t
, &row
, _( "Maximum peers per _torrent:" ), w
, NULL
);
1225 w
= new_spin_button( TR_PREFS_KEY_PEER_LIMIT_GLOBAL
, core
, 1, 3000, 5 );
1226 hig_workarea_add_row( t
, &row
, _( "Maximum peers _overall:" ), w
, NULL
);
1228 hig_workarea_add_section_divider( t
, &row
);
1229 hig_workarea_add_section_title( t
, &row
, _( "Options" ) );
1231 w
= gtk_button_new_with_mnemonic( _( "Edit GNOME Proxy Settings" ) );
1232 g_signal_connect( w
, "clicked", G_CALLBACK( onGNOMEClicked
), data
);
1233 h
= gtk_hbox_new( FALSE
, 0 );
1234 gtk_box_pack_start( GTK_BOX( h
), w
, FALSE
, FALSE
, 0 );
1235 hig_workarea_add_wide_control( t
, &row
, h
);
1237 hig_workarea_finish( t
, &row
);
1246 gtr_prefs_dialog_new( GtkWindow
* parent
, GObject
* core
)
1251 d
= gtk_dialog_new_with_buttons( _(
1252 "Transmission Preferences" ),
1254 GTK_DIALOG_DESTROY_WITH_PARENT
,
1255 GTK_STOCK_HELP
, GTK_RESPONSE_HELP
,
1256 GTK_STOCK_CLOSE
, GTK_RESPONSE_CLOSE
,
1258 gtk_window_set_role( GTK_WINDOW( d
), "transmission-preferences-dialog" );
1259 gtk_container_set_border_width( GTK_CONTAINER( d
), GUI_PAD
);
1261 n
= gtk_notebook_new( );
1262 gtk_container_set_border_width ( GTK_CONTAINER ( n
), GUI_PAD
);
1264 gtk_notebook_append_page( GTK_NOTEBOOK( n
),
1265 torrentPage( core
),
1266 gtk_label_new ( _( "Torrents" ) ) );
1267 gtk_notebook_append_page( GTK_NOTEBOOK( n
),
1268 bandwidthPage( core
),
1269 gtk_label_new ( _( "Speed" ) ) );
1270 gtk_notebook_append_page( GTK_NOTEBOOK( n
),
1271 privacyPage( core
),
1272 gtk_label_new ( _( "Privacy" ) ) );
1273 gtk_notebook_append_page( GTK_NOTEBOOK( n
),
1274 networkPage( core
),
1275 gtk_label_new ( _( "Network" ) ) );
1276 gtk_notebook_append_page( GTK_NOTEBOOK( n
),
1277 desktopPage( core
),
1278 gtk_label_new ( _( "Desktop" ) ) );
1279 gtk_notebook_append_page( GTK_NOTEBOOK( n
),
1281 gtk_label_new ( _( "Web" ) ) );
1283 g_signal_connect( d
, "response", G_CALLBACK( response_cb
), core
);
1284 gtr_dialog_set_content( GTK_DIALOG( d
), n
);