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