source: trunk/gtk/tr_prefs.c @ 4945

Last change on this file since 4945 was 4945, checked in by charles, 15 years ago

(gtk) gracefully handle users closing the preferences dialog before the portcheck thread is done

  • Property svn:keywords set to Date Rev Author Id
File size: 13.6 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 4945 2008-02-07 16:17:52Z charles $
11 */
12
13#include <glib/gi18n.h>
14#include <gtk/gtk.h>
15#include <third-party/miniupnp/miniwget.h>
16#include <libtransmission/transmission.h>
17#include "conf.h"
18#include "hig.h"
19#include "tr_core.h"
20#include "tr_prefs.h"
21#include "util.h"
22
23/**
24 * This is where we initialize the preferences file with the default values.
25 * If you add a new preferences key, you /must/ add a default value here.
26 */
27void
28tr_prefs_init_global( void )
29{
30    cf_check_older_configs( );
31
32    pref_int_set_default    ( PREF_KEY_MAX_PEERS_GLOBAL, 200 );
33    pref_int_set_default    ( PREF_KEY_MAX_PEERS_PER_TORRENT, 50 );
34
35    pref_flag_set_default   ( PREF_KEY_TOOLBAR, TRUE );
36    pref_flag_set_default   ( PREF_KEY_FILTER_BAR, TRUE );
37    pref_flag_set_default   ( PREF_KEY_STATUS_BAR, TRUE );
38    pref_string_set_default ( PREF_KEY_STATUS_BAR_STATS, "total-ratio" );
39
40    pref_flag_set_default   ( PREF_KEY_DL_LIMIT_ENABLED, FALSE );
41    pref_int_set_default    ( PREF_KEY_DL_LIMIT, 100 );
42    pref_flag_set_default   ( PREF_KEY_UL_LIMIT_ENABLED, FALSE );
43    pref_int_set_default    ( PREF_KEY_UL_LIMIT, 50 );
44
45    pref_flag_set_default   ( PREF_KEY_DIR_ASK, FALSE );
46    pref_string_set_default ( PREF_KEY_DIR_DEFAULT, g_get_home_dir() );
47
48    pref_int_set_default    ( PREF_KEY_PORT, TR_DEFAULT_PORT );
49
50    pref_flag_set_default   ( PREF_KEY_NAT, TRUE );
51    pref_flag_set_default   ( PREF_KEY_PEX, TRUE );
52    pref_flag_set_default   ( PREF_KEY_SYSTRAY, TRUE );
53    pref_flag_set_default   ( PREF_KEY_ASKQUIT, TRUE );
54    pref_flag_set_default   ( PREF_KEY_ENCRYPTED_ONLY, FALSE );
55
56    pref_string_set_default ( PREF_KEY_ADDSTD, toractionname(TR_TOR_COPY) );
57    pref_string_set_default ( PREF_KEY_ADDIPC, toractionname(TR_TOR_COPY) );
58
59    pref_int_set_default    ( PREF_KEY_MSGLEVEL, TR_MSG_INF );
60
61    pref_string_set_default ( PREF_KEY_SORT_MODE, "sort-by-name" );
62    pref_flag_set_default   ( PREF_KEY_SORT_REVERSED, FALSE );
63    pref_flag_set_default   ( PREF_KEY_MINIMAL_VIEW, FALSE );
64
65    pref_save( NULL );
66}
67
68/**
69***
70**/
71
72int
73tr_prefs_get_action( const char * key )
74{
75    char * val = pref_string_get( key );
76    const int ret = toraddaction( val );
77    g_free( val );
78    return ret;
79}
80
81void
82tr_prefs_set_action( const char * key, int action )
83{
84    pref_string_set( key, toractionname(action) );
85}
86
87/**
88***
89**/
90
91#define PREFS_KEY "prefs-key"
92
93static void
94response_cb( GtkDialog * dialog, int response UNUSED, gpointer unused UNUSED )
95{
96    gtk_widget_destroy( GTK_WIDGET(dialog) );
97}
98
99static void
100toggled_cb( GtkToggleButton * w, gpointer core )
101{
102    const char * key = g_object_get_data( G_OBJECT(w), PREFS_KEY );
103    const gboolean flag = gtk_toggle_button_get_active( w );
104    tr_core_set_pref_bool( TR_CORE(core), key, flag );
105}
106
107static GtkWidget*
108new_check_button( const char * mnemonic, const char * key, gpointer core )
109{
110    GtkWidget * w = gtk_check_button_new_with_mnemonic( mnemonic );
111    g_object_set_data_full( G_OBJECT(w), PREFS_KEY, g_strdup(key), g_free );
112    gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(w), pref_flag_get(key) );
113    g_signal_connect( w, "toggled", G_CALLBACK(toggled_cb), core );
114    return w;
115}
116
117static void
118spun_cb( GtkSpinButton * w, gpointer core )
119{
120    const char * key = g_object_get_data( G_OBJECT(w), PREFS_KEY );
121    const int value = gtk_spin_button_get_value_as_int( w );
122    tr_core_set_pref_int( TR_CORE(core), key, value );
123}
124
125static GtkWidget*
126new_spin_button( const char * key, gpointer core, int low, int high, int step )
127{
128    GtkWidget * w = gtk_spin_button_new_with_range( low, high, step );
129    g_object_set_data_full( G_OBJECT(w), PREFS_KEY, g_strdup(key), g_free );
130    gtk_spin_button_set_digits( GTK_SPIN_BUTTON(w), 0 );
131    gtk_spin_button_set_value( GTK_SPIN_BUTTON(w), pref_int_get(key) );
132    g_signal_connect( w, "value-changed", G_CALLBACK(spun_cb), core );
133    return w;
134}
135
136static void
137chosen_cb( GtkFileChooser * w, gpointer core )
138{
139    const char * key = g_object_get_data( G_OBJECT(w), PREFS_KEY );
140    char * value = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(w) );
141    tr_core_set_pref( TR_CORE(core), key, value );
142    g_free( value );
143}
144
145static GtkWidget*
146new_path_chooser_button( const char * key, gpointer core )
147{
148    GtkWidget * w = gtk_file_chooser_button_new( NULL,
149                                    GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER );
150    char * path = pref_string_get( key );
151    g_object_set_data_full( G_OBJECT(w), PREFS_KEY, g_strdup(key), g_free );
152    g_signal_connect( w, "selection-changed", G_CALLBACK(chosen_cb), core );
153    gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER(w), path );
154    return w;
155}
156
157static void
158action_cb( GtkComboBox * w, gpointer core )
159{
160    const char * key = g_object_get_data( G_OBJECT(w), PREFS_KEY );
161    GtkTreeIter iter;
162    if( gtk_combo_box_get_active_iter( GTK_COMBO_BOX(w), &iter ) )
163    {
164        int action;
165        GtkTreeModel * model = gtk_combo_box_get_model( GTK_COMBO_BOX(w) );
166        gtk_tree_model_get( model, &iter, 1, &action, -1 );
167        tr_core_set_pref( core, key, toractionname(action) );
168    }
169}
170
171static GtkWidget*
172new_action_combo( const char * key, gpointer core )
173{
174    const char * s;
175    GtkTreeIter iter;
176    GtkCellRenderer * rend;
177    GtkListStore * model;
178    GtkWidget * w;
179
180    model = gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_INT );
181
182    s = _("Use the torrent file where it is");
183    gtk_list_store_append( model, &iter );
184    gtk_list_store_set( model, &iter, 1, TR_TOR_LEAVE, 0, s, -1 );
185
186    s = _("Keep a copy of the torrent file");
187    gtk_list_store_append( model, &iter );
188    gtk_list_store_set( model, &iter, 1, TR_TOR_COPY, 0, s, -1 );
189
190    s = _("Keep a copy and remove the original");
191    gtk_list_store_append( model, &iter );
192    gtk_list_store_set( model, &iter, 1, TR_TOR_MOVE, 0, s, -1 );
193
194    w = gtk_combo_box_new_with_model( GTK_TREE_MODEL(model) ); 
195    gtk_combo_box_set_active( GTK_COMBO_BOX(w), tr_prefs_get_action(key) );
196    g_object_set_data_full( G_OBJECT(w), PREFS_KEY, g_strdup(key), g_free );
197    rend = gtk_cell_renderer_text_new( );
198    gtk_cell_layout_pack_start( GTK_CELL_LAYOUT(w), rend, TRUE );
199    gtk_cell_layout_add_attribute( GTK_CELL_LAYOUT(w), rend, "text", 0 );
200    g_signal_connect( w, "changed", G_CALLBACK(action_cb), core );
201
202    return w;
203}
204
205static void
206target_cb( GtkWidget * widget, gpointer target )
207{
208    gtk_widget_set_sensitive( GTK_WIDGET(target), gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(widget) ) );
209}
210
211static void
212target_invert_cb( GtkWidget * widget, gpointer target )
213{
214    gtk_widget_set_sensitive( GTK_WIDGET(target), !gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(widget) ) );
215}
216
217struct test_port_data
218{
219    GtkWidget * label;
220    gboolean * alive;
221};
222
223static gpointer
224test_port( gpointer data_gpointer )
225{
226    struct test_port_data * data = data_gpointer;
227
228    if( *data->alive )
229    {
230        GObject * o = G_OBJECT( data->label );
231        GtkSpinButton * spin = GTK_SPIN_BUTTON( g_object_get_data( o, "tr-port-spin" ) );
232        const int port = gtk_spin_button_get_value_as_int( spin );
233        int isOpen;
234        int size;
235        char * text;
236        char url[256];
237
238        g_usleep( G_USEC_PER_SEC * 3 ); /* give portmapping time to kick in */
239        snprintf( url, sizeof(url), "http://portcheck.transmissionbt.com/%d", port );
240        text = miniwget( url, &size );
241        /*g_message(" got len %d, [%*.*s]", size, size, size, text );*/
242        isOpen = text && *text=='1';
243
244        if( *data->alive )
245            gtk_label_set_markup( GTK_LABEL(data->label), isOpen
246                ? _("Port is <b>open</b>")
247                : _("Port is <b>closed</b>") );
248    }
249
250    g_free( data );
251    return NULL;
252}
253
254static void
255testing_port_cb( GtkWidget * unused UNUSED, gpointer l )
256{
257    struct test_port_data * data = g_new0( struct test_port_data, 1 );
258    data->alive = g_object_get_data( G_OBJECT( l ), "alive" );
259    data->label = l;
260    gtk_label_set_markup( GTK_LABEL(l), _( "<i>Testing port...</i>" ) );
261    g_thread_create( test_port, data, FALSE, NULL );
262}
263
264static void
265dialogDestroyed( gpointer alive, GObject * dialog UNUSED )
266{
267    *(gboolean*)alive = FALSE;
268}
269
270GtkWidget *
271tr_prefs_dialog_new( GObject * core, GtkWindow * parent )
272{
273    int row = 0;
274    const char * s;
275    GtkWidget * t;
276    GtkWidget * w, * w2;
277    GtkWidget * l;
278    GtkWidget * h;
279    GtkWidget * d;
280    GtkTooltips * tips;
281    gboolean * alive;
282
283    alive = g_new( gboolean, 1 );
284    *alive = TRUE;
285
286    tips = gtk_tooltips_new( );
287
288    d = gtk_dialog_new_with_buttons( _("Preferences"), parent,
289                                     GTK_DIALOG_DESTROY_WITH_PARENT,
290                                     GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
291                                     NULL );
292    gtk_window_set_role( GTK_WINDOW(d), "transmission-preferences-dialog" );
293    gtk_dialog_set_has_separator( GTK_DIALOG( d ), FALSE );
294    gtk_container_set_border_width( GTK_CONTAINER( d ), GUI_PAD );
295    g_object_weak_ref( G_OBJECT( d ), dialogDestroyed, alive );
296
297    g_signal_connect( d, "response", G_CALLBACK(response_cb), core );
298
299    t = hig_workarea_create ();
300
301    hig_workarea_add_section_title (t, &row, _("Speed Limits"));
302    hig_workarea_add_section_spacer (t, row, 2);
303
304        s = _("_Limit upload speed (KiB/s)");
305        w = new_check_button( s, PREF_KEY_UL_LIMIT_ENABLED, core );
306        w2 = new_spin_button( PREF_KEY_UL_LIMIT, core, 0, INT_MAX, 5 );
307        gtk_widget_set_sensitive( GTK_WIDGET(w2), pref_flag_get( PREF_KEY_UL_LIMIT_ENABLED ) );
308        g_signal_connect( w, "toggled", G_CALLBACK(target_cb), w2 );
309        hig_workarea_add_double_control( t, &row, w, w2 );
310
311        s = _("Li_mit download speed (KiB/s)");
312        w = new_check_button( s, PREF_KEY_DL_LIMIT_ENABLED, core );
313        w2 = new_spin_button( PREF_KEY_DL_LIMIT, core, 0, INT_MAX, 5 );
314        gtk_widget_set_sensitive( GTK_WIDGET(w2), pref_flag_get( PREF_KEY_DL_LIMIT_ENABLED ) );
315        g_signal_connect( w, "toggled", G_CALLBACK(target_cb), w2 );
316        hig_workarea_add_double_control( t, &row, w, w2 );
317
318    hig_workarea_add_section_divider( t, &row );
319    hig_workarea_add_section_title (t, &row, _("Downloads"));
320    hig_workarea_add_section_spacer (t, row, 3);
321
322        s = _("P_rompt for download directory");
323        w = new_check_button( s, PREF_KEY_DIR_ASK, core );
324        w2 = new_path_chooser_button( PREF_KEY_DIR_DEFAULT, core );
325        gtk_widget_set_sensitive( GTK_WIDGET(w2), !pref_flag_get( PREF_KEY_DIR_ASK ) );
326        g_signal_connect( w, "toggled", G_CALLBACK(target_invert_cb), w2 );
327        hig_workarea_add_double_control( t, &row, w, w2 );
328
329        w = new_action_combo( PREF_KEY_ADDSTD, core );
330        s = _("For torrents added _normally:");
331        l = hig_workarea_add_row( t, &row, s, w, NULL );
332
333        w = new_action_combo( PREF_KEY_ADDIPC, core );
334        s = _("For torrents added from _command-line:");
335        l = hig_workarea_add_row( t, &row, s, w, NULL );
336
337    hig_workarea_add_section_divider( t, &row );
338    hig_workarea_add_section_title( t, &row, _( "Peer Connections" ) );
339    hig_workarea_add_section_spacer(t , row, 2 );
340 
341        w = new_spin_button( PREF_KEY_MAX_PEERS_GLOBAL, core, 1, 3000, 5 );
342        hig_workarea_add_row( t, &row, _( "Global maximum connected peers:" ), w, NULL );
343        w = new_spin_button( PREF_KEY_MAX_PEERS_PER_TORRENT, core, 1, 300, 5 );
344        hig_workarea_add_row( t, &row, _( "Maximum connected peers for new torrents:" ), w, NULL );
345
346    hig_workarea_add_section_divider( t, &row );
347    hig_workarea_add_section_title (t, &row, _("Network"));
348    hig_workarea_add_section_spacer (t, row, 2);
349       
350        s = _("_Automatically map port" );
351        w = new_check_button( s, PREF_KEY_NAT, core );
352        hig_workarea_add_wide_control( t, &row, w );
353        gtk_tooltips_set_tip( GTK_TOOLTIPS( tips ), w, _( "NAT traversal uses either NAT-PMP or UPnP" ), NULL );
354
355        h = gtk_hbox_new( FALSE, GUI_PAD );
356        w2 = new_spin_button( PREF_KEY_PORT, core, 1, INT_MAX, 1 );
357        gtk_box_pack_start( GTK_BOX(h), w2, FALSE, FALSE, 0 );
358        l = gtk_label_new( NULL );
359        gtk_misc_set_alignment( GTK_MISC(l), 0.0f, 0.5f );
360        gtk_box_pack_start( GTK_BOX(h), l, FALSE, FALSE, 0 );
361        hig_workarea_add_row( t, &row, _("Incoming TCP _Port"), h, w );
362
363        g_object_set_data( G_OBJECT(l), "tr-port-spin", w2 );
364        g_object_set_data( G_OBJECT(l), "alive", alive );
365        testing_port_cb( NULL, l );
366
367        g_signal_connect( w, "toggled", G_CALLBACK(toggled_cb), l );
368        g_signal_connect( w2, "value-changed", G_CALLBACK(testing_port_cb), l );
369
370    hig_workarea_add_section_divider( t, &row );
371    hig_workarea_add_section_title (t, &row, _("Options"));
372    hig_workarea_add_section_spacer (t, row, 3);
373       
374        s = _("Use peer _exchange if possible");
375        w = new_check_button( s, PREF_KEY_PEX, core );
376        hig_workarea_add_wide_control( t, &row, w );
377       
378        s = _("_Ignore unencrypted peers");
379        w = new_check_button( s, PREF_KEY_ENCRYPTED_ONLY, core );
380        hig_workarea_add_wide_control( t, &row, w );
381       
382        s = _("Show an icon in the system _tray");
383        w = new_check_button( s, PREF_KEY_SYSTRAY, core );
384        hig_workarea_add_wide_control( t, &row, w );
385       
386        s = _("Confirm _quit");
387        w = new_check_button( s, PREF_KEY_ASKQUIT, core );
388        hig_workarea_add_wide_control( t, &row, w );
389
390    hig_workarea_finish (t, &row);
391    gtk_box_pack_start_defaults( GTK_BOX(GTK_DIALOG(d)->vbox), t );
392    gtk_widget_show_all( GTK_DIALOG(d)->vbox );
393    return d;
394}
Note: See TracBrowser for help on using the repository browser.