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