]> git.0d.be Git - empathy.git/blob - src/empathy-preferences.c
99d920fcd7b9b4291f589912a9e8b0e9ab5ad2d4
[empathy.git] / src / empathy-preferences.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2003-2007 Imendio AB
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA  02110-1301  USA
19  *
20  * Authors: Mikael Hallendal <micke@imendio.com>
21  *          Richard Hult <richard@imendio.com>
22  *          Martyn Russell <martyn@imendio.com>
23  *          Danielle Madeley <danielle.madeley@collabora.co.uk>
24  */
25
26 #include <config.h>
27
28 #include <string.h>
29 #include <stdio.h>
30
31 #include <gtk/gtk.h>
32 #include <glib/gi18n.h>
33 #include <telepathy-glib/dbus.h>
34 #include <telepathy-glib/util.h>
35
36 #include <libempathy/empathy-client-factory.h>
37 #include <libempathy/empathy-gsettings.h>
38 #include <libempathy/empathy-utils.h>
39
40 #include <libempathy-gtk/empathy-ui-utils.h>
41 #include <libempathy-gtk/empathy-theme-manager.h>
42 #include <libempathy-gtk/empathy-spell.h>
43 #include <libempathy-gtk/empathy-gtk-enum-types.h>
44 #include <libempathy-gtk/empathy-theme-adium.h>
45
46 #include "empathy-preferences.h"
47
48 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
49 #include <libempathy/empathy-debug.h>
50
51 G_DEFINE_TYPE (EmpathyPreferences, empathy_preferences, GTK_TYPE_DIALOG);
52
53 #define GET_PRIV(self) ((EmpathyPreferencesPriv *)((EmpathyPreferences *) self)->priv)
54
55 static const gchar * empathy_preferences_tabs[] =
56 {
57   "general",
58   "notifications",
59   "sounds",
60   "calls",
61   "location",
62   "spell",
63   "themes",
64 };
65
66 struct _EmpathyPreferencesPriv {
67         GtkWidget *notebook;
68
69         GtkWidget *checkbutton_events_notif_area;
70
71         GtkWidget *treeview_sounds;
72         GtkWidget *treeview_spell_checker;
73
74         GtkWidget *vbox_chat_theme;
75         GtkWidget *combobox_chat_theme;
76         GtkWidget *combobox_chat_theme_variant;
77         GtkWidget *hbox_chat_theme_variant;
78         GtkWidget *sw_chat_theme_preview;
79         EmpathyThemeAdium *chat_theme_preview;
80         EmpathyThemeManager *theme_manager;
81
82         GSettings *gsettings;
83         GSettings *gsettings_chat;
84         GSettings *gsettings_call;
85         GSettings *gsettings_loc;
86         GSettings *gsettings_notify;
87         GSettings *gsettings_sound;
88         GSettings *gsettings_ui;
89         GSettings *gsettings_logger;
90 };
91
92 static void     preferences_setup_widgets                (EmpathyPreferences      *preferences,
93                                                           GtkBuilder              *gui);
94 static void     preferences_languages_setup              (EmpathyPreferences      *preferences);
95 static void     preferences_languages_add                (EmpathyPreferences      *preferences);
96 static void     preferences_languages_save               (EmpathyPreferences      *preferences);
97 static gboolean preferences_languages_save_foreach       (GtkTreeModel           *model,
98                                                           GtkTreePath            *path,
99                                                           GtkTreeIter            *iter,
100                                                           gchar                 **languages);
101 static void     preferences_languages_load               (EmpathyPreferences      *preferences);
102 static gboolean preferences_languages_load_foreach       (GtkTreeModel           *model,
103                                                           GtkTreePath            *path,
104                                                           GtkTreeIter            *iter,
105                                                           GList                  *languages);
106 static void     preferences_languages_cell_toggled_cb    (GtkCellRendererToggle  *cell,
107                                                           gchar                  *path_string,
108                                                           EmpathyPreferences      *preferences);
109
110 enum {
111         COL_LANG_ENABLED,
112         COL_LANG_CODE,
113         COL_LANG_NAME,
114         COL_LANG_COUNT
115 };
116
117 enum {
118         COL_THEME_VISIBLE_NAME,
119         COL_THEME_ADIUM_NAME,
120         COL_THEME_ADIUM_INFO,
121         COL_THEME_COUNT
122 };
123
124 enum {
125         COL_VARIANT_NAME,
126         COL_VARIANT_DEFAULT,
127         COL_VARIANT_COUNT
128 };
129
130 enum {
131         COL_SOUND_ENABLED,
132         COL_SOUND_NAME,
133         COL_SOUND_KEY,
134         COL_SOUND_COUNT
135 };
136
137 typedef struct {
138         const char *name;
139         const char *key;
140 } SoundEventEntry;
141
142 /* TODO: add phone related sounds also? */
143 static SoundEventEntry sound_entries [] = {
144         { N_("Message received"), EMPATHY_PREFS_SOUNDS_INCOMING_MESSAGE },
145         { N_("Message sent"), EMPATHY_PREFS_SOUNDS_OUTGOING_MESSAGE },
146         { N_("New conversation"), EMPATHY_PREFS_SOUNDS_NEW_CONVERSATION },
147         { N_("Contact comes online"), EMPATHY_PREFS_SOUNDS_CONTACT_LOGIN },
148         { N_("Contact goes offline"), EMPATHY_PREFS_SOUNDS_CONTACT_LOGOUT },
149         { N_("Account connected"), EMPATHY_PREFS_SOUNDS_SERVICE_LOGIN },
150         { N_("Account disconnected"), EMPATHY_PREFS_SOUNDS_SERVICE_LOGOUT }
151 };
152
153 static void
154 preferences_setup_widgets (EmpathyPreferences *preferences,
155                            GtkBuilder *gui)
156 {
157         EmpathyPreferencesPriv *priv = GET_PRIV (preferences);
158 #define BIND_ACTIVE(schema, key, widget) \
159         g_settings_bind (priv->gsettings_##schema, EMPATHY_PREFS_##key, \
160                          gtk_builder_get_object (gui, widget), "active", \
161                          G_SETTINGS_BIND_DEFAULT);
162
163         BIND_ACTIVE (notify, NOTIFICATIONS_ENABLED,
164                      "checkbutton_notifications_enabled");
165         BIND_ACTIVE (notify, NOTIFICATIONS_DISABLED_AWAY,
166                      "checkbutton_notifications_disabled_away");
167         BIND_ACTIVE (notify, NOTIFICATIONS_FOCUS,
168                      "checkbutton_notifications_focus");
169         BIND_ACTIVE (notify, NOTIFICATIONS_CONTACT_SIGNIN,
170                      "checkbutton_notifications_contact_signin");
171         BIND_ACTIVE (notify, NOTIFICATIONS_CONTACT_SIGNOUT,
172                      "checkbutton_notifications_contact_signout");
173
174         BIND_ACTIVE (sound, SOUNDS_ENABLED,
175                      "checkbutton_sounds_enabled");
176         BIND_ACTIVE (sound, SOUNDS_DISABLED_AWAY,
177                      "checkbutton_sounds_disabled_away");
178
179         BIND_ACTIVE (ui, UI_SHOW_OFFLINE,
180                      "checkbutton_show_offline");
181         BIND_ACTIVE (ui, UI_SHOW_GROUPS,
182                      "checkbutton_show_groups");
183         BIND_ACTIVE (ui, UI_SEPARATE_CHAT_WINDOWS,
184                      "radiobutton_chats_new_windows");
185         BIND_ACTIVE (ui, UI_EVENTS_NOTIFY_AREA,
186                      "checkbutton_events_notif_area");
187         BIND_ACTIVE (ui, UI_SHOW_BALANCES,
188                      "checkbutton_show_balances");
189
190         BIND_ACTIVE (chat, CHAT_SHOW_SMILEYS,
191                      "checkbutton_show_smileys");
192         BIND_ACTIVE (chat, CHAT_SHOW_CONTACTS_IN_ROOMS,
193                      "checkbutton_show_contacts_in_rooms");
194
195         BIND_ACTIVE (call, CALL_ECHO_CANCELLATION,
196                      "call_echo_cancellation");
197
198         BIND_ACTIVE (loc, LOCATION_PUBLISH,
199                      "checkbutton_location_publish");
200         BIND_ACTIVE (loc, LOCATION_RESOURCE_NETWORK,
201                      "checkbutton_location_resource_network");
202         BIND_ACTIVE (loc, LOCATION_RESOURCE_CELL,
203                      "checkbutton_location_resource_cell");
204         BIND_ACTIVE (loc, LOCATION_RESOURCE_GPS,
205                      "checkbutton_location_resource_gps");
206         BIND_ACTIVE (loc, LOCATION_REDUCE_ACCURACY,
207                      "checkbutton_location_reduce_accuracy");
208
209         BIND_ACTIVE (logger, LOGGER_ENABLED,
210                      "checkbutton_logging");
211
212 #undef BIND_ACTIVE
213
214 #define BIND_SENSITIVE(schema, key, widget) \
215         g_settings_bind (priv->gsettings_##schema, EMPATHY_PREFS_##key, \
216                          gtk_builder_get_object (gui, widget), "sensitive", \
217                         G_SETTINGS_BIND_GET);
218         /* N.B. BIND_SENSITIVE() is in addition to the sensitivity setting of
219          *      BIND_ACTIVE() */
220         BIND_SENSITIVE (notify, NOTIFICATIONS_ENABLED,
221                         "checkbutton_notifications_disabled_away");
222         BIND_SENSITIVE (notify, NOTIFICATIONS_ENABLED,
223                         "checkbutton_notifications_focus");
224         BIND_SENSITIVE (notify, NOTIFICATIONS_ENABLED,
225                         "checkbutton_notifications_contact_signin");
226         BIND_SENSITIVE (notify, NOTIFICATIONS_ENABLED,
227                         "checkbutton_notifications_contact_signout");
228
229         BIND_SENSITIVE (sound, SOUNDS_ENABLED,
230                         "checkbutton_sounds_disabled_away");
231         BIND_SENSITIVE (sound, SOUNDS_ENABLED,
232                         "treeview_sounds");
233
234         BIND_SENSITIVE (loc, LOCATION_PUBLISH,
235                         "checkbutton_location_resource_network");
236         BIND_SENSITIVE (loc, LOCATION_PUBLISH,
237                         "checkbutton_location_resource_cell");
238         BIND_SENSITIVE (loc, LOCATION_PUBLISH,
239                         "checkbutton_location_resource_gps");
240         BIND_SENSITIVE (loc, LOCATION_PUBLISH,
241                         "checkbutton_location_reduce_accuracy");
242
243 #undef BIND_SENSITIVE
244
245         g_settings_bind (priv->gsettings,
246                          EMPATHY_PREFS_AUTOCONNECT,
247                          gtk_builder_get_object (gui,
248                                                  "checkbutton_autoconnect"),
249                          "active",
250                          G_SETTINGS_BIND_DEFAULT);
251 }
252
253 static void
254 preferences_sound_cell_toggled_cb (GtkCellRendererToggle *toggle,
255                                    char *path_string,
256                                    EmpathyPreferences *preferences)
257 {
258         EmpathyPreferencesPriv *priv = GET_PRIV (preferences);
259         GtkTreePath *path;
260         gboolean instore;
261         GtkTreeIter iter;
262         GtkTreeView *view;
263         GtkTreeModel *model;
264         char *key;
265
266         view = GTK_TREE_VIEW (priv->treeview_sounds);
267         model = gtk_tree_view_get_model (view);
268
269         path = gtk_tree_path_new_from_string (path_string);
270
271         gtk_tree_model_get_iter (model, &iter, path);
272         gtk_tree_model_get (model, &iter, COL_SOUND_KEY, &key,
273                             COL_SOUND_ENABLED, &instore, -1);
274
275         instore ^= 1;
276
277         gtk_list_store_set (GTK_LIST_STORE (model), &iter,
278                             COL_SOUND_ENABLED, instore, -1);
279
280         g_settings_set_boolean (priv->gsettings_sound, key, instore);
281
282         g_free (key);
283         gtk_tree_path_free (path);
284 }
285
286 static void
287 preferences_sound_load (EmpathyPreferences *preferences)
288 {
289         EmpathyPreferencesPriv *priv = GET_PRIV (preferences);
290         guint i;
291         GtkTreeView *view;
292         GtkListStore *store;
293         GtkTreeIter iter;
294         gboolean set;
295
296         view = GTK_TREE_VIEW (priv->treeview_sounds);
297         store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
298
299         for (i = 0; i < G_N_ELEMENTS (sound_entries); i++) {
300                 set = g_settings_get_boolean (priv->gsettings_sound,
301                                               sound_entries[i].key);
302
303                 gtk_list_store_insert_with_values (store, &iter, i,
304                                                    COL_SOUND_NAME, gettext (sound_entries[i].name),
305                                                    COL_SOUND_KEY, sound_entries[i].key,
306                                                    COL_SOUND_ENABLED, set, -1);
307         }
308 }
309
310 static void
311 preferences_sound_setup (EmpathyPreferences *preferences)
312 {
313         EmpathyPreferencesPriv *priv = GET_PRIV (preferences);
314         GtkTreeView *view;
315         GtkListStore *store;
316         GtkCellRenderer *renderer;
317         GtkTreeViewColumn *column;
318
319         view = GTK_TREE_VIEW (priv->treeview_sounds);
320
321         store = gtk_list_store_new (COL_SOUND_COUNT,
322                                     G_TYPE_BOOLEAN, /* enabled */
323                                     G_TYPE_STRING,  /* name */
324                                     G_TYPE_STRING); /* key */
325
326         gtk_tree_view_set_model (view, GTK_TREE_MODEL (store));
327
328         renderer = gtk_cell_renderer_toggle_new ();
329         g_signal_connect (renderer, "toggled",
330                           G_CALLBACK (preferences_sound_cell_toggled_cb),
331                           preferences);
332
333         column = gtk_tree_view_column_new ();
334         gtk_tree_view_column_pack_start (column, renderer, FALSE);
335         gtk_tree_view_column_add_attribute (column, renderer,
336                                             "active", COL_SOUND_ENABLED);
337
338         renderer = gtk_cell_renderer_text_new ();
339         gtk_tree_view_column_pack_start (column, renderer, FALSE);
340         gtk_tree_view_column_add_attribute (column, renderer,
341                                             "text", COL_SOUND_NAME);
342
343         gtk_tree_view_append_column (view, column);
344
345         gtk_tree_view_column_set_resizable (column, FALSE);
346         gtk_tree_view_column_set_clickable (GTK_TREE_VIEW_COLUMN (column), TRUE);
347
348         g_object_unref (store);
349 }
350
351 static void
352 preferences_languages_setup (EmpathyPreferences *preferences)
353 {
354         EmpathyPreferencesPriv *priv = GET_PRIV (preferences);
355         GtkTreeView       *view;
356         GtkListStore      *store;
357         GtkTreeSelection  *selection;
358         GtkTreeViewColumn *column;
359         GtkCellRenderer   *renderer;
360         guint              col_offset;
361
362         view = GTK_TREE_VIEW (priv->treeview_spell_checker);
363
364         store = gtk_list_store_new (COL_LANG_COUNT,
365                                     G_TYPE_BOOLEAN,  /* enabled */
366                                     G_TYPE_STRING,   /* code */
367                                     G_TYPE_STRING);  /* name */
368
369         gtk_tree_view_set_model (view, GTK_TREE_MODEL (store));
370
371         selection = gtk_tree_view_get_selection (view);
372         gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
373
374         renderer = gtk_cell_renderer_toggle_new ();
375         g_signal_connect (renderer, "toggled",
376                           G_CALLBACK (preferences_languages_cell_toggled_cb),
377                           preferences);
378
379         column = gtk_tree_view_column_new_with_attributes (NULL, renderer,
380                                                            "active", COL_LANG_ENABLED,
381                                                            NULL);
382
383         gtk_tree_view_append_column (view, column);
384
385         renderer = gtk_cell_renderer_text_new ();
386         col_offset = gtk_tree_view_insert_column_with_attributes (view,
387                                                                   -1, _("Language"),
388                                                                   renderer,
389                                                                   "text", COL_LANG_NAME,
390                                                                   NULL);
391
392         g_object_set_data (G_OBJECT (renderer),
393                            "column", GINT_TO_POINTER (COL_LANG_NAME));
394
395         column = gtk_tree_view_get_column (view, col_offset - 1);
396         gtk_tree_view_column_set_sort_column_id (column, COL_LANG_NAME);
397         gtk_tree_view_column_set_resizable (column, FALSE);
398         gtk_tree_view_column_set_clickable (GTK_TREE_VIEW_COLUMN (column), TRUE);
399
400         g_object_unref (store);
401 }
402
403 static void
404 preferences_languages_add (EmpathyPreferences *preferences)
405 {
406         EmpathyPreferencesPriv *priv = GET_PRIV (preferences);
407         GtkTreeView  *view;
408         GtkListStore *store;
409         GList        *codes, *l;
410
411         view = GTK_TREE_VIEW (priv->treeview_spell_checker);
412         store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
413
414         codes = empathy_spell_get_language_codes ();
415
416         if (!codes) {
417                 gtk_widget_set_sensitive (priv->treeview_spell_checker, FALSE);
418         }
419
420         for (l = codes; l; l = l->next) {
421                 const gchar *code;
422                 const gchar *name;
423
424                 code = l->data;
425                 name = empathy_spell_get_language_name (code);
426                 if (!name) {
427                         continue;
428                 }
429
430                 gtk_list_store_insert_with_values (store, NULL, -1,
431                                     COL_LANG_CODE, code,
432                                     COL_LANG_NAME, name,
433                                     -1);
434         }
435
436         empathy_spell_free_language_codes (codes);
437 }
438
439 static void
440 preferences_languages_save (EmpathyPreferences *preferences)
441 {
442         EmpathyPreferencesPriv *priv = GET_PRIV (preferences);
443         GtkTreeView       *view;
444         GtkTreeModel      *model;
445
446         gchar             *languages = NULL;
447
448         view = GTK_TREE_VIEW (priv->treeview_spell_checker);
449         model = gtk_tree_view_get_model (view);
450
451         gtk_tree_model_foreach (model,
452                                 (GtkTreeModelForeachFunc) preferences_languages_save_foreach,
453                                 &languages);
454
455         /* if user selects no languages, we don't want spell check */
456         g_settings_set_boolean (priv->gsettings_chat,
457                                 EMPATHY_PREFS_CHAT_SPELL_CHECKER_ENABLED,
458                                 languages != NULL);
459
460         g_settings_set_string (priv->gsettings_chat,
461                                EMPATHY_PREFS_CHAT_SPELL_CHECKER_LANGUAGES,
462                                languages != NULL ? languages : "");
463
464         g_free (languages);
465 }
466
467 static gboolean
468 preferences_languages_save_foreach (GtkTreeModel  *model,
469                                     GtkTreePath   *path,
470                                     GtkTreeIter   *iter,
471                                     gchar        **languages)
472 {
473         gboolean  enabled;
474         gchar    *code;
475
476         if (!languages) {
477                 return TRUE;
478         }
479
480         gtk_tree_model_get (model, iter, COL_LANG_ENABLED, &enabled, -1);
481         if (!enabled) {
482                 return FALSE;
483         }
484
485         gtk_tree_model_get (model, iter, COL_LANG_CODE, &code, -1);
486         if (!code) {
487                 return FALSE;
488         }
489
490         if (!(*languages)) {
491                 *languages = g_strdup (code);
492         } else {
493                 gchar *str = *languages;
494                 *languages = g_strdup_printf ("%s,%s", str, code);
495                 g_free (str);
496         }
497
498         g_free (code);
499
500         return FALSE;
501 }
502
503 static void
504 preferences_languages_load (EmpathyPreferences *preferences)
505 {
506         EmpathyPreferencesPriv *priv = GET_PRIV (preferences);
507         GtkTreeView   *view;
508         GtkTreeModel  *model;
509         GList         *enabled_codes;
510
511         enabled_codes = empathy_spell_get_enabled_language_codes ();
512
513         g_settings_set_boolean (priv->gsettings_chat,
514                                 EMPATHY_PREFS_CHAT_SPELL_CHECKER_ENABLED,
515                                 enabled_codes != NULL);
516
517         if (enabled_codes == NULL)
518                 return;
519
520         view = GTK_TREE_VIEW (priv->treeview_spell_checker);
521         model = gtk_tree_view_get_model (view);
522
523         gtk_tree_model_foreach (model,
524                                 (GtkTreeModelForeachFunc) preferences_languages_load_foreach,
525                                 enabled_codes);
526
527         g_list_free (enabled_codes);
528 }
529
530 static gboolean
531 preferences_languages_load_foreach (GtkTreeModel  *model,
532                                     GtkTreePath   *path,
533                                     GtkTreeIter   *iter,
534                                     GList         *languages)
535 {
536         gchar    *code;
537         gboolean  found = FALSE;
538
539         if (!languages) {
540                 return TRUE;
541         }
542
543         gtk_tree_model_get (model, iter, COL_LANG_CODE, &code, -1);
544         if (!code) {
545                 return FALSE;
546         }
547
548         if (g_list_find_custom (languages, code, (GCompareFunc) strcmp)) {
549                 found = TRUE;
550         }
551
552         g_free (code);
553         gtk_list_store_set (GTK_LIST_STORE (model), iter, COL_LANG_ENABLED, found, -1);
554         return FALSE;
555 }
556
557 static void
558 preferences_languages_cell_toggled_cb (GtkCellRendererToggle *cell,
559                                        gchar                 *path_string,
560                                        EmpathyPreferences     *preferences)
561 {
562         EmpathyPreferencesPriv *priv = GET_PRIV (preferences);
563         GtkTreeView  *view;
564         GtkTreeModel *model;
565         GtkListStore *store;
566         GtkTreePath  *path;
567         GtkTreeIter   iter;
568         gboolean      enabled;
569
570         view = GTK_TREE_VIEW (priv->treeview_spell_checker);
571         model = gtk_tree_view_get_model (view);
572         store = GTK_LIST_STORE (model);
573
574         path = gtk_tree_path_new_from_string (path_string);
575
576         gtk_tree_model_get_iter (model, &iter, path);
577         gtk_tree_model_get (model, &iter, COL_LANG_ENABLED, &enabled, -1);
578
579         enabled ^= 1;
580
581         gtk_list_store_set (store, &iter, COL_LANG_ENABLED, enabled, -1);
582         gtk_tree_path_free (path);
583
584         preferences_languages_save (preferences);
585 }
586
587 static void
588 preferences_preview_theme_append_message (EmpathyThemeAdium *view,
589                                           EmpathyContact *sender,
590                                           EmpathyContact *receiver,
591                                           const gchar *text,
592                                           gboolean should_highlight)
593 {
594         EmpathyMessage *message;
595
596         message = g_object_new (EMPATHY_TYPE_MESSAGE,
597                 "sender", sender,
598                 "receiver", receiver,
599                 "body", text,
600                 NULL);
601
602         empathy_theme_adium_append_message (view, message, should_highlight);
603         g_object_unref (message);
604 }
605
606 static void
607 preferences_preview_theme_changed_cb (EmpathyThemeManager *manager,
608                                       EmpathyPreferences  *preferences)
609 {
610         EmpathyPreferencesPriv *priv = GET_PRIV (preferences);
611         EmpathyClientFactory *factory;
612         TpAccount *account;
613         EmpathyContact *juliet;
614         EmpathyContact *romeo;
615
616         DEBUG ("Theme changed, update preview widget");
617
618         if (priv->chat_theme_preview != NULL) {
619                 gtk_widget_destroy (GTK_WIDGET (priv->chat_theme_preview));
620         }
621         priv->chat_theme_preview = empathy_theme_manager_create_view (manager);
622         gtk_container_add (GTK_CONTAINER (priv->sw_chat_theme_preview),
623                            GTK_WIDGET (priv->chat_theme_preview));
624         gtk_widget_show (GTK_WIDGET (priv->chat_theme_preview));
625
626         /* FIXME: It is ugly to add a fake conversation like that.
627          * Would be cool if we could request a TplLogManager for a fake
628          * conversation */
629         factory = empathy_client_factory_dup ();
630
631         account = tp_simple_client_factory_ensure_account (
632                 TP_SIMPLE_CLIENT_FACTORY (factory),
633                 TP_ACCOUNT_OBJECT_PATH_BASE "cm/jabber/account", NULL, NULL);
634         juliet = g_object_new (EMPATHY_TYPE_CONTACT,
635                 "account", account,
636                 "id", "juliet",
637                 /* translators: Contact name for the chat theme preview */
638                 "alias", _("Juliet"),
639                 "is-user", FALSE,
640                 NULL);
641         romeo = g_object_new (EMPATHY_TYPE_CONTACT,
642                 "account", account,
643                 "id", "romeo",
644                 /* translators: Contact name for the chat theme preview */
645                 "alias", _("Romeo"),
646                 "is-user", TRUE,
647                 NULL);
648
649         preferences_preview_theme_append_message (priv->chat_theme_preview,
650                 /* translators: Quote from Romeo & Julier, for chat theme preview */
651                 juliet, romeo, _("O Romeo, Romeo, wherefore art thou Romeo?"),
652                 TRUE /* this message mentions Romeo */);
653         preferences_preview_theme_append_message (priv->chat_theme_preview,
654                 /* translators: Quote from Romeo & Julier, for chat theme preview */
655                 juliet, romeo, _("Deny thy father and refuse thy name;"), FALSE);
656         preferences_preview_theme_append_message (priv->chat_theme_preview,
657                 /* translators: Quote from Romeo & Julier, for chat theme preview */
658                 juliet, romeo, _("Or if thou wilt not, be but sworn my love"), FALSE);
659         preferences_preview_theme_append_message (priv->chat_theme_preview,
660                 /* translators: Quote from Romeo & Julier, for chat theme preview */
661                 juliet, romeo, _("And I'll no longer be a Capulet."), FALSE);
662         preferences_preview_theme_append_message (priv->chat_theme_preview,
663                 /* translators: Quote from Romeo & Julier, for chat theme preview */
664                 romeo, juliet, _("Shall I hear more, or shall I speak at this?"), FALSE);
665
666         /* translators: Quote from Romeo & Julier, for chat theme preview */
667         empathy_theme_adium_append_event (priv->chat_theme_preview, _("Juliet has disconnected"));
668
669         g_object_unref (juliet);
670         g_object_unref (romeo);
671         g_object_unref (account);
672         g_object_unref (factory);
673 }
674
675 static void
676 preferences_theme_variant_changed_cb (GtkComboBox        *combo,
677                                       EmpathyPreferences *preferences)
678 {
679         EmpathyPreferencesPriv *priv = GET_PRIV (preferences);
680         GtkTreeIter   iter;
681
682         if (gtk_combo_box_get_active_iter (combo, &iter)) {
683                 GtkTreeModel *model;
684                 gchar        *name;
685
686                 model = gtk_combo_box_get_model (combo);
687                 gtk_tree_model_get (model, &iter,
688                                     COL_VARIANT_NAME, &name,
689                                     -1);
690
691                 g_settings_set_string (priv->gsettings_chat,
692                                        EMPATHY_PREFS_CHAT_THEME_VARIANT,
693                                        name);
694
695                 g_free (name);
696         }
697 }
698
699 static void
700 preferences_theme_variant_notify_cb (GSettings   *gsettings,
701                                      const gchar *key,
702                                      gpointer     user_data)
703 {
704         EmpathyPreferences *preferences = user_data;
705         EmpathyPreferencesPriv *priv = GET_PRIV (preferences);
706         GtkComboBox        *combo;
707         gchar              *conf_name;
708         GtkTreeModel       *model;
709         GtkTreeIter         iter;
710         GtkTreeIter         default_iter;
711         gboolean            found_default = FALSE;
712         gboolean            found = FALSE;
713         gboolean            ok;
714
715         conf_name = g_settings_get_string (gsettings, EMPATHY_PREFS_CHAT_THEME_VARIANT);
716         combo = GTK_COMBO_BOX (priv->combobox_chat_theme_variant);
717         model = gtk_combo_box_get_model (combo);
718
719         for (ok = gtk_tree_model_get_iter_first (model, &iter);
720              ok && !found;
721              ok = gtk_tree_model_iter_next (model, &iter)) {
722                 gchar *name;
723                 gboolean is_default;
724
725                 gtk_tree_model_get (model, &iter,
726                                     COL_VARIANT_NAME, &name,
727                                     COL_VARIANT_DEFAULT, &is_default,
728                                     -1);
729
730                 if (!tp_strdiff (name, conf_name)) {
731                         found = TRUE;
732                         gtk_combo_box_set_active_iter (combo, &iter);
733                 }
734                 if (is_default) {
735                         found_default = TRUE;
736                         default_iter = iter;
737                 }
738
739                 g_free (name);
740         }
741
742         /* Fallback to the first one. */
743         if (!found) {
744                 if (found_default) {
745                         gtk_combo_box_set_active_iter (combo, &default_iter);
746                 } else if (gtk_tree_model_get_iter_first (model, &iter)) {
747                         gtk_combo_box_set_active_iter (combo, &iter);
748                 }
749         }
750
751         g_free (conf_name);
752 }
753
754 /* return TRUE if we added at least one variant */
755 static gboolean
756 preferences_theme_variants_fill (EmpathyPreferences *preferences,
757                                  GHashTable         *info)
758 {
759         EmpathyPreferencesPriv *priv = GET_PRIV (preferences);
760         GtkTreeModel *model;
761         GtkListStore *store;
762         GPtrArray    *variants;
763         const gchar  *default_variant;
764         guint         i;
765         gboolean      result = FALSE;
766
767         model = gtk_combo_box_get_model (GTK_COMBO_BOX (priv->combobox_chat_theme_variant));
768         store = GTK_LIST_STORE (model);
769         gtk_list_store_clear (store);
770
771         variants = empathy_adium_info_get_available_variants (info);
772         default_variant = empathy_adium_info_get_default_variant (info);
773         for (i = 0; i < variants->len; i++) {
774                 gchar *name = g_ptr_array_index (variants, i);
775
776                 gtk_list_store_insert_with_values (store, NULL, -1,
777                         COL_VARIANT_NAME, name,
778                         COL_VARIANT_DEFAULT, !tp_strdiff (name, default_variant),
779                         -1);
780
781                 result = TRUE;
782         }
783
784         /* Select the variant from the GSetting key */
785         preferences_theme_variant_notify_cb (priv->gsettings_chat,
786                                              EMPATHY_PREFS_CHAT_THEME_VARIANT,
787                                              preferences);
788
789         return result;
790 }
791
792 static void
793 preferences_theme_variants_setup (EmpathyPreferences *preferences)
794 {
795         EmpathyPreferencesPriv *priv = GET_PRIV (preferences);
796         GtkComboBox   *combo;
797         GtkCellLayout *cell_layout;
798         GtkCellRenderer *renderer;
799         GtkListStore  *store;
800
801         combo = GTK_COMBO_BOX (priv->combobox_chat_theme_variant);
802         cell_layout = GTK_CELL_LAYOUT (combo);
803
804         /* Create the model */
805         store = gtk_list_store_new (COL_VARIANT_COUNT,
806                                     G_TYPE_STRING,      /* name */
807                                     G_TYPE_BOOLEAN);    /* is default */
808         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
809                 COL_VARIANT_NAME, GTK_SORT_ASCENDING);
810
811         /* Add cell renderer */
812         renderer = gtk_cell_renderer_text_new ();
813         gtk_cell_layout_pack_start (cell_layout, renderer, TRUE);
814         gtk_cell_layout_set_attributes (cell_layout, renderer,
815                 "text", COL_VARIANT_NAME, NULL);
816
817         gtk_combo_box_set_model (combo, GTK_TREE_MODEL (store));
818         g_object_unref (store);
819
820         g_signal_connect (combo, "changed",
821                           G_CALLBACK (preferences_theme_variant_changed_cb),
822                           preferences);
823
824         /* Track changes of the GSetting key */
825         g_signal_connect (priv->gsettings_chat,
826                           "changed::" EMPATHY_PREFS_CHAT_THEME_VARIANT,
827                           G_CALLBACK (preferences_theme_variant_notify_cb),
828                           preferences);
829 }
830
831 static void
832 preferences_theme_changed_cb (GtkComboBox        *combo,
833                               EmpathyPreferences *preferences)
834 {
835         EmpathyPreferencesPriv *priv = GET_PRIV (preferences);
836         GtkTreeIter   iter;
837
838         if (gtk_combo_box_get_active_iter (combo, &iter)) {
839                 GtkTreeModel *model;
840                 gchar        *name;
841                 GHashTable   *info;
842                 gboolean variant;
843
844                 model = gtk_combo_box_get_model (combo);
845                 gtk_tree_model_get (model, &iter,
846                                     COL_THEME_ADIUM_NAME, &name,
847                                     COL_THEME_ADIUM_INFO, &info,
848                                     -1);
849
850                 g_settings_set_string (priv->gsettings_chat,
851                                        EMPATHY_PREFS_CHAT_THEME,
852                                        name);
853
854                 variant = preferences_theme_variants_fill (preferences, info);
855                 gtk_widget_set_visible (priv->hbox_chat_theme_variant, variant);
856
857                 g_free (name);
858                 tp_clear_pointer (&info, g_hash_table_unref);
859         }
860 }
861
862 static void
863 preferences_theme_notify_cb (GSettings   *gsettings,
864                              const gchar *key,
865                              gpointer     user_data)
866 {
867         EmpathyPreferences *preferences = user_data;
868         EmpathyPreferencesPriv *priv = GET_PRIV (preferences);
869         GtkComboBox        *combo;
870         gchar              *theme;
871         GtkTreeModel       *model;
872         GtkTreeIter         iter;
873         gboolean            found = FALSE;
874         gboolean            ok;
875
876         theme = g_settings_get_string (gsettings, key);
877
878         combo = GTK_COMBO_BOX (priv->combobox_chat_theme);
879         model = gtk_combo_box_get_model (combo);
880         for (ok = gtk_tree_model_get_iter_first (model, &iter);
881              ok && !found;
882              ok = gtk_tree_model_iter_next (model, &iter)) {
883                 gchar *name;
884
885                 gtk_tree_model_get (model, &iter,
886                                     COL_THEME_ADIUM_NAME, &name,
887                                     -1);
888
889                 if (!tp_strdiff (name, theme)) {
890                         found = TRUE;
891                         gtk_combo_box_set_active_iter (combo, &iter);
892                 }
893
894                 g_free (name);
895         }
896
897         /* Fallback to the first one. */
898         if (!found) {
899                 if (gtk_tree_model_get_iter_first (model, &iter)) {
900                         gtk_combo_box_set_active_iter (combo, &iter);
901                 }
902         }
903
904         g_free (theme);
905 }
906
907 static void
908 preferences_themes_setup (EmpathyPreferences *preferences)
909 {
910         EmpathyPreferencesPriv *priv = GET_PRIV (preferences);
911         GtkComboBox   *combo;
912         GtkCellLayout *cell_layout;
913         GtkCellRenderer *renderer;
914         GtkListStore  *store;
915         GList         *adium_themes;
916
917         preferences_theme_variants_setup (preferences);
918
919         combo = GTK_COMBO_BOX (priv->combobox_chat_theme);
920         cell_layout = GTK_CELL_LAYOUT (combo);
921
922         /* Create the model */
923         store = gtk_list_store_new (COL_THEME_COUNT,
924                                     G_TYPE_STRING,      /* Display name */
925                                     G_TYPE_STRING,      /* Adium theme path */
926                                     G_TYPE_HASH_TABLE); /* Adium theme info */
927         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
928                 COL_THEME_VISIBLE_NAME, GTK_SORT_ASCENDING);
929
930         /* Fill the model */
931         adium_themes = empathy_theme_manager_get_adium_themes ();
932         while (adium_themes != NULL) {
933                 GHashTable *info;
934                 const gchar *visible_name;
935                 gchar *name;
936
937                 info = adium_themes->data;
938                 visible_name = tp_asv_get_string (info, "CFBundleName");
939                 name = empathy_theme_manager_dup_theme_name_from_path (
940                         tp_asv_get_string (info, "path"));
941
942                 if (visible_name != NULL && name != NULL) {
943                         gtk_list_store_insert_with_values (store, NULL, -1,
944                                 COL_THEME_VISIBLE_NAME, visible_name,
945                                 COL_THEME_ADIUM_NAME, name,
946                                 COL_THEME_ADIUM_INFO, info,
947                                 -1);
948                 }
949                 g_hash_table_unref (info);
950                 g_free (name);
951                 adium_themes = g_list_delete_link (adium_themes, adium_themes);
952         }
953
954         /* Add cell renderer */
955         renderer = gtk_cell_renderer_text_new ();
956         gtk_cell_layout_pack_start (cell_layout, renderer, TRUE);
957         gtk_cell_layout_set_attributes (cell_layout, renderer,
958                 "text", COL_THEME_VISIBLE_NAME, NULL);
959
960         gtk_combo_box_set_model (combo, GTK_TREE_MODEL (store));
961         g_object_unref (store);
962
963         g_signal_connect (combo, "changed",
964                           G_CALLBACK (preferences_theme_changed_cb),
965                           preferences);
966
967         /* Select the theme from the GSetting key and track changes */
968         preferences_theme_notify_cb (priv->gsettings_chat,
969                                      EMPATHY_PREFS_CHAT_THEME,
970                                      preferences);
971
972         g_signal_connect (priv->gsettings_chat,
973                           "changed::" EMPATHY_PREFS_CHAT_THEME,
974                           G_CALLBACK (preferences_theme_notify_cb),
975                           preferences);
976 }
977
978 static void
979 empathy_preferences_response (GtkDialog *widget,
980                               gint response)
981 {
982         gtk_widget_destroy (GTK_WIDGET (widget));
983 }
984
985 static void
986 empathy_preferences_finalize (GObject *self)
987 {
988         EmpathyPreferencesPriv *priv = GET_PRIV (self);
989
990         g_object_unref (priv->theme_manager);
991
992         g_object_unref (priv->gsettings);
993         g_object_unref (priv->gsettings_chat);
994         g_object_unref (priv->gsettings_call);
995         g_object_unref (priv->gsettings_loc);
996         g_object_unref (priv->gsettings_notify);
997         g_object_unref (priv->gsettings_sound);
998         g_object_unref (priv->gsettings_ui);
999         g_object_unref (priv->gsettings_logger);
1000
1001         G_OBJECT_CLASS (empathy_preferences_parent_class)->finalize (self);
1002 }
1003
1004 static void
1005 empathy_preferences_class_init (EmpathyPreferencesClass *klass)
1006 {
1007         GtkDialogClass *dialog_class = GTK_DIALOG_CLASS (klass);
1008         GObjectClass *object_class = G_OBJECT_CLASS (klass);
1009
1010         dialog_class->response = empathy_preferences_response;
1011
1012         object_class->finalize = empathy_preferences_finalize;
1013
1014         g_type_class_add_private (object_class,
1015                                   sizeof (EmpathyPreferencesPriv));
1016 }
1017
1018 static void
1019 empathy_preferences_init (EmpathyPreferences *preferences)
1020 {
1021         EmpathyPreferencesPriv    *priv;
1022         GtkBuilder                *gui;
1023         gchar                     *filename;
1024         GtkWidget                 *page;
1025
1026         priv = preferences->priv = G_TYPE_INSTANCE_GET_PRIVATE (preferences,
1027                         EMPATHY_TYPE_PREFERENCES, EmpathyPreferencesPriv);
1028
1029         gtk_dialog_add_button (GTK_DIALOG (preferences),
1030                                GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
1031
1032         gtk_container_set_border_width (GTK_CONTAINER (preferences), 5);
1033         gtk_window_set_title (GTK_WINDOW (preferences), _("Preferences"));
1034         gtk_window_set_role (GTK_WINDOW (preferences), "preferences");
1035         gtk_window_set_position (GTK_WINDOW (preferences),
1036                                  GTK_WIN_POS_CENTER_ON_PARENT);
1037         gtk_window_set_icon_name (GTK_WINDOW (preferences), "gtk-preferences");
1038
1039         filename = empathy_file_lookup ("empathy-preferences.ui", "src");
1040         gui = empathy_builder_get_file (filename,
1041                 "notebook", &priv->notebook,
1042                 "vbox_chat_theme", &priv->vbox_chat_theme,
1043                 "combobox_chat_theme", &priv->combobox_chat_theme,
1044                 "combobox_chat_theme_variant", &priv->combobox_chat_theme_variant,
1045                 "hbox_chat_theme_variant", &priv->hbox_chat_theme_variant,
1046                 "sw_chat_theme_preview", &priv->sw_chat_theme_preview,
1047                 "checkbutton_events_notif_area", &priv->checkbutton_events_notif_area,
1048                 "treeview_sounds", &priv->treeview_sounds,
1049                 "treeview_spell_checker", &priv->treeview_spell_checker,
1050                 NULL);
1051         g_free (filename);
1052
1053         gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (preferences))), priv->notebook);
1054         gtk_widget_show (priv->notebook);
1055
1056         priv->gsettings = g_settings_new (EMPATHY_PREFS_SCHEMA);
1057         priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
1058         priv->gsettings_call = g_settings_new (EMPATHY_PREFS_CALL_SCHEMA);
1059         priv->gsettings_loc = g_settings_new (EMPATHY_PREFS_LOCATION_SCHEMA);
1060         priv->gsettings_notify = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
1061         priv->gsettings_sound = g_settings_new (EMPATHY_PREFS_SOUNDS_SCHEMA);
1062         priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
1063         priv->gsettings_logger = g_settings_new (EMPATHY_PREFS_LOGGER_SCHEMA);
1064
1065         /* Create chat theme preview, and track changes */
1066         priv->theme_manager = empathy_theme_manager_dup_singleton ();
1067         tp_g_signal_connect_object (priv->theme_manager, "theme-changed",
1068                           G_CALLBACK (preferences_preview_theme_changed_cb),
1069                           preferences, 0);
1070         preferences_preview_theme_changed_cb (priv->theme_manager, preferences);
1071
1072         preferences_themes_setup (preferences);
1073
1074         preferences_setup_widgets (preferences, gui);
1075
1076         preferences_languages_setup (preferences);
1077         preferences_languages_add (preferences);
1078         preferences_languages_load (preferences);
1079
1080         preferences_sound_setup (preferences);
1081         preferences_sound_load (preferences);
1082
1083         g_object_unref (gui);
1084
1085         if (empathy_spell_supported ()) {
1086                 page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), EMPATHY_PREFERENCES_TAB_SPELL);
1087                 gtk_widget_show (page);
1088         }
1089
1090         page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), EMPATHY_PREFERENCES_TAB_LOCATION);
1091 #ifdef HAVE_GEOCLUE
1092         gtk_widget_show (page);
1093 #else
1094         gtk_widget_hide (page);
1095 #endif
1096 }
1097
1098 static EmpathyPreferencesTab
1099 empathy_preferences_tab_from_string (const gchar *str)
1100 {
1101   guint i;
1102
1103   for (i = 0; i < G_N_ELEMENTS (empathy_preferences_tabs); i++)
1104     {
1105       if (!tp_strdiff (str, empathy_preferences_tabs[i]))
1106         return i;
1107     }
1108
1109   g_warn_if_reached ();
1110   return -1;
1111 }
1112
1113 const gchar *
1114 empathy_preferences_tab_to_string (EmpathyPreferencesTab tab)
1115 {
1116   g_return_val_if_fail (tab < G_N_ELEMENTS (empathy_preferences_tabs), NULL);
1117
1118   return empathy_preferences_tabs[tab];
1119 }
1120
1121 GtkWidget *
1122 empathy_preferences_new (GtkWindow *parent,
1123                          gboolean  shell_running)
1124 {
1125         GtkWidget              *self;
1126         EmpathyPreferencesPriv *priv;
1127         GtkWidget              *notif_page;
1128
1129         g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), NULL);
1130
1131         self = g_object_new (EMPATHY_TYPE_PREFERENCES, NULL);
1132
1133         if (parent != NULL) {
1134                 gtk_window_set_transient_for (GTK_WINDOW (self),
1135                                               parent);
1136         }
1137
1138         /* when running in Gnome Shell we must hide these options since they
1139          * are meaningless in that context:
1140          * - 'Display incoming events in the notification area' (General->Behavior)
1141          * - 'Notifications' tab
1142          */
1143         priv = GET_PRIV (self);
1144         if (shell_running) {
1145                 gtk_widget_hide (priv->checkbutton_events_notif_area);
1146                 notif_page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook),
1147                                                         EMPATHY_PREFERENCES_TAB_NOTIFICATIONS);
1148                 gtk_widget_hide (notif_page);
1149         }
1150
1151         return self;
1152 }
1153
1154 void
1155 empathy_preferences_show_tab (EmpathyPreferences *self,
1156                               const gchar *page)
1157 {
1158         EmpathyPreferencesPriv *priv = GET_PRIV (self);
1159
1160         gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
1161                                        empathy_preferences_tab_from_string (page));
1162 }