]> git.0d.be Git - empathy.git/blob - src/empathy-roster-window.c
Use new tp_account_* getters
[empathy.git] / src / empathy-roster-window.c
1 /*
2  * Copyright (C) 2002-2007 Imendio AB
3  * Copyright (C) 2007-2010 Collabora Ltd.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA  02110-1301  USA
19  *
20  * Authors: Xavier Claessens <xclaesse@gmail.com>
21  *          Danielle Madeley <danielle.madeley@collabora.co.uk>
22  */
23
24 #include <config.h>
25
26 #include <sys/stat.h>
27 #include <gtk/gtk.h>
28 #include <gdk/gdkkeysyms.h>
29 #include <glib/gi18n.h>
30
31 #include <telepathy-glib/account-manager.h>
32 #include <telepathy-glib/util.h>
33 #include <folks/folks.h>
34
35 #include <libempathy/empathy-contact.h>
36 #include <libempathy/empathy-utils.h>
37 #include <libempathy/empathy-request-util.h>
38 #include <libempathy/empathy-chatroom-manager.h>
39 #include <libempathy/empathy-chatroom.h>
40 #include <libempathy/empathy-gsettings.h>
41 #include <libempathy/empathy-individual-manager.h>
42 #include <libempathy/empathy-gsettings.h>
43 #include <libempathy/empathy-status-presets.h>
44
45 #include <libempathy-gtk/empathy-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-view.h>
53 #include <libempathy-gtk/empathy-new-message-dialog.h>
54 #include <libempathy-gtk/empathy-new-call-dialog.h>
55 #include <libempathy-gtk/empathy-log-window.h>
56 #include <libempathy-gtk/empathy-presence-chooser.h>
57 #include <libempathy-gtk/empathy-sound-manager.h>
58 #include <libempathy-gtk/empathy-ui-utils.h>
59
60 #include "empathy-accounts-dialog.h"
61 #include "empathy-call-observer.h"
62 #include "empathy-chat-manager.h"
63 #include "empathy-roster-window.h"
64 #include "empathy-preferences.h"
65 #include "empathy-about-dialog.h"
66 #include "empathy-debug-window.h"
67 #include "empathy-new-chatroom-dialog.h"
68 #include "empathy-chatrooms-window.h"
69 #include "empathy-event-manager.h"
70 #include "empathy-ft-manager.h"
71
72 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
73 #include <libempathy/empathy-debug.h>
74
75 /* Flashing delay for icons (milliseconds). */
76 #define FLASH_TIMEOUT 500
77
78 /* Minimum width of roster window if something goes wrong. */
79 #define MIN_WIDTH 50
80
81 /* Accels (menu shortcuts) can be configured and saved */
82 #define ACCELS_FILENAME "accels.txt"
83
84 /* Name in the geometry file */
85 #define GEOMETRY_NAME "roster-window"
86
87 enum
88 {
89   PAGE_CONTACT_LIST = 0,
90   PAGE_MESSAGE
91 };
92
93 enum
94 {
95   PROP_0,
96   PROP_SHELL_RUNNING
97 };
98
99 G_DEFINE_TYPE (EmpathyRosterWindow, empathy_roster_window, GTK_TYPE_APPLICATION_WINDOW)
100
101 struct _EmpathyRosterWindowPriv {
102   EmpathyRosterView *view;
103   TpAccountManager *account_manager;
104   EmpathyChatroomManager *chatroom_manager;
105   EmpathyEventManager *event_manager;
106   EmpathySoundManager *sound_mgr;
107   EmpathyCallObserver *call_observer;
108   EmpathyIndividualManager *individual_manager;
109   guint flash_timeout_id;
110   gboolean flash_on;
111
112   GSettings *gsettings_ui;
113
114   GtkWidget *preferences;
115   GtkWidget *main_vbox;
116   GtkWidget *throbber;
117   GtkWidget *presence_toolbar;
118   GtkWidget *presence_chooser;
119   GtkWidget *errors_vbox;
120   GtkWidget *auth_vbox;
121   GtkWidget *search_bar;
122   GtkWidget *notebook;
123   GtkWidget *no_entry_label;
124   GtkWidget *button_account_settings;
125   GtkWidget *spinner_loading;
126   GtkWidget *tooltip_widget;
127
128   GMenu *menumodel;
129   GMenu *rooms_section;
130
131   GtkWidget *balance_vbox;
132
133   guint size_timeout_id;
134
135   /* reffed TpAccount* => visible GtkInfoBar* */
136   GHashTable *errors;
137
138   /* EmpathyEvent* => visible GtkInfoBar* */
139   GHashTable *auths;
140
141   /* stores a mapping from TpAccount to Handler ID to prevent
142    * to listen more than once to the status-changed signal */
143   GHashTable *status_changed_handlers;
144
145   /* Actions that are enabled when there are connected accounts */
146   GList *actions_connected;
147
148   gboolean shell_running;
149 };
150
151 static void
152 roster_window_remove_auth (EmpathyRosterWindow *self,
153     EmpathyEvent *event)
154 {
155   GtkWidget *error_widget;
156
157   error_widget = g_hash_table_lookup (self->priv->auths, event);
158   if (error_widget != NULL)
159     {
160       gtk_widget_destroy (error_widget);
161       g_hash_table_remove (self->priv->auths, event);
162     }
163 }
164
165 static void
166 roster_window_auth_add_clicked_cb (GtkButton *button,
167     EmpathyRosterWindow *self)
168 {
169   EmpathyEvent *event;
170
171   event = g_object_get_data (G_OBJECT (button), "event");
172
173   empathy_event_approve (event);
174
175   roster_window_remove_auth (self, event);
176 }
177
178 static void
179 roster_window_auth_close_clicked_cb (GtkButton *button,
180     EmpathyRosterWindow *self)
181 {
182   EmpathyEvent *event;
183
184   event = g_object_get_data (G_OBJECT (button), "event");
185
186   empathy_event_decline (event);
187   roster_window_remove_auth (self, event);
188 }
189
190 static void
191 roster_window_auth_display (EmpathyRosterWindow *self,
192     EmpathyEvent *event)
193 {
194   TpAccount *account = event->account;
195   GtkWidget *info_bar;
196   GtkWidget *content_area;
197   GtkWidget *image;
198   GtkWidget *label;
199   GtkWidget *add_button;
200   GtkWidget *close_button;
201   GtkWidget *action_area;
202   GtkWidget *action_grid;
203   const gchar *icon_name;
204   gchar *str;
205
206   if (g_hash_table_lookup (self->priv->auths, event) != NULL)
207     return;
208
209   info_bar = gtk_info_bar_new ();
210   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_QUESTION);
211
212   gtk_widget_set_no_show_all (info_bar, TRUE);
213   gtk_box_pack_start (GTK_BOX (self->priv->auth_vbox), info_bar, FALSE, TRUE, 0);
214   gtk_widget_show (info_bar);
215
216   icon_name = tp_account_get_icon_name (account);
217   image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_SMALL_TOOLBAR);
218   gtk_widget_show (image);
219
220   str = g_markup_printf_escaped ("<b>%s</b>\n%s",
221       tp_account_get_display_name (account),
222       _("Password required"));
223
224   label = gtk_label_new (str);
225   gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
226   gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
227   gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
228   gtk_widget_show (label);
229
230   g_free (str);
231
232   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
233   gtk_box_pack_start (GTK_BOX (content_area), image, FALSE, FALSE, 0);
234   gtk_box_pack_start (GTK_BOX (content_area), label, TRUE, TRUE, 0);
235
236   image = gtk_image_new_from_stock (GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON);
237   add_button = gtk_button_new ();
238   gtk_button_set_image (GTK_BUTTON (add_button), image);
239   gtk_widget_set_tooltip_text (add_button, _("Provide Password"));
240   gtk_widget_show (add_button);
241
242   image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON);
243   close_button = gtk_button_new ();
244   gtk_button_set_image (GTK_BUTTON (close_button), image);
245   gtk_widget_set_tooltip_text (close_button, _("Disconnect"));
246   gtk_widget_show (close_button);
247
248   action_grid = gtk_grid_new ();
249   gtk_grid_set_column_spacing (GTK_GRID (action_grid), 6);
250   gtk_widget_show (action_grid);
251
252   action_area = gtk_info_bar_get_action_area (GTK_INFO_BAR (info_bar));
253   gtk_box_pack_start (GTK_BOX (action_area), action_grid, FALSE, FALSE, 0);
254
255   gtk_grid_attach (GTK_GRID (action_grid), add_button, 0, 0, 1, 1);
256   gtk_grid_attach (GTK_GRID (action_grid), close_button, 1, 0, 1, 1);
257
258   g_object_set_data_full (G_OBJECT (info_bar),
259       "event", event, NULL);
260   g_object_set_data_full (G_OBJECT (add_button),
261       "event", event, NULL);
262   g_object_set_data_full (G_OBJECT (close_button),
263       "event", event, NULL);
264
265   g_signal_connect (add_button, "clicked",
266       G_CALLBACK (roster_window_auth_add_clicked_cb), self);
267   g_signal_connect (close_button, "clicked",
268       G_CALLBACK (roster_window_auth_close_clicked_cb), self);
269
270   gtk_widget_show (self->priv->auth_vbox);
271
272   g_hash_table_insert (self->priv->auths, event, info_bar);
273 }
274
275 static FolksIndividual *
276 ensure_individual_for_event (EmpathyEvent *event)
277 {
278   TpContact *contact;
279
280   contact = empathy_contact_get_tp_contact (event->contact);
281   if (contact == NULL)
282     return NULL;
283
284   return empathy_ensure_individual_from_tp_contact (contact);
285 }
286
287 static void
288 roster_window_event_added_cb (EmpathyEventManager *manager,
289     EmpathyEvent *event,
290     EmpathyRosterWindow *self)
291 {
292   if (event->contact)
293     {
294       FolksIndividual *individual;
295
296       individual = ensure_individual_for_event (event);
297       if (individual == NULL)
298         return;
299
300       event->roster_view_id = empathy_roster_view_add_event (self->priv->view,
301           individual, event->icon_name, event);
302
303       g_object_unref (individual);
304     }
305   else if (event->type == EMPATHY_EVENT_TYPE_AUTH)
306     {
307       roster_window_auth_display (self, event);
308     }
309 }
310
311 static void
312 roster_window_event_removed_cb (EmpathyEventManager *manager,
313     EmpathyEvent *event,
314     EmpathyRosterWindow *self)
315 {
316   if (event->type == EMPATHY_EVENT_TYPE_AUTH)
317     {
318       roster_window_remove_auth (self, event);
319       return;
320     }
321
322   empathy_roster_view_remove_event (self->priv->view, event->roster_view_id);
323 }
324
325 static gboolean
326 roster_window_load_events_idle_cb (gpointer user_data)
327 {
328   EmpathyRosterWindow *self = user_data;
329   GSList *l;
330
331   l = empathy_event_manager_get_events (self->priv->event_manager);
332   while (l != NULL)
333     {
334       roster_window_event_added_cb (self->priv->event_manager, l->data,
335           self);
336       l = l->next;
337     }
338
339   return FALSE;
340 }
341
342 static void
343 individual_activated_cb (EmpathyRosterView *self,
344     FolksIndividual *individual,
345     gpointer user_data)
346 {
347   EmpathyContact *contact;
348
349   contact = empathy_contact_dup_best_for_action (individual,
350       EMPATHY_ACTION_CHAT);
351
352   if (contact == NULL)
353     return;
354
355   DEBUG ("Starting a chat");
356
357   empathy_chat_with_contact (contact, gtk_get_current_event_time ());
358
359   g_object_unref (contact);
360 }
361
362 static void
363 event_activated_cb (EmpathyRosterView *self,
364     FolksIndividual *individual,
365     gpointer user_data)
366 {
367   EmpathyEvent *event = user_data;
368
369   empathy_event_activate (event);
370 }
371
372 static void
373 button_account_settings_clicked_cb (GtkButton *button,
374     EmpathyRosterWindow *self)
375 {
376   empathy_accounts_dialog_show_application (gdk_screen_get_default (),
377       NULL, FALSE, FALSE);
378 }
379
380 static void
381 display_page_message (EmpathyRosterWindow *self,
382     const gchar *msg,
383     gboolean display_accounts_button,
384     gboolean display_spinner)
385 {
386   if (msg != NULL)
387     {
388       gchar *tmp;
389
390       tmp = g_strdup_printf ("<b><span size='xx-large'>%s</span></b>", msg);
391
392       gtk_label_set_markup (GTK_LABEL (self->priv->no_entry_label), tmp);
393       g_free (tmp);
394
395       gtk_label_set_line_wrap (GTK_LABEL (self->priv->no_entry_label), TRUE);
396       gtk_widget_show (self->priv->no_entry_label);
397     }
398   else
399     {
400       gtk_widget_hide (self->priv->no_entry_label);
401     }
402
403   gtk_widget_set_visible (self->priv->button_account_settings,
404       display_accounts_button);
405   gtk_widget_set_visible (self->priv->spinner_loading,
406       display_spinner);
407
408   gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
409       PAGE_MESSAGE);
410 }
411
412 static void
413 display_page_no_account (EmpathyRosterWindow *self)
414 {
415   display_page_message (self,
416       _("You need to setup an account to see contacts here."), TRUE, FALSE);
417 }
418
419 static void
420 display_page_contact_list (EmpathyRosterWindow *self)
421 {
422   if (!empathy_individual_manager_get_contacts_loaded (
423         self->priv->individual_manager))
424     /* We'll display the contact list once we're done loading */
425     return;
426
427   gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
428       PAGE_CONTACT_LIST);
429 }
430
431 static void
432 roster_window_remove_error (EmpathyRosterWindow *self,
433     TpAccount *account)
434 {
435   GtkWidget *error_widget;
436
437   error_widget = g_hash_table_lookup (self->priv->errors, account);
438   if (error_widget != NULL)
439     {
440       gtk_widget_destroy (error_widget);
441       g_hash_table_remove (self->priv->errors, account);
442     }
443 }
444
445 static void
446 roster_window_account_disabled_cb (TpAccountManager  *manager,
447     TpAccount *account,
448     EmpathyRosterWindow *self)
449 {
450   roster_window_remove_error (self, account);
451 }
452
453 static void
454 roster_window_error_retry_clicked_cb (GtkButton *button,
455     EmpathyRosterWindow *self)
456 {
457   TpAccount *account;
458
459   account = g_object_get_data (G_OBJECT (button), "account");
460   tp_account_reconnect_async (account, NULL, NULL);
461
462   roster_window_remove_error (self, account);
463 }
464
465 static void
466 roster_window_error_edit_clicked_cb (GtkButton *button,
467     EmpathyRosterWindow *self)
468 {
469   TpAccount *account;
470
471   account = g_object_get_data (G_OBJECT (button), "account");
472
473   empathy_accounts_dialog_show_application (
474       gtk_widget_get_screen (GTK_WIDGET (button)),
475       account, FALSE, FALSE);
476
477   roster_window_remove_error (self, account);
478 }
479
480 static void
481 roster_window_error_close_clicked_cb (GtkButton *button,
482     EmpathyRosterWindow *self)
483 {
484   TpAccount *account;
485
486   account = g_object_get_data (G_OBJECT (button), "account");
487   roster_window_remove_error (self, account);
488 }
489
490 static void
491 roster_window_error_upgrade_sw_clicked_cb (GtkButton *button,
492     EmpathyRosterWindow *self)
493 {
494   TpAccount *account;
495   GtkWidget *dialog;
496
497   account = g_object_get_data (G_OBJECT (button), "account");
498   roster_window_remove_error (self, account);
499
500   dialog = gtk_message_dialog_new (GTK_WINDOW (self),
501       GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
502       GTK_BUTTONS_OK,
503       _("Sorry, %s accounts can’t be used until your %s software is updated."),
504       tp_account_get_protocol_name (account),
505       tp_account_get_protocol_name (account));
506
507   g_signal_connect_swapped (dialog, "response",
508       G_CALLBACK (gtk_widget_destroy),
509       dialog);
510
511   gtk_widget_show (dialog);
512 }
513
514 static void
515 roster_window_upgrade_software_error (EmpathyRosterWindow *self,
516     TpAccount *account)
517 {
518   GtkWidget *info_bar;
519   GtkWidget *content_area;
520   GtkWidget *label;
521   GtkWidget *image;
522   GtkWidget *upgrade_button;
523   GtkWidget *close_button;
524   GtkWidget *action_area;
525   GtkWidget *action_grid;
526   gchar *str;
527   const gchar *icon_name;
528   const gchar *error_message;
529   gboolean user_requested;
530
531   error_message =
532     empathy_account_get_error_message (account, &user_requested);
533
534   if (user_requested)
535     return;
536
537   str = g_markup_printf_escaped ("<b>%s</b>\n%s",
538       tp_account_get_display_name (account),
539       error_message);
540
541   /* If there are other errors, remove them */
542   roster_window_remove_error (self, account);
543
544   info_bar = gtk_info_bar_new ();
545   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_ERROR);
546
547   gtk_widget_set_no_show_all (info_bar, TRUE);
548   gtk_box_pack_start (GTK_BOX (self->priv->errors_vbox), info_bar, FALSE, TRUE, 0);
549   gtk_widget_show (info_bar);
550
551   icon_name = tp_account_get_icon_name (account);
552   image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_SMALL_TOOLBAR);
553   gtk_widget_show (image);
554
555   label = gtk_label_new (str);
556   gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
557   gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
558   gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
559   gtk_widget_show (label);
560   g_free (str);
561
562   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
563   gtk_box_pack_start (GTK_BOX (content_area), image, FALSE, FALSE, 0);
564   gtk_box_pack_start (GTK_BOX (content_area), label, TRUE, TRUE, 0);
565
566   image = gtk_image_new_from_stock (GTK_STOCK_REFRESH, GTK_ICON_SIZE_BUTTON);
567   upgrade_button = gtk_button_new ();
568   gtk_button_set_image (GTK_BUTTON (upgrade_button), image);
569   gtk_widget_set_tooltip_text (upgrade_button, _("Update software..."));
570   gtk_widget_show (upgrade_button);
571
572   image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON);
573   close_button = gtk_button_new ();
574   gtk_button_set_image (GTK_BUTTON (close_button), image);
575   gtk_widget_set_tooltip_text (close_button, _("Close"));
576   gtk_widget_show (close_button);
577
578   action_grid = gtk_grid_new ();
579   gtk_grid_set_column_spacing (GTK_GRID (action_grid), 2);
580   gtk_widget_show (action_grid);
581
582   action_area = gtk_info_bar_get_action_area (GTK_INFO_BAR (info_bar));
583   gtk_box_pack_start (GTK_BOX (action_area), action_grid, FALSE, FALSE, 0);
584
585   gtk_grid_attach (GTK_GRID (action_grid), upgrade_button, 0, 0, 1, 1);
586   gtk_grid_attach (GTK_GRID (action_grid), close_button, 1, 0, 1, 1);
587
588   g_object_set_data (G_OBJECT (info_bar), "label", label);
589   g_object_set_data_full (G_OBJECT (info_bar),
590       "account", g_object_ref (account),
591       g_object_unref);
592   g_object_set_data_full (G_OBJECT (upgrade_button),
593       "account", g_object_ref (account),
594       g_object_unref);
595   g_object_set_data_full (G_OBJECT (close_button),
596       "account", g_object_ref (account),
597       g_object_unref);
598
599   g_signal_connect (upgrade_button, "clicked",
600       G_CALLBACK (roster_window_error_upgrade_sw_clicked_cb), self);
601   g_signal_connect (close_button, "clicked",
602       G_CALLBACK (roster_window_error_close_clicked_cb), self);
603
604   gtk_widget_set_tooltip_text (self->priv->errors_vbox, error_message);
605   gtk_widget_show (self->priv->errors_vbox);
606
607   g_hash_table_insert (self->priv->errors, g_object_ref (account), info_bar);
608 }
609
610 static void
611 roster_window_error_display (EmpathyRosterWindow *self,
612     TpAccount *account)
613 {
614   GtkWidget *info_bar;
615   GtkWidget *content_area;
616   GtkWidget *label;
617   GtkWidget *image;
618   GtkWidget *retry_button;
619   GtkWidget *edit_button;
620   GtkWidget *close_button;
621   GtkWidget *action_area;
622   GtkWidget *action_grid;
623   gchar *str;
624   const gchar *icon_name;
625   const gchar *error_message;
626   gboolean user_requested;
627
628   if (!tp_strdiff (TP_ERROR_STR_SOFTWARE_UPGRADE_REQUIRED,
629        tp_account_get_detailed_error (account, NULL)))
630     {
631       roster_window_upgrade_software_error (self, account);
632       return;
633     }
634
635   error_message = empathy_account_get_error_message (account, &user_requested);
636
637   if (user_requested)
638     return;
639
640   str = g_markup_printf_escaped ("<b>%s</b>\n%s",
641       tp_account_get_display_name (account), error_message);
642
643   info_bar = g_hash_table_lookup (self->priv->errors, account);
644   if (info_bar)
645     {
646       label = g_object_get_data (G_OBJECT (info_bar), "label");
647
648       /* Just set the latest error and return */
649       gtk_label_set_markup (GTK_LABEL (label), str);
650       g_free (str);
651
652       return;
653     }
654
655   info_bar = gtk_info_bar_new ();
656   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_ERROR);
657
658   gtk_widget_set_no_show_all (info_bar, TRUE);
659   gtk_box_pack_start (GTK_BOX (self->priv->errors_vbox), info_bar, FALSE, TRUE, 0);
660   gtk_widget_show (info_bar);
661
662   icon_name = tp_account_get_icon_name (account);
663   image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_SMALL_TOOLBAR);
664   gtk_widget_show (image);
665
666   label = gtk_label_new (str);
667   gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
668   gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
669   gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
670   gtk_widget_show (label);
671   g_free (str);
672
673   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
674   gtk_box_pack_start (GTK_BOX (content_area), image, FALSE, FALSE, 0);
675   gtk_box_pack_start (GTK_BOX (content_area), label, TRUE, TRUE, 0);
676
677   image = gtk_image_new_from_stock (GTK_STOCK_REFRESH, GTK_ICON_SIZE_BUTTON);
678   retry_button = gtk_button_new ();
679   gtk_button_set_image (GTK_BUTTON (retry_button), image);
680   gtk_widget_set_tooltip_text (retry_button, _("Reconnect"));
681   gtk_widget_show (retry_button);
682
683   image = gtk_image_new_from_stock (GTK_STOCK_EDIT, GTK_ICON_SIZE_BUTTON);
684   edit_button = gtk_button_new ();
685   gtk_button_set_image (GTK_BUTTON (edit_button), image);
686   gtk_widget_set_tooltip_text (edit_button, _("Edit Account"));
687   gtk_widget_show (edit_button);
688
689   image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON);
690   close_button = gtk_button_new ();
691   gtk_button_set_image (GTK_BUTTON (close_button), image);
692   gtk_widget_set_tooltip_text (close_button, _("Close"));
693   gtk_widget_show (close_button);
694
695   action_grid = gtk_grid_new ();
696   gtk_grid_set_column_spacing (GTK_GRID (action_grid), 2);
697   gtk_widget_show (action_grid);
698
699   action_area = gtk_info_bar_get_action_area (GTK_INFO_BAR (info_bar));
700   gtk_box_pack_start (GTK_BOX (action_area), action_grid, FALSE, FALSE, 0);
701
702   gtk_grid_attach (GTK_GRID (action_grid), retry_button, 0, 0, 1, 1);
703   gtk_grid_attach (GTK_GRID (action_grid), edit_button, 1, 0, 1, 1);
704   gtk_grid_attach (GTK_GRID (action_grid), close_button, 2, 0, 1, 1);
705
706   g_object_set_data (G_OBJECT (info_bar), "label", label);
707   g_object_set_data_full (G_OBJECT (info_bar),
708       "account", g_object_ref (account),
709       g_object_unref);
710   g_object_set_data_full (G_OBJECT (edit_button),
711       "account", g_object_ref (account),
712       g_object_unref);
713   g_object_set_data_full (G_OBJECT (close_button),
714       "account", g_object_ref (account),
715       g_object_unref);
716   g_object_set_data_full (G_OBJECT (retry_button),
717       "account", g_object_ref (account),
718       g_object_unref);
719
720   g_signal_connect (edit_button, "clicked",
721       G_CALLBACK (roster_window_error_edit_clicked_cb), self);
722   g_signal_connect (close_button, "clicked",
723       G_CALLBACK (roster_window_error_close_clicked_cb), self);
724   g_signal_connect (retry_button, "clicked",
725       G_CALLBACK (roster_window_error_retry_clicked_cb), self);
726
727   gtk_widget_set_tooltip_text (self->priv->errors_vbox, error_message);
728   gtk_widget_show (self->priv->errors_vbox);
729
730   g_hash_table_insert (self->priv->errors, g_object_ref (account), info_bar);
731 }
732
733 static void
734 roster_window_update_status (EmpathyRosterWindow *self)
735 {
736   gboolean connected, connecting;
737   GList *l;
738
739   connected = empathy_account_manager_get_accounts_connected (&connecting);
740
741   /* Update the spinner state */
742   if (connecting)
743     {
744       gtk_spinner_start (GTK_SPINNER (self->priv->throbber));
745       gtk_widget_show (self->priv->throbber);
746     }
747   else
748     {
749       gtk_spinner_stop (GTK_SPINNER (self->priv->throbber));
750       gtk_widget_hide (self->priv->throbber);
751     }
752
753   /* Update widgets sensibility */
754   for (l = self->priv->actions_connected; l; l = l->next)
755     g_simple_action_set_enabled (l->data, connected);
756 }
757
758 static void
759 roster_window_balance_update_balance (EmpathyRosterWindow *self,
760     TpAccount *account)
761 {
762   TpConnection *conn;
763   GtkWidget *label;
764   int amount = 0;
765   guint scale = G_MAXINT32;
766   const gchar *currency = "";
767   char *money;
768
769   conn = tp_account_get_connection (account);
770   if (conn == NULL)
771     return;
772
773   if (!tp_connection_get_balance (conn, &amount, &scale, &currency))
774     return;
775
776   if (amount == 0 &&
777       scale == G_MAXINT32 &&
778       tp_str_empty (currency))
779     {
780       /* unknown balance */
781       money = g_strdup ("--");
782     }
783   else
784     {
785       char *tmp = empathy_format_currency (amount, scale, currency);
786
787       money = g_strdup_printf ("%s %s", currency, tmp);
788       g_free (tmp);
789     }
790
791   /* update the money label in the roster */
792   label = g_object_get_data (G_OBJECT (account), "balance-money-label");
793
794   gtk_label_set_text (GTK_LABEL (label), money);
795   g_free (money);
796 }
797
798 static void
799 roster_window_balance_changed_cb (TpConnection *conn,
800     guint balance,
801     guint scale,
802     const gchar *currency,
803     EmpathyRosterWindow *self)
804 {
805   TpAccount *account;
806
807   account = tp_connection_get_account (conn);
808   if (account == NULL)
809     return;
810
811   roster_window_balance_update_balance (self, account);
812 }
813
814 static void
815 roster_window_setup_balance (EmpathyRosterWindow *self,
816     TpAccount *account)
817 {
818   TpConnection *conn = tp_account_get_connection (account);
819   GtkWidget *hbox, *image, *label;
820   const gchar *uri;
821
822   if (conn == NULL)
823     return;
824
825   if (!tp_proxy_is_prepared (conn, TP_CONNECTION_FEATURE_BALANCE))
826     return;
827
828   DEBUG ("Setting up balance for acct: %s",
829       tp_account_get_display_name (account));
830
831   /* create the display widget */
832   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3);
833   gtk_container_set_border_width (GTK_CONTAINER (hbox), 3);
834
835   /* protocol icon */
836   image = gtk_image_new ();
837   gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, TRUE, 0);
838   g_object_bind_property (account, "icon-name", image, "icon-name",
839       G_BINDING_SYNC_CREATE);
840
841   /* account name label */
842   label = gtk_label_new ("");
843   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
844   gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
845   gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
846   g_object_bind_property (account, "display-name", label, "label",
847       G_BINDING_SYNC_CREATE);
848
849   /* balance label */
850   label = gtk_label_new ("");
851   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
852   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 0);
853
854   /* top up button */
855   uri = tp_connection_get_balance_uri (conn);
856
857   if (!tp_str_empty (uri))
858     {
859       GtkWidget *button;
860
861       button = gtk_button_new ();
862       gtk_container_add (GTK_CONTAINER (button),
863           gtk_image_new_from_icon_name ("emblem-symbolic-link",
864             GTK_ICON_SIZE_SMALL_TOOLBAR));
865       gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
866       gtk_widget_set_tooltip_text (button, _("Top up account"));
867       gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, TRUE, 0);
868
869       g_signal_connect_data (button, "clicked",
870           G_CALLBACK (empathy_url_show),
871           g_strdup (uri), (GClosureNotify) g_free,
872           0);
873     }
874
875   gtk_box_pack_start (GTK_BOX (self->priv->balance_vbox), hbox, FALSE, TRUE, 0);
876   gtk_widget_show_all (hbox);
877
878   g_object_set_data (G_OBJECT (account), "balance-money-label", label);
879   g_object_set_data (G_OBJECT (account), "balance-money-hbox", hbox);
880
881   roster_window_balance_update_balance (self, account);
882
883   g_signal_connect (conn, "balance-changed",
884       G_CALLBACK (roster_window_balance_changed_cb), self);
885 }
886
887 static void
888 roster_window_remove_balance_action (EmpathyRosterWindow *self,
889     TpAccount *account)
890 {
891   GtkWidget *hbox =
892     g_object_get_data (G_OBJECT (account), "balance-money-hbox");
893
894   if (hbox == NULL)
895     return;
896
897   g_return_if_fail (GTK_IS_BOX (hbox));
898
899   gtk_widget_destroy (hbox);
900 }
901
902 static void
903 roster_window_connection_changed_cb (TpAccount  *account,
904     guint old_status,
905     guint current,
906     guint reason,
907     gchar *dbus_error_name,
908     GHashTable *details,
909     EmpathyRosterWindow *self)
910 {
911   roster_window_update_status (self);
912
913   if (current == TP_CONNECTION_STATUS_DISCONNECTED &&
914       reason != TP_CONNECTION_STATUS_REASON_REQUESTED)
915     {
916       roster_window_error_display (self, account);
917     }
918
919   if (current == TP_CONNECTION_STATUS_DISCONNECTED)
920     {
921       empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
922               EMPATHY_SOUND_ACCOUNT_DISCONNECTED);
923     }
924
925   if (current == TP_CONNECTION_STATUS_CONNECTED)
926     {
927       empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
928               EMPATHY_SOUND_ACCOUNT_CONNECTED);
929
930       /* Account connected without error, remove error message if any */
931       roster_window_remove_error (self, account);
932     }
933 }
934
935 static void
936 roster_window_accels_load (void)
937 {
938   gchar *filename;
939
940   filename = g_build_filename (g_get_user_config_dir (),
941       PACKAGE_NAME, ACCELS_FILENAME, NULL);
942   if (g_file_test (filename, G_FILE_TEST_EXISTS))
943     {
944       DEBUG ("Loading from:'%s'", filename);
945       gtk_accel_map_load (filename);
946     }
947
948   g_free (filename);
949 }
950
951 static void
952 roster_window_accels_save (void)
953 {
954   gchar *dir;
955   gchar *file_with_path;
956
957   dir = g_build_filename (g_get_user_config_dir (), PACKAGE_NAME, NULL);
958   g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
959   file_with_path = g_build_filename (dir, ACCELS_FILENAME, NULL);
960   g_free (dir);
961
962   DEBUG ("Saving to:'%s'", file_with_path);
963   gtk_accel_map_save (file_with_path);
964
965   g_free (file_with_path);
966 }
967
968 static void
969 empathy_roster_window_finalize (GObject *window)
970 {
971   EmpathyRosterWindow *self = EMPATHY_ROSTER_WINDOW (window);
972   GHashTableIter iter;
973   gpointer key, value;
974
975   /* Save user-defined accelerators. */
976   roster_window_accels_save ();
977
978   g_list_free (self->priv->actions_connected);
979
980   g_object_unref (self->priv->account_manager);
981   g_object_unref (self->priv->sound_mgr);
982   g_hash_table_unref (self->priv->errors);
983   g_hash_table_unref (self->priv->auths);
984
985   /* disconnect all handlers of status-changed signal */
986   g_hash_table_iter_init (&iter, self->priv->status_changed_handlers);
987   while (g_hash_table_iter_next (&iter, &key, &value))
988     g_signal_handler_disconnect (TP_ACCOUNT (key), GPOINTER_TO_UINT (value));
989
990   g_hash_table_unref (self->priv->status_changed_handlers);
991
992   g_object_unref (self->priv->call_observer);
993   g_object_unref (self->priv->event_manager);
994   g_object_unref (self->priv->chatroom_manager);
995
996   g_object_unref (self->priv->gsettings_ui);
997   g_object_unref (self->priv->individual_manager);
998
999   g_object_unref (self->priv->menumodel);
1000   g_object_unref (self->priv->rooms_section);
1001
1002   g_clear_object (&self->priv->tooltip_widget);
1003
1004   G_OBJECT_CLASS (empathy_roster_window_parent_class)->finalize (window);
1005 }
1006
1007 static gboolean
1008 roster_window_key_press_event_cb  (GtkWidget *window,
1009     GdkEventKey *event,
1010     gpointer user_data)
1011 {
1012   if (event->keyval == GDK_KEY_T
1013       && event->state & GDK_SHIFT_MASK
1014       && event->state & GDK_CONTROL_MASK)
1015     empathy_chat_manager_call_undo_closed_chat ();
1016
1017   return FALSE;
1018 }
1019
1020 static void
1021 roster_window_chat_quit_cb (GSimpleAction *action,
1022     GVariant *parameter,
1023     gpointer user_data)
1024 {
1025   EmpathyRosterWindow *self = user_data;
1026
1027   gtk_widget_destroy (GTK_WIDGET (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           TRUE, FALSE);
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, TRUE, FALSE);
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
1565   accounts = tp_account_manager_get_valid_accounts (
1566       self->priv->account_manager);
1567
1568   len = g_list_length (accounts);
1569
1570   if (len == 0)
1571     {
1572       /* No account */
1573       display_page_no_account (self);
1574       goto out;
1575     }
1576
1577   if (!has_enabled_account (accounts))
1578     {
1579       TpAccount *account = NULL;
1580
1581       /* Pass the account if there is only one which can be enabled */
1582       if (len == 1)
1583         account = accounts->data;
1584
1585       display_page_account_not_enabled (self, account);
1586       goto out;
1587     }
1588
1589   display_page_contact_list (self);
1590
1591 out:
1592   g_list_free (accounts);
1593 }
1594
1595 static void
1596 roster_window_account_validity_changed_cb (TpAccountManager  *manager,
1597     TpAccount *account,
1598     gboolean valid,
1599     EmpathyRosterWindow *self)
1600 {
1601   if (valid)
1602     add_account (self, account);
1603   else
1604     roster_window_account_removed_cb (manager, account, self);
1605
1606   set_notebook_page (self);
1607 }
1608
1609 static void
1610 roster_window_connection_items_setup (EmpathyRosterWindow *self)
1611 {
1612   guint i;
1613   const gchar *actions_connected[] = {
1614       "room_join_new",
1615       "room_join_favorites",
1616       "chat_new_message",
1617       "chat_new_call",
1618       "chat_search_contacts",
1619       "chat_add_contact",
1620       "edit_blocked_contacts",
1621   };
1622
1623   for (i = 0; i < G_N_ELEMENTS (actions_connected); i++)
1624     {
1625       GAction *action;
1626
1627       action = g_action_map_lookup_action (G_ACTION_MAP (self),
1628           actions_connected[i]);
1629
1630       self->priv->actions_connected = g_list_prepend (
1631           self->priv->actions_connected, action);
1632     }
1633 }
1634
1635 static void
1636 account_enabled_cb (TpAccountManager *manager,
1637     TpAccount *account,
1638     EmpathyRosterWindow *self)
1639 {
1640   set_notebook_page (self);
1641 }
1642
1643 static void
1644 account_disabled_cb (TpAccountManager *manager,
1645     TpAccount *account,
1646     EmpathyRosterWindow *self)
1647 {
1648   set_notebook_page (self);
1649 }
1650
1651 static void
1652 account_manager_prepared_cb (GObject *source_object,
1653     GAsyncResult *result,
1654     gpointer user_data)
1655 {
1656   GList *accounts, *j;
1657   TpAccountManager *manager = TP_ACCOUNT_MANAGER (source_object);
1658   EmpathyRosterWindow *self = user_data;
1659   GError *error = NULL;
1660
1661   if (!tp_proxy_prepare_finish (manager, result, &error))
1662     {
1663       DEBUG ("Failed to prepare account manager: %s", error->message);
1664       g_error_free (error);
1665       return;
1666     }
1667
1668   accounts = tp_account_manager_get_valid_accounts (self->priv->account_manager);
1669   for (j = accounts; j != NULL; j = j->next)
1670     {
1671       TpAccount *account = TP_ACCOUNT (j->data);
1672
1673       add_account (self, account);
1674     }
1675
1676   g_signal_connect (manager, "account-validity-changed",
1677       G_CALLBACK (roster_window_account_validity_changed_cb), self);
1678   tp_g_signal_connect_object (manager, "account-disabled",
1679       G_CALLBACK (account_disabled_cb), self, 0);
1680   tp_g_signal_connect_object (manager, "account-enabled",
1681       G_CALLBACK (account_enabled_cb), self, 0);
1682
1683   roster_window_update_status (self);
1684
1685   set_notebook_page (self);
1686
1687   g_list_free (accounts);
1688 }
1689
1690 void
1691 empathy_roster_window_set_shell_running (EmpathyRosterWindow *self,
1692     gboolean shell_running)
1693 {
1694   if (self->priv->shell_running == shell_running)
1695     return;
1696
1697   self->priv->shell_running = shell_running;
1698   g_object_notify (G_OBJECT (self), "shell-running");
1699 }
1700
1701 static GObject *
1702 empathy_roster_window_constructor (GType type,
1703     guint n_construct_params,
1704     GObjectConstructParam *construct_params)
1705 {
1706   static GObject *window = NULL;
1707
1708   if (window != NULL)
1709     return g_object_ref (window);
1710
1711   window = G_OBJECT_CLASS (empathy_roster_window_parent_class)->constructor (
1712     type, n_construct_params, construct_params);
1713
1714   g_object_add_weak_pointer (window, (gpointer) &window);
1715
1716   return window;
1717 }
1718
1719 static GActionEntry menubar_entries[] = {
1720   { "chat_new_message", roster_window_chat_new_message_cb, NULL, NULL, NULL },
1721   { "chat_new_call", roster_window_chat_new_call_cb, NULL, NULL, NULL },
1722   { "chat_add_contact", roster_window_chat_add_contact_cb, NULL, NULL, NULL },
1723   { "chat_search_contacts", roster_window_chat_search_contacts_cb, NULL, NULL, NULL },
1724   { "chat_quit", roster_window_chat_quit_cb, NULL, NULL, NULL },
1725
1726   { "edit_accounts", roster_window_edit_accounts_cb, NULL, NULL, NULL },
1727   { "edit_blocked_contacts", roster_window_edit_blocked_contacts_cb, NULL, NULL, NULL },
1728   { "edit_preferences", roster_window_edit_preferences_cb, NULL, NULL, NULL },
1729
1730   { "view_history", roster_window_view_history_cb, NULL, NULL, NULL },
1731   { "view_show_ft_manager", roster_window_view_show_ft_manager, NULL, NULL, NULL },
1732
1733   { "room_join_new", roster_window_room_join_new_cb, NULL, NULL, NULL },
1734   { "room_join_favorites", roster_window_room_join_favorites_cb, NULL, NULL, NULL },
1735   { "room_manage_favorites", roster_window_room_manage_favorites_cb, NULL, NULL, NULL },
1736
1737   { "help_contents", roster_window_help_contents_cb, NULL, NULL, NULL },
1738   { "help_about", roster_window_help_about_cb, NULL, NULL, NULL },
1739 };
1740
1741 static void
1742 empathy_roster_window_set_property (GObject *object,
1743     guint property_id,
1744     const GValue *value,
1745     GParamSpec *pspec)
1746 {
1747   EmpathyRosterWindow *self = EMPATHY_ROSTER_WINDOW (object);
1748
1749   switch (property_id)
1750     {
1751       case PROP_SHELL_RUNNING:
1752         self->priv->shell_running = g_value_get_boolean (value);
1753         break;
1754       default:
1755         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1756         break;
1757     }
1758 }
1759
1760 static void
1761 empathy_roster_window_get_property (GObject    *object,
1762     guint property_id,
1763     GValue *value,
1764     GParamSpec *pspec)
1765 {
1766   EmpathyRosterWindow *self = EMPATHY_ROSTER_WINDOW (object);
1767
1768   switch (property_id)
1769     {
1770       case PROP_SHELL_RUNNING:
1771         g_value_set_boolean (value, self->priv->shell_running);
1772         break;
1773       default:
1774         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1775         break;
1776     }
1777 }
1778
1779 static void
1780 empathy_roster_window_constructed (GObject *self)
1781 {
1782   G_OBJECT_CLASS (empathy_roster_window_parent_class)->constructed (self);
1783 }
1784
1785 static void
1786 empathy_roster_window_class_init (EmpathyRosterWindowClass *klass)
1787 {
1788   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1789   GParamSpec *pspec;
1790
1791   object_class->finalize = empathy_roster_window_finalize;
1792   object_class->constructor = empathy_roster_window_constructor;
1793   object_class->constructed = empathy_roster_window_constructed;
1794
1795   object_class->set_property = empathy_roster_window_set_property;
1796   object_class->get_property = empathy_roster_window_get_property;
1797
1798   pspec = g_param_spec_boolean ("shell-running",
1799       "Shell running",
1800       "Whether the Shell is running or not",
1801       FALSE,
1802       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
1803   g_object_class_install_property (object_class, PROP_SHELL_RUNNING, pspec);
1804
1805   g_type_class_add_private (object_class, sizeof (EmpathyRosterWindowPriv));
1806 }
1807
1808 static void
1809 show_contacts_loading (EmpathyRosterWindow *self)
1810 {
1811   display_page_message (self, NULL, FALSE, TRUE);
1812
1813   gtk_spinner_start (GTK_SPINNER (self->priv->spinner_loading));
1814 }
1815
1816 static void
1817 hide_contacts_loading (EmpathyRosterWindow *self)
1818 {
1819   gtk_spinner_stop (GTK_SPINNER (self->priv->spinner_loading));
1820
1821   set_notebook_page (self);
1822 }
1823
1824 static void
1825 contacts_loaded_cb (EmpathyIndividualManager *manager,
1826     EmpathyRosterWindow *self)
1827 {
1828   hide_contacts_loading (self);
1829 }
1830
1831 static void
1832 roster_window_setup_actions (EmpathyRosterWindow *self)
1833 {
1834   GAction *action;
1835
1836 #define ADD_GSETTINGS_ACTION(schema, key) \
1837   action = g_settings_create_action (self->priv->gsettings_##schema, \
1838       EMPATHY_PREFS_##key); \
1839   g_action_map_add_action (G_ACTION_MAP (self), action); \
1840   g_object_unref (action);
1841
1842   ADD_GSETTINGS_ACTION (ui, UI_SHOW_OFFLINE);
1843
1844 #undef ADD_GSETTINGS_ACTION
1845 }
1846
1847 static void
1848 menu_deactivate_cb (GtkMenuShell *menushell,
1849     gpointer user_data)
1850 {
1851   /* FIXME: we shouldn't have to disconnect the signal (bgo #641327) */
1852   g_signal_handlers_disconnect_by_func (menushell,
1853       menu_deactivate_cb, user_data);
1854
1855   gtk_menu_detach (GTK_MENU (menushell));
1856 }
1857
1858 static void
1859 popup_individual_menu_cb (EmpathyRosterView *view,
1860     FolksIndividual *individual,
1861     guint button,
1862     guint time,
1863     gpointer user_data)
1864 {
1865   GtkWidget *menu;
1866   EmpathyIndividualFeatureFlags features = EMPATHY_INDIVIDUAL_FEATURE_CHAT |
1867     EMPATHY_INDIVIDUAL_FEATURE_CALL |
1868     EMPATHY_INDIVIDUAL_FEATURE_EDIT |
1869     EMPATHY_INDIVIDUAL_FEATURE_INFO |
1870     EMPATHY_INDIVIDUAL_FEATURE_LOG |
1871     EMPATHY_INDIVIDUAL_FEATURE_SMS |
1872     EMPATHY_INDIVIDUAL_FEATURE_CALL_PHONE |
1873     EMPATHY_INDIVIDUAL_FEATURE_REMOVE |
1874     EMPATHY_INDIVIDUAL_FEATURE_FILE_TRANSFER;
1875
1876   menu = empathy_individual_menu_new (individual, features, NULL);
1877
1878   /* menu is initially unowned but gtk_menu_attach_to_widget() takes its
1879    * floating ref. We can either wait for the view to release its ref
1880    * when it is destroyed (when leaving Empathy) or explicitly
1881    * detach the menu when it's not displayed any more.
1882    * We go for the latter as we don't want to keep useless menus in memory
1883    * during the whole lifetime of Empathy. */
1884   g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
1885       NULL);
1886
1887   gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
1888   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, time);
1889 }
1890
1891 static void
1892 view_empty_cb (EmpathyRosterView *view,
1893     GParamSpec *spec,
1894     EmpathyRosterWindow *self)
1895 {
1896   if (empathy_roster_view_is_empty (view))
1897     {
1898       if (empathy_roster_view_is_searching (self->priv->view))
1899         {
1900           display_page_message (self, _("No match found"), FALSE, FALSE);
1901         }
1902     }
1903   else
1904     {
1905       display_page_contact_list (self);
1906       gtk_widget_grab_focus (GTK_WIDGET (self->priv->view));
1907
1908       /* The store is being filled, it will be done after an idle cb.
1909        * So we can then get events. If we do that too soon, event's
1910        * contact is not yet in the store and it won't get marked as
1911        * having events. */
1912       g_idle_add (roster_window_load_events_idle_cb, self);
1913     }
1914 }
1915
1916 static void
1917 tooltip_destroy_cb (GtkWidget *widget,
1918     EmpathyRosterWindow *self)
1919 {
1920   g_clear_object (&self->priv->tooltip_widget);
1921 }
1922
1923 static gboolean
1924 individual_tooltip_cb (EmpathyRosterView *view,
1925     FolksIndividual *individual,
1926     gboolean keyboard_mode,
1927     GtkTooltip *tooltip,
1928     EmpathyRosterWindow *self)
1929 {
1930   if (self->priv->tooltip_widget == NULL)
1931     {
1932       self->priv->tooltip_widget = empathy_individual_widget_new (individual,
1933           EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
1934           EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
1935           EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
1936
1937       gtk_container_set_border_width (
1938           GTK_CONTAINER (self->priv->tooltip_widget), 8);
1939
1940       g_object_ref (self->priv->tooltip_widget);
1941
1942       tp_g_signal_connect_object (self->priv->tooltip_widget, "destroy",
1943           G_CALLBACK (tooltip_destroy_cb), self, 0);
1944
1945       gtk_widget_show (self->priv->tooltip_widget);
1946     }
1947   else
1948     {
1949       empathy_individual_widget_set_individual (
1950         EMPATHY_INDIVIDUAL_WIDGET (self->priv->tooltip_widget), individual);
1951     }
1952
1953   gtk_tooltip_set_custom (tooltip, self->priv->tooltip_widget);
1954
1955   return TRUE;
1956 }
1957
1958 typedef enum
1959 {
1960   DND_DRAG_TYPE_INVALID = -1,
1961   DND_DRAG_TYPE_URI_LIST,
1962 } DndDragType;
1963
1964 #define DRAG_TYPE(T,I) \
1965   { (gchar *) T, 0, I }
1966
1967 static const GtkTargetEntry drag_types_dest[] = {
1968   DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
1969   DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
1970 };
1971
1972 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
1973
1974 static DndDragType
1975 get_drag_type (GtkWidget *widget,
1976     GdkDragContext *context)
1977 {
1978   GdkAtom target;
1979   guint i;
1980
1981   target = gtk_drag_dest_find_target (widget, context, NULL);
1982
1983   for (i = 0; i < G_N_ELEMENTS (drag_atoms_dest); i++)
1984     {
1985       if (target == drag_atoms_dest[i])
1986           return drag_types_dest[i].info;
1987     }
1988
1989   return DND_DRAG_TYPE_INVALID;
1990 }
1991
1992 static gboolean
1993 individual_supports_ft (FolksIndividual *individual)
1994 {
1995   EmpathyContact *contact;
1996   EmpathyCapabilities caps;
1997   gboolean result;
1998
1999   contact = empathy_contact_dup_from_folks_individual (individual);
2000   if (contact == NULL)
2001     return FALSE;
2002
2003   caps = empathy_contact_get_capabilities (contact);
2004   result = (caps & EMPATHY_CAPABILITIES_FT);
2005
2006   g_object_unref (contact);
2007   return result;
2008 }
2009
2010 static gboolean
2011 view_drag_motion_cb (GtkWidget *widget,
2012     GdkDragContext *context,
2013     gint x,
2014     gint y,
2015     guint time_,
2016     EmpathyRosterWindow *self)
2017 {
2018   DndDragType type;
2019
2020   type = get_drag_type (widget, context);
2021
2022   if (type == DND_DRAG_TYPE_URI_LIST)
2023     {
2024       /* Check if contact supports FT */
2025       FolksIndividual *individual;
2026       GtkWidget *child;
2027
2028       individual = empathy_roster_view_get_individual_at_y (self->priv->view,
2029           y, &child);
2030       if (individual == NULL)
2031         goto no_hl;
2032
2033       if (!individual_supports_ft (individual))
2034         goto no_hl;
2035
2036       egg_list_box_drag_highlight_widget (EGG_LIST_BOX (widget), child);
2037       return FALSE;
2038     }
2039
2040 no_hl:
2041   egg_list_box_drag_unhighlight_widget (EGG_LIST_BOX (widget));
2042   return FALSE;
2043 }
2044
2045 static gboolean
2046 view_drag_drop_cb (GtkWidget *widget,
2047     GdkDragContext *context,
2048     gint x,
2049     gint y,
2050     guint time_,
2051     EmpathyRosterWindow *self)
2052 {
2053   DndDragType type;
2054   FolksIndividual *individual;
2055
2056   type = get_drag_type (widget, context);
2057   if (type == DND_DRAG_TYPE_INVALID)
2058     return FALSE;
2059
2060   individual = empathy_roster_view_get_individual_at_y (self->priv->view, y,
2061       NULL);
2062   if (individual == NULL)
2063     return FALSE;
2064
2065   if (!individual_supports_ft (individual))
2066     return FALSE;
2067
2068   gtk_drag_get_data (widget, context,
2069       gtk_drag_dest_find_target (widget, context, NULL), time_);
2070
2071   return TRUE;
2072 }
2073
2074 static void
2075 view_drag_data_received_cb (GtkWidget *widget,
2076     GdkDragContext *context,
2077     gint x,
2078     gint y,
2079     GtkSelectionData *selection,
2080     guint info,
2081     guint time_,
2082     EmpathyRosterWindow *self)
2083 {
2084   gboolean success = FALSE;
2085
2086   if (selection == NULL)
2087     goto out;
2088
2089   if (info == DND_DRAG_TYPE_URI_LIST)
2090     {
2091       const gchar *path;
2092       FolksIndividual *individual;
2093       EmpathyContact *contact;
2094
2095       individual = empathy_roster_view_get_individual_at_y (self->priv->view,
2096           y, NULL);
2097       g_return_if_fail (individual != NULL);
2098
2099       path = (const gchar *) gtk_selection_data_get_data (selection);
2100
2101       contact = empathy_contact_dup_from_folks_individual (individual);
2102       empathy_send_file_from_uri_list (contact, path);
2103
2104       g_object_unref (contact);
2105
2106       success = TRUE;
2107     }
2108
2109 out:
2110   gtk_drag_finish (context, success, FALSE, time_);
2111 }
2112
2113 static void
2114 empathy_roster_window_init (EmpathyRosterWindow *self)
2115 {
2116   GtkBuilder *gui;
2117   GtkWidget *sw;
2118   gchar *filename;
2119   GtkWidget *search_vbox;
2120   guint i;
2121
2122   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2123       EMPATHY_TYPE_ROSTER_WINDOW, EmpathyRosterWindowPriv);
2124
2125   empathy_set_css_provider (GTK_WIDGET (self));
2126
2127   self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2128
2129   self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2130
2131   gtk_window_set_title (GTK_WINDOW (self), _("Contact List"));
2132   gtk_window_set_role (GTK_WINDOW (self), "contact_list");
2133   gtk_window_set_default_size (GTK_WINDOW (self), 225, 325);
2134
2135   /* don't finalize the widget on delete-event, just hide it */
2136   g_signal_connect (self, "delete-event",
2137     G_CALLBACK (gtk_widget_hide_on_delete), NULL);
2138
2139   /* Set up interface */
2140   filename = empathy_file_lookup ("empathy-roster-window.ui", "src");
2141   gui = empathy_builder_get_file (filename,
2142       "main_vbox", &self->priv->main_vbox,
2143       "balance_vbox", &self->priv->balance_vbox,
2144       "errors_vbox", &self->priv->errors_vbox,
2145       "auth_vbox", &self->priv->auth_vbox,
2146       "search_vbox", &search_vbox,
2147       "presence_toolbar", &self->priv->presence_toolbar,
2148       "notebook", &self->priv->notebook,
2149       "no_entry_label", &self->priv->no_entry_label,
2150       "roster_scrolledwindow", &sw,
2151       "button_account_settings", &self->priv->button_account_settings,
2152       "spinner_loading", &self->priv->spinner_loading,
2153       NULL);
2154   g_free (filename);
2155
2156   gtk_container_add (GTK_CONTAINER (self), self->priv->main_vbox);
2157   gtk_widget_show (self->priv->main_vbox);
2158
2159   g_signal_connect (self, "key-press-event",
2160       G_CALLBACK (roster_window_key_press_event_cb), NULL);
2161
2162   g_object_unref (gui);
2163
2164   self->priv->account_manager = tp_account_manager_dup ();
2165
2166   tp_proxy_prepare_async (self->priv->account_manager, NULL,
2167       account_manager_prepared_cb, self);
2168
2169   self->priv->errors = g_hash_table_new_full (g_direct_hash, g_direct_equal,
2170       g_object_unref, NULL);
2171
2172   self->priv->auths = g_hash_table_new (NULL, NULL);
2173
2174   self->priv->status_changed_handlers = g_hash_table_new_full (g_direct_hash,
2175       g_direct_equal, NULL, NULL);
2176
2177   /* set up menus */
2178   g_action_map_add_action_entries (G_ACTION_MAP (self),
2179       menubar_entries, G_N_ELEMENTS (menubar_entries), self);
2180   roster_window_setup_actions (self);
2181
2182   filename = empathy_file_lookup ("empathy-roster-window-menubar.ui", "src");
2183   gui = empathy_builder_get_file (filename,
2184       "appmenu", &self->priv->menumodel,
2185       "rooms", &self->priv->rooms_section,
2186       NULL);
2187   g_free (filename);
2188
2189   g_object_ref (self->priv->menumodel);
2190   g_object_ref (self->priv->rooms_section);
2191
2192   /* Set up connection related actions. */
2193   roster_window_connection_items_setup (self);
2194   roster_window_favorite_chatroom_menu_setup (self);
2195
2196   g_object_unref (gui);
2197
2198   /* Set up contact list. */
2199   empathy_status_presets_get_all ();
2200
2201   /* Set up presence chooser */
2202   self->priv->presence_chooser = empathy_presence_chooser_new ();
2203   gtk_widget_show (self->priv->presence_chooser);
2204   gtk_box_pack_start (GTK_BOX (self->priv->presence_toolbar),
2205       self->priv->presence_chooser,
2206       TRUE, TRUE, 0);
2207
2208   /* Set up the throbber */
2209   self->priv->throbber = gtk_spinner_new ();
2210   gtk_widget_set_size_request (self->priv->throbber, 16, -1);
2211   gtk_widget_set_events (self->priv->throbber, GDK_BUTTON_PRESS_MASK);
2212   g_signal_connect (self->priv->throbber, "button-press-event",
2213     G_CALLBACK (roster_window_throbber_button_press_event_cb),
2214     self);
2215   gtk_box_pack_start (GTK_BOX (self->priv->presence_toolbar),
2216       self->priv->throbber,
2217       FALSE, TRUE, 0);
2218
2219   self->priv->individual_manager = empathy_individual_manager_dup_singleton ();
2220
2221   if (!empathy_individual_manager_get_contacts_loaded (
2222         self->priv->individual_manager))
2223     {
2224       show_contacts_loading (self);
2225
2226       tp_g_signal_connect_object (self->priv->individual_manager,
2227           "contacts-loaded", G_CALLBACK (contacts_loaded_cb), self, 0);
2228     }
2229
2230   self->priv->view = EMPATHY_ROSTER_VIEW (
2231       empathy_roster_view_new (self->priv->individual_manager));
2232
2233   gtk_widget_show (GTK_WIDGET (self->priv->view));
2234
2235   egg_list_box_add_to_scrolled (EGG_LIST_BOX (self->priv->view),
2236       GTK_SCROLLED_WINDOW (sw));
2237
2238   g_signal_connect (self->priv->view, "individual-activated",
2239       G_CALLBACK (individual_activated_cb), self);
2240   g_signal_connect (self->priv->view, "event-activated",
2241       G_CALLBACK (event_activated_cb), self);
2242   g_signal_connect (self->priv->view, "popup-individual-menu",
2243       G_CALLBACK (popup_individual_menu_cb), self);
2244   g_signal_connect (self->priv->view, "notify::empty",
2245       G_CALLBACK (view_empty_cb), self);
2246   g_signal_connect (self->priv->view, "individual-tooltip",
2247       G_CALLBACK (individual_tooltip_cb), self);
2248
2249   /* DnD - destination */
2250   gtk_drag_dest_set (GTK_WIDGET (self->priv->view), GTK_DEST_DEFAULT_MOTION,
2251       drag_types_dest, G_N_ELEMENTS (drag_types_dest), GDK_ACTION_COPY);
2252
2253   for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
2254     drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
2255
2256   g_signal_connect (self->priv->view, "drag-motion",
2257       G_CALLBACK (view_drag_motion_cb), self);
2258   g_signal_connect (self->priv->view, "drag-drop",
2259       G_CALLBACK (view_drag_drop_cb), self);
2260   g_signal_connect (self->priv->view, "drag-data-received",
2261       G_CALLBACK (view_drag_data_received_cb), self);
2262
2263   gtk_widget_set_has_tooltip (GTK_WIDGET (self->priv->view), TRUE);
2264
2265   /* Set up search bar */
2266   self->priv->search_bar = empathy_live_search_new (
2267       GTK_WIDGET (self->priv->view));
2268   empathy_roster_view_set_live_search (self->priv->view,
2269       EMPATHY_LIVE_SEARCH (self->priv->search_bar));
2270   gtk_box_pack_start (GTK_BOX (search_vbox), self->priv->search_bar,
2271       FALSE, TRUE, 0);
2272
2273   g_signal_connect_swapped (self, "map",
2274       G_CALLBACK (gtk_widget_grab_focus), self->priv->view);
2275
2276   /* Load user-defined accelerators. */
2277   roster_window_accels_load ();
2278
2279   gtk_window_set_default_size (GTK_WINDOW (self), -1, 600);
2280   /* Set window size. */
2281   empathy_geometry_bind (GTK_WINDOW (self), GEOMETRY_NAME);
2282
2283   /* Enable event handling */
2284   self->priv->call_observer = empathy_call_observer_dup_singleton ();
2285   self->priv->event_manager = empathy_event_manager_dup_singleton ();
2286
2287   tp_g_signal_connect_object (self->priv->event_manager, "event-added",
2288       G_CALLBACK (roster_window_event_added_cb), self, 0);
2289   tp_g_signal_connect_object (self->priv->event_manager, "event-removed",
2290       G_CALLBACK (roster_window_event_removed_cb), self, 0);
2291
2292   g_signal_connect (self->priv->account_manager, "account-validity-changed",
2293       G_CALLBACK (roster_window_account_validity_changed_cb), self);
2294   g_signal_connect (self->priv->account_manager, "account-removed",
2295       G_CALLBACK (roster_window_account_removed_cb), self);
2296   g_signal_connect (self->priv->account_manager, "account-disabled",
2297       G_CALLBACK (roster_window_account_disabled_cb), self);
2298
2299   g_settings_bind (self->priv->gsettings_ui, EMPATHY_PREFS_UI_SHOW_OFFLINE,
2300       self->priv->view, "show-offline",
2301       G_SETTINGS_BIND_GET);
2302   g_settings_bind (self->priv->gsettings_ui, EMPATHY_PREFS_UI_SHOW_GROUPS,
2303       self->priv->view, "show-groups",
2304       G_SETTINGS_BIND_GET);
2305   g_settings_bind (self->priv->gsettings_ui, "show-balance-in-roster",
2306       self->priv->balance_vbox, "visible",
2307       G_SETTINGS_BIND_GET);
2308
2309   g_signal_connect (self->priv->button_account_settings, "clicked",
2310       G_CALLBACK (button_account_settings_clicked_cb), self);
2311 }
2312
2313 GtkWidget *
2314 empathy_roster_window_new (GtkApplication *app)
2315 {
2316   return g_object_new (EMPATHY_TYPE_ROSTER_WINDOW,
2317       "application", app,
2318       NULL);
2319 }
2320
2321 GMenuModel *
2322 empathy_roster_window_get_menu_model (EmpathyRosterWindow *self)
2323 {
2324   g_return_val_if_fail (EMPATHY_IS_ROSTER_WINDOW (self), NULL);
2325
2326   return G_MENU_MODEL (self->priv->menumodel);
2327 }