]> git.0d.be Git - empathy.git/blob - src/empathy-roster-window.c
don't display 'Send file' menu item in empathy-chat
[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
45 #include <libempathy-gtk/empathy-contact-dialogs.h>
46 #include <libempathy-gtk/empathy-live-search.h>
47 #include <libempathy-gtk/empathy-contact-blocking-dialog.h>
48 #include <libempathy-gtk/empathy-contact-search-dialog.h>
49 #include <libempathy-gtk/empathy-geometry.h>
50 #include <libempathy-gtk/empathy-gtk-enum-types.h>
51 #include <libempathy-gtk/empathy-individual-dialogs.h>
52 #include <libempathy-gtk/empathy-individual-store-manager.h>
53 #include <libempathy-gtk/empathy-roster-view.h>
54 #include <libempathy-gtk/empathy-new-message-dialog.h>
55 #include <libempathy-gtk/empathy-new-call-dialog.h>
56 #include <libempathy-gtk/empathy-log-window.h>
57 #include <libempathy-gtk/empathy-presence-chooser.h>
58 #include <libempathy-gtk/empathy-sound-manager.h>
59 #include <libempathy-gtk/empathy-ui-utils.h>
60
61 #include "empathy-accounts-dialog.h"
62 #include "empathy-call-observer.h"
63 #include "empathy-chat-manager.h"
64 #include "empathy-roster-window.h"
65 #include "empathy-preferences.h"
66 #include "empathy-about-dialog.h"
67 #include "empathy-debug-window.h"
68 #include "empathy-new-chatroom-dialog.h"
69 #include "empathy-chatrooms-window.h"
70 #include "empathy-event-manager.h"
71 #include "empathy-ft-manager.h"
72
73 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
74 #include <libempathy/empathy-debug.h>
75
76 /* Flashing delay for icons (milliseconds). */
77 #define FLASH_TIMEOUT 500
78
79 /* Minimum width of roster window if something goes wrong. */
80 #define MIN_WIDTH 50
81
82 /* Accels (menu shortcuts) can be configured and saved */
83 #define ACCELS_FILENAME "accels.txt"
84
85 /* Name in the geometry file */
86 #define GEOMETRY_NAME "roster-window"
87
88 enum
89 {
90   PAGE_CONTACT_LIST = 0,
91   PAGE_MESSAGE
92 };
93
94 enum
95 {
96   PROP_0,
97   PROP_SHELL_RUNNING
98 };
99
100 G_DEFINE_TYPE (EmpathyRosterWindow, empathy_roster_window, GTK_TYPE_APPLICATION_WINDOW)
101
102 struct _EmpathyRosterWindowPriv {
103   EmpathyRosterView *view;
104   TpAccountManager *account_manager;
105   EmpathyChatroomManager *chatroom_manager;
106   EmpathyEventManager *event_manager;
107   EmpathySoundManager *sound_mgr;
108   EmpathyCallObserver *call_observer;
109   EmpathyIndividualManager *individual_manager;
110   guint flash_timeout_id;
111   gboolean flash_on;
112
113   GSettings *gsettings_ui;
114
115   GtkWidget *preferences;
116   GtkWidget *main_vbox;
117   GtkWidget *throbber;
118   GtkWidget *presence_toolbar;
119   GtkWidget *presence_chooser;
120   GtkWidget *errors_vbox;
121   GtkWidget *auth_vbox;
122   GtkWidget *search_bar;
123   GtkWidget *notebook;
124   GtkWidget *no_entry_label;
125   GtkWidget *button_account_settings;
126   GtkWidget *spinner_loading;
127   GtkWidget *tooltip_widget;
128
129   GMenu *menumodel;
130   GMenu *rooms_section;
131
132   GtkWidget *balance_vbox;
133
134   guint size_timeout_id;
135
136   /* reffed TpAccount* => visible GtkInfoBar* */
137   GHashTable *errors;
138
139   /* EmpathyEvent* => visible GtkInfoBar* */
140   GHashTable *auths;
141
142   /* stores a mapping from TpAccount to Handler ID to prevent
143    * to listen more than once to the status-changed signal */
144   GHashTable *status_changed_handlers;
145
146   /* Actions that are enabled when there are connected accounts */
147   GList *actions_connected;
148
149   gboolean shell_running;
150 };
151
152 static void
153 roster_window_remove_auth (EmpathyRosterWindow *self,
154     EmpathyEvent *event)
155 {
156   GtkWidget *error_widget;
157
158   error_widget = g_hash_table_lookup (self->priv->auths, event);
159   if (error_widget != NULL)
160     {
161       gtk_widget_destroy (error_widget);
162       g_hash_table_remove (self->priv->auths, event);
163     }
164 }
165
166 static void
167 roster_window_auth_add_clicked_cb (GtkButton *button,
168     EmpathyRosterWindow *self)
169 {
170   EmpathyEvent *event;
171
172   event = g_object_get_data (G_OBJECT (button), "event");
173
174   empathy_event_approve (event);
175
176   roster_window_remove_auth (self, event);
177 }
178
179 static void
180 roster_window_auth_close_clicked_cb (GtkButton *button,
181     EmpathyRosterWindow *self)
182 {
183   EmpathyEvent *event;
184
185   event = g_object_get_data (G_OBJECT (button), "event");
186
187   empathy_event_decline (event);
188   roster_window_remove_auth (self, event);
189 }
190
191 static void
192 roster_window_auth_display (EmpathyRosterWindow *self,
193     EmpathyEvent *event)
194 {
195   TpAccount *account = event->account;
196   GtkWidget *info_bar;
197   GtkWidget *content_area;
198   GtkWidget *image;
199   GtkWidget *label;
200   GtkWidget *add_button;
201   GtkWidget *close_button;
202   GtkWidget *action_area;
203   GtkWidget *action_grid;
204   const gchar *icon_name;
205   gchar *str;
206
207   if (g_hash_table_lookup (self->priv->auths, event) != NULL)
208     return;
209
210   info_bar = gtk_info_bar_new ();
211   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_QUESTION);
212
213   gtk_widget_set_no_show_all (info_bar, TRUE);
214   gtk_box_pack_start (GTK_BOX (self->priv->auth_vbox), info_bar, FALSE, TRUE, 0);
215   gtk_widget_show (info_bar);
216
217   icon_name = tp_account_get_icon_name (account);
218   image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_SMALL_TOOLBAR);
219   gtk_widget_show (image);
220
221   str = g_markup_printf_escaped ("<b>%s</b>\n%s",
222       tp_account_get_display_name (account),
223       _("Password required"));
224
225   label = gtk_label_new (str);
226   gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
227   gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
228   gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
229   gtk_widget_show (label);
230
231   g_free (str);
232
233   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
234   gtk_box_pack_start (GTK_BOX (content_area), image, FALSE, FALSE, 0);
235   gtk_box_pack_start (GTK_BOX (content_area), label, TRUE, TRUE, 0);
236
237   image = gtk_image_new_from_stock (GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON);
238   add_button = gtk_button_new ();
239   gtk_button_set_image (GTK_BUTTON (add_button), image);
240   gtk_widget_set_tooltip_text (add_button, _("Provide Password"));
241   gtk_widget_show (add_button);
242
243   image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON);
244   close_button = gtk_button_new ();
245   gtk_button_set_image (GTK_BUTTON (close_button), image);
246   gtk_widget_set_tooltip_text (close_button, _("Disconnect"));
247   gtk_widget_show (close_button);
248
249   action_grid = gtk_grid_new ();
250   gtk_grid_set_column_spacing (GTK_GRID (action_grid), 6);
251   gtk_widget_show (action_grid);
252
253   action_area = gtk_info_bar_get_action_area (GTK_INFO_BAR (info_bar));
254   gtk_box_pack_start (GTK_BOX (action_area), action_grid, FALSE, FALSE, 0);
255
256   gtk_grid_attach (GTK_GRID (action_grid), add_button, 0, 0, 1, 1);
257   gtk_grid_attach (GTK_GRID (action_grid), close_button, 1, 0, 1, 1);
258
259   g_object_set_data_full (G_OBJECT (info_bar),
260       "event", event, NULL);
261   g_object_set_data_full (G_OBJECT (add_button),
262       "event", event, NULL);
263   g_object_set_data_full (G_OBJECT (close_button),
264       "event", event, NULL);
265
266   g_signal_connect (add_button, "clicked",
267       G_CALLBACK (roster_window_auth_add_clicked_cb), self);
268   g_signal_connect (close_button, "clicked",
269       G_CALLBACK (roster_window_auth_close_clicked_cb), self);
270
271   gtk_widget_show (self->priv->auth_vbox);
272
273   g_hash_table_insert (self->priv->auths, event, info_bar);
274 }
275
276 static FolksIndividual *
277 ensure_individual_for_event (EmpathyEvent *event)
278 {
279   TpContact *contact;
280
281   contact = empathy_contact_get_tp_contact (event->contact);
282   if (contact == NULL)
283     return NULL;
284
285   return empathy_ensure_individual_from_tp_contact (contact);
286 }
287
288 static void
289 roster_window_event_added_cb (EmpathyEventManager *manager,
290     EmpathyEvent *event,
291     EmpathyRosterWindow *self)
292 {
293   if (event->contact)
294     {
295       FolksIndividual *individual;
296
297       individual = ensure_individual_for_event (event);
298       if (individual == NULL)
299         return;
300
301       event->roster_view_id = empathy_roster_view_add_event (self->priv->view,
302           individual, event->icon_name, event);
303
304       g_object_unref (individual);
305     }
306   else if (event->type == EMPATHY_EVENT_TYPE_AUTH)
307     {
308       roster_window_auth_display (self, event);
309     }
310 }
311
312 static void
313 roster_window_event_removed_cb (EmpathyEventManager *manager,
314     EmpathyEvent *event,
315     EmpathyRosterWindow *self)
316 {
317   if (event->type == EMPATHY_EVENT_TYPE_AUTH)
318     {
319       roster_window_remove_auth (self, event);
320       return;
321     }
322
323   empathy_roster_view_remove_event (self->priv->view, event->roster_view_id);
324 }
325
326 static gboolean
327 roster_window_load_events_idle_cb (gpointer user_data)
328 {
329   EmpathyRosterWindow *self = user_data;
330   GSList *l;
331
332   l = empathy_event_manager_get_events (self->priv->event_manager);
333   while (l != NULL)
334     {
335       roster_window_event_added_cb (self->priv->event_manager, l->data,
336           self);
337       l = l->next;
338     }
339
340   return FALSE;
341 }
342
343 static void
344 individual_activated_cb (EmpathyRosterView *self,
345     FolksIndividual *individual,
346     gpointer user_data)
347 {
348   EmpathyContact *contact;
349
350   contact = empathy_contact_dup_best_for_action (individual,
351       EMPATHY_ACTION_CHAT);
352
353   if (contact == NULL)
354     return;
355
356   DEBUG ("Starting a chat");
357
358   empathy_chat_with_contact (contact, gtk_get_current_event_time ());
359
360   g_object_unref (contact);
361 }
362
363 static void
364 event_activated_cb (EmpathyRosterView *self,
365     FolksIndividual *individual,
366     gpointer user_data)
367 {
368   EmpathyEvent *event = user_data;
369
370   empathy_event_activate (event);
371 }
372
373 static void
374 button_account_settings_clicked_cb (GtkButton *button,
375     EmpathyRosterWindow *self)
376 {
377   empathy_accounts_dialog_show_application (gdk_screen_get_default (),
378       NULL, FALSE, FALSE);
379 }
380
381 static void
382 display_page_message (EmpathyRosterWindow *self,
383     const gchar *msg,
384     gboolean display_accounts_button,
385     gboolean display_spinner)
386 {
387   if (msg != NULL)
388     {
389       gchar *tmp;
390
391       tmp = g_strdup_printf ("<b><span size='xx-large'>%s</span></b>", msg);
392
393       gtk_label_set_markup (GTK_LABEL (self->priv->no_entry_label), tmp);
394       g_free (tmp);
395
396       gtk_label_set_line_wrap (GTK_LABEL (self->priv->no_entry_label), TRUE);
397       gtk_widget_show (self->priv->no_entry_label);
398     }
399   else
400     {
401       gtk_widget_hide (self->priv->no_entry_label);
402     }
403
404   gtk_widget_set_visible (self->priv->button_account_settings,
405       display_accounts_button);
406   gtk_widget_set_visible (self->priv->spinner_loading,
407       display_spinner);
408
409   gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
410       PAGE_MESSAGE);
411 }
412
413 static void
414 display_page_no_account (EmpathyRosterWindow *self)
415 {
416   display_page_message (self,
417       _("You need to setup an account to see contacts here."), TRUE, FALSE);
418 }
419
420 static void
421 display_page_contact_list (EmpathyRosterWindow *self)
422 {
423   if (!empathy_individual_manager_get_contacts_loaded (
424         self->priv->individual_manager))
425     /* We'll display the contact list once we're done loading */
426     return;
427
428   gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
429       PAGE_CONTACT_LIST);
430 }
431
432 static void
433 roster_window_remove_error (EmpathyRosterWindow *self,
434     TpAccount *account)
435 {
436   GtkWidget *error_widget;
437
438   error_widget = g_hash_table_lookup (self->priv->errors, account);
439   if (error_widget != NULL)
440     {
441       gtk_widget_destroy (error_widget);
442       g_hash_table_remove (self->priv->errors, account);
443     }
444 }
445
446 static void
447 roster_window_account_disabled_cb (TpAccountManager  *manager,
448     TpAccount *account,
449     EmpathyRosterWindow *self)
450 {
451   roster_window_remove_error (self, account);
452 }
453
454 static void
455 roster_window_error_retry_clicked_cb (GtkButton *button,
456     EmpathyRosterWindow *self)
457 {
458   TpAccount *account;
459
460   account = g_object_get_data (G_OBJECT (button), "account");
461   tp_account_reconnect_async (account, NULL, NULL);
462
463   roster_window_remove_error (self, account);
464 }
465
466 static void
467 roster_window_error_edit_clicked_cb (GtkButton *button,
468     EmpathyRosterWindow *self)
469 {
470   TpAccount *account;
471
472   account = g_object_get_data (G_OBJECT (button), "account");
473
474   empathy_accounts_dialog_show_application (
475       gtk_widget_get_screen (GTK_WIDGET (button)),
476       account, FALSE, FALSE);
477
478   roster_window_remove_error (self, account);
479 }
480
481 static void
482 roster_window_error_close_clicked_cb (GtkButton *button,
483     EmpathyRosterWindow *self)
484 {
485   TpAccount *account;
486
487   account = g_object_get_data (G_OBJECT (button), "account");
488   roster_window_remove_error (self, account);
489 }
490
491 static void
492 roster_window_error_upgrade_sw_clicked_cb (GtkButton *button,
493     EmpathyRosterWindow *self)
494 {
495   TpAccount *account;
496   GtkWidget *dialog;
497
498   account = g_object_get_data (G_OBJECT (button), "account");
499   roster_window_remove_error (self, account);
500
501   dialog = gtk_message_dialog_new (GTK_WINDOW (self),
502       GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
503       GTK_BUTTONS_OK,
504       _("Sorry, %s accounts can’t be used until your %s software is updated."),
505       tp_account_get_protocol (account),
506       tp_account_get_protocol (account));
507
508   g_signal_connect_swapped (dialog, "response",
509       G_CALLBACK (gtk_widget_destroy),
510       dialog);
511
512   gtk_widget_show (dialog);
513 }
514
515 static void
516 roster_window_upgrade_software_error (EmpathyRosterWindow *self,
517     TpAccount *account)
518 {
519   GtkWidget *info_bar;
520   GtkWidget *content_area;
521   GtkWidget *label;
522   GtkWidget *image;
523   GtkWidget *upgrade_button;
524   GtkWidget *close_button;
525   GtkWidget *action_area;
526   GtkWidget *action_grid;
527   gchar *str;
528   const gchar *icon_name;
529   const gchar *error_message;
530   gboolean user_requested;
531
532   error_message =
533     empathy_account_get_error_message (account, &user_requested);
534
535   if (user_requested)
536     return;
537
538   str = g_markup_printf_escaped ("<b>%s</b>\n%s",
539       tp_account_get_display_name (account),
540       error_message);
541
542   /* If there are other errors, remove them */
543   roster_window_remove_error (self, account);
544
545   info_bar = gtk_info_bar_new ();
546   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_ERROR);
547
548   gtk_widget_set_no_show_all (info_bar, TRUE);
549   gtk_box_pack_start (GTK_BOX (self->priv->errors_vbox), info_bar, FALSE, TRUE, 0);
550   gtk_widget_show (info_bar);
551
552   icon_name = tp_account_get_icon_name (account);
553   image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_SMALL_TOOLBAR);
554   gtk_widget_show (image);
555
556   label = gtk_label_new (str);
557   gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
558   gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
559   gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
560   gtk_widget_show (label);
561   g_free (str);
562
563   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
564   gtk_box_pack_start (GTK_BOX (content_area), image, FALSE, FALSE, 0);
565   gtk_box_pack_start (GTK_BOX (content_area), label, TRUE, TRUE, 0);
566
567   image = gtk_image_new_from_stock (GTK_STOCK_REFRESH, GTK_ICON_SIZE_BUTTON);
568   upgrade_button = gtk_button_new ();
569   gtk_button_set_image (GTK_BUTTON (upgrade_button), image);
570   gtk_widget_set_tooltip_text (upgrade_button, _("Update software..."));
571   gtk_widget_show (upgrade_button);
572
573   image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON);
574   close_button = gtk_button_new ();
575   gtk_button_set_image (GTK_BUTTON (close_button), image);
576   gtk_widget_set_tooltip_text (close_button, _("Close"));
577   gtk_widget_show (close_button);
578
579   action_grid = gtk_grid_new ();
580   gtk_grid_set_column_spacing (GTK_GRID (action_grid), 2);
581   gtk_widget_show (action_grid);
582
583   action_area = gtk_info_bar_get_action_area (GTK_INFO_BAR (info_bar));
584   gtk_box_pack_start (GTK_BOX (action_area), action_grid, FALSE, FALSE, 0);
585
586   gtk_grid_attach (GTK_GRID (action_grid), upgrade_button, 0, 0, 1, 1);
587   gtk_grid_attach (GTK_GRID (action_grid), close_button, 1, 0, 1, 1);
588
589   g_object_set_data (G_OBJECT (info_bar), "label", label);
590   g_object_set_data_full (G_OBJECT (info_bar),
591       "account", g_object_ref (account),
592       g_object_unref);
593   g_object_set_data_full (G_OBJECT (upgrade_button),
594       "account", g_object_ref (account),
595       g_object_unref);
596   g_object_set_data_full (G_OBJECT (close_button),
597       "account", g_object_ref (account),
598       g_object_unref);
599
600   g_signal_connect (upgrade_button, "clicked",
601       G_CALLBACK (roster_window_error_upgrade_sw_clicked_cb), self);
602   g_signal_connect (close_button, "clicked",
603       G_CALLBACK (roster_window_error_close_clicked_cb), self);
604
605   gtk_widget_set_tooltip_text (self->priv->errors_vbox, error_message);
606   gtk_widget_show (self->priv->errors_vbox);
607
608   g_hash_table_insert (self->priv->errors, g_object_ref (account), info_bar);
609 }
610
611 static void
612 roster_window_error_display (EmpathyRosterWindow *self,
613     TpAccount *account)
614 {
615   GtkWidget *info_bar;
616   GtkWidget *content_area;
617   GtkWidget *label;
618   GtkWidget *image;
619   GtkWidget *retry_button;
620   GtkWidget *edit_button;
621   GtkWidget *close_button;
622   GtkWidget *action_area;
623   GtkWidget *action_grid;
624   gchar *str;
625   const gchar *icon_name;
626   const gchar *error_message;
627   gboolean user_requested;
628
629   if (!tp_strdiff (TP_ERROR_STR_SOFTWARE_UPGRADE_REQUIRED,
630        tp_account_get_detailed_error (account, NULL)))
631     {
632       roster_window_upgrade_software_error (self, account);
633       return;
634     }
635
636   error_message = empathy_account_get_error_message (account, &user_requested);
637
638   if (user_requested)
639     return;
640
641   str = g_markup_printf_escaped ("<b>%s</b>\n%s",
642       tp_account_get_display_name (account), error_message);
643
644   info_bar = g_hash_table_lookup (self->priv->errors, account);
645   if (info_bar)
646     {
647       label = g_object_get_data (G_OBJECT (info_bar), "label");
648
649       /* Just set the latest error and return */
650       gtk_label_set_markup (GTK_LABEL (label), str);
651       g_free (str);
652
653       return;
654     }
655
656   info_bar = gtk_info_bar_new ();
657   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_ERROR);
658
659   gtk_widget_set_no_show_all (info_bar, TRUE);
660   gtk_box_pack_start (GTK_BOX (self->priv->errors_vbox), info_bar, FALSE, TRUE, 0);
661   gtk_widget_show (info_bar);
662
663   icon_name = tp_account_get_icon_name (account);
664   image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_SMALL_TOOLBAR);
665   gtk_widget_show (image);
666
667   label = gtk_label_new (str);
668   gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
669   gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
670   gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
671   gtk_widget_show (label);
672   g_free (str);
673
674   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
675   gtk_box_pack_start (GTK_BOX (content_area), image, FALSE, FALSE, 0);
676   gtk_box_pack_start (GTK_BOX (content_area), label, TRUE, TRUE, 0);
677
678   image = gtk_image_new_from_stock (GTK_STOCK_REFRESH, GTK_ICON_SIZE_BUTTON);
679   retry_button = gtk_button_new ();
680   gtk_button_set_image (GTK_BUTTON (retry_button), image);
681   gtk_widget_set_tooltip_text (retry_button, _("Reconnect"));
682   gtk_widget_show (retry_button);
683
684   image = gtk_image_new_from_stock (GTK_STOCK_EDIT, GTK_ICON_SIZE_BUTTON);
685   edit_button = gtk_button_new ();
686   gtk_button_set_image (GTK_BUTTON (edit_button), image);
687   gtk_widget_set_tooltip_text (edit_button, _("Edit Account"));
688   gtk_widget_show (edit_button);
689
690   image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON);
691   close_button = gtk_button_new ();
692   gtk_button_set_image (GTK_BUTTON (close_button), image);
693   gtk_widget_set_tooltip_text (close_button, _("Close"));
694   gtk_widget_show (close_button);
695
696   action_grid = gtk_grid_new ();
697   gtk_grid_set_column_spacing (GTK_GRID (action_grid), 2);
698   gtk_widget_show (action_grid);
699
700   action_area = gtk_info_bar_get_action_area (GTK_INFO_BAR (info_bar));
701   gtk_box_pack_start (GTK_BOX (action_area), action_grid, FALSE, FALSE, 0);
702
703   gtk_grid_attach (GTK_GRID (action_grid), retry_button, 0, 0, 1, 1);
704   gtk_grid_attach (GTK_GRID (action_grid), edit_button, 1, 0, 1, 1);
705   gtk_grid_attach (GTK_GRID (action_grid), close_button, 2, 0, 1, 1);
706
707   g_object_set_data (G_OBJECT (info_bar), "label", label);
708   g_object_set_data_full (G_OBJECT (info_bar),
709       "account", g_object_ref (account),
710       g_object_unref);
711   g_object_set_data_full (G_OBJECT (edit_button),
712       "account", g_object_ref (account),
713       g_object_unref);
714   g_object_set_data_full (G_OBJECT (close_button),
715       "account", g_object_ref (account),
716       g_object_unref);
717   g_object_set_data_full (G_OBJECT (retry_button),
718       "account", g_object_ref (account),
719       g_object_unref);
720
721   g_signal_connect (edit_button, "clicked",
722       G_CALLBACK (roster_window_error_edit_clicked_cb), self);
723   g_signal_connect (close_button, "clicked",
724       G_CALLBACK (roster_window_error_close_clicked_cb), self);
725   g_signal_connect (retry_button, "clicked",
726       G_CALLBACK (roster_window_error_retry_clicked_cb), self);
727
728   gtk_widget_set_tooltip_text (self->priv->errors_vbox, error_message);
729   gtk_widget_show (self->priv->errors_vbox);
730
731   g_hash_table_insert (self->priv->errors, g_object_ref (account), info_bar);
732 }
733
734 static void
735 roster_window_update_status (EmpathyRosterWindow *self)
736 {
737   gboolean connected, connecting;
738   GList *l;
739
740   connected = empathy_account_manager_get_accounts_connected (&connecting);
741
742   /* Update the spinner state */
743   if (connecting)
744     {
745       gtk_spinner_start (GTK_SPINNER (self->priv->throbber));
746       gtk_widget_show (self->priv->throbber);
747     }
748   else
749     {
750       gtk_spinner_stop (GTK_SPINNER (self->priv->throbber));
751       gtk_widget_hide (self->priv->throbber);
752     }
753
754   /* Update widgets sensibility */
755   for (l = self->priv->actions_connected; l; l = l->next)
756     g_simple_action_set_enabled (l->data, connected);
757 }
758
759 static void
760 roster_window_balance_update_balance (EmpathyRosterWindow *self,
761     TpAccount *account)
762 {
763   TpConnection *conn;
764   GtkWidget *label;
765   int amount = 0;
766   guint scale = G_MAXINT32;
767   const gchar *currency = "";
768   char *money;
769
770   conn = tp_account_get_connection (account);
771   if (conn == NULL)
772     return;
773
774   if (!tp_connection_get_balance (conn, &amount, &scale, &currency))
775     return;
776
777   if (amount == 0 &&
778       scale == G_MAXINT32 &&
779       tp_str_empty (currency))
780     {
781       /* unknown balance */
782       money = g_strdup ("--");
783     }
784   else
785     {
786       char *tmp = empathy_format_currency (amount, scale, currency);
787
788       money = g_strdup_printf ("%s %s", currency, tmp);
789       g_free (tmp);
790     }
791
792   /* update the money label in the roster */
793   label = g_object_get_data (G_OBJECT (account), "balance-money-label");
794
795   gtk_label_set_text (GTK_LABEL (label), money);
796   g_free (money);
797 }
798
799 static void
800 roster_window_balance_changed_cb (TpConnection *conn,
801     guint balance,
802     guint scale,
803     const gchar *currency,
804     EmpathyRosterWindow *self)
805 {
806   TpAccount *account;
807
808   account = tp_connection_get_account (conn);
809   if (account == NULL)
810     return;
811
812   roster_window_balance_update_balance (self, account);
813 }
814
815 static void
816 roster_window_setup_balance (EmpathyRosterWindow *self,
817     TpAccount *account)
818 {
819   TpConnection *conn = tp_account_get_connection (account);
820   GtkWidget *hbox, *image, *label;
821   const gchar *uri;
822
823   if (conn == NULL)
824     return;
825
826   if (!tp_proxy_is_prepared (conn, TP_CONNECTION_FEATURE_BALANCE))
827     return;
828
829   DEBUG ("Setting up balance for acct: %s",
830       tp_account_get_display_name (account));
831
832   /* create the display widget */
833   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3);
834   gtk_container_set_border_width (GTK_CONTAINER (hbox), 3);
835
836   /* protocol icon */
837   image = gtk_image_new ();
838   gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, TRUE, 0);
839   g_object_bind_property (account, "icon-name", image, "icon-name",
840       G_BINDING_SYNC_CREATE);
841
842   /* account name label */
843   label = gtk_label_new ("");
844   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
845   gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
846   gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
847   g_object_bind_property (account, "display-name", label, "label",
848       G_BINDING_SYNC_CREATE);
849
850   /* balance label */
851   label = gtk_label_new ("");
852   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
853   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 0);
854
855   /* top up button */
856   uri = tp_connection_get_balance_uri (conn);
857
858   if (!tp_str_empty (uri))
859     {
860       GtkWidget *button;
861
862       button = gtk_button_new ();
863       gtk_container_add (GTK_CONTAINER (button),
864           gtk_image_new_from_icon_name ("emblem-symbolic-link",
865             GTK_ICON_SIZE_SMALL_TOOLBAR));
866       gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
867       gtk_widget_set_tooltip_text (button, _("Top up account"));
868       gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, TRUE, 0);
869
870       g_signal_connect_data (button, "clicked",
871           G_CALLBACK (empathy_url_show),
872           g_strdup (uri), (GClosureNotify) g_free,
873           0);
874     }
875
876   gtk_box_pack_start (GTK_BOX (self->priv->balance_vbox), hbox, FALSE, TRUE, 0);
877   gtk_widget_show_all (hbox);
878
879   g_object_set_data (G_OBJECT (account), "balance-money-label", label);
880   g_object_set_data (G_OBJECT (account), "balance-money-hbox", hbox);
881
882   roster_window_balance_update_balance (self, account);
883
884   g_signal_connect (conn, "balance-changed",
885       G_CALLBACK (roster_window_balance_changed_cb), self);
886 }
887
888 static void
889 roster_window_remove_balance_action (EmpathyRosterWindow *self,
890     TpAccount *account)
891 {
892   GtkWidget *hbox =
893     g_object_get_data (G_OBJECT (account), "balance-money-hbox");
894
895   if (hbox == NULL)
896     return;
897
898   g_return_if_fail (GTK_IS_BOX (hbox));
899
900   gtk_widget_destroy (hbox);
901 }
902
903 static void
904 roster_window_connection_changed_cb (TpAccount  *account,
905     guint old_status,
906     guint current,
907     guint reason,
908     gchar *dbus_error_name,
909     GHashTable *details,
910     EmpathyRosterWindow *self)
911 {
912   roster_window_update_status (self);
913
914   if (current == TP_CONNECTION_STATUS_DISCONNECTED &&
915       reason != TP_CONNECTION_STATUS_REASON_REQUESTED)
916     {
917       roster_window_error_display (self, account);
918     }
919
920   if (current == TP_CONNECTION_STATUS_DISCONNECTED)
921     {
922       empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
923               EMPATHY_SOUND_ACCOUNT_DISCONNECTED);
924     }
925
926   if (current == TP_CONNECTION_STATUS_CONNECTED)
927     {
928       empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
929               EMPATHY_SOUND_ACCOUNT_CONNECTED);
930
931       /* Account connected without error, remove error message if any */
932       roster_window_remove_error (self, account);
933     }
934 }
935
936 static void
937 roster_window_accels_load (void)
938 {
939   gchar *filename;
940
941   filename = g_build_filename (g_get_user_config_dir (),
942       PACKAGE_NAME, ACCELS_FILENAME, NULL);
943   if (g_file_test (filename, G_FILE_TEST_EXISTS))
944     {
945       DEBUG ("Loading from:'%s'", filename);
946       gtk_accel_map_load (filename);
947     }
948
949   g_free (filename);
950 }
951
952 static void
953 roster_window_accels_save (void)
954 {
955   gchar *dir;
956   gchar *file_with_path;
957
958   dir = g_build_filename (g_get_user_config_dir (), PACKAGE_NAME, NULL);
959   g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
960   file_with_path = g_build_filename (dir, ACCELS_FILENAME, NULL);
961   g_free (dir);
962
963   DEBUG ("Saving to:'%s'", file_with_path);
964   gtk_accel_map_save (file_with_path);
965
966   g_free (file_with_path);
967 }
968
969 static void
970 empathy_roster_window_finalize (GObject *window)
971 {
972   EmpathyRosterWindow *self = EMPATHY_ROSTER_WINDOW (window);
973   GHashTableIter iter;
974   gpointer key, value;
975
976   /* Save user-defined accelerators. */
977   roster_window_accels_save ();
978
979   g_list_free (self->priv->actions_connected);
980
981   g_object_unref (self->priv->account_manager);
982   g_object_unref (self->priv->sound_mgr);
983   g_hash_table_unref (self->priv->errors);
984   g_hash_table_unref (self->priv->auths);
985
986   /* disconnect all handlers of status-changed signal */
987   g_hash_table_iter_init (&iter, self->priv->status_changed_handlers);
988   while (g_hash_table_iter_next (&iter, &key, &value))
989     g_signal_handler_disconnect (TP_ACCOUNT (key), GPOINTER_TO_UINT (value));
990
991   g_hash_table_unref (self->priv->status_changed_handlers);
992
993   g_object_unref (self->priv->call_observer);
994   g_object_unref (self->priv->event_manager);
995   g_object_unref (self->priv->chatroom_manager);
996
997   g_object_unref (self->priv->gsettings_ui);
998   g_object_unref (self->priv->individual_manager);
999
1000   g_object_unref (self->priv->menumodel);
1001   g_object_unref (self->priv->rooms_section);
1002
1003   g_clear_object (&self->priv->tooltip_widget);
1004
1005   G_OBJECT_CLASS (empathy_roster_window_parent_class)->finalize (window);
1006 }
1007
1008 static gboolean
1009 roster_window_key_press_event_cb  (GtkWidget *window,
1010     GdkEventKey *event,
1011     gpointer user_data)
1012 {
1013   if (event->keyval == GDK_KEY_T
1014       && event->state & GDK_SHIFT_MASK
1015       && event->state & GDK_CONTROL_MASK)
1016     empathy_chat_manager_call_undo_closed_chat ();
1017
1018   return FALSE;
1019 }
1020
1021 static void
1022 roster_window_chat_quit_cb (GSimpleAction *action,
1023     GVariant *parameter,
1024     gpointer user_data)
1025 {
1026   EmpathyRosterWindow *self = user_data;
1027
1028   gtk_widget_destroy (GTK_WIDGET (self));
1029 }
1030
1031 static void
1032 roster_window_view_history_cb (GSimpleAction *action,
1033     GVariant *parameter,
1034     gpointer user_data)
1035 {
1036   EmpathyRosterWindow *self = user_data;
1037
1038   empathy_log_window_show (NULL, NULL, FALSE, GTK_WINDOW (self));
1039 }
1040
1041 static void
1042 roster_window_chat_new_message_cb (GSimpleAction *action,
1043     GVariant *parameter,
1044     gpointer user_data)
1045 {
1046   EmpathyRosterWindow *self = user_data;
1047
1048   empathy_new_message_dialog_show (GTK_WINDOW (self));
1049 }
1050
1051 static void
1052 roster_window_chat_new_call_cb (GSimpleAction *action,
1053     GVariant *parameter,
1054     gpointer user_data)
1055 {
1056   EmpathyRosterWindow *self = user_data;
1057
1058   empathy_new_call_dialog_show (GTK_WINDOW (self));
1059 }
1060
1061 static void
1062 roster_window_chat_add_contact_cb (GSimpleAction *action,
1063     GVariant *parameter,
1064     gpointer user_data)
1065 {
1066   EmpathyRosterWindow *self = user_data;
1067
1068   empathy_new_individual_dialog_show (GTK_WINDOW (self));
1069 }
1070
1071 static void
1072 roster_window_chat_search_contacts_cb (GSimpleAction *action,
1073     GVariant *parameter,
1074     gpointer user_data)
1075 {
1076   EmpathyRosterWindow *self = user_data;
1077   GtkWidget *dialog;
1078
1079   dialog = empathy_contact_search_dialog_new (
1080       GTK_WINDOW (self));
1081
1082   gtk_widget_show (dialog);
1083 }
1084
1085 static void
1086 roster_window_view_show_ft_manager (GSimpleAction *action,
1087     GVariant *parameter,
1088     gpointer user_data)
1089 {
1090   empathy_ft_manager_show ();
1091 }
1092
1093 static void
1094 join_chatroom (EmpathyChatroom *chatroom,
1095     gint64 timestamp)
1096 {
1097   TpAccount *account;
1098   const gchar *room;
1099
1100   account = empathy_chatroom_get_account (chatroom);
1101   room = empathy_chatroom_get_room (chatroom);
1102
1103   DEBUG ("Requesting channel for '%s'", room);
1104   empathy_join_muc (account, room, timestamp);
1105 }
1106
1107 typedef struct
1108 {
1109   TpAccount *account;
1110   EmpathyChatroom *chatroom;
1111   gint64 timestamp;
1112   glong sig_id;
1113   guint timeout;
1114 } join_fav_account_sig_ctx;
1115
1116 static join_fav_account_sig_ctx *
1117 join_fav_account_sig_ctx_new (TpAccount *account,
1118     EmpathyChatroom *chatroom,
1119     gint64 timestamp)
1120 {
1121   join_fav_account_sig_ctx *ctx = g_slice_new0 (
1122       join_fav_account_sig_ctx);
1123
1124   ctx->account = g_object_ref (account);
1125   ctx->chatroom = g_object_ref (chatroom);
1126   ctx->timestamp = timestamp;
1127   return ctx;
1128 }
1129
1130 static void
1131 join_fav_account_sig_ctx_free (join_fav_account_sig_ctx *ctx)
1132 {
1133   g_object_unref (ctx->account);
1134   g_object_unref (ctx->chatroom);
1135   g_slice_free (join_fav_account_sig_ctx, ctx);
1136 }
1137
1138 static void
1139 account_status_changed_cb (TpAccount  *account,
1140     TpConnectionStatus old_status,
1141     TpConnectionStatus new_status,
1142     guint reason,
1143     gchar *dbus_error_name,
1144     GHashTable *details,
1145     gpointer user_data)
1146 {
1147   join_fav_account_sig_ctx *ctx = user_data;
1148
1149   switch (new_status)
1150     {
1151       case TP_CONNECTION_STATUS_DISCONNECTED:
1152         /* Don't wait any longer */
1153         goto finally;
1154         break;
1155
1156       case TP_CONNECTION_STATUS_CONNECTING:
1157         /* Wait a bit */
1158         return;
1159
1160       case TP_CONNECTION_STATUS_CONNECTED:
1161         /* We can join the room */
1162         break;
1163
1164       default:
1165         g_assert_not_reached ();
1166     }
1167
1168   join_chatroom (ctx->chatroom, ctx->timestamp);
1169
1170 finally:
1171   g_source_remove (ctx->timeout);
1172   g_signal_handler_disconnect (account, ctx->sig_id);
1173 }
1174
1175 #define JOIN_FAVORITE_TIMEOUT 5
1176
1177 static gboolean
1178 join_favorite_timeout_cb (gpointer data)
1179 {
1180   join_fav_account_sig_ctx *ctx = data;
1181
1182   /* stop waiting for joining the favorite room */
1183   g_signal_handler_disconnect (ctx->account, ctx->sig_id);
1184   return FALSE;
1185 }
1186
1187 static void
1188 roster_window_favorite_chatroom_join (EmpathyChatroom *chatroom)
1189 {
1190   TpAccount *account;
1191
1192   account = empathy_chatroom_get_account (chatroom);
1193   if (tp_account_get_connection_status (account, NULL) !=
1194                TP_CONNECTION_STATUS_CONNECTED)
1195     {
1196       join_fav_account_sig_ctx *ctx;
1197
1198       ctx = join_fav_account_sig_ctx_new (account, chatroom,
1199         empathy_get_current_action_time ());
1200
1201       ctx->sig_id = g_signal_connect_data (account, "status-changed",
1202         G_CALLBACK (account_status_changed_cb), ctx,
1203         (GClosureNotify) join_fav_account_sig_ctx_free, 0);
1204
1205       ctx->timeout = g_timeout_add_seconds (JOIN_FAVORITE_TIMEOUT,
1206         join_favorite_timeout_cb, ctx);
1207       return;
1208     }
1209
1210   join_chatroom (chatroom, empathy_get_current_action_time ());
1211 }
1212
1213 static void
1214 roster_window_favorite_chatroom_menu_activate_cb (GAction *action,
1215     GVariant *parameter,
1216     EmpathyChatroom *chatroom)
1217 {
1218   roster_window_favorite_chatroom_join (chatroom);
1219 }
1220
1221 static gchar *
1222 dup_join_action_name (EmpathyChatroom *chatroom,
1223     gboolean prefix)
1224 {
1225   return g_strconcat (prefix ? "win." : "", "join-",
1226       empathy_chatroom_get_name (chatroom), NULL);
1227 }
1228
1229 static void
1230 roster_window_favorite_chatroom_menu_add (EmpathyRosterWindow *self,
1231     EmpathyChatroom *chatroom)
1232 {
1233   GMenuItem *item;
1234   const gchar *name, *account_name;
1235   gchar *label, *action_name;
1236   GAction *action;
1237
1238   name = empathy_chatroom_get_name (chatroom);
1239   account_name = tp_account_get_display_name (
1240       empathy_chatroom_get_account (chatroom));
1241
1242   label = g_strdup_printf ("%s (%s)", name, account_name);
1243   action_name = dup_join_action_name (chatroom, FALSE);
1244
1245   action = (GAction *) g_simple_action_new (action_name, NULL);
1246   g_free (action_name);
1247
1248   g_signal_connect (action, "activate",
1249       G_CALLBACK (roster_window_favorite_chatroom_menu_activate_cb),
1250       chatroom);
1251
1252   g_action_map_add_action (G_ACTION_MAP (self), action);
1253
1254   action_name = dup_join_action_name (chatroom, TRUE);
1255
1256   item = g_menu_item_new (label, action_name);
1257   g_menu_item_set_attribute (item, "room-name", "s", name);
1258   g_menu_append_item (self->priv->rooms_section, item);
1259
1260   g_free (label);
1261   g_free (action_name);
1262   g_object_unref (action);
1263 }
1264
1265 static void
1266 roster_window_favorite_chatroom_menu_added_cb (EmpathyChatroomManager *manager,
1267     EmpathyChatroom *chatroom,
1268     EmpathyRosterWindow *self)
1269 {
1270   roster_window_favorite_chatroom_menu_add (self, chatroom);
1271 }
1272
1273 static void
1274 roster_window_favorite_chatroom_menu_removed_cb (
1275     EmpathyChatroomManager *manager,
1276     EmpathyChatroom *chatroom,
1277     EmpathyRosterWindow *self)
1278 {
1279   GList *chatrooms;
1280   gchar *act;
1281   gint i;
1282
1283   act = dup_join_action_name (chatroom, TRUE);
1284
1285   g_action_map_remove_action (G_ACTION_MAP (self), act);
1286
1287   for (i = 0; i < g_menu_model_get_n_items (
1288         G_MENU_MODEL (self->priv->rooms_section)); i++)
1289     {
1290       const gchar *name;
1291
1292       if (g_menu_model_get_item_attribute (
1293             G_MENU_MODEL (self->priv->rooms_section), i, "room-name",
1294             "s", &name)
1295           && !tp_strdiff (name, empathy_chatroom_get_name (chatroom)))
1296         {
1297           g_menu_remove (self->priv->rooms_section, i);
1298           break;
1299         }
1300     }
1301
1302   chatrooms = empathy_chatroom_manager_get_chatrooms (
1303       self->priv->chatroom_manager, NULL);
1304
1305   g_list_free (chatrooms);
1306 }
1307
1308 static void
1309 roster_window_favorite_chatroom_menu_setup (EmpathyRosterWindow *self)
1310 {
1311   GList *chatrooms, *l;
1312
1313   self->priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
1314
1315   chatrooms = empathy_chatroom_manager_get_chatrooms (
1316     self->priv->chatroom_manager, NULL);
1317
1318   for (l = chatrooms; l; l = l->next)
1319     roster_window_favorite_chatroom_menu_add (self, l->data);
1320
1321   g_signal_connect (self->priv->chatroom_manager, "chatroom-added",
1322       G_CALLBACK (roster_window_favorite_chatroom_menu_added_cb),
1323       self);
1324
1325   g_signal_connect (self->priv->chatroom_manager, "chatroom-removed",
1326       G_CALLBACK (roster_window_favorite_chatroom_menu_removed_cb),
1327       self);
1328
1329   g_list_free (chatrooms);
1330 }
1331
1332 static void
1333 roster_window_room_join_new_cb (GSimpleAction *action,
1334     GVariant *parameter,
1335     gpointer user_data)
1336 {
1337   EmpathyRosterWindow *self = user_data;
1338
1339   empathy_new_chatroom_dialog_show (GTK_WINDOW (self));
1340 }
1341
1342 static void
1343 roster_window_room_join_favorites_cb (GSimpleAction *action,
1344     GVariant *parameter,
1345     gpointer user_data)
1346 {
1347   EmpathyRosterWindow *self = user_data;
1348   GList *chatrooms, *l;
1349
1350   chatrooms = empathy_chatroom_manager_get_chatrooms (self->priv->chatroom_manager,
1351       NULL);
1352
1353   for (l = chatrooms; l; l = l->next)
1354     roster_window_favorite_chatroom_join (l->data);
1355
1356   g_list_free (chatrooms);
1357 }
1358
1359 static void
1360 roster_window_room_manage_favorites_cb (GSimpleAction *action,
1361     GVariant *parameter,
1362     gpointer user_data)
1363 {
1364   EmpathyRosterWindow *self = user_data;
1365
1366   empathy_chatrooms_window_show (GTK_WINDOW (self));
1367 }
1368
1369 static void
1370 roster_window_edit_accounts_cb (GSimpleAction *action,
1371     GVariant *parameter,
1372     gpointer user_data)
1373 {
1374   empathy_accounts_dialog_show_application (gdk_screen_get_default (),
1375       NULL, FALSE, FALSE);
1376 }
1377
1378 static void
1379 roster_window_edit_blocked_contacts_cb (GSimpleAction *action,
1380     GVariant *parameter,
1381     gpointer user_data)
1382 {
1383   EmpathyRosterWindow *self = user_data;
1384   GtkWidget *dialog;
1385
1386   dialog = empathy_contact_blocking_dialog_new (GTK_WINDOW (self));
1387   gtk_widget_show (dialog);
1388
1389   g_signal_connect (dialog, "response",
1390       G_CALLBACK (gtk_widget_destroy), NULL);
1391 }
1392
1393 void
1394 empathy_roster_window_show_preferences (EmpathyRosterWindow *self,
1395     const gchar *tab)
1396 {
1397   if (self->priv->preferences == NULL)
1398     {
1399       self->priv->preferences = empathy_preferences_new (GTK_WINDOW (self),
1400                                                    self->priv->shell_running);
1401       g_object_add_weak_pointer (G_OBJECT (self->priv->preferences),
1402                (gpointer) &self->priv->preferences);
1403
1404       gtk_widget_show (self->priv->preferences);
1405     }
1406   else
1407     {
1408       gtk_window_present (GTK_WINDOW (self->priv->preferences));
1409     }
1410
1411   if (tab != NULL)
1412     empathy_preferences_show_tab (
1413       EMPATHY_PREFERENCES (self->priv->preferences), tab);
1414 }
1415
1416 static void
1417 roster_window_edit_preferences_cb (GSimpleAction *action,
1418     GVariant *parameter,
1419     gpointer user_data)
1420 {
1421   EmpathyRosterWindow *self = user_data;
1422
1423   empathy_roster_window_show_preferences (self, NULL);
1424 }
1425
1426 static void
1427 roster_window_help_about_cb (GSimpleAction *action,
1428     GVariant *parameter,
1429     gpointer user_data)
1430 {
1431   EmpathyRosterWindow *self = user_data;
1432
1433   empathy_about_dialog_new (GTK_WINDOW (self));
1434 }
1435
1436 static void
1437 roster_window_help_contents_cb (GSimpleAction *action,
1438     GVariant *parameter,
1439     gpointer user_data)
1440 {
1441   EmpathyRosterWindow *self = user_data;
1442
1443   empathy_url_show (GTK_WIDGET (self), "help:empathy");
1444 }
1445
1446 static gboolean
1447 roster_window_throbber_button_press_event_cb (GtkWidget *throbber,
1448     GdkEventButton *event,
1449     EmpathyRosterWindow *self)
1450 {
1451   if (event->type != GDK_BUTTON_PRESS ||
1452       event->button != 1)
1453     return FALSE;
1454
1455   empathy_accounts_dialog_show_application (
1456       gtk_widget_get_screen (GTK_WIDGET (throbber)),
1457       NULL, FALSE, FALSE);
1458
1459   return FALSE;
1460 }
1461
1462 static void
1463 roster_window_account_removed_cb (TpAccountManager  *manager,
1464     TpAccount *account,
1465     EmpathyRosterWindow *self)
1466 {
1467   /* remove errors if any */
1468   roster_window_remove_error (self, account);
1469
1470   /* remove the balance action if required */
1471   roster_window_remove_balance_action (self, account);
1472 }
1473
1474 static void
1475 account_connection_notify_cb (TpAccount *account,
1476     GParamSpec *spec,
1477     EmpathyRosterWindow *self)
1478 {
1479   TpConnection *conn;
1480
1481   conn = tp_account_get_connection (account);
1482
1483   if (conn != NULL)
1484     {
1485       roster_window_setup_balance (self, account);
1486     }
1487   else
1488     {
1489       /* remove balance action if required */
1490       roster_window_remove_balance_action (self, account);
1491     }
1492 }
1493
1494 static void
1495 add_account (EmpathyRosterWindow *self,
1496     TpAccount *account)
1497 {
1498   gulong handler_id;
1499
1500   handler_id = GPOINTER_TO_UINT (g_hash_table_lookup (
1501     self->priv->status_changed_handlers, account));
1502
1503   /* connect signal only if it was not connected yet */
1504   if (handler_id != 0)
1505     return;
1506
1507   handler_id = g_signal_connect (account, "status-changed",
1508     G_CALLBACK (roster_window_connection_changed_cb), self);
1509
1510   g_hash_table_insert (self->priv->status_changed_handlers,
1511     account, GUINT_TO_POINTER (handler_id));
1512
1513   /* roster_window_setup_balance() relies on the TpConnection to be ready on
1514    * the TpAccount so we connect this signal as well. */
1515   tp_g_signal_connect_object (account, "notify::connection",
1516       G_CALLBACK (account_connection_notify_cb), self, 0);
1517
1518   roster_window_setup_balance (self, account);
1519 }
1520
1521 /* @account: if not %NULL, the only account which can be enabled */
1522 static void
1523 display_page_account_not_enabled (EmpathyRosterWindow *self,
1524     TpAccount *account)
1525 {
1526   if (account == NULL)
1527     {
1528       display_page_message (self,
1529           _("You need to enable one of your accounts to see contacts here."),
1530           TRUE, FALSE);
1531     }
1532   else
1533     {
1534       gchar *tmp;
1535
1536       /* translators: argument is an account name */
1537       tmp = g_strdup_printf (_("You need to enable %s to see contacts here."),
1538           tp_account_get_display_name (account));
1539
1540       display_page_message (self, tmp, TRUE, FALSE);
1541       g_free (tmp);
1542     }
1543 }
1544 static gboolean
1545 has_enabled_account (GList *accounts)
1546 {
1547   GList *l;
1548
1549   for (l = accounts; l != NULL; l = g_list_next (l))
1550     {
1551       TpAccount *account = l->data;
1552
1553       if (tp_account_is_enabled (account))
1554         return TRUE;
1555     }
1556
1557   return FALSE;
1558 }
1559
1560 static void
1561 set_notebook_page (EmpathyRosterWindow *self)
1562 {
1563   GList *accounts;
1564   guint len;
1565
1566   accounts = tp_account_manager_get_valid_accounts (
1567       self->priv->account_manager);
1568
1569   len = g_list_length (accounts);
1570
1571   if (len == 0)
1572     {
1573       /* No account */
1574       display_page_no_account (self);
1575       goto out;
1576     }
1577
1578   if (!has_enabled_account (accounts))
1579     {
1580       TpAccount *account = NULL;
1581
1582       /* Pass the account if there is only one which can be enabled */
1583       if (len == 1)
1584         account = accounts->data;
1585
1586       display_page_account_not_enabled (self, account);
1587       goto out;
1588     }
1589
1590   display_page_contact_list (self);
1591
1592 out:
1593   g_list_free (accounts);
1594 }
1595
1596 static void
1597 roster_window_account_validity_changed_cb (TpAccountManager  *manager,
1598     TpAccount *account,
1599     gboolean valid,
1600     EmpathyRosterWindow *self)
1601 {
1602   if (valid)
1603     add_account (self, account);
1604   else
1605     roster_window_account_removed_cb (manager, account, self);
1606
1607   set_notebook_page (self);
1608 }
1609
1610 static void
1611 roster_window_connection_items_setup (EmpathyRosterWindow *self)
1612 {
1613   guint i;
1614   const gchar *actions_connected[] = {
1615       "room_join_new",
1616       "room_join_favorites",
1617       "chat_new_message",
1618       "chat_new_call",
1619       "chat_search_contacts",
1620       "chat_add_contact",
1621       "edit_blocked_contacts",
1622   };
1623
1624   for (i = 0; i < G_N_ELEMENTS (actions_connected); i++)
1625     {
1626       GAction *action;
1627
1628       action = g_action_map_lookup_action (G_ACTION_MAP (self),
1629           actions_connected[i]);
1630
1631       self->priv->actions_connected = g_list_prepend (
1632           self->priv->actions_connected, action);
1633     }
1634 }
1635
1636 static void
1637 account_enabled_cb (TpAccountManager *manager,
1638     TpAccount *account,
1639     EmpathyRosterWindow *self)
1640 {
1641   set_notebook_page (self);
1642 }
1643
1644 static void
1645 account_disabled_cb (TpAccountManager *manager,
1646     TpAccount *account,
1647     EmpathyRosterWindow *self)
1648 {
1649   set_notebook_page (self);
1650 }
1651
1652 static void
1653 account_manager_prepared_cb (GObject *source_object,
1654     GAsyncResult *result,
1655     gpointer user_data)
1656 {
1657   GList *accounts, *j;
1658   TpAccountManager *manager = TP_ACCOUNT_MANAGER (source_object);
1659   EmpathyRosterWindow *self = user_data;
1660   GError *error = NULL;
1661
1662   if (!tp_proxy_prepare_finish (manager, result, &error))
1663     {
1664       DEBUG ("Failed to prepare account manager: %s", error->message);
1665       g_error_free (error);
1666       return;
1667     }
1668
1669   accounts = tp_account_manager_get_valid_accounts (self->priv->account_manager);
1670   for (j = accounts; j != NULL; j = j->next)
1671     {
1672       TpAccount *account = TP_ACCOUNT (j->data);
1673
1674       add_account (self, account);
1675     }
1676
1677   g_signal_connect (manager, "account-validity-changed",
1678       G_CALLBACK (roster_window_account_validity_changed_cb), self);
1679   tp_g_signal_connect_object (manager, "account-disabled",
1680       G_CALLBACK (account_disabled_cb), self, 0);
1681   tp_g_signal_connect_object (manager, "account-enabled",
1682       G_CALLBACK (account_enabled_cb), self, 0);
1683
1684   roster_window_update_status (self);
1685
1686   set_notebook_page (self);
1687
1688   g_list_free (accounts);
1689 }
1690
1691 void
1692 empathy_roster_window_set_shell_running (EmpathyRosterWindow *self,
1693     gboolean shell_running)
1694 {
1695   if (self->priv->shell_running == shell_running)
1696     return;
1697
1698   self->priv->shell_running = shell_running;
1699   g_object_notify (G_OBJECT (self), "shell-running");
1700 }
1701
1702 static GObject *
1703 empathy_roster_window_constructor (GType type,
1704     guint n_construct_params,
1705     GObjectConstructParam *construct_params)
1706 {
1707   static GObject *window = NULL;
1708
1709   if (window != NULL)
1710     return g_object_ref (window);
1711
1712   window = G_OBJECT_CLASS (empathy_roster_window_parent_class)->constructor (
1713     type, n_construct_params, construct_params);
1714
1715   g_object_add_weak_pointer (window, (gpointer) &window);
1716
1717   return window;
1718 }
1719
1720 static GActionEntry menubar_entries[] = {
1721   { "chat_new_message", roster_window_chat_new_message_cb, NULL, NULL, NULL },
1722   { "chat_new_call", roster_window_chat_new_call_cb, NULL, NULL, NULL },
1723   { "chat_add_contact", roster_window_chat_add_contact_cb, NULL, NULL, NULL },
1724   { "chat_search_contacts", roster_window_chat_search_contacts_cb, NULL, NULL, NULL },
1725   { "chat_quit", roster_window_chat_quit_cb, NULL, NULL, NULL },
1726
1727   { "edit_accounts", roster_window_edit_accounts_cb, NULL, NULL, NULL },
1728   { "edit_blocked_contacts", roster_window_edit_blocked_contacts_cb, NULL, NULL, NULL },
1729   { "edit_preferences", roster_window_edit_preferences_cb, NULL, NULL, NULL },
1730
1731   { "view_history", roster_window_view_history_cb, NULL, NULL, NULL },
1732   { "view_show_ft_manager", roster_window_view_show_ft_manager, NULL, NULL, NULL },
1733
1734   { "room_join_new", roster_window_room_join_new_cb, NULL, NULL, NULL },
1735   { "room_join_favorites", roster_window_room_join_favorites_cb, NULL, NULL, NULL },
1736   { "room_manage_favorites", roster_window_room_manage_favorites_cb, NULL, NULL, NULL },
1737
1738   { "help_contents", roster_window_help_contents_cb, NULL, NULL, NULL },
1739   { "help_about", roster_window_help_about_cb, NULL, NULL, NULL },
1740 };
1741
1742 static void
1743 empathy_roster_window_set_property (GObject *object,
1744     guint property_id,
1745     const GValue *value,
1746     GParamSpec *pspec)
1747 {
1748   EmpathyRosterWindow *self = EMPATHY_ROSTER_WINDOW (object);
1749
1750   switch (property_id)
1751     {
1752       case PROP_SHELL_RUNNING:
1753         self->priv->shell_running = g_value_get_boolean (value);
1754         break;
1755       default:
1756         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1757         break;
1758     }
1759 }
1760
1761 static void
1762 empathy_roster_window_get_property (GObject    *object,
1763     guint property_id,
1764     GValue *value,
1765     GParamSpec *pspec)
1766 {
1767   EmpathyRosterWindow *self = EMPATHY_ROSTER_WINDOW (object);
1768
1769   switch (property_id)
1770     {
1771       case PROP_SHELL_RUNNING:
1772         g_value_set_boolean (value, self->priv->shell_running);
1773         break;
1774       default:
1775         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1776         break;
1777     }
1778 }
1779
1780 static void
1781 empathy_roster_window_constructed (GObject *self)
1782 {
1783   G_OBJECT_CLASS (empathy_roster_window_parent_class)->constructed (self);
1784 }
1785
1786 static void
1787 empathy_roster_window_class_init (EmpathyRosterWindowClass *klass)
1788 {
1789   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1790   GParamSpec *pspec;
1791
1792   object_class->finalize = empathy_roster_window_finalize;
1793   object_class->constructor = empathy_roster_window_constructor;
1794   object_class->constructed = empathy_roster_window_constructed;
1795
1796   object_class->set_property = empathy_roster_window_set_property;
1797   object_class->get_property = empathy_roster_window_get_property;
1798
1799   pspec = g_param_spec_boolean ("shell-running",
1800       "Shell running",
1801       "Whether the Shell is running or not",
1802       FALSE,
1803       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
1804   g_object_class_install_property (object_class, PROP_SHELL_RUNNING, pspec);
1805
1806   g_type_class_add_private (object_class, sizeof (EmpathyRosterWindowPriv));
1807 }
1808
1809 static void
1810 show_contacts_loading (EmpathyRosterWindow *self)
1811 {
1812   display_page_message (self, NULL, FALSE, TRUE);
1813
1814   gtk_spinner_start (GTK_SPINNER (self->priv->spinner_loading));
1815 }
1816
1817 static void
1818 hide_contacts_loading (EmpathyRosterWindow *self)
1819 {
1820   gtk_spinner_stop (GTK_SPINNER (self->priv->spinner_loading));
1821
1822   set_notebook_page (self);
1823 }
1824
1825 static void
1826 contacts_loaded_cb (EmpathyIndividualManager *manager,
1827     EmpathyRosterWindow *self)
1828 {
1829   hide_contacts_loading (self);
1830 }
1831
1832 static void
1833 roster_window_setup_actions (EmpathyRosterWindow *self)
1834 {
1835   GAction *action;
1836
1837 #define ADD_GSETTINGS_ACTION(schema, key) \
1838   action = g_settings_create_action (self->priv->gsettings_##schema, \
1839       EMPATHY_PREFS_##key); \
1840   g_action_map_add_action (G_ACTION_MAP (self), action); \
1841   g_object_unref (action);
1842
1843   ADD_GSETTINGS_ACTION (ui, UI_SHOW_OFFLINE);
1844
1845 #undef ADD_GSETTINGS_ACTION
1846 }
1847
1848 static void
1849 menu_deactivate_cb (GtkMenuShell *menushell,
1850     gpointer user_data)
1851 {
1852   /* FIXME: we shouldn't have to disconnect the signal (bgo #641327) */
1853   g_signal_handlers_disconnect_by_func (menushell,
1854       menu_deactivate_cb, user_data);
1855
1856   gtk_menu_detach (GTK_MENU (menushell));
1857 }
1858
1859 static void
1860 popup_individual_menu_cb (EmpathyRosterView *view,
1861     FolksIndividual *individual,
1862     guint button,
1863     guint time,
1864     gpointer user_data)
1865 {
1866   GtkWidget *menu;
1867   EmpathyIndividualFeatureFlags features = EMPATHY_INDIVIDUAL_FEATURE_CHAT |
1868     EMPATHY_INDIVIDUAL_FEATURE_CALL |
1869     EMPATHY_INDIVIDUAL_FEATURE_EDIT |
1870     EMPATHY_INDIVIDUAL_FEATURE_INFO |
1871     EMPATHY_INDIVIDUAL_FEATURE_LOG |
1872     EMPATHY_INDIVIDUAL_FEATURE_SMS |
1873     EMPATHY_INDIVIDUAL_FEATURE_CALL_PHONE |
1874     EMPATHY_INDIVIDUAL_FEATURE_REMOVE |
1875     EMPATHY_INDIVIDUAL_FEATURE_FILE_TRANSFER;
1876
1877   menu = empathy_individual_menu_new (individual, features, NULL);
1878
1879   /* menu is initially unowned but gtk_menu_attach_to_widget() takes its
1880    * floating ref. We can either wait for the view to release its ref
1881    * when it is destroyed (when leaving Empathy) or explicitly
1882    * detach the menu when it's not displayed any more.
1883    * We go for the latter as we don't want to keep useless menus in memory
1884    * during the whole lifetime of Empathy. */
1885   g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
1886       NULL);
1887
1888   gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
1889   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, time);
1890 }
1891
1892 static void
1893 view_empty_cb (EmpathyRosterView *view,
1894     GParamSpec *spec,
1895     EmpathyRosterWindow *self)
1896 {
1897   if (empathy_roster_view_is_empty (view))
1898     {
1899       if (empathy_roster_view_is_searching (self->priv->view))
1900         {
1901           display_page_message (self, _("No match found"), FALSE, FALSE);
1902         }
1903     }
1904   else
1905     {
1906       display_page_contact_list (self);
1907       gtk_widget_grab_focus (GTK_WIDGET (self->priv->view));
1908
1909       /* The store is being filled, it will be done after an idle cb.
1910        * So we can then get events. If we do that too soon, event's
1911        * contact is not yet in the store and it won't get marked as
1912        * having events. */
1913       g_idle_add (roster_window_load_events_idle_cb, self);
1914     }
1915 }
1916
1917 static void
1918 tooltip_destroy_cb (GtkWidget *widget,
1919     EmpathyRosterWindow *self)
1920 {
1921   g_clear_object (&self->priv->tooltip_widget);
1922 }
1923
1924 static gboolean
1925 individual_tooltip_cb (EmpathyRosterView *view,
1926     FolksIndividual *individual,
1927     gboolean keyboard_mode,
1928     GtkTooltip *tooltip,
1929     EmpathyRosterWindow *self)
1930 {
1931   if (self->priv->tooltip_widget == NULL)
1932     {
1933       self->priv->tooltip_widget = empathy_individual_widget_new (individual,
1934           EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
1935           EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
1936           EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
1937
1938       gtk_container_set_border_width (
1939           GTK_CONTAINER (self->priv->tooltip_widget), 8);
1940
1941       g_object_ref (self->priv->tooltip_widget);
1942
1943       tp_g_signal_connect_object (self->priv->tooltip_widget, "destroy",
1944           G_CALLBACK (tooltip_destroy_cb), self, 0);
1945
1946       gtk_widget_show (self->priv->tooltip_widget);
1947     }
1948   else
1949     {
1950       empathy_individual_widget_set_individual (
1951         EMPATHY_INDIVIDUAL_WIDGET (self->priv->tooltip_widget), individual);
1952     }
1953
1954   gtk_tooltip_set_custom (tooltip, self->priv->tooltip_widget);
1955
1956   return TRUE;
1957 }
1958
1959 static void
1960 empathy_roster_window_init (EmpathyRosterWindow *self)
1961 {
1962   GtkBuilder *gui;
1963   GtkWidget *sw;
1964   gchar *filename;
1965   GtkWidget *search_vbox;
1966
1967   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
1968       EMPATHY_TYPE_ROSTER_WINDOW, EmpathyRosterWindowPriv);
1969
1970   empathy_set_css_provider (GTK_WIDGET (self));
1971
1972   self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
1973
1974   self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
1975
1976   gtk_window_set_title (GTK_WINDOW (self), _("Contact List"));
1977   gtk_window_set_role (GTK_WINDOW (self), "contact_list");
1978   gtk_window_set_default_size (GTK_WINDOW (self), 225, 325);
1979
1980   /* don't finalize the widget on delete-event, just hide it */
1981   g_signal_connect (self, "delete-event",
1982     G_CALLBACK (gtk_widget_hide_on_delete), NULL);
1983
1984   /* Set up interface */
1985   filename = empathy_file_lookup ("empathy-roster-window.ui", "src");
1986   gui = empathy_builder_get_file (filename,
1987       "main_vbox", &self->priv->main_vbox,
1988       "balance_vbox", &self->priv->balance_vbox,
1989       "errors_vbox", &self->priv->errors_vbox,
1990       "auth_vbox", &self->priv->auth_vbox,
1991       "search_vbox", &search_vbox,
1992       "presence_toolbar", &self->priv->presence_toolbar,
1993       "notebook", &self->priv->notebook,
1994       "no_entry_label", &self->priv->no_entry_label,
1995       "roster_scrolledwindow", &sw,
1996       "button_account_settings", &self->priv->button_account_settings,
1997       "spinner_loading", &self->priv->spinner_loading,
1998       NULL);
1999   g_free (filename);
2000
2001   gtk_container_add (GTK_CONTAINER (self), self->priv->main_vbox);
2002   gtk_widget_show (self->priv->main_vbox);
2003
2004   g_signal_connect (self, "key-press-event",
2005       G_CALLBACK (roster_window_key_press_event_cb), NULL);
2006
2007   g_object_unref (gui);
2008
2009   self->priv->account_manager = tp_account_manager_dup ();
2010
2011   tp_proxy_prepare_async (self->priv->account_manager, NULL,
2012       account_manager_prepared_cb, self);
2013
2014   self->priv->errors = g_hash_table_new_full (g_direct_hash, g_direct_equal,
2015       g_object_unref, NULL);
2016
2017   self->priv->auths = g_hash_table_new (NULL, NULL);
2018
2019   self->priv->status_changed_handlers = g_hash_table_new_full (g_direct_hash,
2020       g_direct_equal, NULL, NULL);
2021
2022   /* set up menus */
2023   g_action_map_add_action_entries (G_ACTION_MAP (self),
2024       menubar_entries, G_N_ELEMENTS (menubar_entries), self);
2025   roster_window_setup_actions (self);
2026
2027   filename = empathy_file_lookup ("empathy-roster-window-menubar.ui", "src");
2028   gui = empathy_builder_get_file (filename,
2029       "appmenu", &self->priv->menumodel,
2030       "rooms", &self->priv->rooms_section,
2031       NULL);
2032   g_free (filename);
2033
2034   g_object_ref (self->priv->menumodel);
2035   g_object_ref (self->priv->rooms_section);
2036
2037   /* Set up connection related actions. */
2038   roster_window_connection_items_setup (self);
2039   roster_window_favorite_chatroom_menu_setup (self);
2040
2041   g_object_unref (gui);
2042
2043   /* Set up contact list. */
2044   empathy_status_presets_get_all ();
2045
2046   /* Set up presence chooser */
2047   self->priv->presence_chooser = empathy_presence_chooser_new ();
2048   gtk_widget_show (self->priv->presence_chooser);
2049   gtk_box_pack_start (GTK_BOX (self->priv->presence_toolbar),
2050       self->priv->presence_chooser,
2051       TRUE, TRUE, 0);
2052
2053   /* Set up the throbber */
2054   self->priv->throbber = gtk_spinner_new ();
2055   gtk_widget_set_size_request (self->priv->throbber, 16, -1);
2056   gtk_widget_set_events (self->priv->throbber, GDK_BUTTON_PRESS_MASK);
2057   g_signal_connect (self->priv->throbber, "button-press-event",
2058     G_CALLBACK (roster_window_throbber_button_press_event_cb),
2059     self);
2060   gtk_box_pack_start (GTK_BOX (self->priv->presence_toolbar),
2061       self->priv->throbber,
2062       FALSE, TRUE, 0);
2063
2064   self->priv->individual_manager = empathy_individual_manager_dup_singleton ();
2065
2066   if (!empathy_individual_manager_get_contacts_loaded (
2067         self->priv->individual_manager))
2068     {
2069       show_contacts_loading (self);
2070
2071       tp_g_signal_connect_object (self->priv->individual_manager,
2072           "contacts-loaded", G_CALLBACK (contacts_loaded_cb), self, 0);
2073     }
2074
2075   self->priv->view = EMPATHY_ROSTER_VIEW (
2076       empathy_roster_view_new (self->priv->individual_manager));
2077
2078   gtk_widget_show (GTK_WIDGET (self->priv->view));
2079
2080   egg_list_box_add_to_scrolled (EGG_LIST_BOX (self->priv->view),
2081       GTK_SCROLLED_WINDOW (sw));
2082
2083   g_signal_connect (self->priv->view, "individual-activated",
2084       G_CALLBACK (individual_activated_cb), self);
2085   g_signal_connect (self->priv->view, "event-activated",
2086       G_CALLBACK (event_activated_cb), self);
2087   g_signal_connect (self->priv->view, "popup-individual-menu",
2088       G_CALLBACK (popup_individual_menu_cb), self);
2089   g_signal_connect (self->priv->view, "notify::empty",
2090       G_CALLBACK (view_empty_cb), self);
2091   g_signal_connect (self->priv->view, "individual-tooltip",
2092       G_CALLBACK (individual_tooltip_cb), self);
2093
2094   gtk_widget_set_has_tooltip (GTK_WIDGET (self->priv->view), TRUE);
2095
2096   /* Set up search bar */
2097   self->priv->search_bar = empathy_live_search_new (
2098       GTK_WIDGET (self->priv->view));
2099   empathy_roster_view_set_live_search (self->priv->view,
2100       EMPATHY_LIVE_SEARCH (self->priv->search_bar));
2101   gtk_box_pack_start (GTK_BOX (search_vbox), self->priv->search_bar,
2102       FALSE, TRUE, 0);
2103
2104   g_signal_connect_swapped (self, "map",
2105       G_CALLBACK (gtk_widget_grab_focus), self->priv->view);
2106
2107   /* Load user-defined accelerators. */
2108   roster_window_accels_load ();
2109
2110   gtk_window_set_default_size (GTK_WINDOW (self), -1, 600);
2111   /* Set window size. */
2112   empathy_geometry_bind (GTK_WINDOW (self), GEOMETRY_NAME);
2113
2114   /* Enable event handling */
2115   self->priv->call_observer = empathy_call_observer_dup_singleton ();
2116   self->priv->event_manager = empathy_event_manager_dup_singleton ();
2117
2118   tp_g_signal_connect_object (self->priv->event_manager, "event-added",
2119       G_CALLBACK (roster_window_event_added_cb), self, 0);
2120   tp_g_signal_connect_object (self->priv->event_manager, "event-removed",
2121       G_CALLBACK (roster_window_event_removed_cb), self, 0);
2122
2123   g_signal_connect (self->priv->account_manager, "account-validity-changed",
2124       G_CALLBACK (roster_window_account_validity_changed_cb), self);
2125   g_signal_connect (self->priv->account_manager, "account-removed",
2126       G_CALLBACK (roster_window_account_removed_cb), self);
2127   g_signal_connect (self->priv->account_manager, "account-disabled",
2128       G_CALLBACK (roster_window_account_disabled_cb), self);
2129
2130   g_settings_bind (self->priv->gsettings_ui, EMPATHY_PREFS_UI_SHOW_OFFLINE,
2131       self->priv->view, "show-offline",
2132       G_SETTINGS_BIND_GET);
2133   g_settings_bind (self->priv->gsettings_ui, EMPATHY_PREFS_UI_SHOW_GROUPS,
2134       self->priv->view, "show-groups",
2135       G_SETTINGS_BIND_GET);
2136   g_settings_bind (self->priv->gsettings_ui, "show-balance-in-roster",
2137       self->priv->balance_vbox, "visible",
2138       G_SETTINGS_BIND_GET);
2139
2140   g_signal_connect (self->priv->button_account_settings, "clicked",
2141       G_CALLBACK (button_account_settings_clicked_cb), self);
2142 }
2143
2144 GtkWidget *
2145 empathy_roster_window_new (GtkApplication *app)
2146 {
2147   return g_object_new (EMPATHY_TYPE_ROSTER_WINDOW,
2148       "application", app,
2149       NULL);
2150 }
2151
2152 GMenuModel *
2153 empathy_roster_window_get_menu_model (EmpathyRosterWindow *self)
2154 {
2155   g_return_val_if_fail (EMPATHY_IS_ROSTER_WINDOW (self), NULL);
2156
2157   return G_MENU_MODEL (self->priv->menumodel);
2158 }