pidgin/gtkprefs.c

changeset 16238
33bf2fd32108
parent 13046
99a2833aeae3
parent 16215
1f791e032df2
equal deleted inserted replaced
13071:b98e72d4089a 16238:33bf2fd32108
1 /**
2 * @file gtkprefs.c GTK+ Preferences
3 * @ingroup gtkui
4 *
5 * pidgin
6 *
7 * Pidgin is the legal property of its developers, whose names are too numerous
8 * to list here. Please refer to the COPYRIGHT file distributed with this
9 * source distribution.
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 *
25 */
26 #include "internal.h"
27 #include "pidgin.h"
28
29 #include "debug.h"
30 #include "notify.h"
31 #include "prefs.h"
32 #include "proxy.h"
33 #include "prpl.h"
34 #include "request.h"
35 #include "savedstatuses.h"
36 #include "sound.h"
37 #include "util.h"
38 #include "network.h"
39
40 #include "gtkblist.h"
41 #include "gtkconv.h"
42 #include "gtkdebug.h"
43 #include "gtkdialogs.h"
44 #include "gtkimhtml.h"
45 #include "gtkimhtmltoolbar.h"
46 #include "gtkprefs.h"
47 #include "gtksavedstatuses.h"
48 #include "gtksound.h"
49 #include "gtkthemes.h"
50 #include "gtkutils.h"
51 #include "pidginstock.h"
52
53 #define PROXYHOST 0
54 #define PROXYPORT 1
55 #define PROXYUSER 2
56 #define PROXYPASS 3
57
58 static int sound_row_sel = 0;
59 static GtkWidget *prefsnotebook;
60
61 static GtkWidget *sound_entry = NULL;
62 static GtkListStore *smiley_theme_store = NULL;
63 static GtkWidget *prefs_proxy_frame = NULL;
64
65 static GtkWidget *prefs = NULL;
66 static GtkWidget *debugbutton = NULL;
67 static int notebook_page = 0;
68 static GtkTreeRowReference *previous_smiley_row = NULL;
69
70 /*
71 * PROTOTYPES
72 */
73 static void delete_prefs(GtkWidget *, void *);
74
75 static void
76 update_spin_value(GtkWidget *w, GtkWidget *spin)
77 {
78 const char *key = g_object_get_data(G_OBJECT(spin), "val");
79 int value;
80
81 value = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
82
83 purple_prefs_set_int(key, value);
84 }
85
86 GtkWidget *
87 pidgin_prefs_labeled_spin_button(GtkWidget *box, const gchar *title,
88 const char *key, int min, int max, GtkSizeGroup *sg)
89 {
90 GtkWidget *hbox;
91 GtkWidget *label;
92 GtkWidget *spin;
93 GtkObject *adjust;
94 int val;
95
96 val = purple_prefs_get_int(key);
97
98 hbox = gtk_hbox_new(FALSE, 5);
99 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 5);
100 gtk_widget_show(hbox);
101
102 label = gtk_label_new_with_mnemonic(title);
103 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
104 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
105 gtk_widget_show(label);
106
107 adjust = gtk_adjustment_new(val, min, max, 1, 1, 1);
108 spin = gtk_spin_button_new(GTK_ADJUSTMENT(adjust), 1, 0);
109 g_object_set_data(G_OBJECT(spin), "val", (char *)key);
110 if (max < 10000)
111 gtk_widget_set_size_request(spin, 50, -1);
112 else
113 gtk_widget_set_size_request(spin, 60, -1);
114 gtk_box_pack_start(GTK_BOX(hbox), spin, FALSE, FALSE, 0);
115 g_signal_connect(G_OBJECT(adjust), "value-changed",
116 G_CALLBACK(update_spin_value), GTK_WIDGET(spin));
117 gtk_widget_show(spin);
118
119 gtk_label_set_mnemonic_widget(GTK_LABEL(label), spin);
120
121 if (sg) {
122 gtk_size_group_add_widget(sg, label);
123 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
124 }
125
126 pidgin_set_accessible_label (spin, label);
127
128 return hbox;
129 }
130
131 static void
132 entry_set(GtkEntry *entry, gpointer data) {
133 const char *key = (const char*)data;
134
135 purple_prefs_set_string(key, gtk_entry_get_text(entry));
136 }
137
138 GtkWidget *
139 pidgin_prefs_labeled_entry(GtkWidget *page, const gchar *title,
140 const char *key, GtkSizeGroup *sg)
141 {
142 GtkWidget *hbox, *label, *entry;
143 const gchar *value;
144
145 value = purple_prefs_get_string(key);
146
147 hbox = gtk_hbox_new(FALSE, 5);
148 gtk_box_pack_start(GTK_BOX(page), hbox, FALSE, FALSE, 0);
149 gtk_widget_show(hbox);
150
151 label = gtk_label_new_with_mnemonic(title);
152 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
153 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
154 gtk_widget_show(label);
155
156 entry = gtk_entry_new();
157 gtk_entry_set_text(GTK_ENTRY(entry), value);
158 gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 0);
159 g_signal_connect(G_OBJECT(entry), "changed",
160 G_CALLBACK(entry_set), (char*)key);
161 gtk_widget_show(entry);
162
163 gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
164
165 if(sg) {
166 gtk_size_group_add_widget(sg, label);
167 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
168 }
169
170 pidgin_set_accessible_label(entry, label);
171
172 return hbox;
173 }
174
175 static void
176 dropdown_set(GObject *w, const char *key)
177 {
178 const char *str_value;
179 int int_value;
180 PurplePrefType type;
181
182 type = GPOINTER_TO_INT(g_object_get_data(w, "type"));
183
184 if (type == PURPLE_PREF_INT) {
185 int_value = GPOINTER_TO_INT(g_object_get_data(w, "value"));
186
187 purple_prefs_set_int(key, int_value);
188 }
189 else if (type == PURPLE_PREF_STRING) {
190 str_value = (const char *)g_object_get_data(w, "value");
191
192 purple_prefs_set_string(key, str_value);
193 }
194 else if (type == PURPLE_PREF_BOOLEAN) {
195 purple_prefs_set_bool(key,
196 GPOINTER_TO_INT(g_object_get_data(w, "value")));
197 }
198 }
199
200 GtkWidget *
201 pidgin_prefs_dropdown_from_list(GtkWidget *box, const gchar *title,
202 PurplePrefType type, const char *key, GList *menuitems)
203 {
204 GtkWidget *dropdown, *opt, *menu;
205 GtkWidget *label = NULL;
206 GtkWidget *hbox;
207 gchar *text;
208 const char *stored_str = NULL;
209 int stored_int = 0;
210 int int_value = 0;
211 const char *str_value = NULL;
212 int o = 0;
213
214 g_return_val_if_fail(menuitems != NULL, NULL);
215
216 if (title != NULL) {
217 hbox = gtk_hbox_new(FALSE, 5);
218 /*gtk_container_add (GTK_CONTAINER (box), hbox);*/
219 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
220 gtk_widget_show(hbox);
221
222 label = gtk_label_new_with_mnemonic(title);
223 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
224 gtk_widget_show(label);
225 } else {
226 hbox = box;
227 }
228
229 #if 0 /* GTK_CHECK_VERSION(2,4,0) */
230 if(type == PURPLE_PREF_INT)
231 model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
232 else if(type == PURPLE_PREF_STRING)
233 model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
234 dropdown = gtk_combo_box_new_with_model(model);
235 #else
236 dropdown = gtk_option_menu_new();
237 menu = gtk_menu_new();
238 #endif
239
240 if (label != NULL) {
241 gtk_label_set_mnemonic_widget(GTK_LABEL(label), dropdown);
242 pidgin_set_accessible_label (dropdown, label);
243 }
244
245 if (type == PURPLE_PREF_INT)
246 stored_int = purple_prefs_get_int(key);
247 else if (type == PURPLE_PREF_STRING)
248 stored_str = purple_prefs_get_string(key);
249
250 while (menuitems != NULL && (text = (char *) menuitems->data) != NULL) {
251 menuitems = g_list_next(menuitems);
252 g_return_val_if_fail(menuitems != NULL, NULL);
253
254 opt = gtk_menu_item_new_with_label(text);
255
256 g_object_set_data(G_OBJECT(opt), "type", GINT_TO_POINTER(type));
257
258 if (type == PURPLE_PREF_INT) {
259 int_value = GPOINTER_TO_INT(menuitems->data);
260 g_object_set_data(G_OBJECT(opt), "value",
261 GINT_TO_POINTER(int_value));
262 }
263 else if (type == PURPLE_PREF_STRING) {
264 str_value = (const char *)menuitems->data;
265
266 g_object_set_data(G_OBJECT(opt), "value", (char *)str_value);
267 }
268 else if (type == PURPLE_PREF_BOOLEAN) {
269 g_object_set_data(G_OBJECT(opt), "value",
270 menuitems->data);
271 }
272
273 g_signal_connect(G_OBJECT(opt), "activate",
274 G_CALLBACK(dropdown_set), (char *)key);
275
276 gtk_widget_show(opt);
277 gtk_menu_shell_append(GTK_MENU_SHELL(menu), opt);
278
279 if ((type == PURPLE_PREF_INT && stored_int == int_value) ||
280 (type == PURPLE_PREF_STRING && stored_str != NULL &&
281 !strcmp(stored_str, str_value)) ||
282 (type == PURPLE_PREF_BOOLEAN &&
283 (purple_prefs_get_bool(key) == GPOINTER_TO_INT(menuitems->data)))) {
284
285 gtk_menu_set_active(GTK_MENU(menu), o);
286 }
287
288 menuitems = g_list_next(menuitems);
289
290 o++;
291 }
292
293 gtk_option_menu_set_menu(GTK_OPTION_MENU(dropdown), menu);
294 gtk_box_pack_start(GTK_BOX(hbox), dropdown, FALSE, FALSE, 0);
295 gtk_widget_show(dropdown);
296
297 return label;
298 }
299
300 GtkWidget *
301 pidgin_prefs_dropdown(GtkWidget *box, const gchar *title, PurplePrefType type,
302 const char *key, ...)
303 {
304 va_list ap;
305 GList *menuitems = NULL;
306 GtkWidget *dropdown = NULL;
307 char *name;
308 int int_value;
309 const char *str_value;
310
311 g_return_val_if_fail(type == PURPLE_PREF_BOOLEAN || type == PURPLE_PREF_INT ||
312 type == PURPLE_PREF_STRING, NULL);
313
314 va_start(ap, key);
315 while ((name = va_arg(ap, char *)) != NULL) {
316
317 menuitems = g_list_prepend(menuitems, name);
318
319 if (type == PURPLE_PREF_INT || type == PURPLE_PREF_BOOLEAN) {
320 int_value = va_arg(ap, int);
321 menuitems = g_list_prepend(menuitems, GINT_TO_POINTER(int_value));
322 }
323 else {
324 str_value = va_arg(ap, const char *);
325 menuitems = g_list_prepend(menuitems, (char *)str_value);
326 }
327 }
328 va_end(ap);
329
330 g_return_val_if_fail(menuitems != NULL, NULL);
331
332 menuitems = g_list_reverse(menuitems);
333
334 dropdown = pidgin_prefs_dropdown_from_list(box, title, type, key,
335 menuitems);
336
337 g_list_free(menuitems);
338
339 return dropdown;
340 }
341
342 static void
343 delete_prefs(GtkWidget *asdf, void *gdsa)
344 {
345 /* Close any "select sound" request dialogs */
346 purple_request_close_with_handle(prefs);
347
348 /* Unregister callbacks. */
349 purple_prefs_disconnect_by_handle(prefs);
350
351 prefs = NULL;
352 sound_entry = NULL;
353 debugbutton = NULL;
354 notebook_page = 0;
355 smiley_theme_store = NULL;
356 if (previous_smiley_row)
357 gtk_tree_row_reference_free(previous_smiley_row);
358 previous_smiley_row = NULL;
359
360 }
361
362 static void smiley_sel(GtkTreeSelection *sel, GtkTreeModel *model) {
363 GtkTreeIter iter;
364 const char *themename;
365 char *description;
366 GValue val;
367 GtkTreePath *path, *oldpath;
368 struct smiley_theme *new_theme, *old_theme;
369
370 if (!gtk_tree_selection_get_selected(sel, &model, &iter))
371 return;
372
373 old_theme = current_smiley_theme;
374 val.g_type = 0;
375 gtk_tree_model_get_value(model, &iter, 3, &val);
376 path = gtk_tree_model_get_path(model, &iter);
377 themename = g_value_get_string(&val);
378 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/smileys/theme", themename);
379 g_value_unset (&val);
380
381 /* current_smiley_theme is set in callback for the above pref change */
382 new_theme = current_smiley_theme;
383 description = g_strdup_printf("<span size='larger' weight='bold'>%s</span> - %s\n"
384 "<span size='smaller' foreground='white'>%s</span>",
385 new_theme->name, new_theme->author, new_theme->desc);
386 gtk_list_store_set(smiley_theme_store, &iter, 1, description, -1);
387 g_free(description);
388
389 if (new_theme != old_theme && previous_smiley_row) {
390 oldpath = gtk_tree_row_reference_get_path(previous_smiley_row);
391 if (gtk_tree_model_get_iter(model, &iter, oldpath)) {
392 description = g_strdup_printf("<span size='larger' weight='bold'>%s</span> - %s\n"
393 "<span size='smaller' foreground='dim grey'>%s</span>",
394 old_theme->name, old_theme->author, old_theme->desc);
395 gtk_list_store_set(smiley_theme_store, &iter, 1,
396 description, -1);
397 g_free(description);
398 }
399 gtk_tree_path_free(oldpath);
400 }
401 if (previous_smiley_row)
402 gtk_tree_row_reference_free(previous_smiley_row);
403 previous_smiley_row = gtk_tree_row_reference_new(model, path);
404 gtk_tree_path_free(path);
405 }
406
407 static GtkTreeRowReference *theme_refresh_theme_list()
408 {
409 GdkPixbuf *pixbuf;
410 GSList *themes;
411 GtkTreeIter iter;
412 GtkTreeRowReference *row_ref = NULL;
413
414 if (previous_smiley_row)
415 gtk_tree_row_reference_free(previous_smiley_row);
416 previous_smiley_row = NULL;
417
418 pidginthemes_smiley_theme_probe();
419
420 if (!(themes = smiley_themes))
421 return NULL;
422
423 gtk_list_store_clear(smiley_theme_store);
424
425 while (themes) {
426 struct smiley_theme *theme = themes->data;
427 char *description = g_strdup_printf("<span size='larger' weight='bold'>%s</span> - %s\n"
428 "<span size='smaller' foreground='dim grey'>%s</span>",
429 theme->name, theme->author, theme->desc);
430 gtk_list_store_append (smiley_theme_store, &iter);
431
432 /*
433 * LEAK - Gentoo memprof thinks pixbuf is leaking here... but it
434 * looks like it should be ok to me. Anyone know what's up? --Mark
435 */
436 pixbuf = (theme->icon ? gdk_pixbuf_new_from_file(theme->icon, NULL) : NULL);
437
438 gtk_list_store_set(smiley_theme_store, &iter,
439 0, pixbuf,
440 1, description,
441 2, theme->path,
442 3, theme->name,
443 -1);
444
445 if (pixbuf != NULL)
446 g_object_unref(G_OBJECT(pixbuf));
447
448 g_free(description);
449 themes = themes->next;
450
451 /* If this is the currently selected theme,
452 * we will need to select it. Grab the row reference. */
453 if (theme == current_smiley_theme) {
454 GtkTreePath *path = gtk_tree_model_get_path(
455 GTK_TREE_MODEL(smiley_theme_store), &iter);
456 row_ref = gtk_tree_row_reference_new(
457 GTK_TREE_MODEL(smiley_theme_store), path);
458 gtk_tree_path_free(path);
459 }
460 }
461
462 return row_ref;
463 }
464
465 static void theme_install_theme(char *path, char *extn) {
466 #ifndef _WIN32
467 gchar *command;
468 #endif
469 gchar *destdir;
470 gchar *tail;
471 GtkTreeRowReference *theme_rowref;
472
473 /* Just to be safe */
474 g_strchomp(path);
475
476 /* I dont know what you are, get out of here */
477 if (extn != NULL)
478 tail = extn;
479 else if ((tail = strrchr(path, '.')) == NULL)
480 return;
481
482 destdir = g_strconcat(purple_user_dir(), G_DIR_SEPARATOR_S "smileys", NULL);
483
484 /* We'll check this just to make sure. This also lets us do something different on
485 * other platforms, if need be */
486 if (!g_ascii_strcasecmp(tail, ".gz") || !g_ascii_strcasecmp(tail, ".tgz")) {
487 #ifndef _WIN32
488 gchar *path_escaped = g_shell_quote(path);
489 gchar *destdir_escaped = g_shell_quote(destdir);
490 command = g_strdup_printf("tar > /dev/null xzf %s -C %s", path_escaped, destdir_escaped);
491 g_free(path_escaped);
492 g_free(destdir_escaped);
493 #else
494 if(!winpidgin_gz_untar(path, destdir)) {
495 g_free(destdir);
496 return;
497 }
498 #endif
499 }
500 else {
501 g_free(destdir);
502 return;
503 }
504
505 #ifndef _WIN32
506 /* Fire! */
507 if (system(command))
508 {
509 purple_notify_error(NULL, NULL, _("Smiley theme failed to unpack."), NULL);
510 }
511
512 g_free(command);
513 #endif
514 g_free(destdir);
515
516 theme_rowref = theme_refresh_theme_list();
517 if (theme_rowref != NULL)
518 gtk_tree_row_reference_free(theme_rowref);
519 }
520
521 static void
522 theme_got_url(PurpleUtilFetchUrlData *url_data, gpointer user_data,
523 const gchar *themedata, size_t len, const gchar *error_message)
524 {
525 FILE *f;
526 gchar *path;
527
528 if ((error_message != NULL) || (len == 0))
529 return;
530
531 f = purple_mkstemp(&path, TRUE);
532 fwrite(themedata, len, 1, f);
533 fclose(f);
534
535 theme_install_theme(path, user_data);
536
537 g_unlink(path);
538 g_free(path);
539 }
540
541 static void
542 theme_dnd_recv(GtkWidget *widget, GdkDragContext *dc, guint x, guint y,
543 GtkSelectionData *sd, guint info, guint t, gpointer data)
544 {
545 gchar *name = (gchar *)sd->data;
546
547 if ((sd->length >= 0) && (sd->format == 8)) {
548 /* Well, it looks like the drag event was cool.
549 * Let's do something with it */
550
551 if (!g_ascii_strncasecmp(name, "file://", 7)) {
552 GError *converr = NULL;
553 gchar *tmp;
554 /* It looks like we're dealing with a local file. Let's
555 * just untar it in the right place */
556 if(!(tmp = g_filename_from_uri(name, NULL, &converr))) {
557 purple_debug(PURPLE_DEBUG_ERROR, "theme dnd", "%s\n",
558 (converr ? converr->message :
559 "g_filename_from_uri error"));
560 return;
561 }
562 theme_install_theme(tmp, NULL);
563 g_free(tmp);
564 } else if (!g_ascii_strncasecmp(name, "http://", 7)) {
565 /* Oo, a web drag and drop. This is where things
566 * will start to get interesting */
567 purple_util_fetch_url(name, TRUE, NULL, FALSE, theme_got_url, ".tgz");
568 } else if (!g_ascii_strncasecmp(name, "https://", 8)) {
569 /* purple_util_fetch_url() doesn't support HTTPS, but we want users
570 * to be able to drag and drop links from the SF trackers, so
571 * we'll try it as an HTTP URL. */
572 char *tmp = g_strdup(name + 1);
573 tmp[0] = 'h';
574 tmp[1] = 't';
575 tmp[2] = 't';
576 tmp[3] = 'p';
577 purple_util_fetch_url(tmp, TRUE, NULL, FALSE, theme_got_url, ".tgz");
578 g_free(tmp);
579 }
580
581 gtk_drag_finish(dc, TRUE, FALSE, t);
582 }
583
584 gtk_drag_finish(dc, FALSE, FALSE, t);
585 }
586
587 /* Does same as normal sort, except "none" is sorted first */
588 static gint pidgin_sort_smileys (GtkTreeModel *model,
589 GtkTreeIter *a,
590 GtkTreeIter *b,
591 gpointer userdata)
592 {
593 gint ret = 0;
594 gchar *name1 = NULL, *name2 = NULL;
595
596 gtk_tree_model_get(model, a, 3, &name1, -1);
597 gtk_tree_model_get(model, b, 3, &name2, -1);
598
599 if (name1 == NULL || name2 == NULL) {
600 if (!(name1 == NULL && name2 == NULL))
601 ret = (name1 == NULL) ? -1: 1;
602 } else if (!g_ascii_strcasecmp(name1, "none")) {
603 if (!g_utf8_collate(name1, name2))
604 ret = 0;
605 else
606 /* Sort name1 first */
607 ret = -1;
608 } else if (!g_ascii_strcasecmp(name2, "none")) {
609 /* Sort name2 first */
610 ret = 1;
611 } else {
612 /* Neither string is "none", default to normal sort */
613 ret = purple_utf8_strcasecmp(name1,name2);
614 }
615
616 g_free(name1);
617 g_free(name2);
618
619 return ret;
620 }
621
622 static GtkWidget *
623 theme_page()
624 {
625 GtkWidget *ret;
626 GtkWidget *sw;
627 GtkWidget *view;
628 GtkCellRenderer *rend;
629 GtkTreeViewColumn *col;
630 GtkTreeSelection *sel;
631 GtkTreeRowReference *rowref;
632 GtkWidget *label;
633 GtkTargetEntry te[3] = {{"text/plain", 0, 0},{"text/uri-list", 0, 1},{"STRING", 0, 2}};
634
635 ret = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
636 gtk_container_set_border_width (GTK_CONTAINER (ret), PIDGIN_HIG_BORDER);
637
638 label = gtk_label_new(_("Select a smiley theme that you would like to use from the list below. New themes can be installed by dragging and dropping them onto the theme list."));
639
640 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
641 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
642 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
643
644 gtk_box_pack_start(GTK_BOX(ret), label, FALSE, TRUE, 0);
645 gtk_widget_show(label);
646
647 sw = gtk_scrolled_window_new(NULL,NULL);
648 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
649 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_IN);
650
651 gtk_box_pack_start(GTK_BOX(ret), sw, TRUE, TRUE, 0);
652 smiley_theme_store = gtk_list_store_new (4, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
653
654 rowref = theme_refresh_theme_list();
655
656 view = gtk_tree_view_new_with_model (GTK_TREE_MODEL(smiley_theme_store));
657
658 gtk_drag_dest_set(view, GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP, te,
659 sizeof(te) / sizeof(GtkTargetEntry) , GDK_ACTION_COPY | GDK_ACTION_MOVE);
660
661 g_signal_connect(G_OBJECT(view), "drag_data_received", G_CALLBACK(theme_dnd_recv), smiley_theme_store);
662
663 rend = gtk_cell_renderer_pixbuf_new();
664 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
665
666 /* Custom sort so "none" theme is at top of list */
667 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(smiley_theme_store),
668 3, pidgin_sort_smileys, NULL, NULL);
669
670 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(smiley_theme_store),
671 3, GTK_SORT_ASCENDING);
672
673 col = gtk_tree_view_column_new_with_attributes (_("Icon"),
674 rend,
675 "pixbuf", 0,
676 NULL);
677 gtk_tree_view_append_column (GTK_TREE_VIEW(view), col);
678
679 rend = gtk_cell_renderer_text_new();
680 col = gtk_tree_view_column_new_with_attributes (_("Description"),
681 rend,
682 "markup", 1,
683 NULL);
684 gtk_tree_view_append_column (GTK_TREE_VIEW(view), col);
685 g_object_unref(G_OBJECT(smiley_theme_store));
686 gtk_container_add(GTK_CONTAINER(sw), view);
687
688 g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(smiley_sel), NULL);
689
690 if (rowref) {
691 GtkTreePath *path = gtk_tree_row_reference_get_path(rowref);
692 gtk_tree_row_reference_free(rowref);
693 gtk_tree_selection_select_path(sel, path);
694 gtk_tree_path_free(path);
695 }
696
697 gtk_widget_show_all(ret);
698
699 pidgin_set_accessible_label (view, label);
700
701 return ret;
702 }
703
704 static void
705 formatting_toggle_cb(GtkIMHtml *imhtml, GtkIMHtmlButtons buttons, void *toolbar)
706 {
707 gboolean bold, italic, uline;
708
709 gtk_imhtml_get_current_format(GTK_IMHTML(imhtml),
710 &bold, &italic, &uline);
711
712 if (buttons & GTK_IMHTML_BOLD)
713 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/conversations/send_bold", bold);
714 if (buttons & GTK_IMHTML_ITALIC)
715 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/conversations/send_italic", italic);
716 if (buttons & GTK_IMHTML_UNDERLINE)
717 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/conversations/send_underline", uline);
718
719 if (buttons & GTK_IMHTML_GROW || buttons & GTK_IMHTML_SHRINK)
720 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/font_size",
721 gtk_imhtml_get_current_fontsize(GTK_IMHTML(imhtml)));
722 if (buttons & GTK_IMHTML_FACE) {
723 char *face = gtk_imhtml_get_current_fontface(GTK_IMHTML(imhtml));
724 if (!face)
725 face = g_strdup("");
726
727 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/conversations/font_face", face);
728 g_free(face);
729 }
730
731 if (buttons & GTK_IMHTML_FORECOLOR) {
732 char *color = gtk_imhtml_get_current_forecolor(GTK_IMHTML(imhtml));
733 if (!color)
734 color = g_strdup("");
735
736 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/conversations/fgcolor", color);
737 g_free(color);
738 }
739
740 if (buttons & GTK_IMHTML_BACKCOLOR) {
741 char *color;
742 GObject *object;
743
744 color = gtk_imhtml_get_current_backcolor(GTK_IMHTML(imhtml));
745 if (!color)
746 color = g_strdup("");
747
748 /* Block the signal to prevent a loop. */
749 object = g_object_ref(G_OBJECT(imhtml));
750 g_signal_handlers_block_matched(object, G_SIGNAL_MATCH_DATA, 0, 0, NULL,
751 NULL, toolbar);
752 /* Clear the backcolor. */
753 gtk_imhtml_toggle_backcolor(GTK_IMHTML(imhtml), "");
754 /* Unblock the signal. */
755 g_signal_handlers_unblock_matched(object, G_SIGNAL_MATCH_DATA, 0, 0, NULL,
756 NULL, toolbar);
757 g_object_unref(object);
758
759 /* This will fire a toggle signal and get saved below. */
760 gtk_imhtml_toggle_background(GTK_IMHTML(imhtml), color);
761
762 g_free(color);
763 }
764
765 if (buttons & GTK_IMHTML_BACKGROUND) {
766 char *color = gtk_imhtml_get_current_background(GTK_IMHTML(imhtml));
767 if (!color)
768 color = g_strdup("");
769
770 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/conversations/bgcolor", color);
771 g_free(color);
772 }
773 }
774
775 static void
776 formatting_clear_cb(GtkIMHtml *imhtml, void *data)
777 {
778 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/conversations/send_bold", FALSE);
779 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/conversations/send_italic", FALSE);
780 purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/conversations/send_underline", FALSE);
781
782 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/conversations/font_size", 3);
783
784 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/conversations/font_face", "");
785 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/conversations/fgcolor", "");
786 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/conversations/bgcolor", "");
787 }
788
789 static void
790 conversation_usetabs_cb(const char *name, PurplePrefType type,
791 gconstpointer value, gpointer data)
792 {
793 gboolean usetabs = GPOINTER_TO_INT(value);
794
795 if (usetabs)
796 gtk_widget_set_sensitive(GTK_WIDGET(data), TRUE);
797 else
798 gtk_widget_set_sensitive(GTK_WIDGET(data), FALSE);
799 }
800
801 static GtkWidget *
802 interface_page()
803 {
804 GtkWidget *ret;
805 GtkWidget *vbox;
806 GtkWidget *vbox2;
807 GtkWidget *label;
808 GtkSizeGroup *sg;
809 GList *names = NULL;
810
811 ret = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
812 gtk_container_set_border_width(GTK_CONTAINER(ret), PIDGIN_HIG_BORDER);
813
814 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
815
816 vbox = pidgin_make_frame(ret, _("System Tray Icon"));
817 label = pidgin_prefs_dropdown(vbox, _("_Show system tray icon:"), PURPLE_PREF_STRING,
818 PIDGIN_PREFS_ROOT "/docklet/show",
819 _("Always"), "always",
820 _("Never"), "never",
821 _("On unread messages"), "pending",
822 NULL);
823 gtk_size_group_add_widget(sg, label);
824 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
825
826 vbox = pidgin_make_frame(ret, _("Conversation Window Hiding"));
827 label = pidgin_prefs_dropdown(vbox, _("_Hide new IM conversations:"),
828 PURPLE_PREF_STRING, PIDGIN_PREFS_ROOT "/conversations/im/hide_new",
829 _("Never"), "never",
830 _("When away"), "away",
831 _("Always"), "always",
832 NULL);
833 gtk_size_group_add_widget(sg, label);
834 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
835
836
837 /* All the tab options! */
838 vbox = pidgin_make_frame(ret, _("Tabs"));
839
840 pidgin_prefs_checkbox(_("Show IMs and chats in _tabbed windows"),
841 PIDGIN_PREFS_ROOT "/conversations/tabs", vbox);
842
843 /*
844 * Connect a signal to the above preference. When conversations are not
845 * shown in a tabbed window then all tabbing options should be disabled.
846 */
847 vbox2 = gtk_vbox_new(FALSE, 9);
848 gtk_box_pack_start(GTK_BOX(vbox), vbox2, FALSE, FALSE, 0);
849 purple_prefs_connect_callback(prefs, PIDGIN_PREFS_ROOT "/conversations/tabs",
850 conversation_usetabs_cb, vbox2);
851 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/tabs"))
852 gtk_widget_set_sensitive(vbox2, FALSE);
853
854 pidgin_prefs_checkbox(_("Show close b_utton on tabs"),
855 PIDGIN_PREFS_ROOT "/conversations/close_on_tabs", vbox2);
856
857 label = pidgin_prefs_dropdown(vbox2, _("_Placement:"), PURPLE_PREF_INT,
858 PIDGIN_PREFS_ROOT "/conversations/tab_side",
859 _("Top"), GTK_POS_TOP,
860 _("Bottom"), GTK_POS_BOTTOM,
861 _("Left"), GTK_POS_LEFT,
862 _("Right"), GTK_POS_RIGHT,
863 #if GTK_CHECK_VERSION(2,6,0)
864 _("Left Vertical"), GTK_POS_LEFT|8,
865 _("Right Vertical"), GTK_POS_RIGHT|8,
866 #endif
867 NULL);
868 gtk_size_group_add_widget(sg, label);
869 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
870
871 names = pidgin_conv_placement_get_options();
872 label = pidgin_prefs_dropdown_from_list(vbox2, _("N_ew conversations:"),
873 PURPLE_PREF_STRING, PIDGIN_PREFS_ROOT "/conversations/placement", names);
874 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
875
876 gtk_size_group_add_widget(sg, label);
877
878 g_list_free(names);
879
880 gtk_widget_show_all(ret);
881 return ret;
882 }
883
884 static GtkWidget *
885 conv_page()
886 {
887 GtkWidget *ret;
888 GtkWidget *vbox;
889 GtkWidget *toolbar;
890 GtkWidget *iconpref1;
891 GtkWidget *iconpref2;
892 GtkWidget *imhtml;
893 GtkWidget *frame;
894
895 ret = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
896 gtk_container_set_border_width(GTK_CONTAINER(ret), PIDGIN_HIG_BORDER);
897
898 vbox = pidgin_make_frame(ret, _("Conversations"));
899
900 pidgin_prefs_checkbox(_("Show _formatting on incoming messages"),
901 PIDGIN_PREFS_ROOT "/conversations/show_incoming_formatting", vbox);
902
903 iconpref1 = pidgin_prefs_checkbox(_("Show buddy _icons"),
904 PIDGIN_PREFS_ROOT "/conversations/im/show_buddy_icons", vbox);
905 iconpref2 = pidgin_prefs_checkbox(_("Enable buddy ic_on animation"),
906 PIDGIN_PREFS_ROOT "/conversations/im/animate_buddy_icons", vbox);
907 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/im/show_buddy_icons"))
908 gtk_widget_set_sensitive(iconpref2, FALSE);
909 g_signal_connect(G_OBJECT(iconpref1), "clicked",
910 G_CALLBACK(pidgin_toggle_sensitive), iconpref2);
911
912 pidgin_prefs_checkbox(_("_Notify buddies that you are typing to them"),
913 "/core/conversations/im/send_typing", vbox);
914 #ifdef USE_GTKSPELL
915 pidgin_prefs_checkbox(_("Highlight _misspelled words"),
916 PIDGIN_PREFS_ROOT "/conversations/spellcheck", vbox);
917 #endif
918
919 pidgin_prefs_checkbox(_("Use smooth-scrolling"), PIDGIN_PREFS_ROOT "/conversations/use_smooth_scrolling", vbox);
920
921 #ifdef _WIN32
922 pidgin_prefs_checkbox(_("F_lash window when IMs are received"), PIDGIN_PREFS_ROOT "/win32/blink_im", vbox);
923 #endif
924
925 vbox = pidgin_make_frame(ret, _("Default Formatting"));
926
927 frame = pidgin_create_imhtml(TRUE, &imhtml, &toolbar, NULL);
928 gtk_widget_set_name(imhtml, "pidginprefs_font_imhtml");
929 gtk_imhtml_set_whole_buffer_formatting_only(GTK_IMHTML(imhtml), TRUE);
930 gtk_imhtml_set_format_functions(GTK_IMHTML(imhtml),
931 GTK_IMHTML_BOLD |
932 GTK_IMHTML_ITALIC |
933 GTK_IMHTML_UNDERLINE |
934 GTK_IMHTML_GROW |
935 GTK_IMHTML_SHRINK |
936 GTK_IMHTML_FACE |
937 GTK_IMHTML_FORECOLOR |
938 GTK_IMHTML_BACKCOLOR |
939 GTK_IMHTML_BACKGROUND);
940
941 gtk_imhtml_append_text(GTK_IMHTML(imhtml), _("This is how your outgoing message text will appear when you use protocols that support formatting. :)"), 0);
942
943 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 0);
944
945 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_bold"))
946 gtk_imhtml_toggle_bold(GTK_IMHTML(imhtml));
947 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_italic"))
948 gtk_imhtml_toggle_italic(GTK_IMHTML(imhtml));
949 if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_underline"))
950 gtk_imhtml_toggle_underline(GTK_IMHTML(imhtml));
951
952 gtk_imhtml_font_set_size(GTK_IMHTML(imhtml), purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/font_size"));
953 gtk_imhtml_toggle_forecolor(GTK_IMHTML(imhtml), purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/fgcolor"));
954 gtk_imhtml_toggle_background(GTK_IMHTML(imhtml), purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/bgcolor"));
955 gtk_imhtml_toggle_fontface(GTK_IMHTML(imhtml), purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/font_face"));
956
957 g_signal_connect_after(G_OBJECT(imhtml), "format_function_toggle",
958 G_CALLBACK(formatting_toggle_cb), toolbar);
959 g_signal_connect_after(G_OBJECT(imhtml), "format_function_clear",
960 G_CALLBACK(formatting_clear_cb), NULL);
961
962
963 gtk_widget_show_all(ret);
964
965 return ret;
966 }
967
968 static void network_ip_changed(GtkEntry *entry, gpointer data)
969 {
970 /*
971 * TODO: It would be nice if we could validate this and show a
972 * red background in the box when the IP address is invalid
973 * and a green background when the IP address is valid.
974 */
975 purple_network_set_public_ip(gtk_entry_get_text(entry));
976 }
977
978 static void
979 proxy_changed_cb(const char *name, PurplePrefType type,
980 gconstpointer value, gpointer data)
981 {
982 GtkWidget *frame = data;
983 const char *proxy = value;
984
985 if (strcmp(proxy, "none") && strcmp(proxy, "envvar"))
986 gtk_widget_show_all(frame);
987 else
988 gtk_widget_hide(frame);
989 }
990
991 static void proxy_print_option(GtkEntry *entry, int entrynum)
992 {
993 if (entrynum == PROXYHOST)
994 purple_prefs_set_string("/core/proxy/host", gtk_entry_get_text(entry));
995 else if (entrynum == PROXYPORT)
996 purple_prefs_set_int("/core/proxy/port", atoi(gtk_entry_get_text(entry)));
997 else if (entrynum == PROXYUSER)
998 purple_prefs_set_string("/core/proxy/username", gtk_entry_get_text(entry));
999 else if (entrynum == PROXYPASS)
1000 purple_prefs_set_string("/core/proxy/password", gtk_entry_get_text(entry));
1001 }
1002
1003 static GtkWidget *
1004 network_page()
1005 {
1006 GtkWidget *ret;
1007 GtkWidget *vbox, *hbox, *entry;
1008 GtkWidget *table, *label, *auto_ip_checkbox, *ports_checkbox, *spin_button;
1009 GtkSizeGroup *sg;
1010 PurpleProxyInfo *proxy_info = NULL;
1011
1012 ret = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
1013 gtk_container_set_border_width (GTK_CONTAINER (ret), PIDGIN_HIG_BORDER);
1014
1015 vbox = pidgin_make_frame (ret, _("IP Address"));
1016 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1017 pidgin_prefs_labeled_entry(vbox,_("ST_UN server:"),
1018 "/core/network/stun_server", sg);
1019
1020 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
1021 gtk_container_add(GTK_CONTAINER(vbox), hbox);
1022
1023 label = gtk_label_new(NULL);
1024 gtk_container_add(GTK_CONTAINER(hbox), label);
1025 gtk_size_group_add_widget(sg, label);
1026
1027 label = gtk_label_new(NULL);
1028 gtk_label_set_markup(GTK_LABEL(label),
1029 _("<span style=\"italic\">Example: stunserver.org</span>"));
1030 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
1031 gtk_container_add(GTK_CONTAINER(hbox), label);
1032
1033 auto_ip_checkbox = pidgin_prefs_checkbox(_("_Autodetect IP address"),
1034 "/core/network/auto_ip", vbox);
1035
1036 table = gtk_table_new(2, 2, FALSE);
1037 gtk_container_set_border_width(GTK_CONTAINER(table), 0);
1038 gtk_table_set_col_spacings(GTK_TABLE(table), 5);
1039 gtk_table_set_row_spacings(GTK_TABLE(table), 10);
1040 gtk_container_add(GTK_CONTAINER(vbox), table);
1041
1042 label = gtk_label_new_with_mnemonic(_("Public _IP:"));
1043 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
1044 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
1045 gtk_size_group_add_widget(sg, label);
1046
1047 entry = gtk_entry_new();
1048 gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
1049 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 0, 1, GTK_FILL, 0, 0, 0);
1050 g_signal_connect(G_OBJECT(entry), "changed",
1051 G_CALLBACK(network_ip_changed), NULL);
1052
1053 /*
1054 * TODO: This could be better by showing the autodeteced
1055 * IP separately from the user-specified IP.
1056 */
1057 if (purple_network_get_my_ip(-1) != NULL)
1058 gtk_entry_set_text(GTK_ENTRY(entry),
1059 purple_network_get_my_ip(-1));
1060
1061 pidgin_set_accessible_label (entry, label);
1062
1063
1064 if (purple_prefs_get_bool("/core/network/auto_ip")) {
1065 gtk_widget_set_sensitive(GTK_WIDGET(table), FALSE);
1066 }
1067
1068 g_signal_connect(G_OBJECT(auto_ip_checkbox), "clicked",
1069 G_CALLBACK(pidgin_toggle_sensitive), table);
1070
1071 vbox = pidgin_make_frame (ret, _("Ports"));
1072 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1073
1074 ports_checkbox = pidgin_prefs_checkbox(_("_Manually specify range of ports to listen on"),
1075 "/core/network/ports_range_use", vbox);
1076
1077 spin_button = pidgin_prefs_labeled_spin_button(vbox, _("_Start port:"),
1078 "/core/network/ports_range_start", 0, 65535, sg);
1079 if (!purple_prefs_get_bool("/core/network/ports_range_use"))
1080 gtk_widget_set_sensitive(GTK_WIDGET(spin_button), FALSE);
1081 g_signal_connect(G_OBJECT(ports_checkbox), "clicked",
1082 G_CALLBACK(pidgin_toggle_sensitive), spin_button);
1083
1084 spin_button = pidgin_prefs_labeled_spin_button(vbox, _("_End port:"),
1085 "/core/network/ports_range_end", 0, 65535, sg);
1086 if (!purple_prefs_get_bool("/core/network/ports_range_use"))
1087 gtk_widget_set_sensitive(GTK_WIDGET(spin_button), FALSE);
1088 g_signal_connect(G_OBJECT(ports_checkbox), "clicked",
1089 G_CALLBACK(pidgin_toggle_sensitive), spin_button);
1090
1091 if (!purple_running_gnome()) {
1092 vbox = pidgin_make_frame(ret, _("Proxy Server"));
1093 prefs_proxy_frame = gtk_vbox_new(FALSE, 0);
1094 pidgin_prefs_dropdown(vbox, _("Proxy _type:"), PURPLE_PREF_STRING,
1095 "/core/proxy/type",
1096 _("No proxy"), "none",
1097 "SOCKS 4", "socks4",
1098 "SOCKS 5", "socks5",
1099 "HTTP", "http",
1100 _("Use Environmental Settings"), "envvar",
1101 NULL);
1102 gtk_box_pack_start(GTK_BOX(vbox), prefs_proxy_frame, 0, 0, 0);
1103 proxy_info = purple_global_proxy_get_info();
1104
1105 purple_prefs_connect_callback(prefs, "/core/proxy/type",
1106 proxy_changed_cb, prefs_proxy_frame);
1107
1108 table = gtk_table_new(4, 2, FALSE);
1109 gtk_container_set_border_width(GTK_CONTAINER(table), 0);
1110 gtk_table_set_col_spacings(GTK_TABLE(table), 5);
1111 gtk_table_set_row_spacings(GTK_TABLE(table), 10);
1112 gtk_container_add(GTK_CONTAINER(prefs_proxy_frame), table);
1113
1114
1115 label = gtk_label_new_with_mnemonic(_("_Host:"));
1116 gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
1117 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
1118
1119 entry = gtk_entry_new();
1120 gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
1121 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 0, 1, GTK_FILL, 0, 0, 0);
1122 g_signal_connect(G_OBJECT(entry), "changed",
1123 G_CALLBACK(proxy_print_option), (void *)PROXYHOST);
1124
1125 if (proxy_info != NULL && purple_proxy_info_get_host(proxy_info))
1126 gtk_entry_set_text(GTK_ENTRY(entry),
1127 purple_proxy_info_get_host(proxy_info));
1128
1129 hbox = gtk_hbox_new(TRUE, 5);
1130 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
1131 pidgin_set_accessible_label (entry, label);
1132
1133 label = gtk_label_new_with_mnemonic(_("_Port:"));
1134 gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
1135 gtk_table_attach(GTK_TABLE(table), label, 2, 3, 0, 1, GTK_FILL, 0, 0, 0);
1136
1137 entry = gtk_entry_new();
1138 gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
1139 gtk_table_attach(GTK_TABLE(table), entry, 3, 4, 0, 1, GTK_FILL, 0, 0, 0);
1140 g_signal_connect(G_OBJECT(entry), "changed",
1141 G_CALLBACK(proxy_print_option), (void *)PROXYPORT);
1142
1143 if (proxy_info != NULL && purple_proxy_info_get_port(proxy_info) != 0) {
1144 char buf[128];
1145 g_snprintf(buf, sizeof(buf), "%d",
1146 purple_proxy_info_get_port(proxy_info));
1147
1148 gtk_entry_set_text(GTK_ENTRY(entry), buf);
1149 }
1150 pidgin_set_accessible_label (entry, label);
1151
1152 label = gtk_label_new_with_mnemonic(_("_User:"));
1153 gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
1154 gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
1155
1156 entry = gtk_entry_new();
1157 gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
1158 gtk_table_attach(GTK_TABLE(table), entry, 1, 2, 1, 2, GTK_FILL, 0, 0, 0);
1159 g_signal_connect(G_OBJECT(entry), "changed",
1160 G_CALLBACK(proxy_print_option), (void *)PROXYUSER);
1161
1162 if (proxy_info != NULL && purple_proxy_info_get_username(proxy_info) != NULL)
1163 gtk_entry_set_text(GTK_ENTRY(entry),
1164 purple_proxy_info_get_username(proxy_info));
1165
1166 hbox = gtk_hbox_new(TRUE, 5);
1167 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
1168 pidgin_set_accessible_label (entry, label);
1169
1170 label = gtk_label_new_with_mnemonic(_("Pa_ssword:"));
1171 gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
1172 gtk_table_attach(GTK_TABLE(table), label, 2, 3, 1, 2, GTK_FILL, 0, 0, 0);
1173
1174 entry = gtk_entry_new();
1175 gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
1176 gtk_table_attach(GTK_TABLE(table), entry, 3, 4, 1, 2, GTK_FILL , 0, 0, 0);
1177 gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
1178 if (gtk_entry_get_invisible_char(GTK_ENTRY(entry)) == '*')
1179 gtk_entry_set_invisible_char(GTK_ENTRY(entry), PIDGIN_INVISIBLE_CHAR);
1180 g_signal_connect(G_OBJECT(entry), "changed",
1181 G_CALLBACK(proxy_print_option), (void *)PROXYPASS);
1182
1183 if (proxy_info != NULL && purple_proxy_info_get_password(proxy_info) != NULL)
1184 gtk_entry_set_text(GTK_ENTRY(entry),
1185 purple_proxy_info_get_password(proxy_info));
1186 pidgin_set_accessible_label (entry, label);
1187 }
1188
1189 gtk_widget_show_all(ret);
1190 if (proxy_info == NULL ||
1191 purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_NONE ||
1192 purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_USE_ENVVAR)
1193 gtk_widget_hide(table);
1194 return ret;
1195 }
1196
1197 #ifndef _WIN32
1198 static gboolean manual_browser_set(GtkWidget *entry, GdkEventFocus *event, gpointer data) {
1199 const char *program = gtk_entry_get_text(GTK_ENTRY(entry));
1200
1201 purple_prefs_set_path(PIDGIN_PREFS_ROOT "/browsers/command", program);
1202
1203 /* carry on normally */
1204 return FALSE;
1205 }
1206
1207 static GList *get_available_browsers()
1208 {
1209 struct browser {
1210 char *name;
1211 char *command;
1212 };
1213
1214 /* Sorted reverse alphabetically */
1215 static struct browser possible_browsers[] = {
1216 {N_("Seamonkey"), "seamonkey"},
1217 {N_("Opera"), "opera"},
1218 {N_("Netscape"), "netscape"},
1219 {N_("Mozilla"), "mozilla"},
1220 {N_("Konqueror"), "kfmclient"},
1221 {N_("GNOME Default"), "gnome-open"},
1222 {N_("Galeon"), "galeon"},
1223 {N_("Firefox"), "firefox"},
1224 {N_("Firebird"), "mozilla-firebird"},
1225 {N_("Epiphany"), "epiphany"}
1226 };
1227 static const int num_possible_browsers = G_N_ELEMENTS(possible_browsers);
1228
1229 GList *browsers = NULL;
1230 int i = 0;
1231 char *browser_setting = (char *)purple_prefs_get_string(PIDGIN_PREFS_ROOT "/browsers/browser");
1232
1233 browsers = g_list_prepend(browsers, (gpointer)"custom");
1234 browsers = g_list_prepend(browsers, (gpointer)_("Manual"));
1235
1236 for (i = 0; i < num_possible_browsers; i++) {
1237 if (purple_program_is_valid(possible_browsers[i].command)) {
1238 browsers = g_list_prepend(browsers,
1239 possible_browsers[i].command);
1240 browsers = g_list_prepend(browsers, (gpointer)_(possible_browsers[i].name));
1241 if(browser_setting && !strcmp(possible_browsers[i].command, browser_setting))
1242 browser_setting = NULL;
1243 }
1244 }
1245
1246 if(browser_setting)
1247 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/browsers/browser", "custom");
1248
1249 return browsers;
1250 }
1251
1252 static void
1253 browser_changed1_cb(const char *name, PurplePrefType type,
1254 gconstpointer value, gpointer data)
1255 {
1256 GtkWidget *hbox = data;
1257 const char *browser = value;
1258
1259 gtk_widget_set_sensitive(hbox, strcmp(browser, "custom"));
1260 }
1261
1262 static void
1263 browser_changed2_cb(const char *name, PurplePrefType type,
1264 gconstpointer value, gpointer data)
1265 {
1266 GtkWidget *hbox = data;
1267 const char *browser = value;
1268
1269 gtk_widget_set_sensitive(hbox, !strcmp(browser, "custom"));
1270 }
1271
1272 static GtkWidget *
1273 browser_page()
1274 {
1275 GtkWidget *ret;
1276 GtkWidget *vbox;
1277 GtkWidget *hbox;
1278 GtkWidget *label;
1279 GtkWidget *entry;
1280 GtkSizeGroup *sg;
1281 GList *browsers = NULL;
1282
1283 ret = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
1284 gtk_container_set_border_width (GTK_CONTAINER (ret), PIDGIN_HIG_BORDER);
1285
1286 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1287 vbox = pidgin_make_frame (ret, _("Browser Selection"));
1288
1289 browsers = get_available_browsers();
1290 if (browsers != NULL) {
1291 label = pidgin_prefs_dropdown_from_list(vbox,_("_Browser:"), PURPLE_PREF_STRING,
1292 PIDGIN_PREFS_ROOT "/browsers/browser",
1293 browsers);
1294 g_list_free(browsers);
1295 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1296 gtk_size_group_add_widget(sg, label);
1297
1298 hbox = gtk_hbox_new(FALSE, 0);
1299 label = pidgin_prefs_dropdown(hbox, _("_Open link in:"), PURPLE_PREF_INT,
1300 PIDGIN_PREFS_ROOT "/browsers/place",
1301 _("Browser default"), PIDGIN_BROWSER_DEFAULT,
1302 _("Existing window"), PIDGIN_BROWSER_CURRENT,
1303 _("New window"), PIDGIN_BROWSER_NEW_WINDOW,
1304 _("New tab"), PIDGIN_BROWSER_NEW_TAB,
1305 NULL);
1306 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1307 gtk_size_group_add_widget(sg, label);
1308 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
1309
1310 if (!strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/browsers/browser"), "custom"))
1311 gtk_widget_set_sensitive(hbox, FALSE);
1312 purple_prefs_connect_callback(prefs, PIDGIN_PREFS_ROOT "/browsers/browser",
1313 browser_changed1_cb, hbox);
1314 }
1315
1316 hbox = gtk_hbox_new(FALSE, 5);
1317 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
1318 label = gtk_label_new_with_mnemonic(_("_Manual:\n(%s for URL)"));
1319 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
1320 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
1321 gtk_size_group_add_widget(sg, label);
1322
1323 entry = gtk_entry_new();
1324 gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
1325
1326 if (strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/browsers/browser"), "custom"))
1327 gtk_widget_set_sensitive(hbox, FALSE);
1328 purple_prefs_connect_callback(prefs, PIDGIN_PREFS_ROOT "/browsers/browser",
1329 browser_changed2_cb, hbox);
1330
1331 gtk_box_pack_start (GTK_BOX (hbox), entry, FALSE, FALSE, 0);
1332
1333 gtk_entry_set_text(GTK_ENTRY(entry),
1334 purple_prefs_get_path(PIDGIN_PREFS_ROOT "/browsers/command"));
1335 g_signal_connect(G_OBJECT(entry), "focus-out-event",
1336 G_CALLBACK(manual_browser_set), NULL);
1337 pidgin_set_accessible_label (entry, label);
1338
1339 gtk_widget_show_all(ret);
1340 return ret;
1341 }
1342 #endif /*_WIN32*/
1343
1344 static GtkWidget *
1345 logging_page()
1346 {
1347 GtkWidget *ret;
1348 GtkWidget *vbox;
1349 GList *names;
1350
1351 ret = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
1352 gtk_container_set_border_width (GTK_CONTAINER (ret), PIDGIN_HIG_BORDER);
1353
1354
1355 vbox = pidgin_make_frame (ret, _("Logging"));
1356 names = purple_log_logger_get_options();
1357
1358 pidgin_prefs_dropdown_from_list(vbox, _("Log _format:"), PURPLE_PREF_STRING,
1359 "/core/logging/format", names);
1360
1361 g_list_free(names);
1362
1363 pidgin_prefs_checkbox(_("Log all _instant messages"),
1364 "/core/logging/log_ims", vbox);
1365 pidgin_prefs_checkbox(_("Log all c_hats"),
1366 "/core/logging/log_chats", vbox);
1367 pidgin_prefs_checkbox(_("Log all _status changes to system log"),
1368 "/core/logging/log_system", vbox);
1369
1370 gtk_widget_show_all(ret);
1371
1372 return ret;
1373 }
1374
1375 #ifndef _WIN32
1376 static gint sound_cmd_yeah(GtkEntry *entry, gpointer d)
1377 {
1378 purple_prefs_set_path(PIDGIN_PREFS_ROOT "/sound/command",
1379 gtk_entry_get_text(GTK_ENTRY(entry)));
1380 return TRUE;
1381 }
1382
1383 static void
1384 sound_changed1_cb(const char *name, PurplePrefType type,
1385 gconstpointer value, gpointer data)
1386 {
1387 GtkWidget *hbox = data;
1388 const char *method = value;
1389
1390 gtk_widget_set_sensitive(hbox, !strcmp(method, "custom"));
1391 }
1392
1393 static void
1394 sound_changed2_cb(const char *name, PurplePrefType type,
1395 gconstpointer value, gpointer data)
1396 {
1397 GtkWidget *vbox = data;
1398 const char *method = value;
1399
1400 gtk_widget_set_sensitive(vbox, strcmp(method, "none"));
1401 }
1402
1403 #ifdef USE_GSTREAMER
1404 static void
1405 sound_changed3_cb(const char *name, PurplePrefType type,
1406 gconstpointer value, gpointer data)
1407 {
1408 GtkWidget *hbox = data;
1409 const char *method = value;
1410
1411 gtk_widget_set_sensitive(hbox,
1412 !strcmp(method, "automatic") ||
1413 !strcmp(method, "esd"));
1414 }
1415 #endif /* USE_GSTREAMER */
1416 #endif /* !_WIN32 */
1417
1418
1419 static void
1420 event_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer data)
1421 {
1422 GtkTreeModel *model = (GtkTreeModel *)data;
1423 GtkTreeIter iter;
1424 GtkTreePath *path = gtk_tree_path_new_from_string(pth);
1425 char *pref;
1426
1427 gtk_tree_model_get_iter (model, &iter, path);
1428 gtk_tree_model_get (model, &iter,
1429 2, &pref,
1430 -1);
1431
1432 purple_prefs_set_bool(pref, !gtk_cell_renderer_toggle_get_active(cell));
1433 g_free(pref);
1434
1435 gtk_list_store_set(GTK_LIST_STORE (model), &iter,
1436 0, !gtk_cell_renderer_toggle_get_active(cell),
1437 -1);
1438
1439 gtk_tree_path_free(path);
1440 }
1441
1442 static void
1443 test_sound(GtkWidget *button, gpointer i_am_NULL)
1444 {
1445 char *pref;
1446 gboolean temp_value;
1447
1448 pref = g_strdup_printf(PIDGIN_PREFS_ROOT "/sound/enabled/%s",
1449 pidgin_sound_get_event_option(sound_row_sel));
1450
1451 temp_value = purple_prefs_get_bool(pref);
1452
1453 if (!temp_value) purple_prefs_set_bool(pref, TRUE);
1454
1455 purple_sound_play_event(sound_row_sel, NULL);
1456
1457 if (!temp_value) purple_prefs_set_bool(pref, FALSE);
1458
1459 g_free(pref);
1460 }
1461
1462 /*
1463 * Resets a sound file back to default.
1464 */
1465 static void
1466 reset_sound(GtkWidget *button, gpointer i_am_also_NULL)
1467 {
1468 gchar *pref;
1469
1470 pref = g_strdup_printf(PIDGIN_PREFS_ROOT "/sound/file/%s",
1471 pidgin_sound_get_event_option(sound_row_sel));
1472 purple_prefs_set_path(pref, "");
1473 g_free(pref);
1474
1475 gtk_entry_set_text(GTK_ENTRY(sound_entry), "(default)");
1476 }
1477
1478 static void
1479 sound_chosen_cb(void *user_data, const char *filename)
1480 {
1481 gchar *pref;
1482 int sound;
1483
1484 sound = GPOINTER_TO_INT(user_data);
1485
1486 /* Set it -- and forget it */
1487 pref = g_strdup_printf(PIDGIN_PREFS_ROOT "/sound/file/%s",
1488 pidgin_sound_get_event_option(sound));
1489 purple_prefs_set_path(pref, filename);
1490 g_free(pref);
1491
1492 /*
1493 * If the sound we just changed is still the currently selected
1494 * sound, then update the box showing the file name.
1495 */
1496 if (sound == sound_row_sel)
1497 gtk_entry_set_text(GTK_ENTRY(sound_entry), filename);
1498 }
1499
1500 static void select_sound(GtkWidget *button, gpointer being_NULL_is_fun)
1501 {
1502 gchar *pref;
1503 const char *filename;
1504
1505 pref = g_strdup_printf(PIDGIN_PREFS_ROOT "/sound/file/%s",
1506 pidgin_sound_get_event_option(sound_row_sel));
1507 filename = purple_prefs_get_path(pref);
1508 g_free(pref);
1509
1510 if (*filename == '\0')
1511 filename = NULL;
1512
1513 purple_request_file(prefs, _("Sound Selection"), filename, FALSE,
1514 G_CALLBACK(sound_chosen_cb), NULL, GINT_TO_POINTER(sound_row_sel));
1515 }
1516
1517 #ifdef USE_GSTREAMER
1518 static gchar* prefs_sound_volume_format(GtkScale *scale, gdouble val)
1519 {
1520 if(val < 15) {
1521 return g_strdup_printf(_("Quietest"));
1522 } else if(val < 30) {
1523 return g_strdup_printf(_("Quieter"));
1524 } else if(val < 45) {
1525 return g_strdup_printf(_("Quiet"));
1526 } else if(val < 55) {
1527 return g_strdup_printf(_("Normal"));
1528 } else if(val < 70) {
1529 return g_strdup_printf(_("Loud"));
1530 } else if(val < 85) {
1531 return g_strdup_printf(_("Louder"));
1532 } else {
1533 return g_strdup_printf(_("Loudest"));
1534 }
1535 }
1536
1537 static void prefs_sound_volume_changed(GtkRange *range)
1538 {
1539 int val = (int)gtk_range_get_value(GTK_RANGE(range));
1540 purple_prefs_set_int(PIDGIN_PREFS_ROOT "/sound/volume", val);
1541 }
1542 #endif
1543
1544 static void prefs_sound_sel(GtkTreeSelection *sel, GtkTreeModel *model) {
1545 GtkTreeIter iter;
1546 GValue val;
1547 const char *file;
1548 char *pref;
1549
1550 if (! gtk_tree_selection_get_selected (sel, &model, &iter))
1551 return;
1552
1553 val.g_type = 0;
1554 gtk_tree_model_get_value (model, &iter, 3, &val);
1555 sound_row_sel = g_value_get_uint(&val);
1556
1557 pref = g_strdup_printf(PIDGIN_PREFS_ROOT "/sound/file/%s",
1558 pidgin_sound_get_event_option(sound_row_sel));
1559 file = purple_prefs_get_path(pref);
1560 g_free(pref);
1561 if (sound_entry)
1562 gtk_entry_set_text(GTK_ENTRY(sound_entry), (file && *file != '\0') ? file : "(default)");
1563 g_value_unset (&val);
1564 }
1565
1566 static GtkWidget *
1567 sound_page()
1568 {
1569 GtkWidget *ret;
1570 GtkWidget *vbox, *sw, *button;
1571 GtkSizeGroup *sg;
1572 GtkTreeIter iter;
1573 GtkWidget *event_view;
1574 GtkListStore *event_store;
1575 GtkCellRenderer *rend;
1576 GtkTreeViewColumn *col;
1577 GtkTreeSelection *sel;
1578 GtkTreePath *path;
1579 GtkWidget *hbox;
1580 int j;
1581 const char *file;
1582 char *pref;
1583 #ifndef _WIN32
1584 GtkWidget *dd;
1585 GtkWidget *label;
1586 GtkWidget *entry;
1587 const char *cmd;
1588 #endif
1589
1590 ret = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
1591 gtk_container_set_border_width (GTK_CONTAINER (ret), PIDGIN_HIG_BORDER);
1592
1593 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1594
1595 #ifndef _WIN32
1596 vbox = pidgin_make_frame (ret, _("Sound Method"));
1597 dd = pidgin_prefs_dropdown(vbox, _("_Method:"), PURPLE_PREF_STRING,
1598 PIDGIN_PREFS_ROOT "/sound/method",
1599 _("Console beep"), "beep",
1600 #ifdef USE_GSTREAMER
1601 _("Automatic"), "automatic",
1602 "ESD", "esd",
1603 #endif
1604 _("Command"), "custom",
1605 _("No sounds"), "none",
1606 NULL);
1607 gtk_size_group_add_widget(sg, dd);
1608 gtk_misc_set_alignment(GTK_MISC(dd), 0, 0.5);
1609
1610 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
1611 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
1612
1613 label = gtk_label_new_with_mnemonic(_("Sound c_ommand:\n(%s for filename)"));
1614 gtk_size_group_add_widget(sg, label);
1615 gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
1616 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
1617
1618 entry = gtk_entry_new();
1619 gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
1620
1621 gtk_editable_set_editable(GTK_EDITABLE(entry), TRUE);
1622 cmd = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/sound/command");
1623 if(cmd)
1624 gtk_entry_set_text(GTK_ENTRY(entry), cmd);
1625
1626 gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);
1627 g_signal_connect(G_OBJECT(entry), "changed",
1628 G_CALLBACK(sound_cmd_yeah), NULL);
1629
1630 purple_prefs_connect_callback(prefs, PIDGIN_PREFS_ROOT "/sound/method",
1631 sound_changed1_cb, hbox);
1632 gtk_widget_set_sensitive(hbox,
1633 !strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/sound/method"),
1634 "custom"));
1635
1636 pidgin_set_accessible_label (entry, label);
1637 #endif /* _WIN32 */
1638
1639 vbox = pidgin_make_frame (ret, _("Sound Options"));
1640 pidgin_prefs_checkbox(_("Sounds when conversation has _focus"),
1641 PIDGIN_PREFS_ROOT "/sound/conv_focus", vbox);
1642 pidgin_prefs_dropdown(vbox, _("Enable sounds:"),
1643 PURPLE_PREF_INT, "/core/sound/while_status",
1644 _("Only when available"), 1,
1645 _("Only when not available"), 2,
1646 _("Always"), 3,
1647 NULL);
1648
1649 #ifdef USE_GSTREAMER
1650 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
1651 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
1652
1653 label = gtk_label_new_with_mnemonic(_("Volume:"));
1654 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1655 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
1656
1657 sw = gtk_hscale_new_with_range(0.0, 100.0, 5.0);
1658 gtk_range_set_increments(GTK_RANGE(sw), 5.0, 25.0);
1659 gtk_range_set_value(GTK_RANGE(sw), purple_prefs_get_int(PIDGIN_PREFS_ROOT "/sound/volume"));
1660 g_signal_connect (G_OBJECT (sw), "format-value",
1661 G_CALLBACK (prefs_sound_volume_format),
1662 NULL);
1663 g_signal_connect (G_OBJECT (sw), "value-changed",
1664 G_CALLBACK (prefs_sound_volume_changed),
1665 NULL);
1666 gtk_box_pack_start(GTK_BOX(hbox), sw, TRUE, TRUE, 0);
1667
1668 purple_prefs_connect_callback(prefs, PIDGIN_PREFS_ROOT "/sound/method",
1669 sound_changed3_cb, hbox);
1670 sound_changed3_cb(PIDGIN_PREFS_ROOT "/sound/method", PURPLE_PREF_STRING,
1671 purple_prefs_get_string(PIDGIN_PREFS_ROOT "/sound/method"), hbox);
1672 #endif
1673
1674 #ifndef _WIN32
1675 gtk_widget_set_sensitive(vbox,
1676 strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/sound/method"), "none"));
1677 purple_prefs_connect_callback(prefs, PIDGIN_PREFS_ROOT "/sound/method",
1678 sound_changed2_cb, vbox);
1679 #endif
1680
1681 vbox = pidgin_make_frame(ret, _("Sound Events"));
1682
1683 /* The following is an ugly hack to make the frame expand so the
1684 * sound events list is big enough to be usable */
1685 gtk_box_set_child_packing(GTK_BOX(vbox->parent), vbox, TRUE, TRUE, 0,
1686 GTK_PACK_START);
1687 gtk_box_set_child_packing(GTK_BOX(vbox->parent->parent), vbox->parent, TRUE,
1688 TRUE, 0, GTK_PACK_START);
1689 gtk_box_set_child_packing(GTK_BOX(vbox->parent->parent->parent),
1690 vbox->parent->parent, TRUE, TRUE, 0, GTK_PACK_START);
1691
1692 sw = gtk_scrolled_window_new(NULL,NULL);
1693 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1694 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_IN);
1695
1696 gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
1697 event_store = gtk_list_store_new (4, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT);
1698
1699 for (j=0; j < PURPLE_NUM_SOUNDS; j++) {
1700 char *pref = g_strdup_printf(PIDGIN_PREFS_ROOT "/sound/enabled/%s",
1701 pidgin_sound_get_event_option(j));
1702 const char *label = pidgin_sound_get_event_label(j);
1703
1704 if (label == NULL) {
1705 g_free(pref);
1706 continue;
1707 }
1708
1709 gtk_list_store_append (event_store, &iter);
1710 gtk_list_store_set(event_store, &iter,
1711 0, purple_prefs_get_bool(pref),
1712 1, _(label),
1713 2, pref,
1714 3, j,
1715 -1);
1716 g_free(pref);
1717 }
1718
1719 event_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL(event_store));
1720
1721 rend = gtk_cell_renderer_toggle_new();
1722 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (event_view));
1723 g_signal_connect (G_OBJECT (sel), "changed",
1724 G_CALLBACK (prefs_sound_sel),
1725 NULL);
1726 g_signal_connect (G_OBJECT(rend), "toggled",
1727 G_CALLBACK(event_toggled), event_store);
1728 path = gtk_tree_path_new_first();
1729 gtk_tree_selection_select_path(sel, path);
1730 gtk_tree_path_free(path);
1731
1732 col = gtk_tree_view_column_new_with_attributes (_("Play"),
1733 rend,
1734 "active", 0,
1735 NULL);
1736 gtk_tree_view_append_column (GTK_TREE_VIEW(event_view), col);
1737
1738 rend = gtk_cell_renderer_text_new();
1739 col = gtk_tree_view_column_new_with_attributes (_("Event"),
1740 rend,
1741 "text", 1,
1742 NULL);
1743 gtk_tree_view_append_column (GTK_TREE_VIEW(event_view), col);
1744 g_object_unref(G_OBJECT(event_store));
1745 gtk_container_add(GTK_CONTAINER(sw), event_view);
1746
1747 hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
1748 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
1749 sound_entry = gtk_entry_new();
1750 pref = g_strdup_printf(PIDGIN_PREFS_ROOT "/sound/file/%s",
1751 pidgin_sound_get_event_option(0));
1752 file = purple_prefs_get_path(pref);
1753 g_free(pref);
1754 gtk_entry_set_text(GTK_ENTRY(sound_entry), (file && *file != '\0') ? file : "(default)");
1755 gtk_editable_set_editable(GTK_EDITABLE(sound_entry), FALSE);
1756 gtk_box_pack_start(GTK_BOX(hbox), sound_entry, FALSE, FALSE, PIDGIN_HIG_BOX_SPACE);
1757
1758 button = gtk_button_new_with_label(_("Test"));
1759 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(test_sound), NULL);
1760 gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 1);
1761
1762 button = gtk_button_new_with_label(_("Reset"));
1763 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(reset_sound), NULL);
1764 gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 1);
1765
1766 button = gtk_button_new_with_label(_("Choose..."));
1767 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(select_sound), NULL);
1768 gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 1);
1769
1770 gtk_widget_show_all(ret);
1771
1772 return ret;
1773 }
1774
1775
1776 static void
1777 set_idle_away(PurpleSavedStatus *status)
1778 {
1779 purple_prefs_set_int("/core/savedstatus/idleaway", purple_savedstatus_get_creation_time(status));
1780 }
1781
1782 static void
1783 set_startupstatus(PurpleSavedStatus *status)
1784 {
1785 purple_prefs_set_int("/core/savedstatus/startup", purple_savedstatus_get_creation_time(status));
1786 }
1787
1788 static GtkWidget *
1789 away_page()
1790 {
1791 GtkWidget *ret;
1792 GtkWidget *vbox;
1793 GtkWidget *hbox;
1794 GtkWidget *dd;
1795 GtkWidget *label;
1796 GtkWidget *button;
1797 GtkWidget *select;
1798 GtkWidget *menu;
1799 GtkSizeGroup *sg;
1800
1801 ret = gtk_vbox_new(FALSE, PIDGIN_HIG_CAT_SPACE);
1802 gtk_container_set_border_width (GTK_CONTAINER (ret), PIDGIN_HIG_BORDER);
1803
1804 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1805
1806 /* Idle stuff */
1807 vbox = pidgin_make_frame(ret, _("Idle"));
1808
1809 dd = pidgin_prefs_dropdown(vbox, _("_Report idle time:"),
1810 PURPLE_PREF_STRING, "/core/away/idle_reporting",
1811 _("Never"), "none",
1812 _("From last sent message"), "purple",
1813 #if defined(USE_SCREENSAVER) || defined(HAVE_IOKIT)
1814 _("Based on keyboard or mouse use"), "system",
1815 #endif
1816 NULL);
1817 gtk_size_group_add_widget(sg, dd);
1818 gtk_misc_set_alignment(GTK_MISC(dd), 0, 0.5);
1819
1820 /* Away stuff */
1821 vbox = pidgin_make_frame(ret, _("Away"));
1822
1823 dd = pidgin_prefs_dropdown(vbox, _("_Auto-reply:"),
1824 PURPLE_PREF_STRING, "/core/away/auto_reply",
1825 _("Never"), "never",
1826 _("When away"), "away",
1827 _("When both away and idle"), "awayidle",
1828 NULL);
1829 gtk_size_group_add_widget(sg, dd);
1830 gtk_misc_set_alignment(GTK_MISC(dd), 0, 0.5);
1831
1832 /* Auto-away stuff */
1833 vbox = pidgin_make_frame(ret, _("Auto-away"));
1834
1835 button = pidgin_prefs_checkbox(_("Change status when _idle"),
1836 "/core/away/away_when_idle", vbox);
1837
1838 select = pidgin_prefs_labeled_spin_button(vbox,
1839 _("_Minutes before changing status:"), "/core/away/mins_before_away",
1840 1, 24 * 60, sg);
1841 g_signal_connect(G_OBJECT(button), "clicked",
1842 G_CALLBACK(pidgin_toggle_sensitive), select);
1843
1844 hbox = gtk_hbox_new(FALSE, 0);
1845 gtk_container_add(GTK_CONTAINER(vbox), hbox);
1846
1847 label = gtk_label_new_with_mnemonic(_("Change _status to:"));
1848 gtk_size_group_add_widget(sg, label);
1849 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1850 g_signal_connect(G_OBJECT(button), "clicked",
1851 G_CALLBACK(pidgin_toggle_sensitive), label);
1852 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
1853
1854 /* TODO: Show something useful if we don't have any saved statuses. */
1855 menu = pidgin_status_menu(purple_savedstatus_get_idleaway(), G_CALLBACK(set_idle_away));
1856 gtk_box_pack_start(GTK_BOX(hbox), menu, FALSE, FALSE, 0);
1857 g_signal_connect(G_OBJECT(button), "clicked",
1858 G_CALLBACK(pidgin_toggle_sensitive), menu);
1859 gtk_label_set_mnemonic_widget(GTK_LABEL(label), menu);
1860
1861 if (!purple_prefs_get_bool("/core/away/away_when_idle")) {
1862 gtk_widget_set_sensitive(GTK_WIDGET(menu), FALSE);
1863 gtk_widget_set_sensitive(GTK_WIDGET(select), FALSE);
1864 gtk_widget_set_sensitive(GTK_WIDGET(label), FALSE);
1865 }
1866
1867 /* Signon status stuff */
1868 vbox = pidgin_make_frame(ret, _("Status at Startup"));
1869
1870 button = pidgin_prefs_checkbox(_("Use status from last _exit at startup"),
1871 "/core/savedstatus/startup_current_status", vbox);
1872
1873 hbox = gtk_hbox_new(FALSE, 0);
1874 gtk_container_add(GTK_CONTAINER(vbox), hbox);
1875
1876 label = gtk_label_new_with_mnemonic(_("Status to a_pply at startup:"));
1877 gtk_size_group_add_widget(sg, label);
1878 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1879 g_signal_connect(G_OBJECT(button), "clicked",
1880 G_CALLBACK(pidgin_toggle_sensitive), label);
1881 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
1882
1883 /* TODO: Show something useful if we don't have any saved statuses. */
1884 menu = pidgin_status_menu(purple_savedstatus_get_startup(), G_CALLBACK(set_startupstatus));
1885 gtk_box_pack_start(GTK_BOX(hbox), menu, FALSE, FALSE, 0);
1886 g_signal_connect(G_OBJECT(button), "clicked",
1887 G_CALLBACK(pidgin_toggle_sensitive), menu);
1888 gtk_label_set_mnemonic_widget(GTK_LABEL(label), menu);
1889
1890 if (purple_prefs_get_bool("/core/savedstatus/startup_current_status")) {
1891 gtk_widget_set_sensitive(GTK_WIDGET(menu), FALSE);
1892 gtk_widget_set_sensitive(GTK_WIDGET(label), FALSE);
1893 }
1894
1895 gtk_widget_show_all(ret);
1896
1897 return ret;
1898 }
1899
1900 static int
1901 prefs_notebook_add_page(const char *text,
1902 GtkWidget *page,
1903 int ind) {
1904
1905 #if GTK_CHECK_VERSION(2,4,0)
1906 return gtk_notebook_append_page(GTK_NOTEBOOK(prefsnotebook), page, gtk_label_new(text));
1907 #else
1908 gtk_notebook_append_page(GTK_NOTEBOOK(prefsnotebook), page, gtk_label_new(text));
1909 return gtk_notebook_page_num(GTK_NOTEBOOK(prefsnotebook), page);
1910 #endif
1911 }
1912
1913 static void prefs_notebook_init() {
1914 prefs_notebook_add_page(_("Interface"), interface_page(), notebook_page++);
1915 prefs_notebook_add_page(_("Conversations"), conv_page(), notebook_page++);
1916 prefs_notebook_add_page(_("Smiley Themes"), theme_page(), notebook_page++);
1917 prefs_notebook_add_page(_("Sounds"), sound_page(), notebook_page++);
1918 prefs_notebook_add_page(_("Network"), network_page(), notebook_page++);
1919 #ifndef _WIN32
1920 /* We use the registered default browser in windows */
1921 /* if the user is running gnome 2.x or Mac OS X, hide the browsers tab */
1922 if ((purple_running_gnome() == FALSE) && (purple_running_osx() == FALSE)) {
1923 prefs_notebook_add_page(_("Browser"), browser_page(), notebook_page++);
1924 }
1925 #endif
1926 prefs_notebook_add_page(_("Logging"), logging_page(), notebook_page++);
1927 prefs_notebook_add_page(_("Status / Idle"), away_page(), notebook_page++);
1928 }
1929
1930 void pidgin_prefs_show(void)
1931 {
1932 GtkWidget *vbox;
1933 GtkWidget *bbox;
1934 GtkWidget *notebook;
1935 GtkWidget *button;
1936
1937 if (prefs) {
1938 gtk_window_present(GTK_WINDOW(prefs));
1939 return;
1940 }
1941
1942 /* copy the preferences to tmp values...
1943 * I liked "take affect immediately" Oh well :-( */
1944 /* (that should have been "effect," right?) */
1945
1946 /* Back to instant-apply! I win! BU-HAHAHA! */
1947
1948 /* Create the window */
1949 prefs = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1950 gtk_window_set_role(GTK_WINDOW(prefs), "preferences");
1951 gtk_window_set_title(GTK_WINDOW(prefs), _("Preferences"));
1952 gtk_window_set_resizable (GTK_WINDOW(prefs), FALSE);
1953 gtk_container_set_border_width(GTK_CONTAINER(prefs), PIDGIN_HIG_BORDER);
1954 g_signal_connect(G_OBJECT(prefs), "destroy",
1955 G_CALLBACK(delete_prefs), NULL);
1956
1957 vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
1958 gtk_container_add(GTK_CONTAINER(prefs), vbox);
1959 gtk_widget_show(vbox);
1960
1961 /* The notebook */
1962 prefsnotebook = notebook = gtk_notebook_new ();
1963 gtk_box_pack_start (GTK_BOX (vbox), notebook, FALSE, FALSE, 0);
1964 gtk_widget_show(prefsnotebook);
1965
1966 /* The buttons to press! */
1967 bbox = gtk_hbutton_box_new();
1968 gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
1969 gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
1970 gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0);
1971 gtk_widget_show (bbox);
1972
1973 button = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
1974 g_signal_connect_swapped(G_OBJECT(button), "clicked",
1975 G_CALLBACK(gtk_widget_destroy), prefs);
1976 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
1977 gtk_widget_show(button);
1978
1979 prefs_notebook_init();
1980
1981 /* Show everything. */
1982 gtk_widget_show(prefs);
1983 }
1984
1985 static void
1986 set_bool_pref(GtkWidget *w, const char *key)
1987 {
1988 purple_prefs_set_bool(key,
1989 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w)));
1990 }
1991
1992 GtkWidget *
1993 pidgin_prefs_checkbox(const char *text, const char *key, GtkWidget *page)
1994 {
1995 GtkWidget *button;
1996
1997 button = gtk_check_button_new_with_mnemonic(text);
1998 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button),
1999 purple_prefs_get_bool(key));
2000
2001 gtk_box_pack_start(GTK_BOX(page), button, FALSE, FALSE, 0);
2002
2003 g_signal_connect(G_OBJECT(button), "clicked",
2004 G_CALLBACK(set_bool_pref), (char *)key);
2005
2006 gtk_widget_show(button);
2007
2008 return button;
2009 }
2010
2011 static void
2012 smiley_theme_pref_cb(const char *name, PurplePrefType type,
2013 gconstpointer value, gpointer data)
2014 {
2015 const char *themename = value;
2016 GSList *themes;
2017
2018 for (themes = smiley_themes; themes; themes = themes->next) {
2019 struct smiley_theme *smile = themes->data;
2020 if (smile->name && strcmp(themename, smile->name) == 0) {
2021 pidginthemes_load_smiley_theme(smile->path, TRUE);
2022 break;
2023 }
2024 }
2025 }
2026
2027 void
2028 pidgin_prefs_init(void)
2029 {
2030 /* only change this when we have a sane prefs migration path */
2031 purple_prefs_add_none("/gaim");
2032 purple_prefs_add_none(PIDGIN_PREFS_ROOT "");
2033 purple_prefs_add_none("/plugins/gtk");
2034
2035 #ifndef _WIN32
2036 /* Browsers */
2037 purple_prefs_add_none(PIDGIN_PREFS_ROOT "/browsers");
2038 purple_prefs_add_int(PIDGIN_PREFS_ROOT "/browsers/place", PIDGIN_BROWSER_DEFAULT);
2039 purple_prefs_add_path(PIDGIN_PREFS_ROOT "/browsers/command", "");
2040 purple_prefs_add_string(PIDGIN_PREFS_ROOT "/browsers/browser", "mozilla");
2041 #endif
2042
2043 /* Plugins */
2044 purple_prefs_add_none(PIDGIN_PREFS_ROOT "/plugins");
2045 purple_prefs_add_path_list(PIDGIN_PREFS_ROOT "/plugins/loaded", NULL);
2046
2047 /* File locations */
2048 purple_prefs_add_none(PIDGIN_PREFS_ROOT "/filelocations");
2049 purple_prefs_add_path(PIDGIN_PREFS_ROOT "/filelocations/last_save_folder", "");
2050 purple_prefs_add_path(PIDGIN_PREFS_ROOT "/filelocations/last_open_folder", "");
2051 purple_prefs_add_path(PIDGIN_PREFS_ROOT "/filelocations/last_icon_folder", "");
2052
2053 /* Smiley Themes */
2054 purple_prefs_add_none(PIDGIN_PREFS_ROOT "/smileys");
2055 purple_prefs_add_string(PIDGIN_PREFS_ROOT "/smileys/theme", "Default");
2056
2057 /* Smiley Callbacks */
2058 purple_prefs_connect_callback(prefs, PIDGIN_PREFS_ROOT "/smileys/theme",
2059 smiley_theme_pref_cb, NULL);
2060 }
2061
2062 void pidgin_prefs_update_old() {
2063 /* Rename some old prefs */
2064 purple_prefs_rename(PIDGIN_PREFS_ROOT "/logging/log_ims", "/core/logging/log_ims");
2065 purple_prefs_rename(PIDGIN_PREFS_ROOT "/logging/log_chats", "/core/logging/log_chats");
2066 purple_prefs_rename("/core/conversations/placement",
2067 PIDGIN_PREFS_ROOT "/conversations/placement");
2068
2069 purple_prefs_rename(PIDGIN_PREFS_ROOT "/debug/timestamps", "/core/debug/timestamps");
2070 purple_prefs_rename(PIDGIN_PREFS_ROOT "/conversations/im/raise_on_events", "/plugins/gtk/X11/notify/method_raise");
2071
2072 purple_prefs_rename_boolean_toggle(PIDGIN_PREFS_ROOT "/conversations/ignore_colors",
2073 PIDGIN_PREFS_ROOT "/conversations/show_incoming_formatting");
2074
2075 /* this string pref moved into the core, try to be friendly */
2076 purple_prefs_rename(PIDGIN_PREFS_ROOT "/idle/reporting_method", "/core/away/idle_reporting");
2077
2078 /* Remove some no-longer-used prefs */
2079 purple_prefs_remove(PIDGIN_PREFS_ROOT "/blist/auto_expand_contacts");
2080 purple_prefs_remove(PIDGIN_PREFS_ROOT "/blist/button_style");
2081 purple_prefs_remove(PIDGIN_PREFS_ROOT "/blist/grey_idle_buddies");
2082 purple_prefs_remove(PIDGIN_PREFS_ROOT "/blist/raise_on_events");
2083 purple_prefs_remove(PIDGIN_PREFS_ROOT "/blist/show_group_count");
2084 purple_prefs_remove(PIDGIN_PREFS_ROOT "/blist/show_warning_level");
2085 purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/button_type");
2086 purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/ctrl_enter_sends");
2087 purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/enter_sends");
2088 purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/escape_closes");
2089 purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/html_shortcuts");
2090 purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/icons_on_tabs");
2091 purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/send_formatting");
2092 purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/show_smileys");
2093 purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/show_urls_as_links");
2094 purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/smiley_shortcuts");
2095 purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/use_custom_bgcolor");
2096 purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/use_custom_fgcolor");
2097 purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/use_custom_font");
2098 purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/use_custom_size");
2099 purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/chat/old_tab_complete");
2100 purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/chat/tab_completion");
2101 purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/im/hide_on_send");
2102 purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/chat/color_nicks");
2103 purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/chat/raise_on_events");
2104 purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/ignore_fonts");
2105 purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/ignore_font_sizes");
2106 purple_prefs_remove(PIDGIN_PREFS_ROOT "/conversations/passthrough_unknown_commands");
2107 purple_prefs_remove(PIDGIN_PREFS_ROOT "/idle");
2108 purple_prefs_remove(PIDGIN_PREFS_ROOT "/logging/individual_logs");
2109 purple_prefs_remove(PIDGIN_PREFS_ROOT "/sound/signon");
2110 purple_prefs_remove(PIDGIN_PREFS_ROOT "/sound/silent_signon");
2111
2112 /* Convert old queuing prefs to hide_new 3-way pref. */
2113 if (purple_prefs_exists("/plugins/gtk/docklet/queue_messages") &&
2114 purple_prefs_get_bool("/plugins/gtk/docklet/queue_messages"))
2115 {
2116 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/conversations/im/hide_new", "always");
2117 }
2118 else if (purple_prefs_exists(PIDGIN_PREFS_ROOT "/away/queue_messages") &&
2119 purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/away/queue_messages"))
2120 {
2121 purple_prefs_set_string(PIDGIN_PREFS_ROOT "/conversations/im/hide_new", "away");
2122 }
2123 purple_prefs_remove(PIDGIN_PREFS_ROOT "/away/queue_messages");
2124 purple_prefs_remove(PIDGIN_PREFS_ROOT "/away");
2125 purple_prefs_remove("/plugins/gtk/docklet/queue_messages");
2126 }

mercurial