]> git.0d.be Git - empathy.git/blob - src/empathy-roster-window.c
roster-window: display a specific message if there is no offline contact either
[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 #ifdef HAVE_UOA
608 static const gchar *
609 uoa_account_display_string (TpAccount *account)
610 {
611   const gchar *service;
612
613   service = tp_account_get_service (account);
614
615   /* Use well known service name, if available */
616   if (!tp_strdiff (service, "windows-live"))
617     return _("Windows Live");
618   else if (!tp_strdiff (service, "google-talk"))
619     return _("Google Talk");
620   else if (!tp_strdiff (service, "facebook"))
621     return _("Facebook");
622
623   return tp_account_get_display_name (account);
624 }
625
626 static void
627 roster_window_uoa_auth_error (EmpathyRosterWindow *self,
628     TpAccount *account)
629 {
630   GtkWidget *info_bar;
631   GtkWidget *image;
632   GtkWidget *button;
633   gchar *str;
634
635   /* translators: %s is an account name like 'Facebook' or 'Google Talk' */
636   str = g_strdup_printf (_("%s account requires authorisation"),
637       uoa_account_display_string (account));
638
639   info_bar = roster_window_error_create_info_bar (self, account,
640       GTK_MESSAGE_OTHER, str);
641   g_free (str);
642
643   image = gtk_image_new_from_icon_name ("credentials-preferences",
644       GTK_ICON_SIZE_BUTTON);
645   button = gtk_button_new ();
646   gtk_button_set_image (GTK_BUTTON (button), image);
647   gtk_widget_set_tooltip_text (button, _("Online Accounts"));
648   gtk_widget_show (button);
649
650   gtk_info_bar_add_action_widget (GTK_INFO_BAR (info_bar), button,
651       ERROR_RESPONSE_EDIT);
652 }
653 #endif
654
655 static void
656 roster_window_error_display (EmpathyRosterWindow *self,
657     TpAccount *account)
658 {
659   const gchar *error_message;
660   gboolean user_requested;
661   GtkWidget *info_bar;
662   gchar *str;
663
664   error_message =
665     empathy_account_get_error_message (account, &user_requested);
666
667   if (user_requested)
668     return;
669
670 #ifdef HAVE_UOA
671   if (!tp_strdiff (TP_ERROR_STR_AUTHENTICATION_FAILED,
672        tp_account_get_detailed_error (account, NULL)) &&
673       !tp_strdiff (tp_account_get_storage_provider (account),
674        EMPATHY_UOA_PROVIDER))
675     {
676       roster_window_uoa_auth_error (self, account);
677       return;
678     }
679 #endif
680
681   str = g_markup_printf_escaped ("<b>%s</b>\n%s",
682       tp_account_get_display_name (account), error_message);
683
684   info_bar = roster_window_error_create_info_bar (self, account,
685       GTK_MESSAGE_ERROR, str);
686   g_free (str);
687
688   gtk_widget_set_tooltip_text (self->priv->errors_vbox, error_message);
689
690   if (!tp_strdiff (TP_ERROR_STR_SOFTWARE_UPGRADE_REQUIRED,
691        tp_account_get_detailed_error (account, NULL)))
692     {
693       roster_window_error_add_stock_button (GTK_INFO_BAR (info_bar),
694           GTK_STOCK_REFRESH, _("Update software..."),
695           ERROR_RESPONSE_RETRY);
696     }
697   else
698     {
699       roster_window_error_add_stock_button (GTK_INFO_BAR (info_bar),
700           GTK_STOCK_REFRESH, _("Reconnect"),
701           ERROR_RESPONSE_RETRY);
702
703       roster_window_error_add_stock_button (GTK_INFO_BAR (info_bar),
704           GTK_STOCK_EDIT, _("Edit Account"),
705           ERROR_RESPONSE_EDIT);
706     }
707
708   roster_window_error_add_stock_button (GTK_INFO_BAR (info_bar),
709       GTK_STOCK_CLOSE, _("Close"),
710       ERROR_RESPONSE_CLOSE);
711 }
712
713 static void
714 roster_window_update_status (EmpathyRosterWindow *self)
715 {
716   gboolean connected, connecting;
717   GList *l;
718
719   connected = empathy_account_manager_get_accounts_connected (&connecting);
720
721   /* Update the spinner state */
722   if (connecting)
723     {
724       gtk_spinner_start (GTK_SPINNER (self->priv->throbber));
725       gtk_widget_show (self->priv->throbber);
726     }
727   else
728     {
729       gtk_spinner_stop (GTK_SPINNER (self->priv->throbber));
730       gtk_widget_hide (self->priv->throbber);
731     }
732
733   /* Update widgets sensibility */
734   for (l = self->priv->actions_connected; l; l = l->next)
735     g_simple_action_set_enabled (l->data, connected);
736 }
737
738 static void
739 roster_window_balance_update_balance (EmpathyRosterWindow *self,
740     TpAccount *account)
741 {
742   TpConnection *conn;
743   GtkWidget *label;
744   int amount = 0;
745   guint scale = G_MAXINT32;
746   const gchar *currency = "";
747   char *money;
748
749   conn = tp_account_get_connection (account);
750   if (conn == NULL)
751     return;
752
753   if (!tp_connection_get_balance (conn, &amount, &scale, &currency))
754     return;
755
756   if (amount == 0 &&
757       scale == G_MAXINT32 &&
758       tp_str_empty (currency))
759     {
760       /* unknown balance */
761       money = g_strdup ("--");
762     }
763   else
764     {
765       char *tmp = empathy_format_currency (amount, scale, currency);
766
767       money = g_strdup_printf ("%s %s", currency, tmp);
768       g_free (tmp);
769     }
770
771   /* update the money label in the roster */
772   label = g_object_get_data (G_OBJECT (account), "balance-money-label");
773
774   gtk_label_set_text (GTK_LABEL (label), money);
775   g_free (money);
776 }
777
778 static void
779 roster_window_balance_changed_cb (TpConnection *conn,
780     guint balance,
781     guint scale,
782     const gchar *currency,
783     EmpathyRosterWindow *self)
784 {
785   TpAccount *account;
786
787   account = tp_connection_get_account (conn);
788   if (account == NULL)
789     return;
790
791   roster_window_balance_update_balance (self, account);
792 }
793
794 static void
795 roster_window_setup_balance (EmpathyRosterWindow *self,
796     TpAccount *account)
797 {
798   TpConnection *conn = tp_account_get_connection (account);
799   GtkWidget *hbox, *image, *label;
800   const gchar *uri;
801
802   if (conn == NULL)
803     return;
804
805   if (!tp_proxy_is_prepared (conn, TP_CONNECTION_FEATURE_BALANCE))
806     return;
807
808   DEBUG ("Setting up balance for acct: %s",
809       tp_account_get_display_name (account));
810
811   /* create the display widget */
812   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3);
813   gtk_container_set_border_width (GTK_CONTAINER (hbox), 3);
814
815   /* protocol icon */
816   image = gtk_image_new ();
817   gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, TRUE, 0);
818   g_object_bind_property (account, "icon-name", image, "icon-name",
819       G_BINDING_SYNC_CREATE);
820
821   /* account name label */
822   label = gtk_label_new ("");
823   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
824   gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
825   gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
826   g_object_bind_property (account, "display-name", label, "label",
827       G_BINDING_SYNC_CREATE);
828
829   /* balance label */
830   label = gtk_label_new ("");
831   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
832   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 0);
833
834   /* top up button */
835   uri = tp_connection_get_balance_uri (conn);
836
837   if (!tp_str_empty (uri))
838     {
839       GtkWidget *button;
840
841       button = gtk_button_new ();
842       gtk_container_add (GTK_CONTAINER (button),
843           gtk_image_new_from_icon_name ("emblem-symbolic-link",
844             GTK_ICON_SIZE_SMALL_TOOLBAR));
845       gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
846       gtk_widget_set_tooltip_text (button, _("Top up account"));
847       gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, TRUE, 0);
848
849       g_signal_connect_data (button, "clicked",
850           G_CALLBACK (empathy_url_show),
851           g_strdup (uri), (GClosureNotify) g_free,
852           0);
853     }
854
855   gtk_box_pack_start (GTK_BOX (self->priv->balance_vbox), hbox, FALSE, TRUE, 0);
856   gtk_widget_show_all (hbox);
857
858   g_object_set_data (G_OBJECT (account), "balance-money-label", label);
859   g_object_set_data (G_OBJECT (account), "balance-money-hbox", hbox);
860
861   roster_window_balance_update_balance (self, account);
862
863   g_signal_connect (conn, "balance-changed",
864       G_CALLBACK (roster_window_balance_changed_cb), self);
865 }
866
867 static void
868 roster_window_remove_balance_action (EmpathyRosterWindow *self,
869     TpAccount *account)
870 {
871   GtkWidget *hbox =
872     g_object_get_data (G_OBJECT (account), "balance-money-hbox");
873
874   if (hbox == NULL)
875     return;
876
877   g_return_if_fail (GTK_IS_BOX (hbox));
878
879   gtk_widget_destroy (hbox);
880 }
881
882 static void set_notebook_page (EmpathyRosterWindow *self);
883
884 static void
885 roster_window_connection_changed_cb (TpAccount  *account,
886     guint old_status,
887     guint current,
888     guint reason,
889     gchar *dbus_error_name,
890     GHashTable *details,
891     EmpathyRosterWindow *self)
892 {
893   roster_window_update_status (self);
894   set_notebook_page (self);
895
896   if (current == TP_CONNECTION_STATUS_DISCONNECTED &&
897       reason != TP_CONNECTION_STATUS_REASON_REQUESTED)
898     {
899       roster_window_error_display (self, account);
900     }
901
902   if (current == TP_CONNECTION_STATUS_DISCONNECTED)
903     {
904       empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
905               EMPATHY_SOUND_ACCOUNT_DISCONNECTED);
906     }
907
908   if (current == TP_CONNECTION_STATUS_CONNECTED)
909     {
910       empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
911               EMPATHY_SOUND_ACCOUNT_CONNECTED);
912
913       /* Account connected without error, remove error message if any */
914       roster_window_remove_error (self, account);
915     }
916 }
917
918 static void
919 roster_window_accels_load (void)
920 {
921   gchar *filename;
922
923   filename = g_build_filename (g_get_user_config_dir (),
924       PACKAGE_NAME, ACCELS_FILENAME, NULL);
925   if (g_file_test (filename, G_FILE_TEST_EXISTS))
926     {
927       DEBUG ("Loading from:'%s'", filename);
928       gtk_accel_map_load (filename);
929     }
930
931   g_free (filename);
932 }
933
934 static void
935 roster_window_accels_save (void)
936 {
937   gchar *dir;
938   gchar *file_with_path;
939
940   dir = g_build_filename (g_get_user_config_dir (), PACKAGE_NAME, NULL);
941   g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
942   file_with_path = g_build_filename (dir, ACCELS_FILENAME, NULL);
943   g_free (dir);
944
945   DEBUG ("Saving to:'%s'", file_with_path);
946   gtk_accel_map_save (file_with_path);
947
948   g_free (file_with_path);
949 }
950
951 static void
952 empathy_roster_window_finalize (GObject *window)
953 {
954   EmpathyRosterWindow *self = EMPATHY_ROSTER_WINDOW (window);
955   GHashTableIter iter;
956   gpointer key, value;
957
958   /* Save user-defined accelerators. */
959   roster_window_accels_save ();
960
961   g_list_free (self->priv->actions_connected);
962
963   g_object_unref (self->priv->account_manager);
964   g_object_unref (self->priv->sound_mgr);
965   g_hash_table_unref (self->priv->errors);
966   g_hash_table_unref (self->priv->auths);
967
968   /* disconnect all handlers of status-changed signal */
969   g_hash_table_iter_init (&iter, self->priv->status_changed_handlers);
970   while (g_hash_table_iter_next (&iter, &key, &value))
971     g_signal_handler_disconnect (TP_ACCOUNT (key), GPOINTER_TO_UINT (value));
972
973   g_hash_table_unref (self->priv->status_changed_handlers);
974
975   g_object_unref (self->priv->call_observer);
976   g_object_unref (self->priv->event_manager);
977   g_object_unref (self->priv->chatroom_manager);
978
979   g_object_unref (self->priv->gsettings_ui);
980   g_object_unref (self->priv->individual_manager);
981
982   g_object_unref (self->priv->menumodel);
983   g_object_unref (self->priv->rooms_section);
984
985   g_clear_object (&self->priv->tooltip_widget);
986
987   G_OBJECT_CLASS (empathy_roster_window_parent_class)->finalize (window);
988 }
989
990 static gboolean
991 roster_window_key_press_event_cb  (GtkWidget *window,
992     GdkEventKey *event,
993     gpointer user_data)
994 {
995   if (event->keyval == GDK_KEY_T
996       && event->state & GDK_SHIFT_MASK
997       && event->state & GDK_CONTROL_MASK)
998     empathy_chat_manager_call_undo_closed_chat ();
999
1000   return FALSE;
1001 }
1002
1003 static void
1004 unprepare_cb (GObject *source,
1005     GAsyncResult *result,
1006     gpointer user_data)
1007 {
1008   GtkWidget *self = user_data;
1009
1010   gtk_widget_destroy (self);
1011 }
1012
1013 static void
1014 roster_window_chat_quit_cb (GSimpleAction *action,
1015     GVariant *parameter,
1016     gpointer user_data)
1017 {
1018   EmpathyRosterWindow *self = user_data;
1019
1020   /* Destroying the window will make us leave the main loop and so exit the
1021    * process. Before doing so we want to unprepare the individual manager.
1022    * Just hide the window now and actually destroy it once Folks is done.
1023    */
1024   gtk_widget_hide (GTK_WIDGET (self));
1025
1026   empathy_individual_manager_unprepare_async (self->priv->individual_manager,
1027       unprepare_cb, self);
1028 }
1029
1030 static void
1031 roster_window_view_history_cb (GSimpleAction *action,
1032     GVariant *parameter,
1033     gpointer user_data)
1034 {
1035   EmpathyRosterWindow *self = user_data;
1036
1037   empathy_log_window_show (NULL, NULL, FALSE, GTK_WINDOW (self));
1038 }
1039
1040 static void
1041 roster_window_chat_new_message_cb (GSimpleAction *action,
1042     GVariant *parameter,
1043     gpointer user_data)
1044 {
1045   EmpathyRosterWindow *self = user_data;
1046
1047   empathy_new_message_dialog_show (GTK_WINDOW (self));
1048 }
1049
1050 static void
1051 roster_window_chat_new_call_cb (GSimpleAction *action,
1052     GVariant *parameter,
1053     gpointer user_data)
1054 {
1055   EmpathyRosterWindow *self = user_data;
1056
1057   empathy_new_call_dialog_show (GTK_WINDOW (self));
1058 }
1059
1060 static void
1061 roster_window_chat_add_contact_cb (GSimpleAction *action,
1062     GVariant *parameter,
1063     gpointer user_data)
1064 {
1065   EmpathyRosterWindow *self = user_data;
1066
1067   empathy_new_individual_dialog_show (GTK_WINDOW (self));
1068 }
1069
1070 static void
1071 roster_window_chat_search_contacts_cb (GSimpleAction *action,
1072     GVariant *parameter,
1073     gpointer user_data)
1074 {
1075   EmpathyRosterWindow *self = user_data;
1076   GtkWidget *dialog;
1077
1078   dialog = empathy_contact_search_dialog_new (
1079       GTK_WINDOW (self));
1080
1081   gtk_widget_show (dialog);
1082 }
1083
1084 static void
1085 roster_window_view_show_ft_manager (GSimpleAction *action,
1086     GVariant *parameter,
1087     gpointer user_data)
1088 {
1089   empathy_ft_manager_show ();
1090 }
1091
1092 static void
1093 join_chatroom (EmpathyChatroom *chatroom,
1094     gint64 timestamp)
1095 {
1096   TpAccount *account;
1097   const gchar *room;
1098
1099   account = empathy_chatroom_get_account (chatroom);
1100   room = empathy_chatroom_get_room (chatroom);
1101
1102   DEBUG ("Requesting channel for '%s'", room);
1103   empathy_join_muc (account, room, timestamp);
1104 }
1105
1106 typedef struct
1107 {
1108   TpAccount *account;
1109   EmpathyChatroom *chatroom;
1110   gint64 timestamp;
1111   glong sig_id;
1112   guint timeout;
1113 } join_fav_account_sig_ctx;
1114
1115 static join_fav_account_sig_ctx *
1116 join_fav_account_sig_ctx_new (TpAccount *account,
1117     EmpathyChatroom *chatroom,
1118     gint64 timestamp)
1119 {
1120   join_fav_account_sig_ctx *ctx = g_slice_new0 (
1121       join_fav_account_sig_ctx);
1122
1123   ctx->account = g_object_ref (account);
1124   ctx->chatroom = g_object_ref (chatroom);
1125   ctx->timestamp = timestamp;
1126   return ctx;
1127 }
1128
1129 static void
1130 join_fav_account_sig_ctx_free (join_fav_account_sig_ctx *ctx)
1131 {
1132   g_object_unref (ctx->account);
1133   g_object_unref (ctx->chatroom);
1134   g_slice_free (join_fav_account_sig_ctx, ctx);
1135 }
1136
1137 static void
1138 account_status_changed_cb (TpAccount  *account,
1139     TpConnectionStatus old_status,
1140     TpConnectionStatus new_status,
1141     guint reason,
1142     gchar *dbus_error_name,
1143     GHashTable *details,
1144     gpointer user_data)
1145 {
1146   join_fav_account_sig_ctx *ctx = user_data;
1147
1148   switch (new_status)
1149     {
1150       case TP_CONNECTION_STATUS_DISCONNECTED:
1151         /* Don't wait any longer */
1152         goto finally;
1153         break;
1154
1155       case TP_CONNECTION_STATUS_CONNECTING:
1156         /* Wait a bit */
1157         return;
1158
1159       case TP_CONNECTION_STATUS_CONNECTED:
1160         /* We can join the room */
1161         break;
1162
1163       default:
1164         g_assert_not_reached ();
1165     }
1166
1167   join_chatroom (ctx->chatroom, ctx->timestamp);
1168
1169 finally:
1170   g_source_remove (ctx->timeout);
1171   g_signal_handler_disconnect (account, ctx->sig_id);
1172 }
1173
1174 #define JOIN_FAVORITE_TIMEOUT 5
1175
1176 static gboolean
1177 join_favorite_timeout_cb (gpointer data)
1178 {
1179   join_fav_account_sig_ctx *ctx = data;
1180
1181   /* stop waiting for joining the favorite room */
1182   g_signal_handler_disconnect (ctx->account, ctx->sig_id);
1183   return FALSE;
1184 }
1185
1186 static void
1187 roster_window_favorite_chatroom_join (EmpathyChatroom *chatroom)
1188 {
1189   TpAccount *account;
1190
1191   account = empathy_chatroom_get_account (chatroom);
1192   if (tp_account_get_connection_status (account, NULL) !=
1193                TP_CONNECTION_STATUS_CONNECTED)
1194     {
1195       join_fav_account_sig_ctx *ctx;
1196
1197       ctx = join_fav_account_sig_ctx_new (account, chatroom,
1198         empathy_get_current_action_time ());
1199
1200       ctx->sig_id = g_signal_connect_data (account, "status-changed",
1201         G_CALLBACK (account_status_changed_cb), ctx,
1202         (GClosureNotify) join_fav_account_sig_ctx_free, 0);
1203
1204       ctx->timeout = g_timeout_add_seconds (JOIN_FAVORITE_TIMEOUT,
1205         join_favorite_timeout_cb, ctx);
1206       return;
1207     }
1208
1209   join_chatroom (chatroom, empathy_get_current_action_time ());
1210 }
1211
1212 static void
1213 roster_window_favorite_chatroom_menu_activate_cb (GAction *action,
1214     GVariant *parameter,
1215     EmpathyChatroom *chatroom)
1216 {
1217   roster_window_favorite_chatroom_join (chatroom);
1218 }
1219
1220 static gchar *
1221 dup_join_action_name (EmpathyChatroom *chatroom,
1222     gboolean prefix)
1223 {
1224   return g_strconcat (prefix ? "win." : "", "join-",
1225       empathy_chatroom_get_name (chatroom), NULL);
1226 }
1227
1228 static void
1229 roster_window_favorite_chatroom_menu_add (EmpathyRosterWindow *self,
1230     EmpathyChatroom *chatroom)
1231 {
1232   GMenuItem *item;
1233   const gchar *name, *account_name;
1234   gchar *label, *action_name;
1235   GAction *action;
1236
1237   name = empathy_chatroom_get_name (chatroom);
1238   account_name = tp_account_get_display_name (
1239       empathy_chatroom_get_account (chatroom));
1240
1241   label = g_strdup_printf ("%s (%s)", name, account_name);
1242   action_name = dup_join_action_name (chatroom, FALSE);
1243
1244   action = (GAction *) g_simple_action_new (action_name, NULL);
1245   g_free (action_name);
1246
1247   g_signal_connect (action, "activate",
1248       G_CALLBACK (roster_window_favorite_chatroom_menu_activate_cb),
1249       chatroom);
1250
1251   g_action_map_add_action (G_ACTION_MAP (self), action);
1252
1253   action_name = dup_join_action_name (chatroom, TRUE);
1254
1255   item = g_menu_item_new (label, action_name);
1256   g_menu_item_set_attribute (item, "room-name", "s", name);
1257   g_menu_append_item (self->priv->rooms_section, item);
1258
1259   g_free (label);
1260   g_free (action_name);
1261   g_object_unref (action);
1262 }
1263
1264 static void
1265 roster_window_favorite_chatroom_menu_added_cb (EmpathyChatroomManager *manager,
1266     EmpathyChatroom *chatroom,
1267     EmpathyRosterWindow *self)
1268 {
1269   roster_window_favorite_chatroom_menu_add (self, chatroom);
1270 }
1271
1272 static void
1273 roster_window_favorite_chatroom_menu_removed_cb (
1274     EmpathyChatroomManager *manager,
1275     EmpathyChatroom *chatroom,
1276     EmpathyRosterWindow *self)
1277 {
1278   GList *chatrooms;
1279   gchar *act;
1280   gint i;
1281
1282   act = dup_join_action_name (chatroom, TRUE);
1283
1284   g_action_map_remove_action (G_ACTION_MAP (self), act);
1285
1286   for (i = 0; i < g_menu_model_get_n_items (
1287         G_MENU_MODEL (self->priv->rooms_section)); i++)
1288     {
1289       const gchar *name;
1290
1291       if (g_menu_model_get_item_attribute (
1292             G_MENU_MODEL (self->priv->rooms_section), i, "room-name",
1293             "s", &name)
1294           && !tp_strdiff (name, empathy_chatroom_get_name (chatroom)))
1295         {
1296           g_menu_remove (self->priv->rooms_section, i);
1297           break;
1298         }
1299     }
1300
1301   chatrooms = empathy_chatroom_manager_get_chatrooms (
1302       self->priv->chatroom_manager, NULL);
1303
1304   g_list_free (chatrooms);
1305 }
1306
1307 static void
1308 roster_window_favorite_chatroom_menu_setup (EmpathyRosterWindow *self)
1309 {
1310   GList *chatrooms, *l;
1311
1312   self->priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
1313
1314   chatrooms = empathy_chatroom_manager_get_chatrooms (
1315     self->priv->chatroom_manager, NULL);
1316
1317   for (l = chatrooms; l; l = l->next)
1318     roster_window_favorite_chatroom_menu_add (self, l->data);
1319
1320   g_signal_connect (self->priv->chatroom_manager, "chatroom-added",
1321       G_CALLBACK (roster_window_favorite_chatroom_menu_added_cb),
1322       self);
1323
1324   g_signal_connect (self->priv->chatroom_manager, "chatroom-removed",
1325       G_CALLBACK (roster_window_favorite_chatroom_menu_removed_cb),
1326       self);
1327
1328   g_list_free (chatrooms);
1329 }
1330
1331 static void
1332 roster_window_room_join_new_cb (GSimpleAction *action,
1333     GVariant *parameter,
1334     gpointer user_data)
1335 {
1336   EmpathyRosterWindow *self = user_data;
1337
1338   empathy_new_chatroom_dialog_show (GTK_WINDOW (self));
1339 }
1340
1341 static void
1342 roster_window_room_join_favorites_cb (GSimpleAction *action,
1343     GVariant *parameter,
1344     gpointer user_data)
1345 {
1346   EmpathyRosterWindow *self = user_data;
1347   GList *chatrooms, *l;
1348
1349   chatrooms = empathy_chatroom_manager_get_chatrooms (self->priv->chatroom_manager,
1350       NULL);
1351
1352   for (l = chatrooms; l; l = l->next)
1353     roster_window_favorite_chatroom_join (l->data);
1354
1355   g_list_free (chatrooms);
1356 }
1357
1358 static void
1359 roster_window_room_manage_favorites_cb (GSimpleAction *action,
1360     GVariant *parameter,
1361     gpointer user_data)
1362 {
1363   EmpathyRosterWindow *self = user_data;
1364
1365   empathy_chatrooms_window_show (GTK_WINDOW (self));
1366 }
1367
1368 static void
1369 roster_window_edit_accounts_cb (GSimpleAction *action,
1370     GVariant *parameter,
1371     gpointer user_data)
1372 {
1373   empathy_accounts_dialog_show_application (gdk_screen_get_default (),
1374       NULL, FALSE, FALSE);
1375 }
1376
1377 static void
1378 roster_window_edit_blocked_contacts_cb (GSimpleAction *action,
1379     GVariant *parameter,
1380     gpointer user_data)
1381 {
1382   EmpathyRosterWindow *self = user_data;
1383   GtkWidget *dialog;
1384
1385   dialog = empathy_contact_blocking_dialog_new (GTK_WINDOW (self));
1386   gtk_widget_show (dialog);
1387
1388   g_signal_connect (dialog, "response",
1389       G_CALLBACK (gtk_widget_destroy), NULL);
1390 }
1391
1392 void
1393 empathy_roster_window_show_preferences (EmpathyRosterWindow *self,
1394     const gchar *tab)
1395 {
1396   if (self->priv->preferences == NULL)
1397     {
1398       self->priv->preferences = empathy_preferences_new (GTK_WINDOW (self),
1399                                                    self->priv->shell_running);
1400       g_object_add_weak_pointer (G_OBJECT (self->priv->preferences),
1401                (gpointer) &self->priv->preferences);
1402
1403       gtk_widget_show (self->priv->preferences);
1404     }
1405   else
1406     {
1407       gtk_window_present (GTK_WINDOW (self->priv->preferences));
1408     }
1409
1410   if (tab != NULL)
1411     empathy_preferences_show_tab (
1412       EMPATHY_PREFERENCES (self->priv->preferences), tab);
1413 }
1414
1415 static void
1416 roster_window_edit_preferences_cb (GSimpleAction *action,
1417     GVariant *parameter,
1418     gpointer user_data)
1419 {
1420   EmpathyRosterWindow *self = user_data;
1421
1422   empathy_roster_window_show_preferences (self, NULL);
1423 }
1424
1425 static void
1426 roster_window_help_about_cb (GSimpleAction *action,
1427     GVariant *parameter,
1428     gpointer user_data)
1429 {
1430   EmpathyRosterWindow *self = user_data;
1431
1432   empathy_about_dialog_new (GTK_WINDOW (self));
1433 }
1434
1435 static void
1436 roster_window_help_contents_cb (GSimpleAction *action,
1437     GVariant *parameter,
1438     gpointer user_data)
1439 {
1440   EmpathyRosterWindow *self = user_data;
1441
1442   empathy_url_show (GTK_WIDGET (self), "help:empathy");
1443 }
1444
1445 static gboolean
1446 roster_window_throbber_button_press_event_cb (GtkWidget *throbber,
1447     GdkEventButton *event,
1448     EmpathyRosterWindow *self)
1449 {
1450   if (event->type != GDK_BUTTON_PRESS ||
1451       event->button != 1)
1452     return FALSE;
1453
1454   empathy_accounts_dialog_show_application (
1455       gtk_widget_get_screen (GTK_WIDGET (throbber)),
1456       NULL, FALSE, FALSE);
1457
1458   return FALSE;
1459 }
1460
1461 static void
1462 roster_window_account_removed_cb (TpAccountManager  *manager,
1463     TpAccount *account,
1464     EmpathyRosterWindow *self)
1465 {
1466   /* remove errors if any */
1467   roster_window_remove_error (self, account);
1468
1469   /* remove the balance action if required */
1470   roster_window_remove_balance_action (self, account);
1471 }
1472
1473 static void
1474 account_connection_notify_cb (TpAccount *account,
1475     GParamSpec *spec,
1476     EmpathyRosterWindow *self)
1477 {
1478   TpConnection *conn;
1479
1480   conn = tp_account_get_connection (account);
1481
1482   if (conn != NULL)
1483     {
1484       roster_window_setup_balance (self, account);
1485     }
1486   else
1487     {
1488       /* remove balance action if required */
1489       roster_window_remove_balance_action (self, account);
1490     }
1491 }
1492
1493 static void
1494 add_account (EmpathyRosterWindow *self,
1495     TpAccount *account)
1496 {
1497   gulong handler_id;
1498
1499   handler_id = GPOINTER_TO_UINT (g_hash_table_lookup (
1500     self->priv->status_changed_handlers, account));
1501
1502   /* connect signal only if it was not connected yet */
1503   if (handler_id != 0)
1504     return;
1505
1506   handler_id = g_signal_connect (account, "status-changed",
1507     G_CALLBACK (roster_window_connection_changed_cb), self);
1508
1509   g_hash_table_insert (self->priv->status_changed_handlers,
1510     account, GUINT_TO_POINTER (handler_id));
1511
1512   /* roster_window_setup_balance() relies on the TpConnection to be ready on
1513    * the TpAccount so we connect this signal as well. */
1514   tp_g_signal_connect_object (account, "notify::connection",
1515       G_CALLBACK (account_connection_notify_cb), self, 0);
1516
1517   roster_window_setup_balance (self, account);
1518 }
1519
1520 /* @account: if not %NULL, the only account which can be enabled */
1521 static void
1522 display_page_account_not_enabled (EmpathyRosterWindow *self,
1523     TpAccount *account)
1524 {
1525   if (account == NULL)
1526     {
1527       display_page_message (self,
1528           _("You need to enable one of your accounts to see contacts here."),
1529           PAGE_MESSAGE_FLAG_ACCOUNTS);
1530     }
1531   else
1532     {
1533       gchar *tmp;
1534
1535       /* translators: argument is an account name */
1536       tmp = g_strdup_printf (_("You need to enable %s to see contacts here."),
1537           tp_account_get_display_name (account));
1538
1539       display_page_message (self, tmp, PAGE_MESSAGE_FLAG_ACCOUNTS);
1540       g_free (tmp);
1541     }
1542 }
1543 static gboolean
1544 has_enabled_account (GList *accounts)
1545 {
1546   GList *l;
1547
1548   for (l = accounts; l != NULL; l = g_list_next (l))
1549     {
1550       TpAccount *account = l->data;
1551
1552       if (tp_account_is_enabled (account))
1553         return TRUE;
1554     }
1555
1556   return FALSE;
1557 }
1558
1559 static void
1560 set_notebook_page (EmpathyRosterWindow *self)
1561 {
1562   GList *accounts;
1563   guint len;
1564   TpConnectionPresenceType presence;
1565   gboolean connected, connecting;
1566
1567   connected = empathy_account_manager_get_accounts_connected (&connecting);
1568
1569   /* Display the loading page if either:
1570    * - We are fetching contacts from Folks (startup)
1571    * - There is no account connected but at least one is connecting
1572    */
1573   if (!empathy_individual_manager_get_contacts_loaded (
1574         self->priv->individual_manager) ||
1575       (!connected && connecting))
1576     {
1577       display_page_message (self, NULL, PAGE_MESSAGE_FLAG_SPINNER);
1578       gtk_spinner_start (GTK_SPINNER (self->priv->spinner_loading));
1579       return;
1580     }
1581
1582   gtk_spinner_stop (GTK_SPINNER (self->priv->spinner_loading));
1583
1584   accounts = tp_account_manager_dup_valid_accounts (
1585       self->priv->account_manager);
1586
1587   len = g_list_length (accounts);
1588
1589   if (len == 0)
1590     {
1591       /* No account */
1592       display_page_no_account (self);
1593       goto out;
1594     }
1595
1596   if (!has_enabled_account (accounts))
1597     {
1598       TpAccount *account = NULL;
1599
1600       /* Pass the account if there is only one which can be enabled */
1601       if (len == 1)
1602         account = accounts->data;
1603
1604       display_page_account_not_enabled (self, account);
1605       goto out;
1606     }
1607
1608   presence = tp_account_manager_get_most_available_presence (
1609       self->priv->account_manager, NULL, NULL);
1610
1611   if (presence == TP_CONNECTION_PRESENCE_TYPE_OFFLINE)
1612     {
1613       display_page_message (self,
1614           _("Change your presence to see contacts here"),
1615           PAGE_MESSAGE_FLAG_ONLINE);
1616       goto out;
1617     }
1618
1619   if (empathy_roster_view_is_empty (self->priv->view))
1620     {
1621       if (empathy_roster_view_is_searching (self->priv->view))
1622         {
1623           display_page_message (self, _("No match found"),
1624               PAGE_MESSAGE_FLAG_NONE);
1625         }
1626       else
1627         {
1628           if (g_settings_get_boolean (self->priv->gsettings_ui,
1629                 EMPATHY_PREFS_UI_SHOW_OFFLINE))
1630             display_page_message (self, _("You haven't added any contact yet"),
1631                 PAGE_MESSAGE_FLAG_NONE);
1632           else
1633             display_page_message (self, _("No online contacts"),
1634                 PAGE_MESSAGE_FLAG_NONE);
1635         }
1636       goto out;
1637     }
1638
1639   display_page_contact_list (self);
1640
1641 out:
1642   g_list_free_full (accounts, g_object_unref);
1643 }
1644
1645 static void
1646 roster_window_account_validity_changed_cb (TpAccountManager  *manager,
1647     TpAccount *account,
1648     gboolean valid,
1649     EmpathyRosterWindow *self)
1650 {
1651   if (valid)
1652     add_account (self, account);
1653   else
1654     roster_window_account_removed_cb (manager, account, self);
1655
1656   set_notebook_page (self);
1657 }
1658
1659 static void
1660 roster_window_connection_items_setup (EmpathyRosterWindow *self)
1661 {
1662   guint i;
1663   const gchar *actions_connected[] = {
1664       "room_join_new",
1665       "room_join_favorites",
1666       "chat_new_message",
1667       "chat_new_call",
1668       "chat_search_contacts",
1669       "chat_add_contact",
1670       "edit_blocked_contacts",
1671   };
1672
1673   for (i = 0; i < G_N_ELEMENTS (actions_connected); i++)
1674     {
1675       GAction *action;
1676
1677       action = g_action_map_lookup_action (G_ACTION_MAP (self),
1678           actions_connected[i]);
1679
1680       self->priv->actions_connected = g_list_prepend (
1681           self->priv->actions_connected, action);
1682     }
1683 }
1684
1685 static void
1686 account_enabled_cb (TpAccountManager *manager,
1687     TpAccount *account,
1688     EmpathyRosterWindow *self)
1689 {
1690   set_notebook_page (self);
1691 }
1692
1693 static void
1694 account_disabled_cb (TpAccountManager *manager,
1695     TpAccount *account,
1696     EmpathyRosterWindow *self)
1697 {
1698   set_notebook_page (self);
1699 }
1700
1701 static void
1702 account_manager_prepared_cb (GObject *source_object,
1703     GAsyncResult *result,
1704     gpointer user_data)
1705 {
1706   GList *accounts, *j;
1707   TpAccountManager *manager = TP_ACCOUNT_MANAGER (source_object);
1708   EmpathyRosterWindow *self = user_data;
1709   GError *error = NULL;
1710
1711   if (!tp_proxy_prepare_finish (manager, result, &error))
1712     {
1713       DEBUG ("Failed to prepare account manager: %s", error->message);
1714       g_error_free (error);
1715       return;
1716     }
1717
1718   accounts = tp_account_manager_dup_valid_accounts (
1719       self->priv->account_manager);
1720   for (j = accounts; j != NULL; j = j->next)
1721     {
1722       TpAccount *account = TP_ACCOUNT (j->data);
1723
1724       add_account (self, account);
1725     }
1726
1727   g_signal_connect (manager, "account-validity-changed",
1728       G_CALLBACK (roster_window_account_validity_changed_cb), self);
1729   tp_g_signal_connect_object (manager, "account-disabled",
1730       G_CALLBACK (account_disabled_cb), self, 0);
1731   tp_g_signal_connect_object (manager, "account-enabled",
1732       G_CALLBACK (account_enabled_cb), self, 0);
1733
1734   roster_window_update_status (self);
1735
1736   set_notebook_page (self);
1737
1738   g_list_free_full (accounts, g_object_unref);
1739 }
1740
1741 void
1742 empathy_roster_window_set_shell_running (EmpathyRosterWindow *self,
1743     gboolean shell_running)
1744 {
1745   if (self->priv->shell_running == shell_running)
1746     return;
1747
1748   self->priv->shell_running = shell_running;
1749   g_object_notify (G_OBJECT (self), "shell-running");
1750 }
1751
1752 static GObject *
1753 empathy_roster_window_constructor (GType type,
1754     guint n_construct_params,
1755     GObjectConstructParam *construct_params)
1756 {
1757   static GObject *window = NULL;
1758
1759   if (window != NULL)
1760     return g_object_ref (window);
1761
1762   window = G_OBJECT_CLASS (empathy_roster_window_parent_class)->constructor (
1763     type, n_construct_params, construct_params);
1764
1765   g_object_add_weak_pointer (window, (gpointer) &window);
1766
1767   return window;
1768 }
1769
1770 static GActionEntry menubar_entries[] = {
1771   { "chat_new_message", roster_window_chat_new_message_cb, NULL, NULL, NULL },
1772   { "chat_new_call", roster_window_chat_new_call_cb, NULL, NULL, NULL },
1773   { "chat_add_contact", roster_window_chat_add_contact_cb, NULL, NULL, NULL },
1774   { "chat_search_contacts", roster_window_chat_search_contacts_cb, NULL, NULL, NULL },
1775   { "chat_quit", roster_window_chat_quit_cb, NULL, NULL, NULL },
1776
1777   { "edit_accounts", roster_window_edit_accounts_cb, NULL, NULL, NULL },
1778   { "edit_blocked_contacts", roster_window_edit_blocked_contacts_cb, NULL, NULL, NULL },
1779   { "edit_preferences", roster_window_edit_preferences_cb, NULL, NULL, NULL },
1780
1781   { "view_history", roster_window_view_history_cb, NULL, NULL, NULL },
1782   { "view_show_ft_manager", roster_window_view_show_ft_manager, NULL, NULL, NULL },
1783
1784   { "room_join_new", roster_window_room_join_new_cb, NULL, NULL, NULL },
1785   { "room_join_favorites", roster_window_room_join_favorites_cb, NULL, NULL, NULL },
1786   { "room_manage_favorites", roster_window_room_manage_favorites_cb, NULL, NULL, NULL },
1787
1788   { "help_contents", roster_window_help_contents_cb, NULL, NULL, NULL },
1789   { "help_about", roster_window_help_about_cb, NULL, NULL, NULL },
1790 };
1791
1792 static void
1793 empathy_roster_window_set_property (GObject *object,
1794     guint property_id,
1795     const GValue *value,
1796     GParamSpec *pspec)
1797 {
1798   EmpathyRosterWindow *self = EMPATHY_ROSTER_WINDOW (object);
1799
1800   switch (property_id)
1801     {
1802       case PROP_SHELL_RUNNING:
1803         self->priv->shell_running = g_value_get_boolean (value);
1804         break;
1805       default:
1806         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1807         break;
1808     }
1809 }
1810
1811 static void
1812 empathy_roster_window_get_property (GObject    *object,
1813     guint property_id,
1814     GValue *value,
1815     GParamSpec *pspec)
1816 {
1817   EmpathyRosterWindow *self = EMPATHY_ROSTER_WINDOW (object);
1818
1819   switch (property_id)
1820     {
1821       case PROP_SHELL_RUNNING:
1822         g_value_set_boolean (value, self->priv->shell_running);
1823         break;
1824       default:
1825         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1826         break;
1827     }
1828 }
1829
1830 static void
1831 empathy_roster_window_constructed (GObject *self)
1832 {
1833   G_OBJECT_CLASS (empathy_roster_window_parent_class)->constructed (self);
1834 }
1835
1836 static void
1837 empathy_roster_window_class_init (EmpathyRosterWindowClass *klass)
1838 {
1839   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1840   GParamSpec *pspec;
1841
1842   object_class->finalize = empathy_roster_window_finalize;
1843   object_class->constructor = empathy_roster_window_constructor;
1844   object_class->constructed = empathy_roster_window_constructed;
1845
1846   object_class->set_property = empathy_roster_window_set_property;
1847   object_class->get_property = empathy_roster_window_get_property;
1848
1849   pspec = g_param_spec_boolean ("shell-running",
1850       "Shell running",
1851       "Whether the Shell is running or not",
1852       FALSE,
1853       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
1854   g_object_class_install_property (object_class, PROP_SHELL_RUNNING, pspec);
1855
1856   g_type_class_add_private (object_class, sizeof (EmpathyRosterWindowPriv));
1857 }
1858
1859 static void
1860 contacts_loaded_cb (EmpathyIndividualManager *manager,
1861     EmpathyRosterWindow *self)
1862 {
1863   set_notebook_page (self);
1864 }
1865
1866 static void
1867 roster_window_setup_actions (EmpathyRosterWindow *self)
1868 {
1869   GAction *action;
1870
1871 #define ADD_GSETTINGS_ACTION(schema, key) \
1872   action = g_settings_create_action (self->priv->gsettings_##schema, \
1873       EMPATHY_PREFS_##key); \
1874   g_action_map_add_action (G_ACTION_MAP (self), action); \
1875   g_object_unref (action);
1876
1877   ADD_GSETTINGS_ACTION (ui, UI_SHOW_OFFLINE);
1878
1879 #undef ADD_GSETTINGS_ACTION
1880 }
1881
1882 static void
1883 menu_deactivate_cb (GtkMenuShell *menushell,
1884     gpointer user_data)
1885 {
1886   /* FIXME: we shouldn't have to disconnect the signal (bgo #641327) */
1887   g_signal_handlers_disconnect_by_func (menushell,
1888       menu_deactivate_cb, user_data);
1889
1890   gtk_menu_detach (GTK_MENU (menushell));
1891 }
1892
1893 static void
1894 popup_individual_menu_cb (EmpathyRosterView *view,
1895     FolksIndividual *individual,
1896     guint button,
1897     guint time,
1898     gpointer user_data)
1899 {
1900   GtkWidget *menu;
1901   EmpathyIndividualFeatureFlags features = EMPATHY_INDIVIDUAL_FEATURE_CHAT |
1902     EMPATHY_INDIVIDUAL_FEATURE_CALL |
1903     EMPATHY_INDIVIDUAL_FEATURE_EDIT |
1904     EMPATHY_INDIVIDUAL_FEATURE_INFO |
1905     EMPATHY_INDIVIDUAL_FEATURE_LOG |
1906     EMPATHY_INDIVIDUAL_FEATURE_SMS |
1907     EMPATHY_INDIVIDUAL_FEATURE_CALL_PHONE |
1908     EMPATHY_INDIVIDUAL_FEATURE_REMOVE |
1909     EMPATHY_INDIVIDUAL_FEATURE_FILE_TRANSFER;
1910
1911   menu = empathy_individual_menu_new (individual, features, NULL);
1912
1913   /* menu is initially unowned but gtk_menu_attach_to_widget() takes its
1914    * floating ref. We can either wait for the view to release its ref
1915    * when it is destroyed (when leaving Empathy) or explicitly
1916    * detach the menu when it's not displayed any more.
1917    * We go for the latter as we don't want to keep useless menus in memory
1918    * during the whole lifetime of Empathy. */
1919   g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
1920       NULL);
1921
1922   gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
1923   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, time);
1924 }
1925
1926 static void
1927 view_empty_cb (EmpathyRosterView *view,
1928     GParamSpec *spec,
1929     EmpathyRosterWindow *self)
1930 {
1931   set_notebook_page (self);
1932
1933   if (!empathy_roster_view_is_empty (view))
1934     {
1935       gtk_widget_grab_focus (GTK_WIDGET (self->priv->view));
1936
1937       /* The store is being filled, it will be done after an idle cb.
1938        * So we can then get events. If we do that too soon, event's
1939        * contact is not yet in the store and it won't get marked as
1940        * having events. */
1941       g_idle_add (roster_window_load_events_idle_cb, self);
1942     }
1943 }
1944
1945 static void
1946 tooltip_destroy_cb (GtkWidget *widget,
1947     EmpathyRosterWindow *self)
1948 {
1949   g_clear_object (&self->priv->tooltip_widget);
1950 }
1951
1952 static gboolean
1953 individual_tooltip_cb (EmpathyRosterView *view,
1954     FolksIndividual *individual,
1955     gboolean keyboard_mode,
1956     GtkTooltip *tooltip,
1957     EmpathyRosterWindow *self)
1958 {
1959   if (self->priv->tooltip_widget == NULL)
1960     {
1961       self->priv->tooltip_widget = empathy_individual_widget_new (individual,
1962           EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
1963           EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
1964           EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
1965
1966       gtk_container_set_border_width (
1967           GTK_CONTAINER (self->priv->tooltip_widget), 8);
1968
1969       g_object_ref (self->priv->tooltip_widget);
1970
1971       tp_g_signal_connect_object (self->priv->tooltip_widget, "destroy",
1972           G_CALLBACK (tooltip_destroy_cb), self, 0);
1973
1974       gtk_widget_show (self->priv->tooltip_widget);
1975     }
1976   else
1977     {
1978       empathy_individual_widget_set_individual (
1979         EMPATHY_INDIVIDUAL_WIDGET (self->priv->tooltip_widget), individual);
1980     }
1981
1982   gtk_tooltip_set_custom (tooltip, self->priv->tooltip_widget);
1983
1984   return TRUE;
1985 }
1986
1987 typedef enum
1988 {
1989   DND_DRAG_TYPE_INVALID = -1,
1990   DND_DRAG_TYPE_URI_LIST,
1991 } DndDragType;
1992
1993 #define DRAG_TYPE(T,I) \
1994   { (gchar *) T, 0, I }
1995
1996 static const GtkTargetEntry drag_types_dest[] = {
1997   DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
1998   DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
1999 };
2000
2001 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
2002
2003 static DndDragType
2004 get_drag_type (GtkWidget *widget,
2005     GdkDragContext *context)
2006 {
2007   GdkAtom target;
2008   guint i;
2009
2010   target = gtk_drag_dest_find_target (widget, context, NULL);
2011
2012   for (i = 0; i < G_N_ELEMENTS (drag_atoms_dest); i++)
2013     {
2014       if (target == drag_atoms_dest[i])
2015           return drag_types_dest[i].info;
2016     }
2017
2018   return DND_DRAG_TYPE_INVALID;
2019 }
2020
2021 static gboolean
2022 individual_supports_ft (FolksIndividual *individual)
2023 {
2024   EmpathyContact *contact;
2025   EmpathyCapabilities caps;
2026   gboolean result;
2027
2028   contact = empathy_contact_dup_from_folks_individual (individual);
2029   if (contact == NULL)
2030     return FALSE;
2031
2032   caps = empathy_contact_get_capabilities (contact);
2033   result = (caps & EMPATHY_CAPABILITIES_FT);
2034
2035   g_object_unref (contact);
2036   return result;
2037 }
2038
2039 static gboolean
2040 view_drag_motion_cb (GtkWidget *widget,
2041     GdkDragContext *context,
2042     gint x,
2043     gint y,
2044     guint time_,
2045     EmpathyRosterWindow *self)
2046 {
2047   DndDragType type;
2048
2049   type = get_drag_type (widget, context);
2050
2051   if (type == DND_DRAG_TYPE_URI_LIST)
2052     {
2053       /* Check if contact supports FT */
2054       FolksIndividual *individual;
2055       GtkWidget *child;
2056
2057       individual = empathy_roster_view_get_individual_at_y (self->priv->view,
2058           y, &child);
2059       if (individual == NULL)
2060         goto no_hl;
2061
2062       if (!individual_supports_ft (individual))
2063         goto no_hl;
2064
2065       egg_list_box_drag_highlight_widget (EGG_LIST_BOX (widget), child);
2066       return FALSE;
2067     }
2068
2069 no_hl:
2070   egg_list_box_drag_unhighlight_widget (EGG_LIST_BOX (widget));
2071   return FALSE;
2072 }
2073
2074 static gboolean
2075 view_drag_drop_cb (GtkWidget *widget,
2076     GdkDragContext *context,
2077     gint x,
2078     gint y,
2079     guint time_,
2080     EmpathyRosterWindow *self)
2081 {
2082   DndDragType type;
2083   FolksIndividual *individual;
2084
2085   type = get_drag_type (widget, context);
2086   if (type == DND_DRAG_TYPE_INVALID)
2087     return FALSE;
2088
2089   individual = empathy_roster_view_get_individual_at_y (self->priv->view, y,
2090       NULL);
2091   if (individual == NULL)
2092     return FALSE;
2093
2094   if (!individual_supports_ft (individual))
2095     return FALSE;
2096
2097   gtk_drag_get_data (widget, context,
2098       gtk_drag_dest_find_target (widget, context, NULL), time_);
2099
2100   return TRUE;
2101 }
2102
2103 static void
2104 view_drag_data_received_cb (GtkWidget *widget,
2105     GdkDragContext *context,
2106     gint x,
2107     gint y,
2108     GtkSelectionData *selection,
2109     guint info,
2110     guint time_,
2111     EmpathyRosterWindow *self)
2112 {
2113   gboolean success = FALSE;
2114
2115   if (selection == NULL)
2116     goto out;
2117
2118   if (info == DND_DRAG_TYPE_URI_LIST)
2119     {
2120       const gchar *path;
2121       FolksIndividual *individual;
2122       EmpathyContact *contact;
2123
2124       individual = empathy_roster_view_get_individual_at_y (self->priv->view,
2125           y, NULL);
2126       g_return_if_fail (individual != NULL);
2127
2128       path = (const gchar *) gtk_selection_data_get_data (selection);
2129
2130       contact = empathy_contact_dup_from_folks_individual (individual);
2131       empathy_send_file_from_uri_list (contact, path);
2132
2133       g_object_unref (contact);
2134
2135       success = TRUE;
2136     }
2137
2138 out:
2139   gtk_drag_finish (context, success, FALSE, time_);
2140 }
2141
2142 static void
2143 roster_window_most_available_presence_changed_cb (TpAccountManager *manager,
2144     TpConnectionPresenceType presence,
2145     const gchar *status,
2146     const gchar *message,
2147     EmpathyRosterWindow *self)
2148 {
2149   set_notebook_page (self);
2150 }
2151
2152 static void
2153 empathy_roster_window_init (EmpathyRosterWindow *self)
2154 {
2155   GtkBuilder *gui;
2156   GtkWidget *sw;
2157   gchar *filename;
2158   GtkWidget *search_vbox;
2159   guint i;
2160   EmpathyRosterModel *model;
2161
2162   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2163       EMPATHY_TYPE_ROSTER_WINDOW, EmpathyRosterWindowPriv);
2164
2165   empathy_set_css_provider (GTK_WIDGET (self));
2166
2167   self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2168
2169   self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2170
2171   gtk_window_set_title (GTK_WINDOW (self), _("Contact List"));
2172   gtk_window_set_role (GTK_WINDOW (self), "contact_list");
2173   gtk_window_set_default_size (GTK_WINDOW (self), 225, 325);
2174
2175   /* don't finalize the widget on delete-event, just hide it */
2176   g_signal_connect (self, "delete-event",
2177     G_CALLBACK (gtk_widget_hide_on_delete), NULL);
2178
2179   /* Set up interface */
2180   filename = empathy_file_lookup ("empathy-roster-window.ui", "src");
2181   gui = empathy_builder_get_file (filename,
2182       "main_vbox", &self->priv->main_vbox,
2183       "balance_vbox", &self->priv->balance_vbox,
2184       "errors_vbox", &self->priv->errors_vbox,
2185       "auth_vbox", &self->priv->auth_vbox,
2186       "search_vbox", &search_vbox,
2187       "presence_toolbar", &self->priv->presence_toolbar,
2188       "notebook", &self->priv->notebook,
2189       "no_entry_label", &self->priv->no_entry_label,
2190       "roster_scrolledwindow", &sw,
2191       "button_account_settings", &self->priv->button_account_settings,
2192       "button_online", &self->priv->button_online,
2193       "spinner_loading", &self->priv->spinner_loading,
2194       NULL);
2195   g_free (filename);
2196
2197   gtk_container_add (GTK_CONTAINER (self), self->priv->main_vbox);
2198   gtk_widget_show (self->priv->main_vbox);
2199
2200   g_signal_connect (self, "key-press-event",
2201       G_CALLBACK (roster_window_key_press_event_cb), NULL);
2202
2203   g_object_unref (gui);
2204
2205   self->priv->account_manager = tp_account_manager_dup ();
2206
2207   tp_proxy_prepare_async (self->priv->account_manager, NULL,
2208       account_manager_prepared_cb, self);
2209
2210   self->priv->errors = g_hash_table_new_full (g_direct_hash, g_direct_equal,
2211       g_object_unref, NULL);
2212
2213   self->priv->auths = g_hash_table_new (NULL, NULL);
2214
2215   self->priv->status_changed_handlers = g_hash_table_new_full (g_direct_hash,
2216       g_direct_equal, NULL, NULL);
2217
2218   /* set up menus */
2219   g_action_map_add_action_entries (G_ACTION_MAP (self),
2220       menubar_entries, G_N_ELEMENTS (menubar_entries), self);
2221   roster_window_setup_actions (self);
2222
2223   filename = empathy_file_lookup ("empathy-roster-window-menubar.ui", "src");
2224   gui = empathy_builder_get_file (filename,
2225       "appmenu", &self->priv->menumodel,
2226       "rooms", &self->priv->rooms_section,
2227       NULL);
2228   g_free (filename);
2229
2230   g_object_ref (self->priv->menumodel);
2231   g_object_ref (self->priv->rooms_section);
2232
2233   /* Set up connection related actions. */
2234   roster_window_connection_items_setup (self);
2235   roster_window_favorite_chatroom_menu_setup (self);
2236
2237   g_object_unref (gui);
2238
2239   /* Set up contact list. */
2240   empathy_status_presets_get_all ();
2241
2242   /* Set up presence chooser */
2243   self->priv->presence_chooser = empathy_presence_chooser_new ();
2244   gtk_widget_show (self->priv->presence_chooser);
2245   gtk_box_pack_start (GTK_BOX (self->priv->presence_toolbar),
2246       self->priv->presence_chooser,
2247       TRUE, TRUE, 0);
2248
2249   /* Set up the throbber */
2250   self->priv->throbber = gtk_spinner_new ();
2251   gtk_widget_set_size_request (self->priv->throbber, 16, -1);
2252   gtk_widget_set_events (self->priv->throbber, GDK_BUTTON_PRESS_MASK);
2253   g_signal_connect (self->priv->throbber, "button-press-event",
2254     G_CALLBACK (roster_window_throbber_button_press_event_cb),
2255     self);
2256   gtk_box_pack_start (GTK_BOX (self->priv->presence_toolbar),
2257       self->priv->throbber,
2258       FALSE, TRUE, 0);
2259
2260   self->priv->individual_manager = empathy_individual_manager_dup_singleton ();
2261
2262   model = EMPATHY_ROSTER_MODEL (empathy_roster_model_manager_new (self->priv->individual_manager));
2263
2264   tp_g_signal_connect_object (self->priv->individual_manager,
2265       "contacts-loaded", G_CALLBACK (contacts_loaded_cb), self, 0);
2266
2267   self->priv->view = EMPATHY_ROSTER_VIEW (
2268       empathy_roster_view_new (model));
2269
2270   g_object_unref (model);
2271
2272   gtk_widget_show (GTK_WIDGET (self->priv->view));
2273
2274   egg_list_box_add_to_scrolled (EGG_LIST_BOX (self->priv->view),
2275       GTK_SCROLLED_WINDOW (sw));
2276
2277   g_signal_connect (self->priv->view, "individual-activated",
2278       G_CALLBACK (individual_activated_cb), self);
2279   g_signal_connect (self->priv->view, "event-activated",
2280       G_CALLBACK (event_activated_cb), self);
2281   g_signal_connect (self->priv->view, "popup-individual-menu",
2282       G_CALLBACK (popup_individual_menu_cb), self);
2283   g_signal_connect (self->priv->view, "notify::empty",
2284       G_CALLBACK (view_empty_cb), self);
2285   g_signal_connect (self->priv->view, "individual-tooltip",
2286       G_CALLBACK (individual_tooltip_cb), self);
2287
2288   /* DnD - destination */
2289   gtk_drag_dest_set (GTK_WIDGET (self->priv->view), GTK_DEST_DEFAULT_MOTION,
2290       drag_types_dest, G_N_ELEMENTS (drag_types_dest), GDK_ACTION_COPY);
2291
2292   for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
2293     drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
2294
2295   g_signal_connect (self->priv->view, "drag-motion",
2296       G_CALLBACK (view_drag_motion_cb), self);
2297   g_signal_connect (self->priv->view, "drag-drop",
2298       G_CALLBACK (view_drag_drop_cb), self);
2299   g_signal_connect (self->priv->view, "drag-data-received",
2300       G_CALLBACK (view_drag_data_received_cb), self);
2301
2302   gtk_widget_set_has_tooltip (GTK_WIDGET (self->priv->view), TRUE);
2303
2304   /* Set up search bar */
2305   self->priv->search_bar = empathy_live_search_new (
2306       GTK_WIDGET (self->priv->view));
2307   empathy_roster_view_set_live_search (self->priv->view,
2308       EMPATHY_LIVE_SEARCH (self->priv->search_bar));
2309   gtk_box_pack_start (GTK_BOX (search_vbox), self->priv->search_bar,
2310       FALSE, TRUE, 0);
2311
2312   g_signal_connect_swapped (self, "map",
2313       G_CALLBACK (gtk_widget_grab_focus), self->priv->view);
2314
2315   /* Load user-defined accelerators. */
2316   roster_window_accels_load ();
2317
2318   gtk_window_set_default_size (GTK_WINDOW (self), -1, 600);
2319   /* Set window size. */
2320   empathy_geometry_bind (GTK_WINDOW (self), GEOMETRY_NAME);
2321
2322   /* Enable event handling */
2323   self->priv->call_observer = empathy_call_observer_dup_singleton ();
2324   self->priv->event_manager = empathy_event_manager_dup_singleton ();
2325
2326   tp_g_signal_connect_object (self->priv->event_manager, "event-added",
2327       G_CALLBACK (roster_window_event_added_cb), self, 0);
2328   tp_g_signal_connect_object (self->priv->event_manager, "event-removed",
2329       G_CALLBACK (roster_window_event_removed_cb), self, 0);
2330
2331   g_signal_connect (self->priv->account_manager, "account-validity-changed",
2332       G_CALLBACK (roster_window_account_validity_changed_cb), self);
2333   g_signal_connect (self->priv->account_manager, "account-removed",
2334       G_CALLBACK (roster_window_account_removed_cb), self);
2335   g_signal_connect (self->priv->account_manager, "account-disabled",
2336       G_CALLBACK (roster_window_account_disabled_cb), self);
2337   g_signal_connect (self->priv->account_manager,
2338       "most-available-presence-changed",
2339       G_CALLBACK (roster_window_most_available_presence_changed_cb), self);
2340
2341   g_settings_bind (self->priv->gsettings_ui, EMPATHY_PREFS_UI_SHOW_OFFLINE,
2342       self->priv->view, "show-offline",
2343       G_SETTINGS_BIND_GET);
2344   g_settings_bind (self->priv->gsettings_ui, EMPATHY_PREFS_UI_SHOW_GROUPS,
2345       self->priv->view, "show-groups",
2346       G_SETTINGS_BIND_GET);
2347   g_settings_bind (self->priv->gsettings_ui, "show-balance-in-roster",
2348       self->priv->balance_vbox, "visible",
2349       G_SETTINGS_BIND_GET);
2350
2351   g_signal_connect (self->priv->button_account_settings, "clicked",
2352       G_CALLBACK (button_account_settings_clicked_cb), self);
2353   g_signal_connect (self->priv->button_online, "clicked",
2354       G_CALLBACK (button_online_clicked_cb), self);
2355 }
2356
2357 GtkWidget *
2358 empathy_roster_window_new (GtkApplication *app)
2359 {
2360   return g_object_new (EMPATHY_TYPE_ROSTER_WINDOW,
2361       "application", app,
2362       NULL);
2363 }
2364
2365 GMenuModel *
2366 empathy_roster_window_get_menu_model (EmpathyRosterWindow *self)
2367 {
2368   g_return_val_if_fail (EMPATHY_IS_ROSTER_WINDOW (self), NULL);
2369
2370   return G_MENU_MODEL (self->priv->menumodel);
2371 }