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