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