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

Last change on this file since 14525 was 14525, checked in by mikedld, 6 years ago

Fix some issues revealed by coverity

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