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