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