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