]> git.0d.be Git - empathy.git/blob - src/empathy-roster-window.c
45db8a8929b4e11310c8721efb991dd19d3baf0b
[empathy.git] / src / empathy-roster-window.c
1 /*
2  * Copyright (C) 2002-2007 Imendio AB
3  * Copyright (C) 2007-2010 Collabora Ltd.
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: Xavier Claessens <xclaesse@gmail.com>
21  *          Danielle Madeley <danielle.madeley@collabora.co.uk>
22  */
23
24 #include <config.h>
25
26 #include <sys/stat.h>
27 #include <gtk/gtk.h>
28 #include <gdk/gdkkeysyms.h>
29 #include <glib/gi18n.h>
30
31 #include <telepathy-glib/account-manager.h>
32 #include <telepathy-glib/util.h>
33 #include <folks/folks.h>
34
35 #include <libempathy/empathy-contact.h>
36 #include <libempathy/empathy-utils.h>
37 #include <libempathy/empathy-request-util.h>
38 #include <libempathy/empathy-chatroom-manager.h>
39 #include <libempathy/empathy-chatroom.h>
40 #include <libempathy/empathy-contact-list.h>
41 #include <libempathy/empathy-gsettings.h>
42 #include <libempathy/empathy-individual-manager.h>
43 #include <libempathy/empathy-gsettings.h>
44 #include <libempathy/empathy-status-presets.h>
45 #include <libempathy/empathy-tp-contact-factory.h>
46
47 #include <libempathy-gtk/empathy-contact-dialogs.h>
48 #include <libempathy-gtk/empathy-live-search.h>
49 #include <libempathy-gtk/empathy-contact-blocking-dialog.h>
50 #include <libempathy-gtk/empathy-contact-search-dialog.h>
51 #include <libempathy-gtk/empathy-geometry.h>
52 #include <libempathy-gtk/empathy-gtk-enum-types.h>
53 #include <libempathy-gtk/empathy-individual-dialogs.h>
54 #include <libempathy-gtk/empathy-individual-store.h>
55 #include <libempathy-gtk/empathy-individual-store-manager.h>
56 #include <libempathy-gtk/empathy-individual-view.h>
57 #include <libempathy-gtk/empathy-new-message-dialog.h>
58 #include <libempathy-gtk/empathy-new-call-dialog.h>
59 #include <libempathy-gtk/empathy-log-window.h>
60 #include <libempathy-gtk/empathy-presence-chooser.h>
61 #include <libempathy-gtk/empathy-sound-manager.h>
62 #include <libempathy-gtk/empathy-ui-utils.h>
63
64 #include "empathy-accounts-dialog.h"
65 #include "empathy-call-observer.h"
66 #include "empathy-chat-manager.h"
67 #include "empathy-roster-window.h"
68 #include "empathy-preferences.h"
69 #include "empathy-about-dialog.h"
70 #include "empathy-debug-window.h"
71 #include "empathy-new-chatroom-dialog.h"
72 #include "empathy-map-view.h"
73 #include "empathy-chatrooms-window.h"
74 #include "empathy-event-manager.h"
75 #include "empathy-ft-manager.h"
76
77 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
78 #include <libempathy/empathy-debug.h>
79
80 /* Flashing delay for icons (milliseconds). */
81 #define FLASH_TIMEOUT 500
82
83 /* Minimum width of roster window if something goes wrong. */
84 #define MIN_WIDTH 50
85
86 /* Accels (menu shortcuts) can be configured and saved */
87 #define ACCELS_FILENAME "accels.txt"
88
89 /* Name in the geometry file */
90 #define GEOMETRY_NAME "roster-window"
91
92 enum
93 {
94   PAGE_CONTACT_LIST = 0,
95   PAGE_NO_MATCH
96 };
97
98 enum
99 {
100   PROP_0,
101   PROP_SHELL_RUNNING
102 };
103
104 G_DEFINE_TYPE (EmpathyRosterWindow, empathy_roster_window, GTK_TYPE_WINDOW);
105
106 #define GET_PRIV(self) ((EmpathyRosterWindowPriv *)((EmpathyRosterWindow *) self)->priv)
107
108 struct _EmpathyRosterWindowPriv {
109   EmpathyIndividualStore *individual_store;
110   EmpathyIndividualView *individual_view;
111   TpAccountManager *account_manager;
112   EmpathyChatroomManager *chatroom_manager;
113   EmpathyEventManager *event_manager;
114   EmpathySoundManager *sound_mgr;
115   EmpathyCallObserver *call_observer;
116   guint flash_timeout_id;
117   gboolean flash_on;
118   gboolean empty;
119
120   GSettings *gsettings_ui;
121   GSettings *gsettings_contacts;
122
123   GtkWidget *preferences;
124   GtkWidget *main_vbox;
125   GtkWidget *throbber;
126   GtkWidget *throbber_tool_item;
127   GtkWidget *presence_toolbar;
128   GtkWidget *presence_chooser;
129   GtkWidget *errors_vbox;
130   GtkWidget *auth_vbox;
131   GtkWidget *search_bar;
132   GtkWidget *notebook;
133   GtkWidget *no_entry_label;
134
135   GtkToggleAction *show_protocols;
136   GtkRadioAction *sort_by_name;
137   GtkRadioAction *sort_by_status;
138   GtkRadioAction *normal_with_avatars;
139   GtkRadioAction *normal_size;
140   GtkRadioAction *compact_size;
141
142   GtkUIManager *ui_manager;
143   GtkAction *view_history;
144   GtkAction *room_join_favorites;
145   GtkWidget *room_menu;
146   GtkWidget *room_separator;
147   GtkWidget *edit_context;
148   GtkWidget *edit_context_separator;
149
150   GtkActionGroup *balance_action_group;
151   GtkAction *view_balance_show_in_roster;
152   GtkWidget *balance_vbox;
153
154   guint size_timeout_id;
155
156   /* reffed TpAccount* => visible GtkInfoBar* */
157   GHashTable *errors;
158
159   /* EmpathyEvent* => visible GtkInfoBar* */
160   GHashTable *auths;
161
162   /* stores a mapping from TpAccount to Handler ID to prevent
163    * to listen more than once to the status-changed signal */
164   GHashTable *status_changed_handlers;
165
166   /* Actions that are enabled when there are connected accounts */
167   GList *actions_connected;
168
169   gboolean shell_running;
170 };
171
172 static void
173 roster_window_flash_stop (EmpathyRosterWindow *window)
174 {
175   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
176
177   if (priv->flash_timeout_id == 0)
178     return;
179
180   DEBUG ("Stop flashing");
181   g_source_remove (priv->flash_timeout_id);
182   priv->flash_timeout_id = 0;
183   priv->flash_on = FALSE;
184 }
185
186 typedef struct
187 {
188   EmpathyEvent *event;
189   gboolean on;
190   EmpathyRosterWindow *window;
191 } FlashForeachData;
192
193 static gboolean
194 roster_window_flash_foreach (GtkTreeModel *model,
195     GtkTreePath *path,
196     GtkTreeIter *iter,
197     gpointer user_data)
198 {
199   FlashForeachData *data = (FlashForeachData *) user_data;
200   FolksIndividual *individual;
201   EmpathyContact *contact;
202   const gchar *icon_name;
203   GtkTreePath *parent_path = NULL;
204   GtkTreeIter parent_iter;
205   GdkPixbuf *pixbuf = NULL;
206
207   gtk_tree_model_get (model, iter,
208           EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
209           -1);
210
211   if (individual == NULL)
212     return FALSE;
213
214   contact = empathy_contact_dup_from_folks_individual (individual);
215   if (contact != data->event->contact)
216     goto out;
217
218   if (data->on)
219     {
220       icon_name = data->event->icon_name;
221       pixbuf = empathy_pixbuf_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
222     }
223   else
224     {
225       pixbuf = empathy_individual_store_get_individual_status_icon (
226               GET_PRIV (data->window)->individual_store,
227               individual);
228       if (pixbuf != NULL)
229         g_object_ref (pixbuf);
230     }
231
232   gtk_tree_store_set (GTK_TREE_STORE (model), iter,
233       EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, pixbuf,
234       -1);
235
236   /* To make sure the parent is shown correctly, we emit
237    * the row-changed signal on the parent so it prompts
238    * it to be refreshed by the filter func.
239    */
240   if (gtk_tree_model_iter_parent (model, &parent_iter, iter))
241     {
242       parent_path = gtk_tree_model_get_path (model, &parent_iter);
243     }
244
245   if (parent_path != NULL)
246     {
247       gtk_tree_model_row_changed (model, parent_path, &parent_iter);
248       gtk_tree_path_free (parent_path);
249     }
250
251 out:
252   g_object_unref (individual);
253   tp_clear_object (&contact);
254   tp_clear_object (&pixbuf);
255
256   return FALSE;
257 }
258
259 static gboolean
260 roster_window_flash_cb (EmpathyRosterWindow *window)
261 {
262   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
263   GtkTreeModel *model;
264   GSList *events, *l;
265   gboolean found_event = FALSE;
266   FlashForeachData  data;
267
268   priv->flash_on = !priv->flash_on;
269   data.on = priv->flash_on;
270   model = GTK_TREE_MODEL (priv->individual_store);
271
272   events = empathy_event_manager_get_events (priv->event_manager);
273   for (l = events; l; l = l->next)
274     {
275       data.event = l->data;
276       data.window = window;
277       if (!data.event->contact || !data.event->must_ack)
278         continue;
279
280       found_event = TRUE;
281       gtk_tree_model_foreach (model,
282           roster_window_flash_foreach,
283           &data);
284     }
285
286   if (!found_event)
287     roster_window_flash_stop (window);
288
289   return TRUE;
290 }
291
292 static void
293 roster_window_flash_start (EmpathyRosterWindow *window)
294 {
295   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
296
297   if (priv->flash_timeout_id != 0)
298     return;
299
300   DEBUG ("Start flashing");
301   priv->flash_timeout_id = g_timeout_add (FLASH_TIMEOUT,
302       (GSourceFunc) roster_window_flash_cb,
303       window);
304
305   roster_window_flash_cb (window);
306 }
307
308 static void
309 roster_window_remove_auth (EmpathyRosterWindow *window,
310     EmpathyEvent *event)
311 {
312   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
313   GtkWidget *error_widget;
314
315   error_widget = g_hash_table_lookup (priv->auths, event);
316   if (error_widget != NULL)
317     {
318       gtk_widget_destroy (error_widget);
319       g_hash_table_remove (priv->auths, event);
320     }
321 }
322
323 static void
324 roster_window_auth_add_clicked_cb (GtkButton *button,
325     EmpathyRosterWindow *window)
326 {
327   EmpathyEvent *event;
328
329   event = g_object_get_data (G_OBJECT (button), "event");
330
331   empathy_event_approve (event);
332
333   roster_window_remove_auth (window, event);
334 }
335
336 static void
337 roster_window_auth_close_clicked_cb (GtkButton *button,
338     EmpathyRosterWindow *window)
339 {
340   EmpathyEvent *event;
341
342   event = g_object_get_data (G_OBJECT (button), "event");
343
344   empathy_event_decline (event);
345   roster_window_remove_auth (window, event);
346 }
347
348 static void
349 roster_window_auth_display (EmpathyRosterWindow *window,
350     EmpathyEvent *event)
351 {
352   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
353   TpAccount *account = event->account;
354   GtkWidget *info_bar;
355   GtkWidget *content_area;
356   GtkWidget *image;
357   GtkWidget *label;
358   GtkWidget *add_button;
359   GtkWidget *close_button;
360   GtkWidget *action_area;
361   GtkWidget *action_grid;
362   const gchar *icon_name;
363   gchar *str;
364
365   if (g_hash_table_lookup (priv->auths, event) != NULL)
366     return;
367
368   info_bar = gtk_info_bar_new ();
369   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_QUESTION);
370
371   gtk_widget_set_no_show_all (info_bar, TRUE);
372   gtk_box_pack_start (GTK_BOX (priv->auth_vbox), info_bar, FALSE, TRUE, 0);
373   gtk_widget_show (info_bar);
374
375   icon_name = tp_account_get_icon_name (account);
376   image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_SMALL_TOOLBAR);
377   gtk_widget_show (image);
378
379   str = g_markup_printf_escaped ("<b>%s</b>\n%s",
380       tp_account_get_display_name (account),
381       _("Password required"));
382
383   label = gtk_label_new (str);
384   gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
385   gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
386   gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
387   gtk_widget_show (label);
388
389   g_free (str);
390
391   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
392   gtk_box_pack_start (GTK_BOX (content_area), image, FALSE, FALSE, 0);
393   gtk_box_pack_start (GTK_BOX (content_area), label, TRUE, TRUE, 0);
394
395   image = gtk_image_new_from_stock (GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON);
396   add_button = gtk_button_new ();
397   gtk_button_set_image (GTK_BUTTON (add_button), image);
398   gtk_widget_set_tooltip_text (add_button, _("Provide Password"));
399   gtk_widget_show (add_button);
400
401   image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON);
402   close_button = gtk_button_new ();
403   gtk_button_set_image (GTK_BUTTON (close_button), image);
404   gtk_widget_set_tooltip_text (close_button, _("Disconnect"));
405   gtk_widget_show (close_button);
406
407   action_grid = gtk_grid_new ();
408   gtk_grid_set_column_spacing (GTK_GRID (action_grid), 6);
409   gtk_widget_show (action_grid);
410
411   action_area = gtk_info_bar_get_action_area (GTK_INFO_BAR (info_bar));
412   gtk_box_pack_start (GTK_BOX (action_area), action_grid, FALSE, FALSE, 0);
413
414   gtk_grid_attach (GTK_GRID (action_grid), add_button, 0, 0, 1, 1);
415   gtk_grid_attach (GTK_GRID (action_grid), close_button, 1, 0, 1, 1);
416
417   g_object_set_data_full (G_OBJECT (info_bar),
418       "event", event, NULL);
419   g_object_set_data_full (G_OBJECT (add_button),
420       "event", event, NULL);
421   g_object_set_data_full (G_OBJECT (close_button),
422       "event", event, NULL);
423
424   g_signal_connect (add_button, "clicked",
425       G_CALLBACK (roster_window_auth_add_clicked_cb), window);
426   g_signal_connect (close_button, "clicked",
427       G_CALLBACK (roster_window_auth_close_clicked_cb), window);
428
429   gtk_widget_show (priv->auth_vbox);
430
431   g_hash_table_insert (priv->auths, event, info_bar);
432 }
433
434 static void
435 modify_event_count (GtkTreeModel *model,
436     GtkTreeIter *iter,
437     EmpathyEvent *event,
438     gboolean increase)
439 {
440   FolksIndividual *individual;
441   EmpathyContact *contact;
442   guint count;
443
444   gtk_tree_model_get (model, iter,
445       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
446       EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &count,
447       -1);
448
449   if (individual == NULL)
450     return;
451
452   increase ? count++ : count--;
453
454   contact = empathy_contact_dup_from_folks_individual (individual);
455   if (contact == event->contact)
456     gtk_tree_store_set (GTK_TREE_STORE (model), iter,
457         EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, count, -1);
458
459   tp_clear_object (&contact);
460   g_object_unref (individual);
461 }
462
463 static gboolean
464 increase_event_count_foreach (GtkTreeModel *model,
465     GtkTreePath *path,
466     GtkTreeIter *iter,
467     gpointer user_data)
468 {
469   EmpathyEvent *event = user_data;
470
471   modify_event_count (model, iter, event, TRUE);
472
473   return FALSE;
474 }
475
476 static void
477 increase_event_count (EmpathyRosterWindow *self,
478     EmpathyEvent *event)
479 {
480   EmpathyRosterWindowPriv *priv = GET_PRIV (self);
481   GtkTreeModel *model;
482
483   model = GTK_TREE_MODEL (priv->individual_store);
484
485   gtk_tree_model_foreach (model, increase_event_count_foreach, event);
486 }
487
488 static gboolean
489 decrease_event_count_foreach (GtkTreeModel *model,
490     GtkTreePath *path,
491     GtkTreeIter *iter,
492     gpointer user_data)
493 {
494   EmpathyEvent *event = user_data;
495
496   modify_event_count (model, iter, event, FALSE);
497
498   return FALSE;
499 }
500
501 static void
502 decrease_event_count (EmpathyRosterWindow *self,
503     EmpathyEvent *event)
504 {
505   EmpathyRosterWindowPriv *priv = GET_PRIV (self);
506   GtkTreeModel *model;
507
508   model = GTK_TREE_MODEL (priv->individual_store);
509
510   gtk_tree_model_foreach (model, decrease_event_count_foreach, event);
511 }
512
513 static void
514 roster_window_event_added_cb (EmpathyEventManager *manager,
515     EmpathyEvent *event,
516     EmpathyRosterWindow *window)
517 {
518   if (event->contact)
519     {
520       increase_event_count (window, event);
521
522       roster_window_flash_start (window);
523     }
524   else if (event->type == EMPATHY_EVENT_TYPE_AUTH)
525     {
526       roster_window_auth_display (window, event);
527     }
528 }
529
530 static void
531 roster_window_event_removed_cb (EmpathyEventManager *manager,
532     EmpathyEvent *event,
533     EmpathyRosterWindow *window)
534 {
535   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
536   FlashForeachData data;
537
538   if (event->type == EMPATHY_EVENT_TYPE_AUTH)
539     {
540       roster_window_remove_auth (window, event);
541       return;
542     }
543
544   if (!event->contact)
545     return;
546
547   decrease_event_count (window, event);
548
549   data.on = FALSE;
550   data.event = event;
551   data.window = window;
552
553   gtk_tree_model_foreach (GTK_TREE_MODEL (priv->individual_store),
554       roster_window_flash_foreach,
555       &data);
556 }
557
558 static gboolean
559 roster_window_load_events_idle_cb (gpointer user_data)
560 {
561   EmpathyRosterWindow *window = user_data;
562   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
563   GSList *l;
564
565   l = empathy_event_manager_get_events (priv->event_manager);
566   while (l != NULL)
567     {
568       roster_window_event_added_cb (priv->event_manager, l->data,
569           window);
570       l = l->next;
571     }
572
573   return FALSE;
574 }
575
576 static void
577 roster_window_row_activated_cb (EmpathyIndividualView *view,
578     GtkTreePath *path,
579     GtkTreeViewColumn *col,
580     EmpathyRosterWindow *window)
581 {
582   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
583   EmpathyContact *contact = NULL;
584   FolksIndividual *individual;
585   GtkTreeModel *model;
586   GtkTreeIter iter;
587   GSList *events, *l;
588
589   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->individual_view));
590   gtk_tree_model_get_iter (model, &iter, path);
591
592   gtk_tree_model_get (model, &iter, EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL,
593       &individual, -1);
594
595   if (individual != NULL)
596     contact = empathy_contact_dup_from_folks_individual (individual);
597
598   if (contact == NULL)
599     goto OUT;
600
601   /* If the contact has an event activate it, otherwise the
602    * default handler of row-activated will be called. */
603   events = empathy_event_manager_get_events (priv->event_manager);
604   for (l = events; l; l = l->next)
605     {
606       EmpathyEvent *event = l->data;
607
608       if (event->contact == contact)
609         {
610           DEBUG ("Activate event");
611           empathy_event_activate (event);
612
613           /* We don't want the default handler of this signal
614            * (e.g. open a chat) */
615           g_signal_stop_emission_by_name (view, "row-activated");
616           break;
617         }
618     }
619
620   g_object_unref (contact);
621 OUT:
622   tp_clear_object (&individual);
623 }
624
625 static void
626 roster_window_row_deleted_cb (GtkTreeModel *model,
627     GtkTreePath *path,
628     EmpathyRosterWindow *window)
629 {
630   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
631   GtkTreeIter help_iter;
632
633   if (!gtk_tree_model_get_iter_first (model, &help_iter))
634     {
635       priv->empty = TRUE;
636
637       if (empathy_individual_view_is_searching (priv->individual_view))
638         {
639           gchar *tmp;
640
641           tmp = g_strdup_printf ("<b><span size='xx-large'>%s</span></b>",
642               _("No match found"));
643
644           gtk_label_set_markup (GTK_LABEL (priv->no_entry_label), tmp);
645           g_free (tmp);
646
647           gtk_label_set_line_wrap (GTK_LABEL (priv->no_entry_label),
648               TRUE);
649
650           gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
651               PAGE_NO_MATCH);
652         }
653     }
654 }
655
656 static void
657 roster_window_row_inserted_cb (GtkTreeModel      *model,
658     GtkTreePath *path,
659     GtkTreeIter *iter,
660     EmpathyRosterWindow *window)
661 {
662   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
663
664   if (priv->empty)
665     {
666       priv->empty = FALSE;
667       gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
668           PAGE_CONTACT_LIST);
669       gtk_widget_grab_focus (GTK_WIDGET (priv->individual_view));
670
671       /* The store is being filled, it will be done after an idle cb.
672        * So we can then get events. If we do that too soon, event's
673        * contact is not yet in the store and it won't get marked as
674        * having events. */
675       g_idle_add (roster_window_load_events_idle_cb, window);
676     }
677 }
678
679 static void
680 roster_window_remove_error (EmpathyRosterWindow *window,
681     TpAccount *account)
682 {
683   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
684   GtkWidget *error_widget;
685
686   error_widget = g_hash_table_lookup (priv->errors, account);
687   if (error_widget != NULL)
688     {
689       gtk_widget_destroy (error_widget);
690       g_hash_table_remove (priv->errors, account);
691     }
692 }
693
694 static void
695 roster_window_account_disabled_cb (TpAccountManager  *manager,
696     TpAccount *account,
697     EmpathyRosterWindow *window)
698 {
699   roster_window_remove_error (window, account);
700 }
701
702 static void
703 roster_window_error_retry_clicked_cb (GtkButton *button,
704     EmpathyRosterWindow *window)
705 {
706   TpAccount *account;
707
708   account = g_object_get_data (G_OBJECT (button), "account");
709   tp_account_reconnect_async (account, NULL, NULL);
710
711   roster_window_remove_error (window, account);
712 }
713
714 static void
715 roster_window_error_edit_clicked_cb (GtkButton *button,
716     EmpathyRosterWindow *window)
717 {
718   TpAccount *account;
719
720   account = g_object_get_data (G_OBJECT (button), "account");
721
722   empathy_accounts_dialog_show_application (
723       gtk_widget_get_screen (GTK_WIDGET (button)),
724       account, FALSE, FALSE);
725
726   roster_window_remove_error (window, account);
727 }
728
729 static void
730 roster_window_error_close_clicked_cb (GtkButton *button,
731     EmpathyRosterWindow *window)
732 {
733   TpAccount *account;
734
735   account = g_object_get_data (G_OBJECT (button), "account");
736   roster_window_remove_error (window, account);
737 }
738
739 static void
740 roster_window_error_upgrade_sw_clicked_cb (GtkButton *button,
741     EmpathyRosterWindow *window)
742 {
743   TpAccount *account;
744   GtkWidget *dialog;
745
746   account = g_object_get_data (G_OBJECT (button), "account");
747   roster_window_remove_error (window, account);
748
749   dialog = gtk_message_dialog_new (GTK_WINDOW (window),
750       GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
751       GTK_BUTTONS_OK,
752       _("Sorry, %s accounts can’t be used until your %s software is updated."),
753       tp_account_get_protocol (account),
754       tp_account_get_protocol (account));
755
756   g_signal_connect_swapped (dialog, "response",
757       G_CALLBACK (gtk_widget_destroy),
758       dialog);
759
760   gtk_widget_show (dialog);
761 }
762
763 static void
764 roster_window_upgrade_software_error (EmpathyRosterWindow *window,
765     TpAccount *account)
766 {
767   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
768   GtkWidget *info_bar;
769   GtkWidget *content_area;
770   GtkWidget *label;
771   GtkWidget *image;
772   GtkWidget *upgrade_button;
773   GtkWidget *close_button;
774   GtkWidget *action_area;
775   GtkWidget *action_grid;
776   gchar *str;
777   const gchar *icon_name;
778   const gchar *error_message;
779   gboolean user_requested;
780
781   error_message =
782     empathy_account_get_error_message (account, &user_requested);
783
784   if (user_requested)
785     return;
786
787   str = g_markup_printf_escaped ("<b>%s</b>\n%s",
788       tp_account_get_display_name (account),
789       error_message);
790
791   /* If there are other errors, remove them */
792   roster_window_remove_error (window, account);
793
794   info_bar = gtk_info_bar_new ();
795   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_ERROR);
796
797   gtk_widget_set_no_show_all (info_bar, TRUE);
798   gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar, FALSE, TRUE, 0);
799   gtk_widget_show (info_bar);
800
801   icon_name = tp_account_get_icon_name (account);
802   image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_SMALL_TOOLBAR);
803   gtk_widget_show (image);
804
805   label = gtk_label_new (str);
806   gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
807   gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
808   gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
809   gtk_widget_show (label);
810   g_free (str);
811
812   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
813   gtk_box_pack_start (GTK_BOX (content_area), image, FALSE, FALSE, 0);
814   gtk_box_pack_start (GTK_BOX (content_area), label, TRUE, TRUE, 0);
815
816   image = gtk_image_new_from_stock (GTK_STOCK_REFRESH, GTK_ICON_SIZE_BUTTON);
817   upgrade_button = gtk_button_new ();
818   gtk_button_set_image (GTK_BUTTON (upgrade_button), image);
819   gtk_widget_set_tooltip_text (upgrade_button, _("Update software..."));
820   gtk_widget_show (upgrade_button);
821
822   image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON);
823   close_button = gtk_button_new ();
824   gtk_button_set_image (GTK_BUTTON (close_button), image);
825   gtk_widget_set_tooltip_text (close_button, _("Close"));
826   gtk_widget_show (close_button);
827
828   action_grid = gtk_grid_new ();
829   gtk_grid_set_column_spacing (GTK_GRID (action_grid), 2);
830   gtk_widget_show (action_grid);
831
832   action_area = gtk_info_bar_get_action_area (GTK_INFO_BAR (info_bar));
833   gtk_box_pack_start (GTK_BOX (action_area), action_grid, FALSE, FALSE, 0);
834
835   gtk_grid_attach (GTK_GRID (action_grid), upgrade_button, 0, 0, 1, 1);
836   gtk_grid_attach (GTK_GRID (action_grid), close_button, 1, 0, 1, 1);
837
838   g_object_set_data (G_OBJECT (info_bar), "label", label);
839   g_object_set_data_full (G_OBJECT (info_bar),
840       "account", g_object_ref (account),
841       g_object_unref);
842   g_object_set_data_full (G_OBJECT (upgrade_button),
843       "account", g_object_ref (account),
844       g_object_unref);
845   g_object_set_data_full (G_OBJECT (close_button),
846       "account", g_object_ref (account),
847       g_object_unref);
848
849   g_signal_connect (upgrade_button, "clicked",
850       G_CALLBACK (roster_window_error_upgrade_sw_clicked_cb), window);
851   g_signal_connect (close_button, "clicked",
852       G_CALLBACK (roster_window_error_close_clicked_cb), window);
853
854   gtk_widget_set_tooltip_text (priv->errors_vbox, error_message);
855   gtk_widget_show (priv->errors_vbox);
856
857   g_hash_table_insert (priv->errors, g_object_ref (account), info_bar);
858 }
859
860 static void
861 roster_window_error_display (EmpathyRosterWindow *window,
862          TpAccount         *account)
863 {
864   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
865   GtkWidget *info_bar;
866   GtkWidget *content_area;
867   GtkWidget *label;
868   GtkWidget *image;
869   GtkWidget *retry_button;
870   GtkWidget *edit_button;
871   GtkWidget *close_button;
872   GtkWidget *action_area;
873   GtkWidget *action_grid;
874   gchar *str;
875   const gchar *icon_name;
876   const gchar *error_message;
877   gboolean user_requested;
878
879   if (!tp_strdiff (TP_ERROR_STR_SOFTWARE_UPGRADE_REQUIRED,
880        tp_account_get_detailed_error (account, NULL)))
881     {
882       roster_window_upgrade_software_error (window, account);
883       return;
884     }
885
886   error_message = empathy_account_get_error_message (account, &user_requested);
887
888   if (user_requested)
889     return;
890
891   str = g_markup_printf_escaped ("<b>%s</b>\n%s",
892       tp_account_get_display_name (account), error_message);
893
894   info_bar = g_hash_table_lookup (priv->errors, account);
895   if (info_bar)
896     {
897       label = g_object_get_data (G_OBJECT (info_bar), "label");
898
899       /* Just set the latest error and return */
900       gtk_label_set_markup (GTK_LABEL (label), str);
901       g_free (str);
902
903       return;
904     }
905
906   info_bar = gtk_info_bar_new ();
907   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_ERROR);
908
909   gtk_widget_set_no_show_all (info_bar, TRUE);
910   gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar, FALSE, TRUE, 0);
911   gtk_widget_show (info_bar);
912
913   icon_name = tp_account_get_icon_name (account);
914   image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_SMALL_TOOLBAR);
915   gtk_widget_show (image);
916
917   label = gtk_label_new (str);
918   gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
919   gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
920   gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
921   gtk_widget_show (label);
922   g_free (str);
923
924   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
925   gtk_box_pack_start (GTK_BOX (content_area), image, FALSE, FALSE, 0);
926   gtk_box_pack_start (GTK_BOX (content_area), label, TRUE, TRUE, 0);
927
928   image = gtk_image_new_from_stock (GTK_STOCK_REFRESH, GTK_ICON_SIZE_BUTTON);
929   retry_button = gtk_button_new ();
930   gtk_button_set_image (GTK_BUTTON (retry_button), image);
931   gtk_widget_set_tooltip_text (retry_button, _("Reconnect"));
932   gtk_widget_show (retry_button);
933
934   image = gtk_image_new_from_stock (GTK_STOCK_EDIT, GTK_ICON_SIZE_BUTTON);
935   edit_button = gtk_button_new ();
936   gtk_button_set_image (GTK_BUTTON (edit_button), image);
937   gtk_widget_set_tooltip_text (edit_button, _("Edit Account"));
938   gtk_widget_show (edit_button);
939
940   image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON);
941   close_button = gtk_button_new ();
942   gtk_button_set_image (GTK_BUTTON (close_button), image);
943   gtk_widget_set_tooltip_text (close_button, _("Close"));
944   gtk_widget_show (close_button);
945
946   action_grid = gtk_grid_new ();
947   gtk_grid_set_column_spacing (GTK_GRID (action_grid), 2);
948   gtk_widget_show (action_grid);
949
950   action_area = gtk_info_bar_get_action_area (GTK_INFO_BAR (info_bar));
951   gtk_box_pack_start (GTK_BOX (action_area), action_grid, FALSE, FALSE, 0);
952
953   gtk_grid_attach (GTK_GRID (action_grid), retry_button, 0, 0, 1, 1);
954   gtk_grid_attach (GTK_GRID (action_grid), edit_button, 1, 0, 1, 1);
955   gtk_grid_attach (GTK_GRID (action_grid), close_button, 2, 0, 1, 1);
956
957   g_object_set_data (G_OBJECT (info_bar), "label", label);
958   g_object_set_data_full (G_OBJECT (info_bar),
959       "account", g_object_ref (account),
960       g_object_unref);
961   g_object_set_data_full (G_OBJECT (edit_button),
962       "account", g_object_ref (account),
963       g_object_unref);
964   g_object_set_data_full (G_OBJECT (close_button),
965       "account", g_object_ref (account),
966       g_object_unref);
967   g_object_set_data_full (G_OBJECT (retry_button),
968       "account", g_object_ref (account),
969       g_object_unref);
970
971   g_signal_connect (edit_button, "clicked",
972       G_CALLBACK (roster_window_error_edit_clicked_cb), window);
973   g_signal_connect (close_button, "clicked",
974       G_CALLBACK (roster_window_error_close_clicked_cb), window);
975   g_signal_connect (retry_button, "clicked",
976       G_CALLBACK (roster_window_error_retry_clicked_cb), window);
977
978   gtk_widget_set_tooltip_text (priv->errors_vbox, error_message);
979   gtk_widget_show (priv->errors_vbox);
980
981   g_hash_table_insert (priv->errors, g_object_ref (account), info_bar);
982 }
983
984 static void
985 roster_window_update_status (EmpathyRosterWindow *window)
986 {
987   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
988   gboolean connected, connecting;
989   GList *l, *children;
990
991   connected = empathy_account_manager_get_accounts_connected (&connecting);
992
993   /* Update the spinner state */
994   if (connecting)
995     {
996       gtk_spinner_start (GTK_SPINNER (priv->throbber));
997       gtk_widget_show (priv->throbber_tool_item);
998     }
999   else
1000     {
1001       gtk_spinner_stop (GTK_SPINNER (priv->throbber));
1002       gtk_widget_hide (priv->throbber_tool_item);
1003     }
1004
1005   /* Update widgets sensibility */
1006   for (l = priv->actions_connected; l; l = l->next)
1007     gtk_action_set_sensitive (l->data, connected);
1008
1009   /* Update favourite rooms sensitivity */
1010   children = gtk_container_get_children (GTK_CONTAINER (priv->room_menu));
1011   for (l = children; l != NULL; l = l->next)
1012     {
1013       if (g_object_get_data (G_OBJECT (l->data), "is_favorite") != NULL)
1014         gtk_widget_set_sensitive (GTK_WIDGET (l->data), connected);
1015     }
1016
1017   g_list_free (children);
1018 }
1019
1020 static char *
1021 roster_window_account_to_action_name (TpAccount *account)
1022 {
1023   char *r;
1024
1025   /* action names can't have '/' in them, replace it with '.' */
1026   r = g_strdup (tp_account_get_path_suffix (account));
1027   r = g_strdelimit (r, "/", '.');
1028
1029   return r;
1030 }
1031
1032 static void
1033 roster_window_balance_activate_cb (GtkAction *action,
1034     EmpathyRosterWindow *window)
1035 {
1036   const char *uri;
1037
1038   uri = g_object_get_data (G_OBJECT (action), "manage-credit-uri");
1039
1040   if (!tp_str_empty (uri))
1041     {
1042       DEBUG ("Top-up credit URI: %s", uri);
1043       empathy_url_show (GTK_WIDGET (window), uri);
1044     }
1045   else
1046     {
1047       DEBUG ("unknown protocol for top-up");
1048     }
1049 }
1050
1051 static void
1052 roster_window_balance_update_balance (GtkAction *action,
1053     TpConnection *conn)
1054 {
1055   TpAccount *account = tp_connection_get_account (conn);
1056   GtkWidget *label;
1057   int amount = 0;
1058   guint scale = G_MAXINT32;
1059   const gchar *currency = "";
1060   char *money, *str;
1061
1062   if (!tp_connection_get_balance (conn, &amount, &scale, &currency))
1063     return;
1064
1065   if (amount == 0 &&
1066       scale == G_MAXINT32 &&
1067       tp_str_empty (currency))
1068     {
1069       /* unknown balance */
1070       money = g_strdup ("--");
1071     }
1072   else
1073     {
1074       char *tmp = empathy_format_currency (amount, scale, currency);
1075
1076       money = g_strdup_printf ("%s %s", currency, tmp);
1077       g_free (tmp);
1078     }
1079
1080   /* Translators: this string will be something like:
1081    * Top up My Account ($1.23)..." */
1082   str = g_strdup_printf (_("Top up %s (%s)..."),
1083     tp_account_get_display_name (account), money);
1084
1085   gtk_action_set_label (action, str);
1086   g_free (str);
1087
1088   /* update the money label in the roster */
1089   label = g_object_get_data (G_OBJECT (action), "money-label");
1090
1091   gtk_label_set_text (GTK_LABEL (label), money);
1092   g_free (money);
1093 }
1094
1095 static void
1096 roster_window_balance_changed_cb (TpConnection *conn,
1097     guint balance,
1098     guint scale,
1099     const gchar *currency,
1100     GtkAction *action)
1101 {
1102   roster_window_balance_update_balance (action, conn);
1103 }
1104
1105 static GtkAction *
1106 roster_window_setup_balance_create_action (EmpathyRosterWindow *window,
1107     TpAccount *account)
1108 {
1109   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
1110   GtkAction *action;
1111   char *name, *ui;
1112   guint merge_id;
1113   GError *error = NULL;
1114
1115   /* create the action group if required */
1116   if (priv->balance_action_group == NULL)
1117     {
1118       priv->balance_action_group =
1119         gtk_action_group_new ("balance-action-group");
1120
1121       gtk_ui_manager_insert_action_group (priv->ui_manager,
1122         priv->balance_action_group, -1);
1123     }
1124
1125   /* create the action */
1126   name = roster_window_account_to_action_name (account);
1127   action = gtk_action_new (name,
1128       tp_account_get_display_name (account),
1129       _("Top up account credit"), NULL);
1130
1131   g_object_bind_property (account, "icon-name", action, "icon-name",
1132       G_BINDING_SYNC_CREATE);
1133
1134   g_signal_connect (action, "activate",
1135       G_CALLBACK (roster_window_balance_activate_cb), window);
1136
1137   gtk_action_group_add_action (priv->balance_action_group, action);
1138   g_object_unref (action);
1139
1140   ui = g_strdup_printf (
1141       "<ui>"
1142       " <menubar name='menubar'>"
1143       "  <menu action='view'>"
1144       "   <placeholder name='view_balance_placeholder'>"
1145       "    <menuitem action='%s'/>"
1146       "   </placeholder>"
1147       "  </menu>"
1148       " </menubar>"
1149       "</ui>",
1150       name);
1151
1152   merge_id = gtk_ui_manager_add_ui_from_string (priv->ui_manager,
1153       ui, -1, &error);
1154
1155   if (error != NULL)
1156     {
1157       DEBUG ("Failed to add balance UI for %s: %s",
1158         tp_account_get_display_name (account),
1159         error->message);
1160       g_error_free (error);
1161     }
1162
1163   g_object_set_data (G_OBJECT (action),
1164       "merge-id", GUINT_TO_POINTER (merge_id));
1165
1166   g_free (name);
1167   g_free (ui);
1168
1169   return action;
1170 }
1171
1172 static GtkWidget *
1173 roster_window_setup_balance_create_widget (EmpathyRosterWindow *window,
1174     GtkAction *action,
1175     TpAccount *account)
1176 {
1177   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
1178   GtkWidget *hbox, *image, *label, *button;
1179
1180   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
1181
1182   /* protocol icon */
1183   image = gtk_image_new ();
1184   gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, TRUE, 0);
1185   g_object_bind_property (action, "icon-name", image, "icon-name",
1186       G_BINDING_SYNC_CREATE);
1187
1188   /* account name label */
1189   label = gtk_label_new ("");
1190   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1191   gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
1192   g_object_bind_property (account, "display-name", label, "label",
1193       G_BINDING_SYNC_CREATE);
1194
1195   /* balance label */
1196   label = gtk_label_new ("");
1197   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1198   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 0);
1199   g_object_set_data (G_OBJECT (action), "money-label", label);
1200
1201   /* top up button */
1202   button = gtk_button_new_with_label (_("Top Up..."));
1203   gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, TRUE, 0);
1204   g_signal_connect_swapped (button, "clicked",
1205       G_CALLBACK (gtk_action_activate), action);
1206
1207   gtk_box_pack_start (GTK_BOX (priv->balance_vbox), hbox, FALSE, TRUE, 0);
1208   gtk_widget_show_all (hbox);
1209
1210   /* tie the lifetime of the widget to the lifetime of the action */
1211   g_object_weak_ref (G_OBJECT (action),
1212       (GWeakNotify) gtk_widget_destroy, hbox);
1213
1214   return hbox;
1215 }
1216
1217 static void
1218 roster_window_setup_balance (EmpathyRosterWindow *window,
1219     TpAccount *account)
1220 {
1221   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
1222   TpConnection *conn = tp_account_get_connection (account);
1223   GtkAction *action;
1224   const gchar *uri;
1225
1226   if (conn == NULL)
1227     return;
1228
1229   if (!tp_proxy_is_prepared (conn, TP_CONNECTION_FEATURE_BALANCE))
1230     return;
1231
1232   DEBUG ("Setting up balance for acct: %s",
1233       tp_account_get_display_name (account));
1234
1235   /* create the action */
1236   action = roster_window_setup_balance_create_action (window, account);
1237
1238   if (action == NULL)
1239     return;
1240
1241   gtk_action_set_visible (priv->view_balance_show_in_roster, TRUE);
1242
1243   /* create the display widget */
1244   roster_window_setup_balance_create_widget (window, action, account);
1245
1246   /* check the current balance and monitor for any changes */
1247   uri = tp_connection_get_balance_uri (conn);
1248
1249   g_object_set_data_full (G_OBJECT (action), "manage-credit-uri",
1250       g_strdup (uri), g_free);
1251   gtk_action_set_sensitive (GTK_ACTION (action), !tp_str_empty (uri));
1252
1253   roster_window_balance_update_balance (GTK_ACTION (action), conn);
1254
1255   g_signal_connect (conn, "balance-changed",
1256       G_CALLBACK (roster_window_balance_changed_cb), action);
1257 }
1258
1259 static void
1260 roster_window_remove_balance_action (EmpathyRosterWindow *window,
1261     TpAccount *account)
1262 {
1263   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
1264   GtkAction *action;
1265   char *name;
1266   GList *a;
1267
1268   if (priv->balance_action_group == NULL)
1269     return;
1270
1271   name = roster_window_account_to_action_name (account);
1272
1273   action = gtk_action_group_get_action (
1274       priv->balance_action_group, name);
1275
1276   if (action != NULL)
1277     {
1278       guint merge_id;
1279
1280       DEBUG ("Removing action");
1281
1282       merge_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (action),
1283             "merge-id"));
1284
1285       gtk_ui_manager_remove_ui (priv->ui_manager,
1286           merge_id);
1287       gtk_action_group_remove_action (
1288           priv->balance_action_group, action);
1289     }
1290
1291   g_free (name);
1292
1293   a = gtk_action_group_list_actions (
1294       priv->balance_action_group);
1295
1296   gtk_action_set_visible (priv->view_balance_show_in_roster,
1297       g_list_length (a) > 0);
1298
1299   g_list_free (a);
1300 }
1301
1302 static void
1303 roster_window_connection_changed_cb (TpAccount  *account,
1304     guint old_status,
1305     guint current,
1306     guint reason,
1307     gchar *dbus_error_name,
1308     GHashTable *details,
1309     EmpathyRosterWindow *window)
1310 {
1311   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
1312
1313   roster_window_update_status (window);
1314
1315   if (current == TP_CONNECTION_STATUS_DISCONNECTED &&
1316       reason != TP_CONNECTION_STATUS_REASON_REQUESTED)
1317     {
1318       roster_window_error_display (window, account);
1319     }
1320
1321   if (current == TP_CONNECTION_STATUS_DISCONNECTED)
1322     {
1323       empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (window),
1324               EMPATHY_SOUND_ACCOUNT_DISCONNECTED);
1325
1326       /* remove balance action if required */
1327       roster_window_remove_balance_action (window, account);
1328     }
1329
1330   if (current == TP_CONNECTION_STATUS_CONNECTED)
1331     {
1332       empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (window),
1333               EMPATHY_SOUND_ACCOUNT_CONNECTED);
1334
1335       /* Account connected without error, remove error message if any */
1336       roster_window_remove_error (window, account);
1337       roster_window_setup_balance (window, account);
1338     }
1339 }
1340
1341 static void
1342 roster_window_accels_load (void)
1343 {
1344   gchar *filename;
1345
1346   filename = g_build_filename (g_get_user_config_dir (),
1347       PACKAGE_NAME, ACCELS_FILENAME, NULL);
1348   if (g_file_test (filename, G_FILE_TEST_EXISTS))
1349     {
1350       DEBUG ("Loading from:'%s'", filename);
1351       gtk_accel_map_load (filename);
1352     }
1353
1354   g_free (filename);
1355 }
1356
1357 static void
1358 roster_window_accels_save (void)
1359 {
1360   gchar *dir;
1361   gchar *file_with_path;
1362
1363   dir = g_build_filename (g_get_user_config_dir (), PACKAGE_NAME, NULL);
1364   g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
1365   file_with_path = g_build_filename (dir, ACCELS_FILENAME, NULL);
1366   g_free (dir);
1367
1368   DEBUG ("Saving to:'%s'", file_with_path);
1369   gtk_accel_map_save (file_with_path);
1370
1371   g_free (file_with_path);
1372 }
1373
1374 static void
1375 empathy_roster_window_finalize (GObject *window)
1376 {
1377   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
1378   GHashTableIter iter;
1379   gpointer key, value;
1380
1381   /* Save user-defined accelerators. */
1382   roster_window_accels_save ();
1383
1384   g_list_free (priv->actions_connected);
1385
1386   g_object_unref (priv->account_manager);
1387   g_object_unref (priv->individual_store);
1388   g_object_unref (priv->sound_mgr);
1389   g_hash_table_unref (priv->errors);
1390   g_hash_table_unref (priv->auths);
1391
1392   /* disconnect all handlers of status-changed signal */
1393   g_hash_table_iter_init (&iter, priv->status_changed_handlers);
1394   while (g_hash_table_iter_next (&iter, &key, &value))
1395     g_signal_handler_disconnect (TP_ACCOUNT (key), GPOINTER_TO_UINT (value));
1396
1397   g_hash_table_unref (priv->status_changed_handlers);
1398
1399   g_signal_handlers_disconnect_by_func (priv->event_manager,
1400       roster_window_event_added_cb, window);
1401   g_signal_handlers_disconnect_by_func (priv->event_manager,
1402       roster_window_event_removed_cb, window);
1403
1404   g_object_unref (priv->call_observer);
1405   g_object_unref (priv->event_manager);
1406   g_object_unref (priv->ui_manager);
1407   g_object_unref (priv->chatroom_manager);
1408
1409   g_object_unref (priv->gsettings_ui);
1410   g_object_unref (priv->gsettings_contacts);
1411
1412   G_OBJECT_CLASS (empathy_roster_window_parent_class)->finalize (window);
1413 }
1414
1415 static gboolean
1416 roster_window_key_press_event_cb  (GtkWidget *window,
1417     GdkEventKey *event,
1418     gpointer user_data)
1419 {
1420   if (event->keyval == GDK_KEY_T
1421       && event->state & GDK_SHIFT_MASK
1422       && event->state & GDK_CONTROL_MASK)
1423     empathy_chat_manager_call_undo_closed_chat ();
1424
1425   return FALSE;
1426 }
1427
1428 static void
1429 roster_window_chat_quit_cb (GtkAction *action,
1430     EmpathyRosterWindow *window)
1431 {
1432   gtk_widget_destroy (GTK_WIDGET (window));
1433 }
1434
1435 static void
1436 roster_window_view_history_cb (GtkAction *action,
1437     EmpathyRosterWindow *window)
1438 {
1439   empathy_log_window_show (NULL, NULL, FALSE, GTK_WINDOW (window));
1440 }
1441
1442 static void
1443 roster_window_chat_new_message_cb (GtkAction *action,
1444     EmpathyRosterWindow *window)
1445 {
1446   empathy_new_message_dialog_show (GTK_WINDOW (window));
1447 }
1448
1449 static void
1450 roster_window_chat_new_call_cb (GtkAction *action,
1451     EmpathyRosterWindow *window)
1452 {
1453   empathy_new_call_dialog_show (GTK_WINDOW (window));
1454 }
1455
1456 static void
1457 roster_window_chat_add_contact_cb (GtkAction *action,
1458     EmpathyRosterWindow *window)
1459 {
1460   empathy_new_individual_dialog_show (GTK_WINDOW (window));
1461 }
1462
1463 static void
1464 roster_window_chat_search_contacts_cb (GtkAction *action,
1465     EmpathyRosterWindow *window)
1466 {
1467   GtkWidget *dialog = empathy_contact_search_dialog_new (
1468       GTK_WINDOW (window));
1469
1470   gtk_widget_show (dialog);
1471 }
1472
1473 static void
1474 roster_window_view_show_ft_manager (GtkAction *action,
1475     EmpathyRosterWindow *window)
1476 {
1477   empathy_ft_manager_show ();
1478 }
1479
1480 static void
1481 roster_window_view_show_offline_cb (GtkToggleAction   *action,
1482     EmpathyRosterWindow *window)
1483 {
1484   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
1485   gboolean current;
1486
1487   current = gtk_toggle_action_get_active (action);
1488   g_settings_set_boolean (priv->gsettings_ui,
1489       EMPATHY_PREFS_UI_SHOW_OFFLINE,
1490       current);
1491
1492   empathy_individual_view_set_show_offline (priv->individual_view,
1493       current);
1494 }
1495
1496 static void
1497 roster_window_notify_sort_contact_cb (GSettings         *gsettings,
1498     const gchar *key,
1499     EmpathyRosterWindow *window)
1500 {
1501   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
1502   gchar *str;
1503
1504   str = g_settings_get_string (gsettings, key);
1505
1506   if (str != NULL)
1507     {
1508       GType       type;
1509       GEnumClass *enum_class;
1510       GEnumValue *enum_value;
1511
1512       type = empathy_individual_store_sort_get_type ();
1513       enum_class = G_ENUM_CLASS (g_type_class_peek (type));
1514       enum_value = g_enum_get_value_by_nick (enum_class, str);
1515       if (enum_value)
1516         {
1517           /* By changing the value of the GtkRadioAction,
1518              it emits a signal that calls roster_window_view_sort_contacts_cb
1519              which updates the contacts list */
1520           gtk_radio_action_set_current_value (priv->sort_by_name,
1521                       enum_value->value);
1522         }
1523       else
1524         {
1525           g_warning ("Wrong value for sort_criterium configuration : %s", str);
1526         }
1527     g_free (str);
1528   }
1529 }
1530
1531 static void
1532 roster_window_view_sort_contacts_cb (GtkRadioAction *action,
1533     GtkRadioAction *current,
1534     EmpathyRosterWindow *window)
1535 {
1536   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
1537   EmpathyIndividualStoreSort value;
1538   GSList *group;
1539   GType type;
1540   GEnumClass *enum_class;
1541   GEnumValue *enum_value;
1542
1543   value = gtk_radio_action_get_current_value (action);
1544   group = gtk_radio_action_get_group (action);
1545
1546   /* Get string from index */
1547   type = empathy_individual_store_sort_get_type ();
1548   enum_class = G_ENUM_CLASS (g_type_class_peek (type));
1549   enum_value = g_enum_get_value (enum_class, g_slist_index (group, current));
1550
1551   if (!enum_value)
1552     g_warning ("No GEnumValue for EmpathyContactListSort with GtkRadioAction index:%d",
1553         g_slist_index (group, action));
1554   else
1555     g_settings_set_string (priv->gsettings_contacts,
1556         EMPATHY_PREFS_CONTACTS_SORT_CRITERIUM,
1557         enum_value->value_nick);
1558
1559   empathy_individual_store_set_sort_criterium (priv->individual_store,
1560       value);
1561 }
1562
1563 static void
1564 roster_window_view_show_protocols_cb (GtkToggleAction *action,
1565     EmpathyRosterWindow *window)
1566 {
1567   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
1568   gboolean value;
1569
1570   value = gtk_toggle_action_get_active (action);
1571
1572   g_settings_set_boolean (priv->gsettings_ui,
1573       EMPATHY_PREFS_UI_SHOW_PROTOCOLS,
1574       value);
1575
1576   empathy_individual_store_set_show_protocols (priv->individual_store,
1577       value);
1578 }
1579
1580 /* Matches GtkRadioAction values set in empathy-roster-window.ui */
1581 #define CONTACT_LIST_NORMAL_SIZE_WITH_AVATARS   0
1582 #define CONTACT_LIST_NORMAL_SIZE      1
1583 #define CONTACT_LIST_COMPACT_SIZE     2
1584
1585 static void
1586 roster_window_view_contacts_list_size_cb (GtkRadioAction *action,
1587     GtkRadioAction *current,
1588     EmpathyRosterWindow *window)
1589 {
1590   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
1591   GSettings *gsettings_ui;
1592   gint value;
1593
1594   value = gtk_radio_action_get_current_value (action);
1595   /* create a new GSettings, so we can delay the setting until both
1596    * values are set */
1597   gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
1598
1599   DEBUG ("radio button toggled, value = %i", value);
1600
1601   g_settings_delay (gsettings_ui);
1602   g_settings_set_boolean (gsettings_ui, EMPATHY_PREFS_UI_SHOW_AVATARS,
1603       value == CONTACT_LIST_NORMAL_SIZE_WITH_AVATARS);
1604
1605   g_settings_set_boolean (gsettings_ui, EMPATHY_PREFS_UI_COMPACT_CONTACT_LIST,
1606       value == CONTACT_LIST_COMPACT_SIZE);
1607   g_settings_apply (gsettings_ui);
1608
1609   /* FIXME: these enums probably have the wrong namespace */
1610   empathy_individual_store_set_show_avatars (priv->individual_store,
1611       value == CONTACT_LIST_NORMAL_SIZE_WITH_AVATARS);
1612   empathy_individual_store_set_is_compact (priv->individual_store,
1613       value == CONTACT_LIST_COMPACT_SIZE);
1614
1615   g_object_unref (gsettings_ui);
1616 }
1617
1618 static void roster_window_notify_show_protocols_cb (GSettings *gsettings,
1619     const gchar *key,
1620     EmpathyRosterWindow *window)
1621 {
1622   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
1623
1624   gtk_toggle_action_set_active (priv->show_protocols,
1625       g_settings_get_boolean (gsettings, EMPATHY_PREFS_UI_SHOW_PROTOCOLS));
1626 }
1627
1628
1629 static void
1630 roster_window_notify_contact_list_size_cb (GSettings *gsettings,
1631     const gchar *key,
1632     EmpathyRosterWindow *window)
1633 {
1634   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
1635   gint value;
1636
1637   if (g_settings_get_boolean (gsettings,
1638       EMPATHY_PREFS_UI_COMPACT_CONTACT_LIST))
1639     value = CONTACT_LIST_COMPACT_SIZE;
1640   else if (g_settings_get_boolean (gsettings,
1641       EMPATHY_PREFS_UI_SHOW_AVATARS))
1642     value = CONTACT_LIST_NORMAL_SIZE_WITH_AVATARS;
1643   else
1644     value = CONTACT_LIST_NORMAL_SIZE;
1645
1646   DEBUG ("setting changed, value = %i", value);
1647
1648   /* By changing the value of the GtkRadioAction,
1649      it emits a signal that calls roster_window_view_contacts_list_size_cb
1650      which updates the contacts list */
1651   gtk_radio_action_set_current_value (priv->normal_with_avatars, value);
1652 }
1653
1654 static void
1655 roster_window_edit_search_contacts_cb (GtkCheckMenuItem  *item,
1656     EmpathyRosterWindow *window)
1657 {
1658   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
1659
1660   empathy_individual_view_start_search (priv->individual_view);
1661 }
1662
1663 static void
1664 roster_window_view_show_map_cb (GtkCheckMenuItem  *item,
1665     EmpathyRosterWindow *window)
1666 {
1667 #ifdef HAVE_LIBCHAMPLAIN
1668   empathy_map_view_show ();
1669 #endif
1670 }
1671
1672 static void
1673 join_chatroom (EmpathyChatroom *chatroom,
1674     gint64 timestamp)
1675 {
1676   TpAccount *account;
1677   const gchar *room;
1678
1679   account = empathy_chatroom_get_account (chatroom);
1680   room = empathy_chatroom_get_room (chatroom);
1681
1682   DEBUG ("Requesting channel for '%s'", room);
1683   empathy_join_muc (account, room, timestamp);
1684 }
1685
1686 typedef struct
1687 {
1688   TpAccount *account;
1689   EmpathyChatroom *chatroom;
1690   gint64 timestamp;
1691   glong sig_id;
1692   guint timeout;
1693 } join_fav_account_sig_ctx;
1694
1695 static join_fav_account_sig_ctx *
1696 join_fav_account_sig_ctx_new (TpAccount *account,
1697     EmpathyChatroom *chatroom,
1698     gint64 timestamp)
1699 {
1700   join_fav_account_sig_ctx *ctx = g_slice_new0 (
1701       join_fav_account_sig_ctx);
1702
1703   ctx->account = g_object_ref (account);
1704   ctx->chatroom = g_object_ref (chatroom);
1705   ctx->timestamp = timestamp;
1706   return ctx;
1707 }
1708
1709 static void
1710 join_fav_account_sig_ctx_free (join_fav_account_sig_ctx *ctx)
1711 {
1712   g_object_unref (ctx->account);
1713   g_object_unref (ctx->chatroom);
1714   g_slice_free (join_fav_account_sig_ctx, ctx);
1715 }
1716
1717 static void
1718 account_status_changed_cb (TpAccount  *account,
1719     TpConnectionStatus old_status,
1720     TpConnectionStatus new_status,
1721     guint reason,
1722     gchar *dbus_error_name,
1723     GHashTable *details,
1724     gpointer user_data)
1725 {
1726   join_fav_account_sig_ctx *ctx = user_data;
1727
1728   switch (new_status)
1729     {
1730       case TP_CONNECTION_STATUS_DISCONNECTED:
1731         /* Don't wait any longer */
1732         goto finally;
1733         break;
1734
1735       case TP_CONNECTION_STATUS_CONNECTING:
1736         /* Wait a bit */
1737         return;
1738
1739       case TP_CONNECTION_STATUS_CONNECTED:
1740         /* We can join the room */
1741         break;
1742
1743       default:
1744         g_assert_not_reached ();
1745     }
1746
1747   join_chatroom (ctx->chatroom, ctx->timestamp);
1748
1749 finally:
1750   g_source_remove (ctx->timeout);
1751   g_signal_handler_disconnect (account, ctx->sig_id);
1752 }
1753
1754 #define JOIN_FAVORITE_TIMEOUT 5
1755
1756 static gboolean
1757 join_favorite_timeout_cb (gpointer data)
1758 {
1759   join_fav_account_sig_ctx *ctx = data;
1760
1761   /* stop waiting for joining the favorite room */
1762   g_signal_handler_disconnect (ctx->account, ctx->sig_id);
1763   return FALSE;
1764 }
1765
1766 static void
1767 roster_window_favorite_chatroom_join (EmpathyChatroom *chatroom)
1768 {
1769   TpAccount *account;
1770
1771   account = empathy_chatroom_get_account (chatroom);
1772   if (tp_account_get_connection_status (account, NULL) !=
1773                TP_CONNECTION_STATUS_CONNECTED)
1774     {
1775       join_fav_account_sig_ctx *ctx;
1776
1777       ctx = join_fav_account_sig_ctx_new (account, chatroom,
1778         empathy_get_current_action_time ());
1779
1780       ctx->sig_id = g_signal_connect_data (account, "status-changed",
1781         G_CALLBACK (account_status_changed_cb), ctx,
1782         (GClosureNotify) join_fav_account_sig_ctx_free, 0);
1783
1784       ctx->timeout = g_timeout_add_seconds (JOIN_FAVORITE_TIMEOUT,
1785         join_favorite_timeout_cb, ctx);
1786       return;
1787     }
1788
1789   join_chatroom (chatroom, empathy_get_current_action_time ());
1790 }
1791
1792 static void
1793 roster_window_favorite_chatroom_menu_activate_cb (GtkMenuItem *menu_item,
1794     EmpathyChatroom *chatroom)
1795 {
1796   roster_window_favorite_chatroom_join (chatroom);
1797 }
1798
1799 static void
1800 roster_window_favorite_chatroom_menu_add (EmpathyRosterWindow *window,
1801     EmpathyChatroom *chatroom)
1802 {
1803   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
1804   GtkWidget *menu_item;
1805   const gchar *name, *account_name;
1806   gchar *label;
1807
1808   if (g_object_get_data (G_OBJECT (chatroom), "menu_item"))
1809     return;
1810
1811   name = empathy_chatroom_get_name (chatroom);
1812   account_name = tp_account_get_display_name (
1813       empathy_chatroom_get_account (chatroom));
1814   label = g_strdup_printf ("%s (%s)", name, account_name);
1815   menu_item = gtk_menu_item_new_with_label (label);
1816   g_free (label);
1817   g_object_set_data (G_OBJECT (menu_item), "is_favorite",
1818       GUINT_TO_POINTER (TRUE));
1819
1820   g_object_set_data (G_OBJECT (chatroom), "menu_item", menu_item);
1821   g_signal_connect (menu_item, "activate",
1822       G_CALLBACK (roster_window_favorite_chatroom_menu_activate_cb),
1823       chatroom);
1824
1825   gtk_menu_shell_insert (GTK_MENU_SHELL (priv->room_menu),
1826       menu_item, 4);
1827
1828   gtk_widget_show (menu_item);
1829 }
1830
1831 static void
1832 roster_window_favorite_chatroom_menu_added_cb (EmpathyChatroomManager *manager,
1833     EmpathyChatroom *chatroom,
1834     EmpathyRosterWindow *window)
1835 {
1836   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
1837
1838   roster_window_favorite_chatroom_menu_add (window, chatroom);
1839   gtk_widget_show (priv->room_separator);
1840   gtk_action_set_sensitive (priv->room_join_favorites, TRUE);
1841 }
1842
1843 static void
1844 roster_window_favorite_chatroom_menu_removed_cb (
1845     EmpathyChatroomManager *manager,
1846     EmpathyChatroom *chatroom,
1847     EmpathyRosterWindow *window)
1848 {
1849   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
1850   GtkWidget *menu_item;
1851   GList *chatrooms;
1852
1853   menu_item = g_object_get_data (G_OBJECT (chatroom), "menu_item");
1854   g_object_set_data (G_OBJECT (chatroom), "menu_item", NULL);
1855   gtk_widget_destroy (menu_item);
1856
1857   chatrooms = empathy_chatroom_manager_get_chatrooms (priv->chatroom_manager,
1858       NULL);
1859   if (chatrooms)
1860     gtk_widget_show (priv->room_separator);
1861   else
1862     gtk_widget_hide (priv->room_separator);
1863
1864   gtk_action_set_sensitive (priv->room_join_favorites, chatrooms != NULL);
1865   g_list_free (chatrooms);
1866 }
1867
1868 static void
1869 roster_window_favorite_chatroom_menu_setup (EmpathyRosterWindow *window)
1870 {
1871   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
1872   GList *chatrooms, *l;
1873   GtkWidget *room;
1874
1875   priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
1876   chatrooms = empathy_chatroom_manager_get_chatrooms (
1877     priv->chatroom_manager, NULL);
1878   room = gtk_ui_manager_get_widget (priv->ui_manager,
1879     "/menubar/room");
1880   priv->room_menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (room));
1881   priv->room_separator = gtk_ui_manager_get_widget (priv->ui_manager,
1882     "/menubar/room/room_separator");
1883
1884   for (l = chatrooms; l; l = l->next)
1885     roster_window_favorite_chatroom_menu_add (window, l->data);
1886
1887   if (!chatrooms)
1888     gtk_widget_hide (priv->room_separator);
1889
1890   gtk_action_set_sensitive (priv->room_join_favorites, chatrooms != NULL);
1891
1892   g_signal_connect (priv->chatroom_manager, "chatroom-added",
1893       G_CALLBACK (roster_window_favorite_chatroom_menu_added_cb),
1894       window);
1895
1896   g_signal_connect (priv->chatroom_manager, "chatroom-removed",
1897       G_CALLBACK (roster_window_favorite_chatroom_menu_removed_cb),
1898       window);
1899
1900   g_list_free (chatrooms);
1901 }
1902
1903 static void
1904 roster_window_room_join_new_cb (GtkAction *action,
1905     EmpathyRosterWindow *window)
1906 {
1907   empathy_new_chatroom_dialog_show (GTK_WINDOW (window));
1908 }
1909
1910 static void
1911 roster_window_room_join_favorites_cb (GtkAction *action,
1912     EmpathyRosterWindow *window)
1913 {
1914   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
1915   GList *chatrooms, *l;
1916
1917   chatrooms = empathy_chatroom_manager_get_chatrooms (priv->chatroom_manager,
1918       NULL);
1919
1920   for (l = chatrooms; l; l = l->next)
1921     roster_window_favorite_chatroom_join (l->data);
1922
1923   g_list_free (chatrooms);
1924 }
1925
1926 static void
1927 roster_window_room_manage_favorites_cb (GtkAction *action,
1928     EmpathyRosterWindow *window)
1929 {
1930   empathy_chatrooms_window_show (GTK_WINDOW (window));
1931 }
1932
1933 static void
1934 roster_window_edit_cb (GtkAction *action,
1935     EmpathyRosterWindow *window)
1936 {
1937   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
1938   GtkWidget *submenu;
1939
1940   /* FIXME: It should use the UIManager to merge the contact/group submenu */
1941   submenu = empathy_individual_view_get_individual_menu (
1942       priv->individual_view);
1943   if (submenu)
1944     {
1945       GtkMenuItem *item;
1946       GtkWidget   *label;
1947
1948       item = GTK_MENU_ITEM (priv->edit_context);
1949       label = gtk_bin_get_child (GTK_BIN (item));
1950       gtk_label_set_text (GTK_LABEL (label), _("Contact"));
1951
1952       gtk_widget_show (priv->edit_context);
1953       gtk_widget_show (priv->edit_context_separator);
1954
1955       gtk_menu_item_set_submenu (item, submenu);
1956
1957       return;
1958     }
1959
1960   submenu = empathy_individual_view_get_group_menu (priv->individual_view);
1961   if (submenu)
1962     {
1963       GtkMenuItem *item;
1964       GtkWidget   *label;
1965
1966       item = GTK_MENU_ITEM (priv->edit_context);
1967       label = gtk_bin_get_child (GTK_BIN (item));
1968       gtk_label_set_text (GTK_LABEL (label), _("Group"));
1969
1970       gtk_widget_show (priv->edit_context);
1971       gtk_widget_show (priv->edit_context_separator);
1972
1973       gtk_menu_item_set_submenu (item, submenu);
1974
1975       return;
1976     }
1977
1978   gtk_widget_hide (priv->edit_context);
1979   gtk_widget_hide (priv->edit_context_separator);
1980
1981   return;
1982 }
1983
1984 static void
1985 roster_window_edit_accounts_cb (GtkAction *action,
1986     EmpathyRosterWindow *window)
1987 {
1988   empathy_accounts_dialog_show_application (gdk_screen_get_default (),
1989       NULL, FALSE, FALSE);
1990 }
1991
1992 static void
1993 roster_window_edit_personal_information_cb (GtkAction *action,
1994     EmpathyRosterWindow *window)
1995 {
1996   empathy_contact_personal_dialog_show (GTK_WINDOW (window));
1997 }
1998
1999 static void
2000 roster_window_edit_blocked_contacts_cb (GtkAction *action,
2001     EmpathyRosterWindow *window)
2002 {
2003   GtkWidget *dialog;
2004
2005   dialog = empathy_contact_blocking_dialog_new (GTK_WINDOW (window));
2006   gtk_widget_show (dialog);
2007
2008   g_signal_connect (dialog, "response",
2009       G_CALLBACK (gtk_widget_destroy), NULL);
2010 }
2011
2012 void
2013 empathy_roster_window_show_preferences (EmpathyRosterWindow *window,
2014     const gchar *tab)
2015 {
2016   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
2017
2018   if (priv->preferences == NULL)
2019     {
2020       priv->preferences = empathy_preferences_new (GTK_WINDOW (window),
2021                                                    priv->shell_running);
2022       g_object_add_weak_pointer (G_OBJECT (priv->preferences),
2023                (gpointer) &priv->preferences);
2024
2025       gtk_widget_show (priv->preferences);
2026     }
2027   else
2028     {
2029       gtk_window_present (GTK_WINDOW (priv->preferences));
2030     }
2031
2032   if (tab != NULL)
2033     empathy_preferences_show_tab (
2034       EMPATHY_PREFERENCES (priv->preferences), tab);
2035 }
2036
2037 static void
2038 roster_window_edit_preferences_cb (GtkAction *action,
2039     EmpathyRosterWindow *window)
2040 {
2041   empathy_roster_window_show_preferences (window, NULL);
2042 }
2043
2044 static void
2045 roster_window_help_about_cb (GtkAction *action,
2046     EmpathyRosterWindow *window)
2047 {
2048   empathy_about_dialog_new (GTK_WINDOW (window));
2049 }
2050
2051 static void
2052 roster_window_help_debug_cb (GtkAction *action,
2053     EmpathyRosterWindow *window)
2054 {
2055   empathy_launch_program (BIN_DIR, "empathy-debugger", NULL);
2056 }
2057
2058 static void
2059 roster_window_help_contents_cb (GtkAction *action,
2060     EmpathyRosterWindow *window)
2061 {
2062   empathy_url_show (GTK_WIDGET (window), "ghelp:empathy");
2063 }
2064
2065 static gboolean
2066 roster_window_throbber_button_press_event_cb (GtkWidget *throbber,
2067     GdkEventButton *event,
2068     EmpathyRosterWindow *window)
2069 {
2070   if (event->type != GDK_BUTTON_PRESS ||
2071       event->button != 1)
2072     return FALSE;
2073
2074   empathy_accounts_dialog_show_application (
2075       gtk_widget_get_screen (GTK_WIDGET (throbber)),
2076       NULL, FALSE, FALSE);
2077
2078   return FALSE;
2079 }
2080
2081 static void
2082 roster_window_account_removed_cb (TpAccountManager  *manager,
2083     TpAccount *account,
2084     EmpathyRosterWindow *window)
2085 {
2086   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
2087   GList *a;
2088
2089   a = tp_account_manager_get_valid_accounts (manager);
2090
2091   gtk_action_set_sensitive (priv->view_history,
2092     g_list_length (a) > 0);
2093
2094   g_list_free (a);
2095
2096   /* remove errors if any */
2097   roster_window_remove_error (window, account);
2098
2099   /* remove the balance action if required */
2100   roster_window_remove_balance_action (window, account);
2101 }
2102
2103 static void
2104 roster_window_account_validity_changed_cb (TpAccountManager  *manager,
2105     TpAccount *account,
2106     gboolean valid,
2107     EmpathyRosterWindow *window)
2108 {
2109   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
2110
2111   if (valid)
2112     {
2113       gulong handler_id;
2114       handler_id = GPOINTER_TO_UINT (g_hash_table_lookup (
2115         priv->status_changed_handlers, account));
2116
2117       /* connect signal only if it was not connected yet */
2118       if (handler_id == 0)
2119         {
2120           handler_id = g_signal_connect (account,
2121             "status-changed",
2122             G_CALLBACK (roster_window_connection_changed_cb),
2123             window);
2124           g_hash_table_insert (priv->status_changed_handlers,
2125             account, GUINT_TO_POINTER (handler_id));
2126         }
2127     }
2128
2129   roster_window_account_removed_cb (manager, account, window);
2130 }
2131
2132 static void
2133 roster_window_notify_show_offline_cb (GSettings *gsettings,
2134     const gchar *key,
2135     gpointer toggle_action)
2136 {
2137   gtk_toggle_action_set_active (toggle_action,
2138       g_settings_get_boolean (gsettings, key));
2139 }
2140
2141 static void
2142 roster_window_connection_items_setup (EmpathyRosterWindow *window,
2143     GtkBuilder *gui)
2144 {
2145   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
2146   GList *list;
2147   GObject *action;
2148   guint i;
2149   const gchar *actions_connected[] = {
2150       "room_join_new",
2151       "room_join_favorites",
2152       "chat_new_message",
2153       "chat_new_call",
2154       "chat_search_contacts",
2155       "chat_add_contact",
2156       "edit_personal_information",
2157       "edit_blocked_contacts",
2158       "edit_search_contacts"
2159   };
2160
2161   for (i = 0, list = NULL; i < G_N_ELEMENTS (actions_connected); i++)
2162     {
2163       action = gtk_builder_get_object (gui, actions_connected[i]);
2164       list = g_list_prepend (list, action);
2165     }
2166
2167   priv->actions_connected = list;
2168 }
2169
2170 static void
2171 account_manager_prepared_cb (GObject *source_object,
2172     GAsyncResult *result,
2173     gpointer user_data)
2174 {
2175   GList *accounts, *j;
2176   TpAccountManager *manager = TP_ACCOUNT_MANAGER (source_object);
2177   EmpathyRosterWindow *window = user_data;
2178   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
2179   GError *error = NULL;
2180
2181   if (!tp_proxy_prepare_finish (manager, result, &error))
2182     {
2183       DEBUG ("Failed to prepare account manager: %s", error->message);
2184       g_error_free (error);
2185       return;
2186     }
2187
2188   accounts = tp_account_manager_get_valid_accounts (priv->account_manager);
2189   for (j = accounts; j != NULL; j = j->next)
2190     {
2191       TpAccount *account = TP_ACCOUNT (j->data);
2192       gulong handler_id;
2193
2194       handler_id = g_signal_connect (account, "status-changed",
2195           G_CALLBACK (roster_window_connection_changed_cb),
2196           window);
2197       g_hash_table_insert (priv->status_changed_handlers,
2198           account, GUINT_TO_POINTER (handler_id));
2199
2200       roster_window_setup_balance (window, account);
2201     }
2202
2203   g_signal_connect (manager, "account-validity-changed",
2204       G_CALLBACK (roster_window_account_validity_changed_cb), window);
2205
2206   roster_window_update_status (window);
2207
2208   /* Disable the "Previous Conversations" menu entry if there is no account */
2209   gtk_action_set_sensitive (priv->view_history,
2210       g_list_length (accounts) > 0);
2211
2212   g_list_free (accounts);
2213 }
2214
2215 void
2216 empathy_roster_window_set_shell_running (EmpathyRosterWindow *window,
2217     gboolean shell_running)
2218 {
2219   EmpathyRosterWindowPriv *priv = GET_PRIV (window);
2220
2221   if (priv->shell_running == shell_running)
2222     return;
2223
2224   priv->shell_running = shell_running;
2225   g_object_notify (G_OBJECT (window), "shell-running");
2226 }
2227
2228 static GObject *
2229 empathy_roster_window_constructor (GType type,
2230     guint n_construct_params,
2231     GObjectConstructParam *construct_params)
2232 {
2233   static GObject *window = NULL;
2234
2235   if (window != NULL)
2236     return g_object_ref (window);
2237
2238   window = G_OBJECT_CLASS (empathy_roster_window_parent_class)->constructor (
2239     type, n_construct_params, construct_params);
2240
2241   g_object_add_weak_pointer (window, (gpointer) &window);
2242
2243   return window;
2244 }
2245
2246 static void
2247 empathy_roster_window_set_property (GObject *object,
2248     guint property_id,
2249     const GValue *value,
2250     GParamSpec *pspec)
2251 {
2252   EmpathyRosterWindow *self = EMPATHY_ROSTER_WINDOW (object);
2253   EmpathyRosterWindowPriv *priv = GET_PRIV (self);
2254
2255   switch (property_id)
2256     {
2257       case PROP_SHELL_RUNNING:
2258         priv->shell_running = g_value_get_boolean (value);
2259         break;
2260       default:
2261         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2262         break;
2263     }
2264 }
2265
2266 static void
2267 empathy_roster_window_get_property (GObject    *object,
2268     guint property_id,
2269     GValue *value,
2270     GParamSpec *pspec)
2271 {
2272   EmpathyRosterWindow *self = EMPATHY_ROSTER_WINDOW (object);
2273   EmpathyRosterWindowPriv *priv = GET_PRIV (self);
2274
2275   switch (property_id)
2276     {
2277       case PROP_SHELL_RUNNING:
2278         g_value_set_boolean (value, priv->shell_running);
2279         break;
2280       default:
2281         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2282         break;
2283     }
2284 }
2285
2286 static void
2287 empathy_roster_window_class_init (EmpathyRosterWindowClass *klass)
2288 {
2289   GObjectClass *object_class = G_OBJECT_CLASS (klass);
2290   GParamSpec *pspec;
2291
2292   object_class->finalize = empathy_roster_window_finalize;
2293   object_class->constructor = empathy_roster_window_constructor;
2294
2295   object_class->set_property = empathy_roster_window_set_property;
2296   object_class->get_property = empathy_roster_window_get_property;
2297
2298   pspec = g_param_spec_boolean ("shell-running",
2299       "Shell running",
2300       "Whether the Shell is running or not",
2301       FALSE,
2302       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
2303   g_object_class_install_property (object_class, PROP_SHELL_RUNNING, pspec);
2304
2305   g_type_class_add_private (object_class, sizeof (EmpathyRosterWindowPriv));
2306 }
2307
2308 static void
2309 empathy_roster_window_init (EmpathyRosterWindow *window)
2310 {
2311   EmpathyRosterWindowPriv *priv;
2312   EmpathyIndividualManager *individual_manager;
2313   GtkBuilder *gui, *gui_mgr;
2314   GtkWidget *sw;
2315   GtkToggleAction *show_offline_widget;
2316   GtkAction *show_map_widget;
2317   GtkToolItem *item;
2318   gboolean show_offline;
2319   gchar *filename;
2320   GtkTreeModel *model;
2321   GtkWidget *search_vbox;
2322   GtkWidget *menubar;
2323
2324   priv = window->priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
2325       EMPATHY_TYPE_ROSTER_WINDOW, EmpathyRosterWindowPriv);
2326
2327   priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2328   priv->gsettings_contacts = g_settings_new (EMPATHY_PREFS_CONTACTS_SCHEMA);
2329
2330   priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2331
2332   gtk_window_set_title (GTK_WINDOW (window), _("Contact List"));
2333   gtk_window_set_role (GTK_WINDOW (window), "contact_list");
2334   gtk_window_set_default_size (GTK_WINDOW (window), 225, 325);
2335
2336   /* don't finalize the widget on delete-event, just hide it */
2337   g_signal_connect (window, "delete-event",
2338     G_CALLBACK (gtk_widget_hide_on_delete), NULL);
2339
2340   /* Set up interface */
2341   filename = empathy_file_lookup ("empathy-roster-window.ui", "src");
2342   gui = empathy_builder_get_file (filename,
2343       "main_vbox", &priv->main_vbox,
2344       "balance_vbox", &priv->balance_vbox,
2345       "errors_vbox", &priv->errors_vbox,
2346       "auth_vbox", &priv->auth_vbox,
2347       "search_vbox", &search_vbox,
2348       "presence_toolbar", &priv->presence_toolbar,
2349       "notebook", &priv->notebook,
2350       "no_entry_label", &priv->no_entry_label,
2351       "roster_scrolledwindow", &sw,
2352       NULL);
2353   g_free (filename);
2354
2355   /* Set UI manager */
2356   filename = empathy_file_lookup ("empathy-roster-window-menubar.ui", "src");
2357   gui_mgr = empathy_builder_get_file (filename,
2358       "ui_manager", &priv->ui_manager,
2359       "view_show_offline", &show_offline_widget,
2360       "view_show_protocols", &priv->show_protocols,
2361       "view_sort_by_name", &priv->sort_by_name,
2362       "view_sort_by_status", &priv->sort_by_status,
2363       "view_normal_size_with_avatars", &priv->normal_with_avatars,
2364       "view_normal_size", &priv->normal_size,
2365       "view_compact_size", &priv->compact_size,
2366       "view_history", &priv->view_history,
2367       "view_show_map", &show_map_widget,
2368       "room_join_favorites", &priv->room_join_favorites,
2369       "view_balance_show_in_roster", &priv->view_balance_show_in_roster,
2370       "menubar", &menubar,
2371       NULL);
2372   g_free (filename);
2373
2374   /* The UI manager is living in its own .ui file as Glade doesn't support
2375    * those. The GtkMenubar has to be in this file as well to we manually add
2376    * it to the first position of the vbox. */
2377   gtk_box_pack_start (GTK_BOX (priv->main_vbox), menubar, FALSE, FALSE, 0);
2378   gtk_box_reorder_child (GTK_BOX (priv->main_vbox), menubar, 0);
2379
2380   gtk_container_add (GTK_CONTAINER (window), priv->main_vbox);
2381   gtk_widget_show (priv->main_vbox);
2382
2383   g_signal_connect (window, "key-press-event",
2384       G_CALLBACK (roster_window_key_press_event_cb), NULL);
2385
2386   empathy_builder_connect (gui_mgr, window,
2387       "chat_quit", "activate", roster_window_chat_quit_cb,
2388       "chat_new_message", "activate", roster_window_chat_new_message_cb,
2389       "chat_new_call", "activate", roster_window_chat_new_call_cb,
2390       "view_history", "activate", roster_window_view_history_cb,
2391       "room_join_new", "activate", roster_window_room_join_new_cb,
2392       "room_join_favorites", "activate", roster_window_room_join_favorites_cb,
2393       "room_manage_favorites", "activate", roster_window_room_manage_favorites_cb,
2394       "chat_add_contact", "activate", roster_window_chat_add_contact_cb,
2395       "chat_search_contacts", "activate", roster_window_chat_search_contacts_cb,
2396       "view_show_ft_manager", "activate", roster_window_view_show_ft_manager,
2397       "view_show_offline", "toggled", roster_window_view_show_offline_cb,
2398       "view_show_protocols", "toggled", roster_window_view_show_protocols_cb,
2399       "view_sort_by_name", "changed", roster_window_view_sort_contacts_cb,
2400       "view_normal_size_with_avatars", "changed", roster_window_view_contacts_list_size_cb,
2401       "view_show_map", "activate", roster_window_view_show_map_cb,
2402       "edit", "activate", roster_window_edit_cb,
2403       "edit_accounts", "activate", roster_window_edit_accounts_cb,
2404       "edit_personal_information", "activate", roster_window_edit_personal_information_cb,
2405       "edit_blocked_contacts", "activate", roster_window_edit_blocked_contacts_cb,
2406       "edit_preferences", "activate", roster_window_edit_preferences_cb,
2407       "edit_search_contacts", "activate", roster_window_edit_search_contacts_cb,
2408       "help_about", "activate", roster_window_help_about_cb,
2409       "help_debug", "activate", roster_window_help_debug_cb,
2410       "help_contents", "activate", roster_window_help_contents_cb,
2411       NULL);
2412
2413   /* Set up connection related widgets. */
2414   roster_window_connection_items_setup (window, gui_mgr);
2415
2416   g_object_ref (priv->ui_manager);
2417   g_object_unref (gui);
2418   g_object_unref (gui_mgr);
2419
2420 #ifndef HAVE_LIBCHAMPLAIN
2421   gtk_action_set_visible (show_map_widget, FALSE);
2422 #endif
2423
2424   priv->account_manager = tp_account_manager_dup ();
2425
2426   tp_proxy_prepare_async (priv->account_manager, NULL,
2427       account_manager_prepared_cb, window);
2428
2429   priv->errors = g_hash_table_new_full (g_direct_hash, g_direct_equal,
2430       g_object_unref, NULL);
2431
2432   priv->auths = g_hash_table_new (NULL, NULL);
2433
2434   priv->status_changed_handlers = g_hash_table_new_full (g_direct_hash,
2435       g_direct_equal, NULL, NULL);
2436
2437   /* Set up menu */
2438   roster_window_favorite_chatroom_menu_setup (window);
2439
2440   priv->edit_context = gtk_ui_manager_get_widget (priv->ui_manager,
2441       "/menubar/edit/edit_context");
2442   priv->edit_context_separator = gtk_ui_manager_get_widget (
2443       priv->ui_manager,
2444       "/menubar/edit/edit_context_separator");
2445   gtk_widget_hide (priv->edit_context);
2446   gtk_widget_hide (priv->edit_context_separator);
2447
2448   /* Set up contact list. */
2449   empathy_status_presets_get_all ();
2450
2451   /* Set up presence chooser */
2452   priv->presence_chooser = empathy_presence_chooser_new ();
2453   gtk_widget_show (priv->presence_chooser);
2454   item = gtk_tool_item_new ();
2455   gtk_widget_show (GTK_WIDGET (item));
2456   gtk_widget_set_size_request (priv->presence_chooser, 10, -1);
2457   gtk_container_add (GTK_CONTAINER (item), priv->presence_chooser);
2458   gtk_tool_item_set_is_important (item, TRUE);
2459   gtk_tool_item_set_expand (item, TRUE);
2460   gtk_toolbar_insert (GTK_TOOLBAR (priv->presence_toolbar), item, -1);
2461
2462   /* Set up the throbber */
2463   priv->throbber = gtk_spinner_new ();
2464   gtk_widget_set_size_request (priv->throbber, 16, -1);
2465   gtk_widget_set_events (priv->throbber, GDK_BUTTON_PRESS_MASK);
2466   g_signal_connect (priv->throbber, "button-press-event",
2467     G_CALLBACK (roster_window_throbber_button_press_event_cb),
2468     window);
2469   gtk_widget_show (priv->throbber);
2470
2471   item = gtk_tool_item_new ();
2472   gtk_container_set_border_width (GTK_CONTAINER (item), 6);
2473   gtk_toolbar_insert (GTK_TOOLBAR (priv->presence_toolbar), item, -1);
2474   gtk_container_add (GTK_CONTAINER (item), priv->throbber);
2475   priv->throbber_tool_item = GTK_WIDGET (item);
2476
2477   /* XXX: this class is designed to live for the duration of the program,
2478    * so it's got a race condition between its signal handlers and its
2479    * finalization. The class is planned to be removed, so we won't fix
2480    * this before then. */
2481   individual_manager = empathy_individual_manager_dup_singleton ();
2482   priv->individual_store = EMPATHY_INDIVIDUAL_STORE (
2483       empathy_individual_store_manager_new (individual_manager));
2484   g_object_unref (individual_manager);
2485
2486   /* For the moment, we disallow Persona drops onto the roster contact list
2487    * (e.g. from things such as the EmpathyPersonaView in the linking dialogue).
2488    * No code is hooked up to do anything on a Persona drop, so allowing them
2489    * would achieve nothing except confusion. */
2490   priv->individual_view = empathy_individual_view_new (
2491       priv->individual_store,
2492       /* EmpathyIndividualViewFeatureFlags */
2493       EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE |
2494       EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2495       EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE |
2496       EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE |
2497       EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE |
2498       EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP |
2499       EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG |
2500       EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP |
2501       EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL |
2502       EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP,
2503       /* EmpathyIndividualFeatureFlags  */
2504       EMPATHY_INDIVIDUAL_FEATURE_CHAT |
2505       EMPATHY_INDIVIDUAL_FEATURE_CALL |
2506       EMPATHY_INDIVIDUAL_FEATURE_EDIT |
2507       EMPATHY_INDIVIDUAL_FEATURE_INFO |
2508       EMPATHY_INDIVIDUAL_FEATURE_LINK |
2509       EMPATHY_INDIVIDUAL_FEATURE_SMS |
2510       EMPATHY_INDIVIDUAL_FEATURE_CALL_PHONE);
2511
2512   gtk_widget_show (GTK_WIDGET (priv->individual_view));
2513   gtk_container_add (GTK_CONTAINER (sw),
2514       GTK_WIDGET (priv->individual_view));
2515   g_signal_connect (priv->individual_view, "row-activated",
2516      G_CALLBACK (roster_window_row_activated_cb), window);
2517
2518   /* Set up search bar */
2519   priv->search_bar = empathy_live_search_new (
2520       GTK_WIDGET (priv->individual_view));
2521   empathy_individual_view_set_live_search (priv->individual_view,
2522       EMPATHY_LIVE_SEARCH (priv->search_bar));
2523   gtk_box_pack_start (GTK_BOX (search_vbox), priv->search_bar,
2524       FALSE, TRUE, 0);
2525
2526   g_signal_connect_swapped (window, "map",
2527       G_CALLBACK (gtk_widget_grab_focus), priv->individual_view);
2528
2529   /* Connect to proper signals to check if contact list is empty or not */
2530   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->individual_view));
2531   priv->empty = TRUE;
2532   g_signal_connect (model, "row-inserted",
2533       G_CALLBACK (roster_window_row_inserted_cb), window);
2534   g_signal_connect (model, "row-deleted",
2535       G_CALLBACK (roster_window_row_deleted_cb), window);
2536
2537   /* Load user-defined accelerators. */
2538   roster_window_accels_load ();
2539
2540   /* Set window size. */
2541   empathy_geometry_bind (GTK_WINDOW (window), GEOMETRY_NAME);
2542
2543   /* bind view_balance_show_in_roster */
2544   g_settings_bind (priv->gsettings_ui, "show-balance-in-roster",
2545       priv->view_balance_show_in_roster, "active",
2546       G_SETTINGS_BIND_DEFAULT);
2547   g_object_bind_property (priv->view_balance_show_in_roster, "active",
2548       priv->balance_vbox, "visible",
2549       G_BINDING_SYNC_CREATE);
2550
2551   /* Enable event handling */
2552   priv->call_observer = empathy_call_observer_dup_singleton ();
2553   priv->event_manager = empathy_event_manager_dup_singleton ();
2554
2555   g_signal_connect (priv->event_manager, "event-added",
2556       G_CALLBACK (roster_window_event_added_cb), window);
2557   g_signal_connect (priv->event_manager, "event-removed",
2558       G_CALLBACK (roster_window_event_removed_cb), window);
2559   g_signal_connect (priv->account_manager, "account-validity-changed",
2560       G_CALLBACK (roster_window_account_validity_changed_cb), window);
2561   g_signal_connect (priv->account_manager, "account-removed",
2562       G_CALLBACK (roster_window_account_removed_cb), window);
2563   g_signal_connect (priv->account_manager, "account-disabled",
2564       G_CALLBACK (roster_window_account_disabled_cb), window);
2565
2566   /* Show offline ? */
2567   show_offline = g_settings_get_boolean (priv->gsettings_ui,
2568       EMPATHY_PREFS_UI_SHOW_OFFLINE);
2569
2570   g_signal_connect (priv->gsettings_ui,
2571       "changed::" EMPATHY_PREFS_UI_SHOW_OFFLINE,
2572       G_CALLBACK (roster_window_notify_show_offline_cb), show_offline_widget);
2573
2574   gtk_toggle_action_set_active (show_offline_widget, show_offline);
2575
2576   /* Show protocol ? */
2577   g_signal_connect (priv->gsettings_ui,
2578       "changed::" EMPATHY_PREFS_UI_SHOW_PROTOCOLS,
2579       G_CALLBACK (roster_window_notify_show_protocols_cb), window);
2580
2581   roster_window_notify_show_protocols_cb (priv->gsettings_ui,
2582       EMPATHY_PREFS_UI_SHOW_PROTOCOLS, window);
2583
2584   /* Sort by name / by status ? */
2585   g_signal_connect (priv->gsettings_contacts,
2586       "changed::" EMPATHY_PREFS_CONTACTS_SORT_CRITERIUM,
2587       G_CALLBACK (roster_window_notify_sort_contact_cb), window);
2588
2589   roster_window_notify_sort_contact_cb (priv->gsettings_contacts,
2590       EMPATHY_PREFS_CONTACTS_SORT_CRITERIUM, window);
2591
2592   /* Contacts list size */
2593   g_signal_connect (priv->gsettings_ui,
2594       "changed::" EMPATHY_PREFS_UI_COMPACT_CONTACT_LIST,
2595       G_CALLBACK (roster_window_notify_contact_list_size_cb), window);
2596
2597   g_signal_connect (priv->gsettings_ui,
2598       "changed::" EMPATHY_PREFS_UI_SHOW_AVATARS,
2599       G_CALLBACK (roster_window_notify_contact_list_size_cb),
2600       window);
2601
2602   roster_window_notify_contact_list_size_cb (priv->gsettings_ui,
2603       EMPATHY_PREFS_UI_SHOW_AVATARS, window);
2604 }
2605
2606 GtkWidget *
2607 empathy_roster_window_dup (void)
2608 {
2609   return g_object_new (EMPATHY_TYPE_ROSTER_WINDOW, NULL);
2610 }