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

Last change on this file since 13683 was 13683, checked in by jordan, 8 years ago

(trunk, libT) first drop of the tr_quark patch.

  • Property svn:keywords set to Date Rev Author Id
File size: 44.0 KB
Line 
1/*
2 * This file Copyright (C) Mnemosyne LLC
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 13683 2012-12-22 20:35:19Z jordan $
11 */
12
13#include <ctype.h> /* isspace */
14#include <limits.h> /* USHRT_MAX */
15#include <string.h> /* strcmp () */
16#include <unistd.h>
17#include <glib/gi18n.h>
18#include <gtk/gtk.h>
19#include <libtransmission/transmission.h>
20#include <libtransmission/utils.h>
21#include <libtransmission/version.h>
22#include "conf.h"
23#include "hig.h"
24#include "tr-core.h"
25#include "tr-prefs.h"
26#include "util.h"
27
28/**
29***
30**/
31
32#define PREF_KEY "pref-key"
33
34static void
35response_cb (GtkDialog *     dialog,
36             int             response,
37             gpointer unused UNUSED)
38{
39    if (response == GTK_RESPONSE_HELP)
40    {
41        char * uri = g_strconcat (gtr_get_help_uri (), "/html/preferences.html", NULL);
42        gtr_open_uri (uri);
43        g_free (uri);
44    }
45
46    if (response == GTK_RESPONSE_CLOSE)
47        gtk_widget_destroy (GTK_WIDGET (dialog));
48}
49
50static void
51toggled_cb (GtkToggleButton * w,
52            gpointer          core)
53{
54    const tr_quark key = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (w), PREF_KEY));
55    const gboolean flag = gtk_toggle_button_get_active (w);
56
57    gtr_core_set_pref_bool (TR_CORE (core), key, flag);
58}
59
60static GtkWidget*
61new_check_button (const char      * mnemonic,
62                  const tr_quark    key,
63                  gpointer          core)
64{
65    GtkWidget * w = gtk_check_button_new_with_mnemonic (mnemonic);
66    g_object_set_data (G_OBJECT(w), PREF_KEY, GINT_TO_POINTER(key));
67    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w),
68                                 gtr_pref_flag_get (key));
69    g_signal_connect (w, "toggled", G_CALLBACK (toggled_cb), core);
70    return w;
71}
72
73#define IDLE_DATA "idle-data"
74
75struct spin_idle_data
76{
77    gpointer    core;
78    GTimer *    last_change;
79    gboolean    isDouble;
80};
81
82static void
83spin_idle_data_free (gpointer gdata)
84{
85    struct spin_idle_data * data = gdata;
86
87    g_timer_destroy (data->last_change);
88    g_free (data);
89}
90
91static gboolean
92spun_cb_idle (gpointer spin)
93{
94    gboolean keep_waiting = TRUE;
95    GObject * o = G_OBJECT (spin);
96    struct spin_idle_data * data = g_object_get_data (o, IDLE_DATA);
97
98    /* has the user stopped making changes? */
99    if (g_timer_elapsed (data->last_change, NULL) > 0.33f)
100    {
101        /* update the core */
102        const tr_quark key = GPOINTER_TO_INT (g_object_get_data (o, PREF_KEY));
103
104        if (data->isDouble)
105        {
106            const double value = gtk_spin_button_get_value (GTK_SPIN_BUTTON (spin));
107            gtr_core_set_pref_double (TR_CORE (data->core), key, value);
108        }
109        else
110        {
111            const int value = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (spin));
112            gtr_core_set_pref_int (TR_CORE (data->core), key, value);
113        }
114
115        /* cleanup */
116        g_object_set_data (o, IDLE_DATA, NULL);
117        keep_waiting = FALSE;
118        g_object_unref (G_OBJECT (o));
119    }
120
121    return keep_waiting;
122}
123
124static void
125spun_cb (GtkSpinButton * w, gpointer core, gboolean isDouble)
126{
127    /* user may be spinning through many values, so let's hold off
128       for a moment to keep from flooding the core with changes */
129    GObject * o = G_OBJECT (w);
130    struct spin_idle_data * data = g_object_get_data (o, IDLE_DATA);
131
132    if (data == NULL)
133    {
134        data = g_new (struct spin_idle_data, 1);
135        data->core = core;
136        data->last_change = g_timer_new ();
137        data->isDouble = isDouble;
138        g_object_set_data_full (o, IDLE_DATA, data, spin_idle_data_free);
139        g_object_ref (G_OBJECT (o));
140        gdk_threads_add_timeout_seconds (1, spun_cb_idle, w);
141    }
142    g_timer_start (data->last_change);
143}
144
145static void
146spun_cb_int (GtkSpinButton * w, gpointer core)
147{
148    spun_cb (w, core, FALSE);
149}
150
151static void
152spun_cb_double (GtkSpinButton * w, gpointer core)
153{
154    spun_cb (w, core, TRUE);
155}
156
157static GtkWidget*
158new_spin_button (const tr_quark  key,
159                 gpointer        core,
160                 int             low,
161                 int             high,
162                 int             step)
163{
164    GtkWidget * w = gtk_spin_button_new_with_range (low, high, step);
165    g_object_set_data (G_OBJECT(w), PREF_KEY, GINT_TO_POINTER(key));
166    gtk_spin_button_set_digits (GTK_SPIN_BUTTON (w), 0);
167    gtk_spin_button_set_value (GTK_SPIN_BUTTON (w), gtr_pref_int_get (key));
168    g_signal_connect (w, "value-changed", G_CALLBACK (spun_cb_int), core);
169    return w;
170}
171
172static GtkWidget*
173new_spin_button_double (const tr_quark  key,
174                        gpointer        core,
175                        double          low,
176                        double          high,
177                        double          step)
178{
179    GtkWidget * w = gtk_spin_button_new_with_range (low, high, step);
180    g_object_set_data (G_OBJECT(w), PREF_KEY, GINT_TO_POINTER(key));
181    gtk_spin_button_set_digits (GTK_SPIN_BUTTON (w), 2);
182    gtk_spin_button_set_value (GTK_SPIN_BUTTON (w), gtr_pref_double_get (key));
183    g_signal_connect (w, "value-changed", G_CALLBACK (spun_cb_double), core);
184    return w;
185}
186
187static void
188entry_changed_cb (GtkEntry * w, gpointer core)
189{
190  const tr_quark key = GPOINTER_TO_INT (g_object_get_data (G_OBJECT(w), PREF_KEY));
191  const char * value = gtk_entry_get_text (w);
192
193  gtr_core_set_pref (TR_CORE(core), key, value);
194}
195
196static GtkWidget*
197new_entry (const tr_quark  key,
198           gpointer        core)
199{
200  GtkWidget * w = gtk_entry_new ();
201  const char * value = gtr_pref_string_get (key);
202
203  if (value)
204    gtk_entry_set_text (GTK_ENTRY (w), value);
205
206  g_object_set_data (G_OBJECT (w), PREF_KEY, GINT_TO_POINTER(key));
207  g_signal_connect (w, "changed", G_CALLBACK (entry_changed_cb), core);
208  return w;
209}
210
211static void
212chosen_cb (GtkFileChooser * w, gpointer core)
213{
214  const tr_quark key = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (w), PREF_KEY));
215  char * value = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (w));
216  gtr_core_set_pref (TR_CORE (core), key, value);
217  g_free (value);
218}
219
220static GtkWidget*
221new_path_chooser_button (const tr_quark key, gpointer core)
222{
223  GtkWidget *  w = gtk_file_chooser_button_new (NULL, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
224  const char * path = gtr_pref_string_get (key);
225  g_object_set_data (G_OBJECT (w), PREF_KEY, GINT_TO_POINTER(key));
226  g_signal_connect (w, "selection-changed", G_CALLBACK (chosen_cb), core);
227
228  if (path != NULL)
229    gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (w), path);
230
231  return w;
232}
233
234static GtkWidget*
235new_file_chooser_button (const tr_quark key, gpointer core)
236{
237  GtkWidget *  w = gtk_file_chooser_button_new (NULL, GTK_FILE_CHOOSER_ACTION_OPEN);
238  const char * path = gtr_pref_string_get (key);
239  g_object_set_data (G_OBJECT(w), PREF_KEY, GINT_TO_POINTER(key));
240  if (path != NULL)
241    gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (w), path);
242  g_signal_connect (w, "selection-changed", G_CALLBACK (chosen_cb), core);
243  return w;
244}
245
246static void
247target_cb (GtkWidget * tb, gpointer target)
248{
249  const gboolean b = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (tb));
250
251  gtk_widget_set_sensitive (GTK_WIDGET (target), b);
252}
253
254/****
255*****  Download Tab
256****/
257
258static GtkWidget*
259downloadPage (GObject * core)
260{
261    GtkWidget * t;
262    GtkWidget * w;
263    GtkWidget * l;
264    const char * s;
265    guint row = 0;
266
267    t = hig_workarea_create ();
268
269    hig_workarea_add_section_title (t, &row, _("Location"));
270
271    w = new_path_chooser_button (TR_KEY_download_dir, core);
272    hig_workarea_add_row (t, &row, _("Save to _Location:"), w, NULL);
273
274    hig_workarea_add_section_divider (t, &row);
275    hig_workarea_add_section_title (t, &row, _("Queue"));
276
277    s = _("Maximum active _downloads:");
278    w = new_spin_button (TR_KEY_download_queue_size, core, 0, INT_MAX, 1);
279    hig_workarea_add_row (t, &row, s, w, NULL);
280
281    s = _("Downloads sharing data in the last N minutes are _active:");
282    w = new_spin_button (TR_KEY_queue_stalled_minutes, core, 1, INT_MAX, 15);
283    hig_workarea_add_row (t, &row, s, w, NULL);
284
285    hig_workarea_add_section_divider (t, &row);
286    hig_workarea_add_section_title (t, &row, _("Incomplete"));
287
288    s = _("Append \"._part\" to incomplete files' names");
289    w = new_check_button (s, TR_KEY_rename_partial_files, core);
290    hig_workarea_add_wide_control (t, &row, w);
291
292    s = _("Keep _incomplete torrents in:");
293    l = new_check_button (s, TR_KEY_incomplete_dir_enabled, core);
294    w = new_path_chooser_button (TR_KEY_incomplete_dir, core);
295    gtk_widget_set_sensitive (GTK_WIDGET (w), gtr_pref_flag_get (TR_KEY_incomplete_dir_enabled));
296    g_signal_connect (l, "toggled", G_CALLBACK (target_cb), w);
297    hig_workarea_add_row_w (t, &row, l, w, NULL);
298
299    s = _("Call _script when torrent is completed:");
300    l = new_check_button (s, TR_KEY_script_torrent_done_enabled, core);
301    w = new_file_chooser_button (TR_KEY_script_torrent_done_filename, core);
302    gtk_widget_set_sensitive (GTK_WIDGET (w), gtr_pref_flag_get (TR_KEY_script_torrent_done_enabled));
303    g_signal_connect (l, "toggled", G_CALLBACK (target_cb), w);
304    hig_workarea_add_row_w (t, &row, l, w, NULL);
305
306    return t;
307}
308
309/****
310*****  Torrent Tab
311****/
312
313static GtkWidget*
314torrentPage (GObject * core)
315{
316    const char * s;
317    GtkWidget *  t;
318    GtkWidget *  w;
319    GtkWidget *  w2;
320    GtkWidget *  l;
321    guint row = 0;
322
323    t = hig_workarea_create ();
324    hig_workarea_add_section_title (t, &row, C_("Gerund", "Adding"));
325
326    s = _("_Start when added");
327    w = new_check_button (s, TR_KEY_start_added_torrents, core);
328    hig_workarea_add_wide_control (t, &row, w);
329
330    s = _("Show _options dialog");
331    w = new_check_button (s, TR_KEY_show_options_window, core);
332    hig_workarea_add_wide_control (t, &row, w);
333
334    s = _("Mo_ve .torrent file to the trash");
335    w = new_check_button (s, TR_KEY_trash_original_torrent_files, core);
336    hig_workarea_add_wide_control (t, &row, w);
337
338    s = _("Automatically _add torrents from:");
339    l = new_check_button (s, TR_KEY_watch_dir_enabled, core);
340    w = new_path_chooser_button (TR_KEY_watch_dir, core);
341    gtk_widget_set_sensitive (GTK_WIDGET (w), gtr_pref_flag_get (TR_KEY_watch_dir_enabled));
342    g_signal_connect (l, "toggled", G_CALLBACK (target_cb), w);
343    hig_workarea_add_row_w (t, &row, l, w, NULL);
344
345    hig_workarea_add_section_divider (t, &row);
346    hig_workarea_add_section_title (t, &row, C_("Gerund", "Seeding"));
347
348    s = _("Stop seeding at _ratio:");
349    w = new_check_button (s, TR_KEY_ratio_limit_enabled, core);
350    w2 = new_spin_button_double (TR_KEY_ratio_limit, core, 0, 1000, .05);
351    gtk_widget_set_sensitive (GTK_WIDGET (w2), gtr_pref_flag_get (TR_KEY_ratio_limit_enabled));
352    g_signal_connect (w, "toggled", G_CALLBACK (target_cb), w2);
353    hig_workarea_add_row_w (t, &row, w, w2, NULL);
354
355    s = _("Stop seeding if idle for _N minutes:");
356    w = new_check_button (s, TR_KEY_idle_seeding_limit_enabled, core);
357    w2 = new_spin_button (TR_KEY_idle_seeding_limit, core, 1, 9999, 5);
358    gtk_widget_set_sensitive (GTK_WIDGET (w2), gtr_pref_flag_get (TR_KEY_idle_seeding_limit_enabled));
359    g_signal_connect (w, "toggled", G_CALLBACK (target_cb), w2);
360    hig_workarea_add_row_w (t, &row, w, w2, NULL);
361
362    return t;
363}
364
365/****
366*****  Desktop Tab
367****/
368
369static GtkWidget*
370desktopPage (GObject * core)
371{
372    GtkWidget * t;
373    GtkWidget * w;
374    const char * s;
375    guint row = 0;
376
377    t = hig_workarea_create ();
378    hig_workarea_add_section_title (t, &row, _("Desktop"));
379
380    s = _("_Inhibit hibernation when torrents are active");
381    w = new_check_button (s, TR_KEY_inhibit_desktop_hibernation, core);
382    hig_workarea_add_wide_control (t, &row, w);
383
384    s = _("Show Transmission icon in the _notification area");
385    w = new_check_button (s, TR_KEY_show_notification_area_icon, core);
386    hig_workarea_add_wide_control (t, &row, w);
387
388    hig_workarea_add_section_divider (t, &row);
389    hig_workarea_add_section_title (t, &row, _("Notification"));
390
391    s = _("Show a notification when torrents are a_dded");
392    w = new_check_button (s, TR_KEY_torrent_added_notification_enabled, core);
393    hig_workarea_add_wide_control (t, &row, w);
394
395    s = _("Show a notification when torrents _finish");
396    w = new_check_button (s, TR_KEY_torrent_complete_notification_enabled, core);
397    hig_workarea_add_wide_control (t, &row, w);
398
399    s = _("Play a _sound when torrents finish");
400    w = new_check_button (s, TR_KEY_torrent_complete_sound_enabled, core);
401    hig_workarea_add_wide_control (t, &row, w);
402
403    return t;
404}
405
406/****
407*****  Peer Tab
408****/
409
410struct blocklist_data
411{
412    gulong      updateBlocklistTag;
413    GtkWidget * updateBlocklistButton;
414    GtkWidget * updateBlocklistDialog;
415    GtkWidget * label;
416    GtkWidget * check;
417    TrCore    * core;
418};
419
420static void
421updateBlocklistText (GtkWidget * w, TrCore * core)
422{
423    char buf1[512];
424    char buf2[512];
425    const int n = tr_blocklistGetRuleCount (gtr_core_session (core));
426    g_snprintf (buf1, sizeof (buf1),
427                ngettext ("Blocklist contains %'d rule",
428                          "Blocklist contains %'d rules", n), n);
429    g_snprintf (buf2, sizeof (buf2), "<i>%s</i>", buf1);
430    gtk_label_set_markup (GTK_LABEL (w), buf2);
431}
432
433/* prefs dialog is being destroyed, so stop listening to blocklist updates */
434static void
435privacyPageDestroyed (gpointer gdata, GObject * dead UNUSED)
436{
437    struct blocklist_data * data = gdata;
438    if (data->updateBlocklistTag > 0)
439        g_signal_handler_disconnect (data->core, data->updateBlocklistTag);
440    g_free (data);
441}
442
443/* user hit "close" in the blocklist-update dialog */
444static void
445onBlocklistUpdateResponse (GtkDialog * dialog, gint response UNUSED, gpointer gdata)
446{
447    struct blocklist_data * data = gdata;
448    gtk_widget_destroy (GTK_WIDGET (dialog));
449    gtk_widget_set_sensitive (data->updateBlocklistButton, TRUE);
450    data->updateBlocklistDialog = NULL;
451    g_signal_handler_disconnect (data->core, data->updateBlocklistTag);
452}
453
454/* core says the blocklist was updated */
455static void
456onBlocklistUpdated (TrCore * core, int n, gpointer gdata)
457{
458    const bool success = n >= 0;
459    const int count = n >=0 ? n : tr_blocklistGetRuleCount (gtr_core_session (core));
460    const char * s = ngettext ("Blocklist has %'d rule.", "Blocklist has %'d rules.", count);
461    struct blocklist_data * data = gdata;
462    GtkMessageDialog * d = GTK_MESSAGE_DIALOG (data->updateBlocklistDialog);
463    gtk_widget_set_sensitive (data->updateBlocklistButton, TRUE);
464    gtk_message_dialog_set_markup (d, success ? _("<b>Update succeeded!</b>") : _("<b>Unable to update.</b>"));
465    gtk_message_dialog_format_secondary_text (d, s, count);
466    updateBlocklistText (data->label, core);
467}
468
469/* user pushed a button to update the blocklist */
470static void
471onBlocklistUpdate (GtkButton * w, gpointer gdata)
472{
473    GtkWidget * d;
474    struct blocklist_data * data = gdata;
475    d = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (w))),
476                                GTK_DIALOG_DESTROY_WITH_PARENT,
477                                GTK_MESSAGE_INFO,
478                                GTK_BUTTONS_CLOSE,
479                                "%s", _("Update Blocklist"));
480    gtk_widget_set_sensitive (data->updateBlocklistButton, FALSE);
481    gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (d), "%s", _("Getting new blocklist
"));
482    data->updateBlocklistDialog = d;
483    g_signal_connect (d, "response", G_CALLBACK (onBlocklistUpdateResponse), data);
484    gtk_widget_show (d);
485    gtr_core_blocklist_update (data->core);
486    data->updateBlocklistTag = g_signal_connect (data->core, "blocklist-updated", G_CALLBACK (onBlocklistUpdated), data);
487}
488
489static void
490on_blocklist_url_changed (GtkEditable * e, gpointer gbutton)
491{
492  gchar * url = gtk_editable_get_chars (e, 0, -1);
493  const gboolean err = tr_urlParse (url, -1, NULL, NULL, NULL, NULL);
494  gtk_widget_set_sensitive (GTK_WIDGET (gbutton), !err);
495  g_free (url);
496}
497
498static void
499onIntComboChanged (GtkComboBox * combo_box, gpointer core)
500{
501  const int val = gtr_combo_box_get_active_enum (combo_box);
502  const tr_quark key = GPOINTER_TO_INT (g_object_get_data (G_OBJECT(combo_box), PREF_KEY));
503  gtr_core_set_pref_int (TR_CORE (core), key, val);
504}
505
506static GtkWidget*
507new_encryption_combo (GObject * core, const tr_quark key)
508{
509  GtkWidget * w = gtr_combo_box_new_enum (_("Allow encryption"),   TR_CLEAR_PREFERRED,
510                                          _("Prefer encryption"),  TR_ENCRYPTION_PREFERRED,
511                                          _("Require encryption"), TR_ENCRYPTION_REQUIRED,
512                                          NULL);
513  gtr_combo_box_set_active_enum (GTK_COMBO_BOX (w), gtr_pref_int_get (key));
514  g_object_set_data (G_OBJECT (w), PREF_KEY, GINT_TO_POINTER(key));
515  g_signal_connect (w, "changed", G_CALLBACK (onIntComboChanged), core);
516  return w;
517}
518
519static GtkWidget*
520privacyPage (GObject * core)
521{
522    const char * s;
523    GtkWidget * t;
524    GtkWidget * w;
525    GtkWidget * b;
526    GtkWidget * h;
527    GtkWidget * e;
528    struct blocklist_data * data;
529    guint row = 0;
530
531    data = g_new0 (struct blocklist_data, 1);
532    data->core = TR_CORE (core);
533
534    t = hig_workarea_create ();
535    hig_workarea_add_section_title (t, &row, _("Blocklist"));
536
537    b = new_check_button (_("Enable _blocklist:"), TR_KEY_blocklist_enabled, core);
538    e = new_entry (TR_KEY_blocklist_url, core);
539    gtk_widget_set_size_request (e, 300, -1);
540    hig_workarea_add_row_w (t, &row, b, e, NULL);
541    data->check = b;
542    g_signal_connect (b, "toggled", G_CALLBACK (target_cb), e);
543    target_cb (b, e);
544
545    w = gtk_label_new ("");
546    gtk_misc_set_alignment (GTK_MISC (w), 0.0f, 0.5f);
547    updateBlocklistText (w, TR_CORE (core));
548    data->label = w;
549    h = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, GUI_PAD_BIG);
550    gtk_box_pack_start (GTK_BOX (h), w, TRUE, TRUE, 0);
551    b = data->updateBlocklistButton = gtk_button_new_with_mnemonic (_("_Update"));
552    g_object_set_data (G_OBJECT (b), "session", gtr_core_session (TR_CORE (core)));
553    g_signal_connect (b, "clicked", G_CALLBACK (onBlocklistUpdate), data);
554    g_signal_connect (data->check, "toggled", G_CALLBACK (target_cb), b); target_cb (data->check, b);
555    gtk_box_pack_start (GTK_BOX (h), b, FALSE, FALSE, 0);
556    g_signal_connect (data->check, "toggled", G_CALLBACK (target_cb), w); target_cb (data->check, w);
557    hig_workarea_add_wide_control (t, &row, h);
558    g_signal_connect (e, "changed", G_CALLBACK (on_blocklist_url_changed), data->updateBlocklistButton);
559    on_blocklist_url_changed (GTK_EDITABLE (e), data->updateBlocklistButton);
560
561    s = _("Enable _automatic updates");
562    w = new_check_button (s, TR_KEY_blocklist_updates_enabled, core);
563    hig_workarea_add_wide_control (t, &row, w);
564    g_signal_connect (data->check, "toggled", G_CALLBACK (target_cb), w); target_cb (data->check, w);
565
566    hig_workarea_add_section_divider (t, &row);
567    hig_workarea_add_section_title (t, &row, _("Privacy"));
568
569    s = _("_Encryption mode:");
570    w = new_encryption_combo (core, TR_KEY_encryption);
571    hig_workarea_add_row (t, &row, s, w, NULL);
572
573    s = _("Use PE_X to find more peers");
574    w = new_check_button (s, TR_KEY_pex_enabled, core);
575    s = _("PEX is a tool for exchanging peer lists with the peers you're connected to.");
576    gtk_widget_set_tooltip_text (w, s);
577    hig_workarea_add_wide_control (t, &row, w);
578
579    s = _("Use _DHT to find more peers");
580    w = new_check_button (s, TR_KEY_dht_enabled, core);
581    s = _("DHT is a tool for finding peers without a tracker.");
582    gtk_widget_set_tooltip_text (w, s);
583    hig_workarea_add_wide_control (t, &row, w);
584
585    s = _("Use _Local Peer Discovery to find more peers");
586    w = new_check_button (s, TR_KEY_lpd_enabled, core);
587    s = _("LPD is a tool for finding peers on your local network.");
588    gtk_widget_set_tooltip_text (w, s);
589    hig_workarea_add_wide_control (t, &row, w);
590
591    g_object_weak_ref (G_OBJECT (t), privacyPageDestroyed, data);
592    return t;
593}
594
595/****
596*****  Web Tab
597****/
598
599enum
600{
601    COL_ADDRESS,
602    N_COLS
603};
604
605static GtkTreeModel*
606whitelist_tree_model_new (const char * whitelist)
607{
608    int            i;
609    char **        rules;
610    GtkListStore * store = gtk_list_store_new (N_COLS,
611                                               G_TYPE_STRING,
612                                               G_TYPE_STRING);
613
614    rules = g_strsplit (whitelist, ",", 0);
615
616    for (i = 0; rules && rules[i]; ++i)
617    {
618        GtkTreeIter iter;
619        const char * s = rules[i];
620        while (isspace (*s)) ++s;
621        gtk_list_store_append (store, &iter);
622        gtk_list_store_set (store, &iter, COL_ADDRESS, s, -1);
623    }
624
625    g_strfreev (rules);
626    return GTK_TREE_MODEL (store);
627}
628
629struct remote_page
630{
631    TrCore *           core;
632    GtkTreeView *      view;
633    GtkListStore *     store;
634    GtkWidget *        remove_button;
635    GSList *           widgets;
636    GSList *           auth_widgets;
637    GSList *           whitelist_widgets;
638    GtkToggleButton *  rpc_tb;
639    GtkToggleButton *  auth_tb;
640    GtkToggleButton *  whitelist_tb;
641};
642
643static void
644refreshWhitelist (struct remote_page * page)
645{
646    GtkTreeIter iter;
647    GString * gstr = g_string_new (NULL);
648    GtkTreeModel * model = GTK_TREE_MODEL (page->store);
649
650    if (gtk_tree_model_iter_nth_child (model, &iter, NULL, 0)) do
651    {
652        char * address;
653        gtk_tree_model_get (model, &iter, COL_ADDRESS, &address, -1);
654        g_string_append (gstr, address);
655        g_string_append (gstr, ",");
656        g_free (address);
657    }
658    while (gtk_tree_model_iter_next (model, &iter));
659
660    g_string_truncate (gstr, gstr->len - 1); /* remove the trailing comma */
661
662    gtr_core_set_pref (page->core, TR_KEY_rpc_whitelist, gstr->str);
663
664    g_string_free (gstr, TRUE);
665}
666
667static void
668onAddressEdited (GtkCellRendererText  * r UNUSED,
669                 gchar *                  path_string,
670                 gchar *                  address,
671                 gpointer                 gpage)
672{
673    GtkTreeIter          iter;
674    struct remote_page * page = gpage;
675    GtkTreeModel *       model = GTK_TREE_MODEL (page->store);
676    GtkTreePath *        path = gtk_tree_path_new_from_string (path_string);
677
678    if (gtk_tree_model_get_iter (model, &iter, path))
679        gtk_list_store_set (page->store, &iter, COL_ADDRESS, address, -1);
680
681    gtk_tree_path_free (path);
682    refreshWhitelist (page);
683}
684
685static void
686onAddWhitelistClicked (GtkButton * b UNUSED,
687                 gpointer      gpage)
688{
689    GtkTreeIter          iter;
690    GtkTreePath *        path;
691    struct remote_page * page = gpage;
692
693    gtk_list_store_append (page->store, &iter);
694    gtk_list_store_set (page->store, &iter,
695                        COL_ADDRESS,  "0.0.0.0",
696                        -1);
697
698    path = gtk_tree_model_get_path (GTK_TREE_MODEL (page->store), &iter);
699    gtk_tree_view_set_cursor (
700        page->view, path,
701        gtk_tree_view_get_column (page->view, COL_ADDRESS),
702        TRUE);
703    gtk_tree_path_free (path);
704}
705
706static void
707onRemoveWhitelistClicked (GtkButton * b UNUSED,
708                    gpointer      gpage)
709{
710    struct remote_page * page = gpage;
711    GtkTreeSelection *   sel = gtk_tree_view_get_selection (page->view);
712    GtkTreeIter          iter;
713
714    if (gtk_tree_selection_get_selected (sel, NULL, &iter))
715    {
716        gtk_list_store_remove (page->store, &iter);
717        refreshWhitelist (page);
718    }
719}
720
721static void
722refreshRPCSensitivity (struct remote_page * page)
723{
724    GSList *           l;
725    const int          rpc_active = gtk_toggle_button_get_active (
726        page->rpc_tb);
727    const int          auth_active = gtk_toggle_button_get_active (
728        page->auth_tb);
729    const int          whitelist_active = gtk_toggle_button_get_active (
730        page->whitelist_tb);
731    GtkTreeSelection * sel = gtk_tree_view_get_selection (page->view);
732    const int          have_addr =
733        gtk_tree_selection_get_selected (sel, NULL,
734                                         NULL);
735    const int          n_rules = gtk_tree_model_iter_n_children (
736        GTK_TREE_MODEL (page->store), NULL);
737
738    for (l = page->widgets; l != NULL; l = l->next)
739        gtk_widget_set_sensitive (GTK_WIDGET (l->data), rpc_active);
740
741    for (l = page->auth_widgets; l != NULL; l = l->next)
742        gtk_widget_set_sensitive (GTK_WIDGET (
743                                      l->data), rpc_active && auth_active);
744
745    for (l = page->whitelist_widgets; l != NULL; l = l->next)
746        gtk_widget_set_sensitive (GTK_WIDGET (l->data),
747                                  rpc_active && whitelist_active);
748
749    gtk_widget_set_sensitive (page->remove_button,
750                              rpc_active && have_addr && n_rules > 1);
751}
752
753static void
754onRPCToggled (GtkToggleButton * tb UNUSED,
755              gpointer             page)
756{
757    refreshRPCSensitivity (page);
758}
759
760static void
761onWhitelistSelectionChanged (GtkTreeSelection * sel UNUSED,
762                       gpointer               page)
763{
764    refreshRPCSensitivity (page);
765}
766
767static void
768onLaunchClutchCB (GtkButton * w UNUSED, gpointer data UNUSED)
769{
770  char * uri;
771  const int port = gtr_pref_int_get (TR_KEY_rpc_port);
772
773  uri = g_strdup_printf ("http://localhost:%d/", port);
774  gtr_open_uri (uri);
775  g_free (uri);
776}
777
778static void
779remotePageFree (gpointer gpage)
780{
781    struct remote_page * page = gpage;
782
783    g_slist_free (page->widgets);
784    g_slist_free (page->auth_widgets);
785    g_slist_free (page->whitelist_widgets);
786    g_free (page);
787}
788
789static GtkWidget*
790webPage (GObject * core)
791{
792    GtkWidget * t;
793    GtkWidget * w;
794    GtkWidget * h;
795    const char * s;
796    guint row = 0;
797    struct remote_page * page = g_new0 (struct remote_page, 1);
798
799    page->core = TR_CORE (core);
800
801    t = hig_workarea_create ();
802    g_object_set_data_full (G_OBJECT (t), "page", page, remotePageFree);
803
804    hig_workarea_add_section_title (t, &row, _("Web Client"));
805
806    /* "enabled" checkbutton */
807    s = _("_Enable web client");
808    w = new_check_button (s, TR_KEY_rpc_enabled, core);
809    page->rpc_tb = GTK_TOGGLE_BUTTON (w);
810    g_signal_connect (w, "clicked", G_CALLBACK (onRPCToggled), page);
811    h = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, GUI_PAD_BIG);
812    gtk_box_pack_start (GTK_BOX (h), w, TRUE, TRUE, 0);
813    w = gtk_button_new_with_mnemonic (_("_Open web client"));
814    page->widgets = g_slist_prepend (page->widgets, w);
815    g_signal_connect (w, "clicked", G_CALLBACK (onLaunchClutchCB), NULL);
816    gtk_box_pack_start (GTK_BOX (h), w, FALSE, FALSE, 0);
817    hig_workarea_add_wide_control (t, &row, h);
818
819    /* port */
820    w = new_spin_button (TR_KEY_rpc_port, core, 0, USHRT_MAX, 1);
821    page->widgets = g_slist_prepend (page->widgets, w);
822    w = hig_workarea_add_row (t, &row, _("HTTP _port:"), w, NULL);
823    page->widgets = g_slist_prepend (page->widgets, w);
824
825    /* require authentication */
826    s = _("Use _authentication");
827    w = new_check_button (s, TR_KEY_rpc_authentication_required, core);
828    hig_workarea_add_wide_control (t, &row, w);
829    page->auth_tb = GTK_TOGGLE_BUTTON (w);
830    page->widgets = g_slist_prepend (page->widgets, w);
831    g_signal_connect (w, "clicked", G_CALLBACK (onRPCToggled), page);
832
833    /* username */
834    s = _("_Username:");
835    w = new_entry (TR_KEY_rpc_username, core);
836    page->auth_widgets = g_slist_prepend (page->auth_widgets, w);
837    w = hig_workarea_add_row (t, &row, s, w, NULL);
838    page->auth_widgets = g_slist_prepend (page->auth_widgets, w);
839
840    /* password */
841    s = _("Pass_word:");
842    w = new_entry (TR_KEY_rpc_password, core);
843    gtk_entry_set_visibility (GTK_ENTRY (w), FALSE);
844    page->auth_widgets = g_slist_prepend (page->auth_widgets, w);
845    w = hig_workarea_add_row (t, &row, s, w, NULL);
846    page->auth_widgets = g_slist_prepend (page->auth_widgets, w);
847
848    /* require authentication */
849    s = _("Only allow these IP a_ddresses to connect:");
850    w = new_check_button (s, TR_KEY_rpc_whitelist_enabled, core);
851    hig_workarea_add_wide_control (t, &row, w);
852    page->whitelist_tb = GTK_TOGGLE_BUTTON (w);
853    page->widgets = g_slist_prepend (page->widgets, w);
854    g_signal_connect (w, "clicked", G_CALLBACK (onRPCToggled), page);
855
856    /* access control list */
857    {
858        const char *        val = gtr_pref_string_get (TR_KEY_rpc_whitelist);
859        GtkTreeModel *      m = whitelist_tree_model_new (val);
860        GtkTreeViewColumn * c;
861        GtkCellRenderer *   r;
862        GtkTreeSelection *  sel;
863        GtkTreeView *       v;
864        GtkWidget *         w;
865        GtkWidget *         h;
866
867        page->store = GTK_LIST_STORE (m);
868        w = gtk_tree_view_new_with_model (m);
869        g_signal_connect (w, "button-release-event",
870                          G_CALLBACK (on_tree_view_button_released), NULL);
871
872        page->whitelist_widgets = g_slist_prepend (page->whitelist_widgets, w);
873        v = page->view = GTK_TREE_VIEW (w);
874        gtk_widget_set_tooltip_text (w, _("IP addresses may use wildcards, such as 192.168.*.*"));
875        sel = gtk_tree_view_get_selection (v);
876        g_signal_connect (sel, "changed",
877                          G_CALLBACK (onWhitelistSelectionChanged), page);
878        g_object_unref (G_OBJECT (m));
879        gtk_tree_view_set_headers_visible (v, TRUE);
880        w = gtk_frame_new (NULL);
881        gtk_frame_set_shadow_type (GTK_FRAME (w), GTK_SHADOW_IN);
882        gtk_container_add (GTK_CONTAINER (w), GTK_WIDGET (v));
883
884        /* ip address column */
885        r = gtk_cell_renderer_text_new ();
886        g_signal_connect (r, "edited",
887                          G_CALLBACK (onAddressEdited), page);
888        g_object_set (G_OBJECT (r), "editable", TRUE, NULL);
889        c = gtk_tree_view_column_new_with_attributes (NULL, r,
890                                                      "text", COL_ADDRESS,
891                                                      NULL);
892        gtk_tree_view_column_set_expand (c, TRUE);
893        gtk_tree_view_append_column (v, c);
894        gtk_tree_view_set_headers_visible (v, FALSE);
895
896        s = _("Addresses:");
897        w = hig_workarea_add_row (t, &row, s, w, NULL);
898        gtk_misc_set_alignment (GTK_MISC (w), 0.0f, 0.0f);
899        gtk_misc_set_padding (GTK_MISC (w), 0, GUI_PAD);
900        page->whitelist_widgets = g_slist_prepend (page->whitelist_widgets, w);
901
902        h = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, GUI_PAD);
903        w = gtk_button_new_from_stock (GTK_STOCK_REMOVE);
904        g_signal_connect (w, "clicked", G_CALLBACK (
905                              onRemoveWhitelistClicked), page);
906        page->remove_button = w;
907        onWhitelistSelectionChanged (sel, page);
908        gtk_box_pack_start (GTK_BOX (h), w, TRUE, TRUE, 0);
909        w = gtk_button_new_from_stock (GTK_STOCK_ADD);
910        page->whitelist_widgets = g_slist_prepend (page->whitelist_widgets, w);
911        g_signal_connect (w, "clicked", G_CALLBACK (onAddWhitelistClicked), page);
912        gtk_box_pack_start (GTK_BOX (h), w, TRUE, TRUE, 0);
913        w = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
914        gtk_box_pack_start (GTK_BOX (w), gtk_alignment_new (0, 0, 0, 0),
915                            TRUE, TRUE, 0);
916        gtk_box_pack_start (GTK_BOX (w), h, FALSE, FALSE, 0);
917        hig_workarea_add_wide_control (t, &row, w);
918    }
919
920    refreshRPCSensitivity (page);
921    return t;
922}
923
924/****
925*****  Bandwidth Tab
926****/
927
928struct BandwidthPage
929{
930  TrCore *  core;
931  GSList *  sched_widgets;
932};
933
934static void
935refreshSchedSensitivity (struct BandwidthPage * p)
936{
937  GSList * l;
938  const gboolean sched_enabled = gtr_pref_flag_get (TR_KEY_alt_speed_time_enabled);
939
940  for (l=p->sched_widgets; l!=NULL; l=l->next)
941    gtk_widget_set_sensitive (GTK_WIDGET (l->data), sched_enabled);
942}
943
944static void
945onSchedToggled (GtkToggleButton * tb UNUSED,
946                gpointer             user_data)
947{
948    refreshSchedSensitivity (user_data);
949}
950
951static void
952onTimeComboChanged (GtkComboBox * w, gpointer core)
953{
954  GtkTreeIter iter;
955
956  if (gtk_combo_box_get_active_iter (w, &iter))
957    {
958      int val = 0;
959      const tr_quark key = GPOINTER_TO_INT (g_object_get_data (G_OBJECT(w), PREF_KEY));
960      gtk_tree_model_get (gtk_combo_box_get_model(w), &iter, 0, &val, -1);
961      gtr_core_set_pref_int (TR_CORE(core), key, val);
962    }
963}
964
965static GtkWidget*
966new_time_combo (GObject * core, const tr_quark key)
967{
968  int val;
969  int i;
970  GtkWidget * w;
971  GtkCellRenderer * r;
972  GtkListStore * store;
973
974  /* build a store at 15 minute intervals */
975  store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
976  for (i=0; i<60*24; i+=15)
977    {
978      char buf[128];
979      GtkTreeIter iter;
980      struct tm   tm;
981      tm.tm_hour = i / 60;
982      tm.tm_min = i % 60;
983      tm.tm_sec = 0;
984      strftime (buf, sizeof (buf), "%H:%M", &tm);
985      gtk_list_store_append (store, &iter);
986      gtk_list_store_set (store, &iter, 0, i, 1, buf, -1);
987    }
988
989  /* build the widget */
990  w = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store));
991  gtk_combo_box_set_wrap_width (GTK_COMBO_BOX (w), 4);
992  r = gtk_cell_renderer_text_new ();
993  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (w), r, TRUE);
994  gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (w), r, "text", 1, NULL);
995  g_object_set_data (G_OBJECT(w), PREF_KEY, GINT_TO_POINTER(key));
996  val = gtr_pref_int_get (key);
997  gtk_combo_box_set_active (GTK_COMBO_BOX (w), val / (15));
998  g_signal_connect (w, "changed", G_CALLBACK (onTimeComboChanged), core);
999
1000  /* cleanup */
1001  g_object_unref (G_OBJECT (store));
1002  return w;
1003}
1004
1005static GtkWidget*
1006new_week_combo (GObject * core, const tr_quark key)
1007{
1008  GtkWidget * w = gtr_combo_box_new_enum (_("Every Day"), TR_SCHED_ALL,
1009                                          _("Weekdays"),  TR_SCHED_WEEKDAY,
1010                                          _("Weekends"),  TR_SCHED_WEEKEND,
1011                                          _("Sunday"),    TR_SCHED_SUN,
1012                                          _("Monday"),    TR_SCHED_MON,
1013                                          _("Tuesday"),   TR_SCHED_TUES,
1014                                          _("Wednesday"), TR_SCHED_WED,
1015                                          _("Thursday"),  TR_SCHED_THURS,
1016                                          _("Friday"),    TR_SCHED_FRI,
1017                                          _("Saturday"),  TR_SCHED_SAT,
1018                                          NULL);
1019  gtr_combo_box_set_active_enum (GTK_COMBO_BOX (w), gtr_pref_int_get (key));
1020  g_object_set_data (G_OBJECT(w), PREF_KEY, GINT_TO_POINTER(key));
1021  g_signal_connect (w, "changed", G_CALLBACK (onIntComboChanged), core);
1022  return w;
1023}
1024
1025static void
1026bandwidthPageFree (gpointer gpage)
1027{
1028    struct BandwidthPage * page = gpage;
1029
1030    g_slist_free (page->sched_widgets);
1031    g_free (page);
1032}
1033
1034static GtkWidget*
1035bandwidthPage (GObject * core)
1036{
1037    const char * s;
1038    GtkWidget * t;
1039    GtkWidget * l;
1040    GtkWidget * w, * w2, * h;
1041    char buf[512];
1042    guint row = 0;
1043    struct BandwidthPage * page = tr_new0 (struct BandwidthPage, 1);
1044
1045    page->core = TR_CORE (core);
1046
1047    t = hig_workarea_create ();
1048    hig_workarea_add_section_title (t, &row, _("Speed Limits"));
1049
1050        g_snprintf (buf, sizeof (buf), _("_Upload (%s):"), _ (speed_K_str));
1051        w = new_check_button (buf, TR_KEY_speed_limit_up_enabled, core);
1052        w2 = new_spin_button (TR_KEY_speed_limit_up, core, 0, INT_MAX, 5);
1053        gtk_widget_set_sensitive (GTK_WIDGET (w2), gtr_pref_flag_get (TR_KEY_speed_limit_up_enabled));
1054        g_signal_connect (w, "toggled", G_CALLBACK (target_cb), w2);
1055        hig_workarea_add_row_w (t, &row, w, w2, NULL);
1056
1057        g_snprintf (buf, sizeof (buf), _("_Download (%s):"), _ (speed_K_str));
1058        w = new_check_button (buf, TR_KEY_speed_limit_down_enabled, core);
1059        w2 = new_spin_button (TR_KEY_speed_limit_down, core, 0, INT_MAX, 5);
1060        gtk_widget_set_sensitive (GTK_WIDGET (w2), gtr_pref_flag_get (TR_KEY_speed_limit_down_enabled));
1061        g_signal_connect (w, "toggled", G_CALLBACK (target_cb), w2);
1062        hig_workarea_add_row_w (t, &row, w, w2, NULL);
1063
1064    hig_workarea_add_section_divider (t, &row);
1065    h = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, GUI_PAD);
1066    w = gtk_image_new_from_stock ("alt-speed-on", -1);
1067    gtk_box_pack_start (GTK_BOX (h), w, FALSE, FALSE, 0);
1068    g_snprintf (buf, sizeof (buf), "<b>%s</b>", _("Alternative Speed Limits"));
1069    w = gtk_label_new (buf);
1070    gtk_misc_set_alignment (GTK_MISC (w), 0.0f, 0.5f);
1071    gtk_label_set_use_markup (GTK_LABEL (w), TRUE);
1072    gtk_box_pack_start (GTK_BOX (h), w, FALSE, FALSE, 0);
1073    hig_workarea_add_section_title_widget (t, &row, h);
1074
1075        s = _("Override normal speed limits manually or at scheduled times");
1076        g_snprintf (buf, sizeof (buf), "<small>%s</small>", s);
1077        w = gtk_label_new (buf);
1078        gtk_label_set_use_markup (GTK_LABEL (w), TRUE);
1079        gtk_misc_set_alignment (GTK_MISC (w), 0.5f, 0.5f);
1080        hig_workarea_add_wide_control (t, &row, w);
1081
1082        g_snprintf (buf, sizeof (buf), _("U_pload (%s):"), _ (speed_K_str));
1083        w = new_spin_button (TR_KEY_alt_speed_up, core, 0, INT_MAX, 5);
1084        hig_workarea_add_row (t, &row, buf, w, NULL);
1085
1086        g_snprintf (buf, sizeof (buf), _("Do_wnload (%s):"), _ (speed_K_str));
1087        w = new_spin_button (TR_KEY_alt_speed_down, core, 0, INT_MAX, 5);
1088        hig_workarea_add_row (t, &row, buf, w, NULL);
1089
1090        s = _("_Scheduled times:");
1091        h = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1092        w2 = new_time_combo (core, TR_KEY_alt_speed_time_begin);
1093        page->sched_widgets = g_slist_prepend (page->sched_widgets, w2);
1094        gtk_box_pack_start (GTK_BOX (h), w2, TRUE, TRUE, 0);
1095        w2 = l = gtk_label_new_with_mnemonic (_(" _to "));
1096        page->sched_widgets = g_slist_prepend (page->sched_widgets, w2);
1097        gtk_box_pack_start (GTK_BOX (h), w2, FALSE, FALSE, 0);
1098        w2 = new_time_combo (core, TR_KEY_alt_speed_time_end);
1099        gtk_label_set_mnemonic_widget (GTK_LABEL (l), w2);
1100        page->sched_widgets = g_slist_prepend (page->sched_widgets, w2);
1101        gtk_box_pack_start (GTK_BOX (h), w2, TRUE, TRUE, 0);
1102        w = new_check_button (s, TR_KEY_alt_speed_time_enabled, core);
1103        g_signal_connect (w, "toggled", G_CALLBACK (onSchedToggled), page);
1104        hig_workarea_add_row_w (t, &row, w, h, NULL);
1105
1106        s = _("_On days:");
1107        w = new_week_combo (core, TR_KEY_alt_speed_time_day);
1108        page->sched_widgets = g_slist_prepend (page->sched_widgets, w);
1109        w = hig_workarea_add_row (t, &row, s, w, NULL);
1110        page->sched_widgets = g_slist_prepend (page->sched_widgets, w);
1111
1112    g_object_set_data_full (G_OBJECT (t), "page", page, bandwidthPageFree);
1113
1114    refreshSchedSensitivity (page);
1115    return t;
1116}
1117
1118/****
1119*****  Network Tab
1120****/
1121
1122struct network_page_data
1123{
1124    TrCore     * core;
1125    GtkWidget  * portLabel;
1126    GtkWidget  * portButton;
1127    GtkWidget  * portSpin;
1128    gulong       portTag;
1129    gulong       prefsTag;
1130};
1131
1132static void
1133onCorePrefsChanged (TrCore * core UNUSED, const tr_quark key, gpointer gdata)
1134{
1135  if (key == TR_KEY_peer_port)
1136    {
1137      struct network_page_data * data = gdata;
1138      gdk_threads_enter ();
1139      gtr_label_set_text (GTK_LABEL (data->portLabel), _("Status unknown"));
1140      gtk_widget_set_sensitive (data->portButton, TRUE);
1141      gtk_widget_set_sensitive (data->portSpin, TRUE);
1142      gdk_threads_leave ();
1143    }
1144}
1145
1146static void
1147networkPageDestroyed (gpointer gdata, GObject * dead UNUSED)
1148{
1149  struct network_page_data * data = gdata;
1150
1151  if (data->prefsTag > 0)
1152    g_signal_handler_disconnect (data->core, data->prefsTag);
1153  if (data->portTag > 0)
1154    g_signal_handler_disconnect (data->core, data->portTag);
1155
1156  g_free (data);
1157}
1158
1159static void
1160onPortTested (TrCore * core UNUSED, gboolean isOpen, gpointer vdata)
1161{
1162  struct network_page_data * data = vdata;
1163  const char * markup = isOpen ? _("Port is <b>open</b>") : _("Port is <b>closed</b>");
1164
1165  //gdk_threads_enter ();
1166  gtk_label_set_markup (GTK_LABEL (data->portLabel), markup);
1167  gtk_widget_set_sensitive (data->portButton, TRUE);
1168  gtk_widget_set_sensitive (data->portSpin, TRUE);
1169  //gdk_threads_leave ();
1170}
1171
1172static void
1173onPortTest (GtkButton * button UNUSED, gpointer vdata)
1174{
1175  struct network_page_data * data = vdata;
1176  gtk_widget_set_sensitive (data->portButton, FALSE);
1177  gtk_widget_set_sensitive (data->portSpin, FALSE);
1178  gtk_label_set_markup (GTK_LABEL (data->portLabel), _("<i>Testing TCP port
</i>"));
1179  if (!data->portTag)
1180    data->portTag = g_signal_connect (data->core, "port-tested", G_CALLBACK (onPortTested), data);
1181  gtr_core_port_test (data->core);
1182}
1183
1184static GtkWidget*
1185networkPage (GObject * core)
1186{
1187  GtkWidget * t;
1188  GtkWidget * w;
1189  GtkWidget * h;
1190  GtkWidget * l;
1191  const char * s;
1192  struct network_page_data * data;
1193  guint row = 0;
1194
1195  /* register to stop listening to core prefs changes when the page is destroyed */
1196  data = g_new0 (struct network_page_data, 1);
1197  data->core = TR_CORE (core);
1198
1199  /* build the page */
1200  t = hig_workarea_create ();
1201  hig_workarea_add_section_title (t, &row, _("Listening Port"));
1202
1203  s = _("_Port used for incoming connections:");
1204  w = data->portSpin = new_spin_button (TR_KEY_peer_port, core, 1, USHRT_MAX, 1);
1205  hig_workarea_add_row (t, &row, s, w, NULL);
1206
1207  h = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, GUI_PAD_BIG);
1208  l = data->portLabel = gtk_label_new (_("Status unknown"));
1209  gtk_misc_set_alignment (GTK_MISC (l), 0.0f, 0.5f);
1210  gtk_box_pack_start (GTK_BOX (h), l, TRUE, TRUE, 0);
1211  w = data->portButton = gtk_button_new_with_mnemonic (_("Te_st Port"));
1212  gtk_box_pack_end (GTK_BOX (h), w, FALSE, FALSE, 0);
1213  g_signal_connect (w, "clicked", G_CALLBACK (onPortTest), data);
1214  hig_workarea_add_row (t, &row, NULL, h, NULL);
1215  data->prefsTag = g_signal_connect (TR_CORE (core), "prefs-changed", G_CALLBACK (onCorePrefsChanged), data);
1216  g_object_weak_ref (G_OBJECT (t), networkPageDestroyed, data);
1217
1218  s = _("Pick a _random port every time Transmission is started");
1219  w = new_check_button (s, TR_KEY_peer_port_random_on_start, core);
1220  hig_workarea_add_wide_control (t, &row, w);
1221
1222  s = _("Use UPnP or NAT-PMP port _forwarding from my router");
1223  w = new_check_button (s, TR_KEY_port_forwarding_enabled, core);
1224  hig_workarea_add_wide_control (t, &row, w);
1225
1226  hig_workarea_add_section_divider (t, &row);
1227  hig_workarea_add_section_title (t, &row, _("Peer Limits"));
1228
1229  w = new_spin_button (TR_KEY_peer_limit_per_torrent, core, 1, FD_SETSIZE, 5);
1230  hig_workarea_add_row (t, &row, _("Maximum peers per _torrent:"), w, NULL);
1231  w = new_spin_button (TR_KEY_peer_limit_global, core, 1, FD_SETSIZE, 5);
1232  hig_workarea_add_row (t, &row, _("Maximum peers _overall:"), w, NULL);
1233
1234  hig_workarea_add_section_divider (t, &row);
1235  hig_workarea_add_section_title (t, &row, _("Options"));
1236
1237#ifdef WITH_UTP
1238  s = _("Enable _uTP for peer communication");
1239  w = new_check_button (s, TR_KEY_utp_enabled, core);
1240  s = _("uTP is a tool for reducing network congestion.");
1241  gtk_widget_set_tooltip_text (w, s);
1242  hig_workarea_add_wide_control (t, &row, w);
1243#endif
1244
1245  return t;
1246}
1247
1248/****
1249*****
1250****/
1251
1252GtkWidget *
1253gtr_prefs_dialog_new (GtkWindow * parent, GObject * core)
1254{
1255  GtkWidget * d;
1256  GtkWidget * n;
1257
1258  d = gtk_dialog_new_with_buttons (_("Transmission Preferences"),
1259                                   parent,
1260                                   GTK_DIALOG_DESTROY_WITH_PARENT,
1261                                   GTK_STOCK_HELP, GTK_RESPONSE_HELP,
1262                                   GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1263                                   NULL);
1264  gtk_window_set_role (GTK_WINDOW (d), "transmission-preferences-dialog");
1265  gtk_container_set_border_width (GTK_CONTAINER (d), GUI_PAD);
1266
1267  n = gtk_notebook_new ();
1268  gtk_container_set_border_width (GTK_CONTAINER (n), GUI_PAD);
1269
1270  gtk_notebook_append_page (GTK_NOTEBOOK (n),
1271                            torrentPage (core),
1272                            gtk_label_new (_("Torrents")));
1273  gtk_notebook_append_page (GTK_NOTEBOOK (n),
1274                            downloadPage (core),
1275                            gtk_label_new (C_("Gerund", "Downloading")));
1276    gtk_notebook_append_page (GTK_NOTEBOOK (n),
1277                              bandwidthPage (core),
1278                              gtk_label_new (_("Speed")));
1279    gtk_notebook_append_page (GTK_NOTEBOOK (n),
1280                              privacyPage (core),
1281                              gtk_label_new (_("Privacy")));
1282    gtk_notebook_append_page (GTK_NOTEBOOK (n),
1283                              networkPage (core),
1284                              gtk_label_new (_("Network")));
1285    gtk_notebook_append_page (GTK_NOTEBOOK (n),
1286                              desktopPage (core),
1287                              gtk_label_new (_("Desktop")));
1288    gtk_notebook_append_page (GTK_NOTEBOOK (n),
1289                              webPage (core),
1290                              gtk_label_new (_("Web")));
1291
1292    g_signal_connect (d, "response", G_CALLBACK (response_cb), core);
1293    gtr_dialog_set_content (GTK_DIALOG (d), n);
1294    return d;
1295}
1296
Note: See TracBrowser for help on using the repository browser.