]> git.0d.be Git - empathy.git/blob - src/empathy-roster-window.c
Add the app menu using the same model as the menu button
[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 #include <libempathy/empathy-tp-contact-factory.h>
45
46 #include <libempathy-gtk/empathy-contact-dialogs.h>
47 #include <libempathy-gtk/empathy-live-search.h>
48 #include <libempathy-gtk/empathy-contact-blocking-dialog.h>
49 #include <libempathy-gtk/empathy-contact-search-dialog.h>
50 #include <libempathy-gtk/empathy-geometry.h>
51 #include <libempathy-gtk/empathy-gtk-enum-types.h>
52 #include <libempathy-gtk/empathy-individual-dialogs.h>
53 #include <libempathy-gtk/empathy-individual-store.h>
54 #include <libempathy-gtk/empathy-individual-store-manager.h>
55 #include <libempathy-gtk/empathy-individual-view.h>
56 #include <libempathy-gtk/empathy-new-message-dialog.h>
57 #include <libempathy-gtk/empathy-new-call-dialog.h>
58 #include <libempathy-gtk/empathy-log-window.h>
59 #include <libempathy-gtk/empathy-presence-chooser.h>
60 #include <libempathy-gtk/empathy-sound-manager.h>
61 #include <libempathy-gtk/empathy-ui-utils.h>
62
63 #include "empathy-accounts-dialog.h"
64 #include "empathy-call-observer.h"
65 #include "empathy-chat-manager.h"
66 #include "empathy-roster-window.h"
67 #include "empathy-preferences.h"
68 #include "empathy-about-dialog.h"
69 #include "empathy-debug-window.h"
70 #include "empathy-new-chatroom-dialog.h"
71 #include "empathy-map-view.h"
72 #include "empathy-chatrooms-window.h"
73 #include "empathy-event-manager.h"
74 #include "empathy-ft-manager.h"
75
76 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
77 #include <libempathy/empathy-debug.h>
78
79 /* Flashing delay for icons (milliseconds). */
80 #define FLASH_TIMEOUT 500
81
82 /* Minimum width of roster window if something goes wrong. */
83 #define MIN_WIDTH 50
84
85 /* Accels (menu shortcuts) can be configured and saved */
86 #define ACCELS_FILENAME "accels.txt"
87
88 /* Name in the geometry file */
89 #define GEOMETRY_NAME "roster-window"
90
91 enum
92 {
93   PAGE_CONTACT_LIST = 0,
94   PAGE_MESSAGE
95 };
96
97 enum
98 {
99   PROP_0,
100   PROP_SHELL_RUNNING
101 };
102
103 G_DEFINE_TYPE (EmpathyRosterWindow, empathy_roster_window, GTK_TYPE_APPLICATION_WINDOW)
104
105 struct _EmpathyRosterWindowPriv {
106   EmpathyIndividualStore *individual_store;
107   EmpathyIndividualView *individual_view;
108   TpAccountManager *account_manager;
109   EmpathyChatroomManager *chatroom_manager;
110   EmpathyEventManager *event_manager;
111   EmpathySoundManager *sound_mgr;
112   EmpathyCallObserver *call_observer;
113   EmpathyIndividualManager *individual_manager;
114   guint flash_timeout_id;
115   gboolean flash_on;
116   gboolean empty;
117
118   GSettings *gsettings_ui;
119   GSettings *gsettings_contacts;
120
121   GtkWidget *preferences;
122   GtkWidget *main_vbox;
123   GtkWidget *throbber;
124   GtkWidget *presence_toolbar;
125   GtkWidget *presence_chooser;
126   GtkWidget *errors_vbox;
127   GtkWidget *auth_vbox;
128   GtkWidget *search_bar;
129   GtkWidget *notebook;
130   GtkWidget *no_entry_label;
131   GtkWidget *button_account_settings;
132   GtkWidget *spinner_loading;
133
134   GtkToggleAction *show_protocols;
135   GtkRadioAction *sort_by_name;
136   GtkRadioAction *sort_by_status;
137   GtkRadioAction *normal_with_avatars;
138   GtkRadioAction *normal_size;
139   GtkRadioAction *compact_size;
140
141   GtkUIManager *ui_manager;
142   GMenu *menubuttonmodel;
143   GMenu *rooms_section;
144   GMenu *balance_section;
145   GAction *view_credit_action;
146   GtkWidget *edit_context;
147   GtkWidget *edit_context_separator;
148
149   GtkActionGroup *balance_action_group;
150   GtkWidget *balance_vbox;
151
152   guint size_timeout_id;
153
154   /* reffed TpAccount* => visible GtkInfoBar* */
155   GHashTable *errors;
156
157   /* EmpathyEvent* => visible GtkInfoBar* */
158   GHashTable *auths;
159
160   /* stores a mapping from TpAccount to Handler ID to prevent
161    * to listen more than once to the status-changed signal */
162   GHashTable *status_changed_handlers;
163
164   /* Actions that are enabled when there are connected accounts */
165   GList *actions_connected;
166
167   gboolean shell_running;
168
169   /* GAction => GMenuItem */
170   GHashTable *topup_menu_items;
171 };
172
173 static void
174 roster_window_flash_stop (EmpathyRosterWindow *self)
175 {
176   if (self->priv->flash_timeout_id == 0)
177     return;
178
179   DEBUG ("Stop flashing");
180   g_source_remove (self->priv->flash_timeout_id);
181   self->priv->flash_timeout_id = 0;
182   self->priv->flash_on = FALSE;
183 }
184
185 typedef struct
186 {
187   EmpathyEvent *event;
188   gboolean on;
189   EmpathyRosterWindow *self;
190 } FlashForeachData;
191
192 static gboolean
193 roster_window_flash_foreach (GtkTreeModel *model,
194     GtkTreePath *path,
195     GtkTreeIter *iter,
196     gpointer user_data)
197 {
198   FlashForeachData *data = (FlashForeachData *) user_data;
199   FolksIndividual *individual;
200   EmpathyContact *contact;
201   const gchar *icon_name;
202   GtkTreePath *parent_path = NULL;
203   GtkTreeIter parent_iter;
204   GdkPixbuf *pixbuf = NULL;
205
206   gtk_tree_model_get (model, iter,
207           EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
208           -1);
209
210   if (individual == NULL)
211     return FALSE;
212
213   contact = empathy_contact_dup_from_folks_individual (individual);
214   if (contact != data->event->contact)
215     goto out;
216
217   if (data->on)
218     {
219       icon_name = data->event->icon_name;
220       pixbuf = empathy_pixbuf_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
221     }
222   else
223     {
224       pixbuf = empathy_individual_store_get_individual_status_icon (
225               data->self->priv->individual_store,
226               individual);
227       if (pixbuf != NULL)
228         g_object_ref (pixbuf);
229     }
230
231   gtk_tree_store_set (GTK_TREE_STORE (model), iter,
232       EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, pixbuf,
233       -1);
234
235   /* To make sure the parent is shown correctly, we emit
236    * the row-changed signal on the parent so it prompts
237    * it to be refreshed by the filter func.
238    */
239   if (gtk_tree_model_iter_parent (model, &parent_iter, iter))
240     {
241       parent_path = gtk_tree_model_get_path (model, &parent_iter);
242     }
243
244   if (parent_path != NULL)
245     {
246       gtk_tree_model_row_changed (model, parent_path, &parent_iter);
247       gtk_tree_path_free (parent_path);
248     }
249
250 out:
251   g_object_unref (individual);
252   tp_clear_object (&contact);
253   tp_clear_object (&pixbuf);
254
255   return FALSE;
256 }
257
258 static gboolean
259 roster_window_flash_cb (EmpathyRosterWindow *self)
260 {
261   GtkTreeModel *model;
262   GSList *events, *l;
263   gboolean found_event = FALSE;
264   FlashForeachData  data;
265
266   self->priv->flash_on = !self->priv->flash_on;
267   data.on = self->priv->flash_on;
268   model = GTK_TREE_MODEL (self->priv->individual_store);
269
270   events = empathy_event_manager_get_events (self->priv->event_manager);
271   for (l = events; l; l = l->next)
272     {
273       data.event = l->data;
274       data.self = self;
275       if (!data.event->contact || !data.event->must_ack)
276         continue;
277
278       found_event = TRUE;
279       gtk_tree_model_foreach (model,
280           roster_window_flash_foreach,
281           &data);
282     }
283
284   if (!found_event)
285     roster_window_flash_stop (self);
286
287   return TRUE;
288 }
289
290 static void
291 roster_window_flash_start (EmpathyRosterWindow *self)
292 {
293   if (self->priv->flash_timeout_id != 0)
294     return;
295
296   DEBUG ("Start flashing");
297   self->priv->flash_timeout_id = g_timeout_add (FLASH_TIMEOUT,
298       (GSourceFunc) roster_window_flash_cb, self);
299
300   roster_window_flash_cb (self);
301 }
302
303 static void
304 roster_window_remove_auth (EmpathyRosterWindow *self,
305     EmpathyEvent *event)
306 {
307   GtkWidget *error_widget;
308
309   error_widget = g_hash_table_lookup (self->priv->auths, event);
310   if (error_widget != NULL)
311     {
312       gtk_widget_destroy (error_widget);
313       g_hash_table_remove (self->priv->auths, event);
314     }
315 }
316
317 static void
318 roster_window_auth_add_clicked_cb (GtkButton *button,
319     EmpathyRosterWindow *self)
320 {
321   EmpathyEvent *event;
322
323   event = g_object_get_data (G_OBJECT (button), "event");
324
325   empathy_event_approve (event);
326
327   roster_window_remove_auth (self, event);
328 }
329
330 static void
331 roster_window_auth_close_clicked_cb (GtkButton *button,
332     EmpathyRosterWindow *self)
333 {
334   EmpathyEvent *event;
335
336   event = g_object_get_data (G_OBJECT (button), "event");
337
338   empathy_event_decline (event);
339   roster_window_remove_auth (self, event);
340 }
341
342 static void
343 roster_window_auth_display (EmpathyRosterWindow *self,
344     EmpathyEvent *event)
345 {
346   TpAccount *account = event->account;
347   GtkWidget *info_bar;
348   GtkWidget *content_area;
349   GtkWidget *image;
350   GtkWidget *label;
351   GtkWidget *add_button;
352   GtkWidget *close_button;
353   GtkWidget *action_area;
354   GtkWidget *action_grid;
355   const gchar *icon_name;
356   gchar *str;
357
358   if (g_hash_table_lookup (self->priv->auths, event) != NULL)
359     return;
360
361   info_bar = gtk_info_bar_new ();
362   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_QUESTION);
363
364   gtk_widget_set_no_show_all (info_bar, TRUE);
365   gtk_box_pack_start (GTK_BOX (self->priv->auth_vbox), info_bar, FALSE, TRUE, 0);
366   gtk_widget_show (info_bar);
367
368   icon_name = tp_account_get_icon_name (account);
369   image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_SMALL_TOOLBAR);
370   gtk_widget_show (image);
371
372   str = g_markup_printf_escaped ("<b>%s</b>\n%s",
373       tp_account_get_display_name (account),
374       _("Password required"));
375
376   label = gtk_label_new (str);
377   gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
378   gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
379   gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
380   gtk_widget_show (label);
381
382   g_free (str);
383
384   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
385   gtk_box_pack_start (GTK_BOX (content_area), image, FALSE, FALSE, 0);
386   gtk_box_pack_start (GTK_BOX (content_area), label, TRUE, TRUE, 0);
387
388   image = gtk_image_new_from_stock (GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON);
389   add_button = gtk_button_new ();
390   gtk_button_set_image (GTK_BUTTON (add_button), image);
391   gtk_widget_set_tooltip_text (add_button, _("Provide Password"));
392   gtk_widget_show (add_button);
393
394   image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON);
395   close_button = gtk_button_new ();
396   gtk_button_set_image (GTK_BUTTON (close_button), image);
397   gtk_widget_set_tooltip_text (close_button, _("Disconnect"));
398   gtk_widget_show (close_button);
399
400   action_grid = gtk_grid_new ();
401   gtk_grid_set_column_spacing (GTK_GRID (action_grid), 6);
402   gtk_widget_show (action_grid);
403
404   action_area = gtk_info_bar_get_action_area (GTK_INFO_BAR (info_bar));
405   gtk_box_pack_start (GTK_BOX (action_area), action_grid, FALSE, FALSE, 0);
406
407   gtk_grid_attach (GTK_GRID (action_grid), add_button, 0, 0, 1, 1);
408   gtk_grid_attach (GTK_GRID (action_grid), close_button, 1, 0, 1, 1);
409
410   g_object_set_data_full (G_OBJECT (info_bar),
411       "event", event, NULL);
412   g_object_set_data_full (G_OBJECT (add_button),
413       "event", event, NULL);
414   g_object_set_data_full (G_OBJECT (close_button),
415       "event", event, NULL);
416
417   g_signal_connect (add_button, "clicked",
418       G_CALLBACK (roster_window_auth_add_clicked_cb), self);
419   g_signal_connect (close_button, "clicked",
420       G_CALLBACK (roster_window_auth_close_clicked_cb), self);
421
422   gtk_widget_show (self->priv->auth_vbox);
423
424   g_hash_table_insert (self->priv->auths, event, info_bar);
425 }
426
427 static void
428 modify_event_count (GtkTreeModel *model,
429     GtkTreeIter *iter,
430     EmpathyEvent *event,
431     gboolean increase)
432 {
433   FolksIndividual *individual;
434   EmpathyContact *contact;
435   guint count;
436
437   gtk_tree_model_get (model, iter,
438       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
439       EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &count,
440       -1);
441
442   if (individual == NULL)
443     return;
444
445   increase ? count++ : count--;
446
447   contact = empathy_contact_dup_from_folks_individual (individual);
448   if (contact == event->contact)
449     gtk_tree_store_set (GTK_TREE_STORE (model), iter,
450         EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, count, -1);
451
452   tp_clear_object (&contact);
453   g_object_unref (individual);
454 }
455
456 static gboolean
457 increase_event_count_foreach (GtkTreeModel *model,
458     GtkTreePath *path,
459     GtkTreeIter *iter,
460     gpointer user_data)
461 {
462   EmpathyEvent *event = user_data;
463
464   modify_event_count (model, iter, event, TRUE);
465
466   return FALSE;
467 }
468
469 static void
470 increase_event_count (EmpathyRosterWindow *self,
471     EmpathyEvent *event)
472 {
473   GtkTreeModel *model;
474
475   model = GTK_TREE_MODEL (self->priv->individual_store);
476
477   gtk_tree_model_foreach (model, increase_event_count_foreach, event);
478 }
479
480 static gboolean
481 decrease_event_count_foreach (GtkTreeModel *model,
482     GtkTreePath *path,
483     GtkTreeIter *iter,
484     gpointer user_data)
485 {
486   EmpathyEvent *event = user_data;
487
488   modify_event_count (model, iter, event, FALSE);
489
490   return FALSE;
491 }
492
493 static void
494 decrease_event_count (EmpathyRosterWindow *self,
495     EmpathyEvent *event)
496 {
497   GtkTreeModel *model;
498
499   model = GTK_TREE_MODEL (self->priv->individual_store);
500
501   gtk_tree_model_foreach (model, decrease_event_count_foreach, event);
502 }
503
504 static void
505 roster_window_event_added_cb (EmpathyEventManager *manager,
506     EmpathyEvent *event,
507     EmpathyRosterWindow *self)
508 {
509   if (event->contact)
510     {
511       increase_event_count (self, event);
512
513       roster_window_flash_start (self);
514     }
515   else if (event->type == EMPATHY_EVENT_TYPE_AUTH)
516     {
517       roster_window_auth_display (self, event);
518     }
519 }
520
521 static void
522 roster_window_event_removed_cb (EmpathyEventManager *manager,
523     EmpathyEvent *event,
524     EmpathyRosterWindow *self)
525 {
526   FlashForeachData data;
527
528   if (event->type == EMPATHY_EVENT_TYPE_AUTH)
529     {
530       roster_window_remove_auth (self, event);
531       return;
532     }
533
534   if (!event->contact)
535     return;
536
537   decrease_event_count (self, event);
538
539   data.on = FALSE;
540   data.event = event;
541   data.self = self;
542
543   gtk_tree_model_foreach (GTK_TREE_MODEL (self->priv->individual_store),
544       roster_window_flash_foreach,
545       &data);
546 }
547
548 static gboolean
549 roster_window_load_events_idle_cb (gpointer user_data)
550 {
551   EmpathyRosterWindow *self = user_data;
552   GSList *l;
553
554   l = empathy_event_manager_get_events (self->priv->event_manager);
555   while (l != NULL)
556     {
557       roster_window_event_added_cb (self->priv->event_manager, l->data,
558           self);
559       l = l->next;
560     }
561
562   return FALSE;
563 }
564
565 static void
566 roster_window_row_activated_cb (EmpathyIndividualView *view,
567     GtkTreePath *path,
568     GtkTreeViewColumn *col,
569     EmpathyRosterWindow *self)
570 {
571   EmpathyContact *contact = NULL;
572   FolksIndividual *individual;
573   GtkTreeModel *model;
574   GtkTreeIter iter;
575   GSList *events, *l;
576
577   model = gtk_tree_view_get_model (GTK_TREE_VIEW (self->priv->individual_view));
578   gtk_tree_model_get_iter (model, &iter, path);
579
580   gtk_tree_model_get (model, &iter, EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL,
581       &individual, -1);
582
583   if (individual != NULL)
584     contact = empathy_contact_dup_from_folks_individual (individual);
585
586   if (contact == NULL)
587     goto OUT;
588
589   /* If the contact has an event activate it, otherwise the
590    * default handler of row-activated will be called. */
591   events = empathy_event_manager_get_events (self->priv->event_manager);
592   for (l = events; l; l = l->next)
593     {
594       EmpathyEvent *event = l->data;
595
596       if (event->contact == contact)
597         {
598           DEBUG ("Activate event");
599           empathy_event_activate (event);
600
601           /* We don't want the default handler of this signal
602            * (e.g. open a chat) */
603           g_signal_stop_emission_by_name (view, "row-activated");
604           break;
605         }
606     }
607
608   g_object_unref (contact);
609 OUT:
610   tp_clear_object (&individual);
611 }
612
613 static void
614 button_account_settings_clicked_cb (GtkButton *button,
615     EmpathyRosterWindow *self)
616 {
617   empathy_accounts_dialog_show_application (gdk_screen_get_default (),
618       NULL, FALSE, FALSE);
619 }
620
621 static void
622 display_page_message (EmpathyRosterWindow *self,
623     const gchar *msg,
624     gboolean display_accounts_button,
625     gboolean display_spinner)
626 {
627   if (msg != NULL)
628     {
629       gchar *tmp;
630
631       tmp = g_strdup_printf ("<b><span size='xx-large'>%s</span></b>", msg);
632
633       gtk_label_set_markup (GTK_LABEL (self->priv->no_entry_label), tmp);
634       g_free (tmp);
635
636       gtk_label_set_line_wrap (GTK_LABEL (self->priv->no_entry_label), TRUE);
637       gtk_widget_show (self->priv->no_entry_label);
638     }
639   else
640     {
641       gtk_widget_hide (self->priv->no_entry_label);
642     }
643
644   gtk_widget_set_visible (self->priv->button_account_settings,
645       display_accounts_button);
646   gtk_widget_set_visible (self->priv->spinner_loading,
647       display_spinner);
648
649   gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
650       PAGE_MESSAGE);
651 }
652
653 static void
654 display_page_no_account (EmpathyRosterWindow *self)
655 {
656   display_page_message (self,
657       _("You need to setup an account to see contacts here."), TRUE, FALSE);
658 }
659
660 static void
661 roster_window_row_deleted_cb (GtkTreeModel *model,
662     GtkTreePath *path,
663     EmpathyRosterWindow *self)
664 {
665   GtkTreeIter help_iter;
666
667   if (!gtk_tree_model_get_iter_first (model, &help_iter))
668     {
669       self->priv->empty = TRUE;
670
671       if (empathy_individual_view_is_searching (self->priv->individual_view))
672         {
673           display_page_message (self, _("No match found"), FALSE, FALSE);
674         }
675     }
676 }
677
678 static void
679 display_page_contact_list (EmpathyRosterWindow *self)
680 {
681   if (!empathy_individual_manager_get_contacts_loaded (
682         self->priv->individual_manager))
683     /* We'll display the contact list once we're done loading */
684     return;
685
686   gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
687       PAGE_CONTACT_LIST);
688 }
689
690 static void
691 roster_window_row_inserted_cb (GtkTreeModel      *model,
692     GtkTreePath *path,
693     GtkTreeIter *iter,
694     EmpathyRosterWindow *self)
695 {
696   if (self->priv->empty)
697     {
698       self->priv->empty = FALSE;
699
700       display_page_contact_list (self);
701       gtk_widget_grab_focus (GTK_WIDGET (self->priv->individual_view));
702
703       /* The store is being filled, it will be done after an idle cb.
704        * So we can then get events. If we do that too soon, event's
705        * contact is not yet in the store and it won't get marked as
706        * having events. */
707       g_idle_add (roster_window_load_events_idle_cb, self);
708     }
709 }
710
711 static void
712 roster_window_remove_error (EmpathyRosterWindow *self,
713     TpAccount *account)
714 {
715   GtkWidget *error_widget;
716
717   error_widget = g_hash_table_lookup (self->priv->errors, account);
718   if (error_widget != NULL)
719     {
720       gtk_widget_destroy (error_widget);
721       g_hash_table_remove (self->priv->errors, account);
722     }
723 }
724
725 static void
726 roster_window_account_disabled_cb (TpAccountManager  *manager,
727     TpAccount *account,
728     EmpathyRosterWindow *self)
729 {
730   roster_window_remove_error (self, account);
731 }
732
733 static void
734 roster_window_error_retry_clicked_cb (GtkButton *button,
735     EmpathyRosterWindow *self)
736 {
737   TpAccount *account;
738
739   account = g_object_get_data (G_OBJECT (button), "account");
740   tp_account_reconnect_async (account, NULL, NULL);
741
742   roster_window_remove_error (self, account);
743 }
744
745 static void
746 roster_window_error_edit_clicked_cb (GtkButton *button,
747     EmpathyRosterWindow *self)
748 {
749   TpAccount *account;
750
751   account = g_object_get_data (G_OBJECT (button), "account");
752
753   empathy_accounts_dialog_show_application (
754       gtk_widget_get_screen (GTK_WIDGET (button)),
755       account, FALSE, FALSE);
756
757   roster_window_remove_error (self, account);
758 }
759
760 static void
761 roster_window_error_close_clicked_cb (GtkButton *button,
762     EmpathyRosterWindow *self)
763 {
764   TpAccount *account;
765
766   account = g_object_get_data (G_OBJECT (button), "account");
767   roster_window_remove_error (self, account);
768 }
769
770 static void
771 roster_window_error_upgrade_sw_clicked_cb (GtkButton *button,
772     EmpathyRosterWindow *self)
773 {
774   TpAccount *account;
775   GtkWidget *dialog;
776
777   account = g_object_get_data (G_OBJECT (button), "account");
778   roster_window_remove_error (self, account);
779
780   dialog = gtk_message_dialog_new (GTK_WINDOW (self),
781       GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
782       GTK_BUTTONS_OK,
783       _("Sorry, %s accounts can’t be used until your %s software is updated."),
784       tp_account_get_protocol (account),
785       tp_account_get_protocol (account));
786
787   g_signal_connect_swapped (dialog, "response",
788       G_CALLBACK (gtk_widget_destroy),
789       dialog);
790
791   gtk_widget_show (dialog);
792 }
793
794 static void
795 roster_window_upgrade_software_error (EmpathyRosterWindow *self,
796     TpAccount *account)
797 {
798   GtkWidget *info_bar;
799   GtkWidget *content_area;
800   GtkWidget *label;
801   GtkWidget *image;
802   GtkWidget *upgrade_button;
803   GtkWidget *close_button;
804   GtkWidget *action_area;
805   GtkWidget *action_grid;
806   gchar *str;
807   const gchar *icon_name;
808   const gchar *error_message;
809   gboolean user_requested;
810
811   error_message =
812     empathy_account_get_error_message (account, &user_requested);
813
814   if (user_requested)
815     return;
816
817   str = g_markup_printf_escaped ("<b>%s</b>\n%s",
818       tp_account_get_display_name (account),
819       error_message);
820
821   /* If there are other errors, remove them */
822   roster_window_remove_error (self, account);
823
824   info_bar = gtk_info_bar_new ();
825   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_ERROR);
826
827   gtk_widget_set_no_show_all (info_bar, TRUE);
828   gtk_box_pack_start (GTK_BOX (self->priv->errors_vbox), info_bar, FALSE, TRUE, 0);
829   gtk_widget_show (info_bar);
830
831   icon_name = tp_account_get_icon_name (account);
832   image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_SMALL_TOOLBAR);
833   gtk_widget_show (image);
834
835   label = gtk_label_new (str);
836   gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
837   gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
838   gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
839   gtk_widget_show (label);
840   g_free (str);
841
842   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
843   gtk_box_pack_start (GTK_BOX (content_area), image, FALSE, FALSE, 0);
844   gtk_box_pack_start (GTK_BOX (content_area), label, TRUE, TRUE, 0);
845
846   image = gtk_image_new_from_stock (GTK_STOCK_REFRESH, GTK_ICON_SIZE_BUTTON);
847   upgrade_button = gtk_button_new ();
848   gtk_button_set_image (GTK_BUTTON (upgrade_button), image);
849   gtk_widget_set_tooltip_text (upgrade_button, _("Update software..."));
850   gtk_widget_show (upgrade_button);
851
852   image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON);
853   close_button = gtk_button_new ();
854   gtk_button_set_image (GTK_BUTTON (close_button), image);
855   gtk_widget_set_tooltip_text (close_button, _("Close"));
856   gtk_widget_show (close_button);
857
858   action_grid = gtk_grid_new ();
859   gtk_grid_set_column_spacing (GTK_GRID (action_grid), 2);
860   gtk_widget_show (action_grid);
861
862   action_area = gtk_info_bar_get_action_area (GTK_INFO_BAR (info_bar));
863   gtk_box_pack_start (GTK_BOX (action_area), action_grid, FALSE, FALSE, 0);
864
865   gtk_grid_attach (GTK_GRID (action_grid), upgrade_button, 0, 0, 1, 1);
866   gtk_grid_attach (GTK_GRID (action_grid), close_button, 1, 0, 1, 1);
867
868   g_object_set_data (G_OBJECT (info_bar), "label", label);
869   g_object_set_data_full (G_OBJECT (info_bar),
870       "account", g_object_ref (account),
871       g_object_unref);
872   g_object_set_data_full (G_OBJECT (upgrade_button),
873       "account", g_object_ref (account),
874       g_object_unref);
875   g_object_set_data_full (G_OBJECT (close_button),
876       "account", g_object_ref (account),
877       g_object_unref);
878
879   g_signal_connect (upgrade_button, "clicked",
880       G_CALLBACK (roster_window_error_upgrade_sw_clicked_cb), self);
881   g_signal_connect (close_button, "clicked",
882       G_CALLBACK (roster_window_error_close_clicked_cb), self);
883
884   gtk_widget_set_tooltip_text (self->priv->errors_vbox, error_message);
885   gtk_widget_show (self->priv->errors_vbox);
886
887   g_hash_table_insert (self->priv->errors, g_object_ref (account), info_bar);
888 }
889
890 static void
891 roster_window_error_display (EmpathyRosterWindow *self,
892     TpAccount *account)
893 {
894   GtkWidget *info_bar;
895   GtkWidget *content_area;
896   GtkWidget *label;
897   GtkWidget *image;
898   GtkWidget *retry_button;
899   GtkWidget *edit_button;
900   GtkWidget *close_button;
901   GtkWidget *action_area;
902   GtkWidget *action_grid;
903   gchar *str;
904   const gchar *icon_name;
905   const gchar *error_message;
906   gboolean user_requested;
907
908   if (!tp_strdiff (TP_ERROR_STR_SOFTWARE_UPGRADE_REQUIRED,
909        tp_account_get_detailed_error (account, NULL)))
910     {
911       roster_window_upgrade_software_error (self, account);
912       return;
913     }
914
915   error_message = empathy_account_get_error_message (account, &user_requested);
916
917   if (user_requested)
918     return;
919
920   str = g_markup_printf_escaped ("<b>%s</b>\n%s",
921       tp_account_get_display_name (account), error_message);
922
923   info_bar = g_hash_table_lookup (self->priv->errors, account);
924   if (info_bar)
925     {
926       label = g_object_get_data (G_OBJECT (info_bar), "label");
927
928       /* Just set the latest error and return */
929       gtk_label_set_markup (GTK_LABEL (label), str);
930       g_free (str);
931
932       return;
933     }
934
935   info_bar = gtk_info_bar_new ();
936   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_ERROR);
937
938   gtk_widget_set_no_show_all (info_bar, TRUE);
939   gtk_box_pack_start (GTK_BOX (self->priv->errors_vbox), info_bar, FALSE, TRUE, 0);
940   gtk_widget_show (info_bar);
941
942   icon_name = tp_account_get_icon_name (account);
943   image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_SMALL_TOOLBAR);
944   gtk_widget_show (image);
945
946   label = gtk_label_new (str);
947   gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
948   gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
949   gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
950   gtk_widget_show (label);
951   g_free (str);
952
953   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
954   gtk_box_pack_start (GTK_BOX (content_area), image, FALSE, FALSE, 0);
955   gtk_box_pack_start (GTK_BOX (content_area), label, TRUE, TRUE, 0);
956
957   image = gtk_image_new_from_stock (GTK_STOCK_REFRESH, GTK_ICON_SIZE_BUTTON);
958   retry_button = gtk_button_new ();
959   gtk_button_set_image (GTK_BUTTON (retry_button), image);
960   gtk_widget_set_tooltip_text (retry_button, _("Reconnect"));
961   gtk_widget_show (retry_button);
962
963   image = gtk_image_new_from_stock (GTK_STOCK_EDIT, GTK_ICON_SIZE_BUTTON);
964   edit_button = gtk_button_new ();
965   gtk_button_set_image (GTK_BUTTON (edit_button), image);
966   gtk_widget_set_tooltip_text (edit_button, _("Edit Account"));
967   gtk_widget_show (edit_button);
968
969   image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON);
970   close_button = gtk_button_new ();
971   gtk_button_set_image (GTK_BUTTON (close_button), image);
972   gtk_widget_set_tooltip_text (close_button, _("Close"));
973   gtk_widget_show (close_button);
974
975   action_grid = gtk_grid_new ();
976   gtk_grid_set_column_spacing (GTK_GRID (action_grid), 2);
977   gtk_widget_show (action_grid);
978
979   action_area = gtk_info_bar_get_action_area (GTK_INFO_BAR (info_bar));
980   gtk_box_pack_start (GTK_BOX (action_area), action_grid, FALSE, FALSE, 0);
981
982   gtk_grid_attach (GTK_GRID (action_grid), retry_button, 0, 0, 1, 1);
983   gtk_grid_attach (GTK_GRID (action_grid), edit_button, 1, 0, 1, 1);
984   gtk_grid_attach (GTK_GRID (action_grid), close_button, 2, 0, 1, 1);
985
986   g_object_set_data (G_OBJECT (info_bar), "label", label);
987   g_object_set_data_full (G_OBJECT (info_bar),
988       "account", g_object_ref (account),
989       g_object_unref);
990   g_object_set_data_full (G_OBJECT (edit_button),
991       "account", g_object_ref (account),
992       g_object_unref);
993   g_object_set_data_full (G_OBJECT (close_button),
994       "account", g_object_ref (account),
995       g_object_unref);
996   g_object_set_data_full (G_OBJECT (retry_button),
997       "account", g_object_ref (account),
998       g_object_unref);
999
1000   g_signal_connect (edit_button, "clicked",
1001       G_CALLBACK (roster_window_error_edit_clicked_cb), self);
1002   g_signal_connect (close_button, "clicked",
1003       G_CALLBACK (roster_window_error_close_clicked_cb), self);
1004   g_signal_connect (retry_button, "clicked",
1005       G_CALLBACK (roster_window_error_retry_clicked_cb), self);
1006
1007   gtk_widget_set_tooltip_text (self->priv->errors_vbox, error_message);
1008   gtk_widget_show (self->priv->errors_vbox);
1009
1010   g_hash_table_insert (self->priv->errors, g_object_ref (account), info_bar);
1011 }
1012
1013 static void
1014 roster_window_update_status (EmpathyRosterWindow *self)
1015 {
1016   gboolean connected, connecting;
1017   GList *l;
1018
1019   connected = empathy_account_manager_get_accounts_connected (&connecting);
1020
1021   /* Update the spinner state */
1022   if (connecting)
1023     {
1024       gtk_spinner_start (GTK_SPINNER (self->priv->throbber));
1025       gtk_widget_show (self->priv->throbber);
1026     }
1027   else
1028     {
1029       gtk_spinner_stop (GTK_SPINNER (self->priv->throbber));
1030       gtk_widget_hide (self->priv->throbber);
1031     }
1032
1033   /* Update widgets sensibility */
1034   for (l = self->priv->actions_connected; l; l = l->next)
1035     g_simple_action_set_enabled (l->data, connected);
1036 }
1037
1038 static char *
1039 account_to_topup_action_name (TpAccount *account,
1040     gboolean prefix)
1041 {
1042   char *r;
1043   gchar *result;
1044
1045   /* action names can't have '/' in them, replace it with '.' */
1046   r = g_strdup (tp_account_get_path_suffix (account));
1047   r = g_strdelimit (r, "/", '.');
1048
1049   result = g_strconcat (prefix ? "win." : "", "topup-", r, NULL);
1050   g_free (r);
1051
1052   return result;
1053 }
1054
1055 static void
1056 roster_window_balance_activate_cb (GtkAction *action,
1057     EmpathyRosterWindow *self)
1058 {
1059   const char *uri;
1060
1061   uri = g_object_get_data (G_OBJECT (action), "manage-credit-uri");
1062
1063   if (!tp_str_empty (uri))
1064     {
1065       DEBUG ("Top-up credit URI: %s", uri);
1066       empathy_url_show (GTK_WIDGET (self), uri);
1067     }
1068   else
1069     {
1070       DEBUG ("unknown protocol for top-up");
1071     }
1072 }
1073
1074 static void
1075 roster_window_balance_update_balance (EmpathyRosterWindow *self,
1076     TpAccount *account)
1077 {
1078   TpConnection *conn;
1079   GAction *action;
1080   GMenuItem *item;
1081   GtkWidget *label;
1082   int amount = 0;
1083   guint scale = G_MAXINT32;
1084   const gchar *currency = "";
1085   char *money;
1086   gchar *name;
1087
1088   g_print ("111111111\n");
1089   conn = tp_account_get_connection (account);
1090   if (conn == NULL)
1091     return;
1092   g_print ("2222222222\n");
1093
1094   name = account_to_topup_action_name (account, FALSE);
1095
1096   action = g_action_map_lookup_action (G_ACTION_MAP (self), name);
1097   g_free (name);
1098
1099   if (action == NULL)
1100     return;
1101   g_print ("333333333\n");
1102
1103   if (!tp_connection_get_balance (conn, &amount, &scale, &currency))
1104     return;
1105   g_print ("44444444444\n");
1106
1107   if (amount == 0 &&
1108       scale == G_MAXINT32 &&
1109       tp_str_empty (currency))
1110     {
1111       /* unknown balance */
1112       money = g_strdup ("--");
1113     }
1114   else
1115     {
1116       char *tmp = empathy_format_currency (amount, scale, currency);
1117
1118       money = g_strdup_printf ("%s %s", currency, tmp);
1119       g_free (tmp);
1120     }
1121
1122   item = g_hash_table_lookup (self->priv->topup_menu_items, action);
1123   g_print ("5555555 %p\n", item);
1124   if (item != NULL)
1125     {
1126       gchar *str;
1127
1128       /* Translators: this string will be something like:
1129        * Top up My Account ($1.23)..." */
1130       str = g_strdup_printf (_("Top up %s (%s)..."),
1131         tp_account_get_display_name (account), money);
1132
1133       g_print ("SET %s\n", str);
1134       g_menu_item_set_label (item, str);
1135       g_free (str);
1136     }
1137
1138   /* update the money label in the roster */
1139   label = g_object_get_data (G_OBJECT (action), "money-label");
1140
1141   gtk_label_set_text (GTK_LABEL (label), money);
1142   g_free (money);
1143 }
1144
1145 static void
1146 roster_window_balance_changed_cb (TpConnection *conn,
1147     guint balance,
1148     guint scale,
1149     const gchar *currency,
1150     EmpathyRosterWindow *self)
1151 {
1152   TpAccount *account;
1153
1154   account = tp_connection_get_account (conn);
1155   if (account == NULL)
1156     return;
1157
1158   roster_window_balance_update_balance (self, account);
1159 }
1160
1161 static GAction *
1162 roster_window_setup_balance_create_action (EmpathyRosterWindow *self,
1163     TpAccount *account)
1164 {
1165   GAction *action;
1166   gchar *name;
1167   GMenuItem *item;
1168
1169   /* create the action */
1170   name = account_to_topup_action_name (account, FALSE);
1171
1172   action = (GAction *) g_simple_action_new (name, NULL);
1173
1174   g_action_map_add_action (G_ACTION_MAP (self), action);
1175   g_free (name);
1176
1177   /* Add to the menu */
1178   name = account_to_topup_action_name (account, TRUE);
1179
1180   item = g_menu_item_new (_("Top up account credit"), name);
1181   g_print ("CREATE %p\n", item);
1182
1183   g_menu_append_item (self->priv->balance_section, item);
1184   g_menu_item_set_label (item, "mushroom");
1185
1186   /* Pass ownership of action to the hash table */
1187   g_hash_table_insert (self->priv->topup_menu_items,
1188       action, g_object_ref (item));
1189
1190   if (0) {
1191   g_object_bind_property (account, "icon-name", action, "icon-name",
1192       G_BINDING_SYNC_CREATE);
1193
1194   g_signal_connect (action, "activate",
1195       G_CALLBACK (roster_window_balance_activate_cb), self);
1196   }
1197
1198   g_free (name);
1199
1200   return action;
1201 }
1202
1203 static GtkWidget *
1204 roster_window_setup_balance_create_widget (EmpathyRosterWindow *self,
1205     GAction *action,
1206     TpAccount *account)
1207 {
1208 #if 0
1209   GtkWidget *hbox, *image, *label, *button;
1210
1211   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
1212
1213   /* protocol icon */
1214   image = gtk_image_new ();
1215   gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, TRUE, 0);
1216   g_object_bind_property (action, "icon-name", image, "icon-name",
1217       G_BINDING_SYNC_CREATE);
1218
1219   /* account name label */
1220   label = gtk_label_new ("");
1221   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1222   gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
1223   g_object_bind_property (account, "display-name", label, "label",
1224       G_BINDING_SYNC_CREATE);
1225
1226   /* balance label */
1227   label = gtk_label_new ("");
1228   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1229   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 0);
1230   g_object_set_data (G_OBJECT (action), "money-label", label);
1231
1232   /* top up button */
1233   button = gtk_button_new_with_label (_("Top Up..."));
1234   gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, TRUE, 0);
1235   g_signal_connect_swapped (button, "clicked",
1236       G_CALLBACK (gtk_action_activate), action);
1237
1238   gtk_box_pack_start (GTK_BOX (self->priv->balance_vbox), hbox, FALSE, TRUE, 0);
1239   gtk_widget_show_all (hbox);
1240
1241   /* tie the lifetime of the widget to the lifetime of the action */
1242   g_object_weak_ref (G_OBJECT (action),
1243       (GWeakNotify) gtk_widget_destroy, hbox);
1244
1245   return hbox;
1246 #endif
1247   return NULL;
1248 }
1249
1250 #define ACTION_SHOW_CREDIT_BALANCE "show-credit-balance"
1251
1252 static void
1253 add_view_credit_balance_item (EmpathyRosterWindow *self)
1254 {
1255   GMenuItem *item;
1256
1257   if (self->priv->view_credit_action != NULL)
1258     return;
1259
1260   self->priv->view_credit_action = (GAction *) g_simple_action_new (
1261       ACTION_SHOW_CREDIT_BALANCE, NULL);
1262
1263   /* TODO: connect sig */
1264   /* bind view_balance_show_in_roster */
1265   /*
1266   g_settings_bind (self->priv->gsettings_ui, "show-balance-in-roster",
1267       self->priv->view_balance_show_in_roster, "active",
1268       G_SETTINGS_BIND_DEFAULT);
1269   g_object_bind_property (self->priv->view_balance_show_in_roster, "active",
1270       self->priv->balance_vbox, "visible",
1271       G_BINDING_SYNC_CREATE);
1272       */
1273
1274   g_action_map_add_action (G_ACTION_MAP (self), self->priv->view_credit_action);
1275
1276   item = g_menu_item_new (_("Credit Balance"),
1277       "win." ACTION_SHOW_CREDIT_BALANCE);
1278
1279   g_menu_append_item (self->priv->balance_section, item);
1280 }
1281
1282 static void
1283 roster_window_setup_balance (EmpathyRosterWindow *self,
1284     TpAccount *account)
1285 {
1286   TpConnection *conn = tp_account_get_connection (account);
1287   GAction *action;
1288   const gchar *uri;
1289
1290   if (conn == NULL)
1291     return;
1292
1293   if (!tp_proxy_is_prepared (conn, TP_CONNECTION_FEATURE_BALANCE))
1294     return;
1295
1296   return; /* FIXME */
1297
1298   DEBUG ("Setting up balance for acct: %s",
1299       tp_account_get_display_name (account));
1300
1301   add_view_credit_balance_item (self);
1302
1303   /* create the action */
1304   action = roster_window_setup_balance_create_action (self, account);
1305
1306   if (action == NULL)
1307     return;
1308
1309   /* create the display widget */
1310   roster_window_setup_balance_create_widget (self, action, account);
1311
1312   /* check the current balance and monitor for any changes */
1313   uri = tp_connection_get_balance_uri (conn);
1314
1315   g_object_set_data_full (G_OBJECT (action), "manage-credit-uri",
1316       g_strdup (uri), g_free);
1317   gtk_action_set_sensitive (GTK_ACTION (action), !tp_str_empty (uri));
1318
1319   roster_window_balance_update_balance (self, account);
1320
1321   g_signal_connect (conn, "balance-changed",
1322       G_CALLBACK (roster_window_balance_changed_cb), self);
1323 }
1324
1325 static void
1326 remove_view_credit_balance_item (EmpathyRosterWindow *self)
1327 {
1328   gint i;
1329
1330   if (self->priv->view_credit_action == NULL)
1331     return;
1332
1333   for (i = 0; i < g_menu_model_get_n_items (
1334         G_MENU_MODEL (self->priv->balance_section)); i++)
1335     {
1336       const gchar *action;
1337
1338       if (g_menu_model_get_item_attribute (
1339             G_MENU_MODEL (self->priv->balance_section), i,
1340             G_MENU_ATTRIBUTE_ACTION, "s", &action))
1341         {
1342           if (!tp_strdiff (action, "win." ACTION_SHOW_CREDIT_BALANCE))
1343             {
1344               g_menu_remove (self->priv->balance_section, i);
1345               break;
1346             }
1347         }
1348     }
1349
1350   g_action_map_remove_action (G_ACTION_MAP (self),
1351       g_action_get_name (self->priv->view_credit_action));
1352
1353   g_clear_object (&self->priv->view_credit_action);
1354 }
1355
1356 static void
1357 roster_window_remove_balance_action (EmpathyRosterWindow *self,
1358     TpAccount *account)
1359 {
1360   /* Remove the "Credit Balance" entry if we just removed the last account
1361    * supporting balance and so that's the last entry in the section. */
1362   if (g_menu_model_get_n_items (G_MENU_MODEL (self->priv->balance_section)) <= 1)
1363     remove_view_credit_balance_item (self);
1364 #if 0
1365   GtkAction *action;
1366   char *name;
1367   GList *a;
1368
1369   name = roster_window_account_to_action_name (account);
1370
1371   action = gtk_action_group_get_action (
1372       self->priv->balance_action_group, name);
1373
1374   if (action != NULL)
1375     {
1376       guint merge_id;
1377
1378       DEBUG ("Removing action");
1379
1380       merge_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (action),
1381             "merge-id"));
1382
1383       gtk_ui_manager_remove_ui (self->priv->ui_manager,
1384           merge_id);
1385       gtk_action_group_remove_action (
1386           self->priv->balance_action_group, action);
1387     }
1388
1389   g_free (name);
1390
1391   a = gtk_action_group_list_actions (
1392       self->priv->balance_action_group);
1393
1394   gtk_action_set_visible (self->priv->view_balance_show_in_roster,
1395       g_list_length (a) > 0);
1396
1397   g_list_free (a);
1398 #endif
1399 }
1400
1401 static void
1402 roster_window_connection_changed_cb (TpAccount  *account,
1403     guint old_status,
1404     guint current,
1405     guint reason,
1406     gchar *dbus_error_name,
1407     GHashTable *details,
1408     EmpathyRosterWindow *self)
1409 {
1410   roster_window_update_status (self);
1411
1412   if (current == TP_CONNECTION_STATUS_DISCONNECTED &&
1413       reason != TP_CONNECTION_STATUS_REASON_REQUESTED)
1414     {
1415       roster_window_error_display (self, account);
1416     }
1417
1418   if (current == TP_CONNECTION_STATUS_DISCONNECTED)
1419     {
1420       empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
1421               EMPATHY_SOUND_ACCOUNT_DISCONNECTED);
1422     }
1423
1424   if (current == TP_CONNECTION_STATUS_CONNECTED)
1425     {
1426       empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
1427               EMPATHY_SOUND_ACCOUNT_CONNECTED);
1428
1429       /* Account connected without error, remove error message if any */
1430       roster_window_remove_error (self, account);
1431     }
1432 }
1433
1434 static void
1435 roster_window_accels_load (void)
1436 {
1437   gchar *filename;
1438
1439   filename = g_build_filename (g_get_user_config_dir (),
1440       PACKAGE_NAME, ACCELS_FILENAME, NULL);
1441   if (g_file_test (filename, G_FILE_TEST_EXISTS))
1442     {
1443       DEBUG ("Loading from:'%s'", filename);
1444       gtk_accel_map_load (filename);
1445     }
1446
1447   g_free (filename);
1448 }
1449
1450 static void
1451 roster_window_accels_save (void)
1452 {
1453   gchar *dir;
1454   gchar *file_with_path;
1455
1456   dir = g_build_filename (g_get_user_config_dir (), PACKAGE_NAME, NULL);
1457   g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
1458   file_with_path = g_build_filename (dir, ACCELS_FILENAME, NULL);
1459   g_free (dir);
1460
1461   DEBUG ("Saving to:'%s'", file_with_path);
1462   gtk_accel_map_save (file_with_path);
1463
1464   g_free (file_with_path);
1465 }
1466
1467 static void
1468 empathy_roster_window_finalize (GObject *window)
1469 {
1470   EmpathyRosterWindow *self = EMPATHY_ROSTER_WINDOW (window);
1471   GHashTableIter iter;
1472   gpointer key, value;
1473
1474   /* Save user-defined accelerators. */
1475   roster_window_accels_save ();
1476
1477   g_list_free (self->priv->actions_connected);
1478
1479   g_object_unref (self->priv->account_manager);
1480   g_object_unref (self->priv->individual_store);
1481   g_object_unref (self->priv->sound_mgr);
1482   g_hash_table_unref (self->priv->errors);
1483   g_hash_table_unref (self->priv->auths);
1484
1485   /* disconnect all handlers of status-changed signal */
1486   g_hash_table_iter_init (&iter, self->priv->status_changed_handlers);
1487   while (g_hash_table_iter_next (&iter, &key, &value))
1488     g_signal_handler_disconnect (TP_ACCOUNT (key), GPOINTER_TO_UINT (value));
1489
1490   g_hash_table_unref (self->priv->status_changed_handlers);
1491
1492   g_signal_handlers_disconnect_by_func (self->priv->event_manager,
1493       roster_window_event_added_cb, self);
1494   g_signal_handlers_disconnect_by_func (self->priv->event_manager,
1495       roster_window_event_removed_cb, self);
1496
1497   g_object_unref (self->priv->call_observer);
1498   g_object_unref (self->priv->event_manager);
1499   g_object_unref (self->priv->chatroom_manager);
1500
1501   g_object_unref (self->priv->gsettings_ui);
1502   g_object_unref (self->priv->gsettings_contacts);
1503   g_object_unref (self->priv->individual_manager);
1504
1505   g_object_unref (self->priv->menubuttonmodel);
1506   g_object_unref (self->priv->rooms_section);
1507   g_clear_object (&self->priv->view_credit_action);
1508   g_hash_table_unref (self->priv->topup_menu_items);
1509
1510   G_OBJECT_CLASS (empathy_roster_window_parent_class)->finalize (window);
1511 }
1512
1513 static gboolean
1514 roster_window_key_press_event_cb  (GtkWidget *window,
1515     GdkEventKey *event,
1516     gpointer user_data)
1517 {
1518   if (event->keyval == GDK_KEY_T
1519       && event->state & GDK_SHIFT_MASK
1520       && event->state & GDK_CONTROL_MASK)
1521     empathy_chat_manager_call_undo_closed_chat ();
1522
1523   return FALSE;
1524 }
1525
1526 static void
1527 roster_window_chat_quit_cb (GSimpleAction *action,
1528     GVariant *parameter,
1529     gpointer user_data)
1530 {
1531   EmpathyRosterWindow *self = user_data;
1532
1533   gtk_widget_destroy (GTK_WIDGET (self));
1534 }
1535
1536 static void
1537 roster_window_view_history_cb (GSimpleAction *action,
1538     GVariant *parameter,
1539     gpointer user_data)
1540 {
1541   EmpathyRosterWindow *self = user_data;
1542
1543   empathy_log_window_show (NULL, NULL, FALSE, GTK_WINDOW (self));
1544 }
1545
1546 static void
1547 roster_window_chat_new_message_cb (GSimpleAction *action,
1548     GVariant *parameter,
1549     gpointer user_data)
1550 {
1551   EmpathyRosterWindow *self = user_data;
1552
1553   empathy_new_message_dialog_show (GTK_WINDOW (self));
1554 }
1555
1556 static void
1557 roster_window_chat_new_call_cb (GSimpleAction *action,
1558     GVariant *parameter,
1559     gpointer user_data)
1560 {
1561   EmpathyRosterWindow *self = user_data;
1562
1563   empathy_new_call_dialog_show (GTK_WINDOW (self));
1564 }
1565
1566 static void
1567 roster_window_chat_add_contact_cb (GSimpleAction *action,
1568     GVariant *parameter,
1569     gpointer user_data)
1570 {
1571   EmpathyRosterWindow *self = user_data;
1572
1573   empathy_new_individual_dialog_show (GTK_WINDOW (self));
1574 }
1575
1576 static void
1577 roster_window_chat_search_contacts_cb (GSimpleAction *action,
1578     GVariant *parameter,
1579     gpointer user_data)
1580 {
1581   EmpathyRosterWindow *self = user_data;
1582   GtkWidget *dialog;
1583
1584   dialog = empathy_contact_search_dialog_new (
1585       GTK_WINDOW (self));
1586
1587   gtk_widget_show (dialog);
1588 }
1589
1590 static void
1591 roster_window_view_show_ft_manager (GSimpleAction *action,
1592     GVariant *parameter,
1593     gpointer user_data)
1594 {
1595   empathy_ft_manager_show ();
1596 }
1597
1598 static void
1599 activate_toggle (GSimpleAction *action,
1600     GVariant *parameter,
1601     gpointer user_data)
1602 {
1603   GVariant *state;
1604
1605   state = g_action_get_state (G_ACTION (action));
1606   g_action_change_state (G_ACTION (action),
1607       g_variant_new_boolean (!g_variant_get_boolean (state)));
1608   g_variant_unref (state);
1609 }
1610
1611 static void
1612 roster_window_view_show_offline_cb (GSimpleAction *action,
1613     GVariant *state,
1614     gpointer user_data)
1615 {
1616   EmpathyRosterWindow *self = user_data;
1617   gboolean current;
1618
1619   current = g_variant_get_boolean (state);
1620
1621   g_settings_set_boolean (self->priv->gsettings_ui,
1622       EMPATHY_PREFS_UI_SHOW_OFFLINE,
1623       current);
1624
1625   empathy_individual_view_set_show_offline (self->priv->individual_view,
1626       current);
1627
1628   g_simple_action_set_state (action, state);
1629 }
1630
1631 #if 0
1632 static void
1633 roster_window_notify_sort_contact_cb (GSettings         *gsettings,
1634     const gchar *key,
1635     EmpathyRosterWindow *self)
1636 {
1637   gchar *str;
1638
1639   str = g_settings_get_string (gsettings, key);
1640
1641   if (str != NULL)
1642     {
1643       GType       type;
1644       GEnumClass *enum_class;
1645       GEnumValue *enum_value;
1646
1647       type = empathy_individual_store_sort_get_type ();
1648       enum_class = G_ENUM_CLASS (g_type_class_peek (type));
1649       enum_value = g_enum_get_value_by_nick (enum_class, str);
1650       if (enum_value)
1651         {
1652           /* By changing the value of the GtkRadioAction,
1653              it emits a signal that calls roster_window_view_sort_contacts_cb
1654              which updates the contacts list */
1655           gtk_radio_action_set_current_value (self->priv->sort_by_name,
1656                       enum_value->value);
1657         }
1658       else
1659         {
1660           g_warning ("Wrong value for sort_criterium configuration : %s", str);
1661         }
1662     g_free (str);
1663   }
1664 }
1665 #endif
1666
1667 #if 0
1668 static void
1669 roster_window_view_sort_contacts_cb (GtkRadioAction *action,
1670     GtkRadioAction *current,
1671     EmpathyRosterWindow *self)
1672 {
1673   EmpathyIndividualStoreSort value;
1674   GSList *group;
1675   GType type;
1676   GEnumClass *enum_class;
1677   GEnumValue *enum_value;
1678
1679   value = gtk_radio_action_get_current_value (action);
1680   group = gtk_radio_action_get_group (action);
1681
1682   /* Get string from index */
1683   type = empathy_individual_store_sort_get_type ();
1684   enum_class = G_ENUM_CLASS (g_type_class_peek (type));
1685   enum_value = g_enum_get_value (enum_class, g_slist_index (group, current));
1686
1687   if (!enum_value)
1688     g_warning ("No GEnumValue for EmpathyContactListSort with GtkRadioAction index:%d",
1689         g_slist_index (group, action));
1690   else
1691     g_settings_set_string (self->priv->gsettings_contacts,
1692         EMPATHY_PREFS_CONTACTS_SORT_CRITERIUM,
1693         enum_value->value_nick);
1694
1695   empathy_individual_store_set_sort_criterium (self->priv->individual_store,
1696       value);
1697 }
1698
1699 static void
1700 roster_window_view_show_protocols_cb (GtkToggleAction *action,
1701     EmpathyRosterWindow *self)
1702 {
1703   gboolean value;
1704
1705   value = gtk_toggle_action_get_active (action);
1706
1707   g_settings_set_boolean (self->priv->gsettings_ui,
1708       EMPATHY_PREFS_UI_SHOW_PROTOCOLS,
1709       value);
1710
1711   empathy_individual_store_set_show_protocols (self->priv->individual_store,
1712       value);
1713 }
1714 #endif
1715
1716 /* Matches GtkRadioAction values set in empathy-roster-window.ui */
1717 #define CONTACT_LIST_NORMAL_SIZE_WITH_AVATARS   0
1718 #define CONTACT_LIST_NORMAL_SIZE      1
1719 #define CONTACT_LIST_COMPACT_SIZE     2
1720
1721 #if 0
1722 static void
1723 roster_window_view_contacts_list_size_cb (GtkRadioAction *action,
1724     GtkRadioAction *current,
1725     EmpathyRosterWindow *self)
1726 {
1727   GSettings *gsettings_ui;
1728   gint value;
1729
1730   value = gtk_radio_action_get_current_value (action);
1731   /* create a new GSettings, so we can delay the setting until both
1732    * values are set */
1733   gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
1734
1735   DEBUG ("radio button toggled, value = %i", value);
1736
1737   g_settings_delay (gsettings_ui);
1738   g_settings_set_boolean (gsettings_ui, EMPATHY_PREFS_UI_SHOW_AVATARS,
1739       value == CONTACT_LIST_NORMAL_SIZE_WITH_AVATARS);
1740
1741   g_settings_set_boolean (gsettings_ui, EMPATHY_PREFS_UI_COMPACT_CONTACT_LIST,
1742       value == CONTACT_LIST_COMPACT_SIZE);
1743   g_settings_apply (gsettings_ui);
1744
1745   /* FIXME: these enums probably have the wrong namespace */
1746   empathy_individual_store_set_show_avatars (self->priv->individual_store,
1747       value == CONTACT_LIST_NORMAL_SIZE_WITH_AVATARS);
1748   empathy_individual_store_set_is_compact (self->priv->individual_store,
1749       value == CONTACT_LIST_COMPACT_SIZE);
1750
1751   g_object_unref (gsettings_ui);
1752 }
1753
1754 static void roster_window_notify_show_protocols_cb (GSettings *gsettings,
1755     const gchar *key,
1756     EmpathyRosterWindow *self)
1757 {
1758   gtk_toggle_action_set_active (self->priv->show_protocols,
1759       g_settings_get_boolean (gsettings, EMPATHY_PREFS_UI_SHOW_PROTOCOLS));
1760 }
1761
1762
1763 static void
1764 roster_window_notify_contact_list_size_cb (GSettings *gsettings,
1765     const gchar *key,
1766     EmpathyRosterWindow *self)
1767 {
1768   gint value;
1769
1770   if (g_settings_get_boolean (gsettings,
1771       EMPATHY_PREFS_UI_COMPACT_CONTACT_LIST))
1772     value = CONTACT_LIST_COMPACT_SIZE;
1773   else if (g_settings_get_boolean (gsettings,
1774       EMPATHY_PREFS_UI_SHOW_AVATARS))
1775     value = CONTACT_LIST_NORMAL_SIZE_WITH_AVATARS;
1776   else
1777     value = CONTACT_LIST_NORMAL_SIZE;
1778
1779   DEBUG ("setting changed, value = %i", value);
1780
1781   /* By changing the value of the GtkRadioAction,
1782      it emits a signal that calls roster_window_view_contacts_list_size_cb
1783      which updates the contacts list */
1784   gtk_radio_action_set_current_value (self->priv->normal_with_avatars, value);
1785 }
1786 #endif
1787
1788 static void
1789 roster_window_edit_search_contacts_cb (GSimpleAction *action,
1790     GVariant *parameter,
1791     gpointer user_data)
1792 {
1793   EmpathyRosterWindow *self = user_data;
1794
1795   empathy_individual_view_start_search (self->priv->individual_view);
1796 }
1797
1798 static void
1799 roster_window_view_show_map_cb (GSimpleAction *action,
1800     GVariant *parameter,
1801     gpointer user_data)
1802 {
1803 #ifdef HAVE_LIBCHAMPLAIN
1804   empathy_map_view_show ();
1805 #endif
1806 }
1807
1808 static void
1809 join_chatroom (EmpathyChatroom *chatroom,
1810     gint64 timestamp)
1811 {
1812   TpAccount *account;
1813   const gchar *room;
1814
1815   account = empathy_chatroom_get_account (chatroom);
1816   room = empathy_chatroom_get_room (chatroom);
1817
1818   DEBUG ("Requesting channel for '%s'", room);
1819   empathy_join_muc (account, room, timestamp);
1820 }
1821
1822 typedef struct
1823 {
1824   TpAccount *account;
1825   EmpathyChatroom *chatroom;
1826   gint64 timestamp;
1827   glong sig_id;
1828   guint timeout;
1829 } join_fav_account_sig_ctx;
1830
1831 static join_fav_account_sig_ctx *
1832 join_fav_account_sig_ctx_new (TpAccount *account,
1833     EmpathyChatroom *chatroom,
1834     gint64 timestamp)
1835 {
1836   join_fav_account_sig_ctx *ctx = g_slice_new0 (
1837       join_fav_account_sig_ctx);
1838
1839   ctx->account = g_object_ref (account);
1840   ctx->chatroom = g_object_ref (chatroom);
1841   ctx->timestamp = timestamp;
1842   return ctx;
1843 }
1844
1845 static void
1846 join_fav_account_sig_ctx_free (join_fav_account_sig_ctx *ctx)
1847 {
1848   g_object_unref (ctx->account);
1849   g_object_unref (ctx->chatroom);
1850   g_slice_free (join_fav_account_sig_ctx, ctx);
1851 }
1852
1853 static void
1854 account_status_changed_cb (TpAccount  *account,
1855     TpConnectionStatus old_status,
1856     TpConnectionStatus new_status,
1857     guint reason,
1858     gchar *dbus_error_name,
1859     GHashTable *details,
1860     gpointer user_data)
1861 {
1862   join_fav_account_sig_ctx *ctx = user_data;
1863
1864   switch (new_status)
1865     {
1866       case TP_CONNECTION_STATUS_DISCONNECTED:
1867         /* Don't wait any longer */
1868         goto finally;
1869         break;
1870
1871       case TP_CONNECTION_STATUS_CONNECTING:
1872         /* Wait a bit */
1873         return;
1874
1875       case TP_CONNECTION_STATUS_CONNECTED:
1876         /* We can join the room */
1877         break;
1878
1879       default:
1880         g_assert_not_reached ();
1881     }
1882
1883   join_chatroom (ctx->chatroom, ctx->timestamp);
1884
1885 finally:
1886   g_source_remove (ctx->timeout);
1887   g_signal_handler_disconnect (account, ctx->sig_id);
1888 }
1889
1890 #define JOIN_FAVORITE_TIMEOUT 5
1891
1892 static gboolean
1893 join_favorite_timeout_cb (gpointer data)
1894 {
1895   join_fav_account_sig_ctx *ctx = data;
1896
1897   /* stop waiting for joining the favorite room */
1898   g_signal_handler_disconnect (ctx->account, ctx->sig_id);
1899   return FALSE;
1900 }
1901
1902 static void
1903 roster_window_favorite_chatroom_join (EmpathyChatroom *chatroom)
1904 {
1905   TpAccount *account;
1906
1907   account = empathy_chatroom_get_account (chatroom);
1908   if (tp_account_get_connection_status (account, NULL) !=
1909                TP_CONNECTION_STATUS_CONNECTED)
1910     {
1911       join_fav_account_sig_ctx *ctx;
1912
1913       ctx = join_fav_account_sig_ctx_new (account, chatroom,
1914         empathy_get_current_action_time ());
1915
1916       ctx->sig_id = g_signal_connect_data (account, "status-changed",
1917         G_CALLBACK (account_status_changed_cb), ctx,
1918         (GClosureNotify) join_fav_account_sig_ctx_free, 0);
1919
1920       ctx->timeout = g_timeout_add_seconds (JOIN_FAVORITE_TIMEOUT,
1921         join_favorite_timeout_cb, ctx);
1922       return;
1923     }
1924
1925   join_chatroom (chatroom, empathy_get_current_action_time ());
1926 }
1927
1928 static void
1929 roster_window_favorite_chatroom_menu_activate_cb (GAction *action,
1930     GVariant *parameter,
1931     EmpathyChatroom *chatroom)
1932 {
1933   roster_window_favorite_chatroom_join (chatroom);
1934 }
1935
1936 static gchar *
1937 dup_join_action_name (EmpathyChatroom *chatroom,
1938     gboolean prefix)
1939 {
1940   return g_strconcat (prefix ? "win." : "", "join-",
1941       empathy_chatroom_get_name (chatroom), NULL);
1942 }
1943
1944 static void
1945 roster_window_favorite_chatroom_menu_add (EmpathyRosterWindow *self,
1946     EmpathyChatroom *chatroom)
1947 {
1948   GMenuItem *item;
1949   const gchar *name, *account_name;
1950   gchar *label, *action_name;
1951   GAction *action;
1952
1953   name = empathy_chatroom_get_name (chatroom);
1954   account_name = tp_account_get_display_name (
1955       empathy_chatroom_get_account (chatroom));
1956
1957   label = g_strdup_printf ("%s (%s)", name, account_name);
1958   action_name = dup_join_action_name (chatroom, FALSE);
1959
1960   action = (GAction *) g_simple_action_new (action_name, NULL);
1961   g_free (action_name);
1962
1963   g_signal_connect (action, "activate",
1964       G_CALLBACK (roster_window_favorite_chatroom_menu_activate_cb),
1965       chatroom);
1966
1967   g_action_map_add_action (G_ACTION_MAP (self), action);
1968
1969   action_name = dup_join_action_name (chatroom, TRUE);
1970
1971   item = g_menu_item_new (label, action_name);
1972   g_menu_item_set_attribute (item, "room-name", "s", name);
1973   g_menu_append_item (self->priv->rooms_section, item);
1974
1975   g_free (label);
1976   g_free (action_name);
1977   g_object_unref (action);
1978 }
1979
1980 static void
1981 roster_window_favorite_chatroom_menu_added_cb (EmpathyChatroomManager *manager,
1982     EmpathyChatroom *chatroom,
1983     EmpathyRosterWindow *self)
1984 {
1985   roster_window_favorite_chatroom_menu_add (self, chatroom);
1986 }
1987
1988 static void
1989 roster_window_favorite_chatroom_menu_removed_cb (
1990     EmpathyChatroomManager *manager,
1991     EmpathyChatroom *chatroom,
1992     EmpathyRosterWindow *self)
1993 {
1994   GList *chatrooms;
1995   gchar *act;
1996   gint i;
1997
1998   act = dup_join_action_name (chatroom, TRUE);
1999
2000   g_action_map_remove_action (G_ACTION_MAP (self), act);
2001
2002   for (i = 0; i < g_menu_model_get_n_items (
2003         G_MENU_MODEL (self->priv->rooms_section)); i++)
2004     {
2005       const gchar *name;
2006
2007       if (g_menu_model_get_item_attribute (
2008             G_MENU_MODEL (self->priv->rooms_section), i, "room-name",
2009             "s", &name)
2010           && !tp_strdiff (name, empathy_chatroom_get_name (chatroom)))
2011         {
2012           g_menu_remove (self->priv->rooms_section, i);
2013           break;
2014         }
2015     }
2016
2017   chatrooms = empathy_chatroom_manager_get_chatrooms (
2018       self->priv->chatroom_manager, NULL);
2019
2020   g_list_free (chatrooms);
2021 }
2022
2023 static void
2024 roster_window_favorite_chatroom_menu_setup (EmpathyRosterWindow *self)
2025 {
2026   GList *chatrooms, *l;
2027
2028   self->priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2029
2030   chatrooms = empathy_chatroom_manager_get_chatrooms (
2031     self->priv->chatroom_manager, NULL);
2032
2033   for (l = chatrooms; l; l = l->next)
2034     roster_window_favorite_chatroom_menu_add (self, l->data);
2035
2036   g_signal_connect (self->priv->chatroom_manager, "chatroom-added",
2037       G_CALLBACK (roster_window_favorite_chatroom_menu_added_cb),
2038       self);
2039
2040   g_signal_connect (self->priv->chatroom_manager, "chatroom-removed",
2041       G_CALLBACK (roster_window_favorite_chatroom_menu_removed_cb),
2042       self);
2043
2044   g_list_free (chatrooms);
2045 }
2046
2047 static void
2048 roster_window_room_join_new_cb (GSimpleAction *action,
2049     GVariant *parameter,
2050     gpointer user_data)
2051 {
2052   EmpathyRosterWindow *self = user_data;
2053
2054   empathy_new_chatroom_dialog_show (GTK_WINDOW (self));
2055 }
2056
2057 static void
2058 roster_window_room_join_favorites_cb (GSimpleAction *action,
2059     GVariant *parameter,
2060     gpointer user_data)
2061 {
2062   EmpathyRosterWindow *self = user_data;
2063   GList *chatrooms, *l;
2064
2065   chatrooms = empathy_chatroom_manager_get_chatrooms (self->priv->chatroom_manager,
2066       NULL);
2067
2068   for (l = chatrooms; l; l = l->next)
2069     roster_window_favorite_chatroom_join (l->data);
2070
2071   g_list_free (chatrooms);
2072 }
2073
2074 static void
2075 roster_window_room_manage_favorites_cb (GSimpleAction *action,
2076     GVariant *parameter,
2077     gpointer user_data)
2078 {
2079   EmpathyRosterWindow *self = user_data;
2080
2081   empathy_chatrooms_window_show (GTK_WINDOW (self));
2082 }
2083
2084 #if 0
2085 static void
2086 roster_window_edit_cb (GtkAction *action,
2087     EmpathyRosterWindow *self)
2088 {
2089   GtkWidget *submenu;
2090
2091   /* FIXME: It should use the UIManager to merge the contact/group submenu */
2092   submenu = empathy_individual_view_get_individual_menu (
2093       self->priv->individual_view);
2094   if (submenu)
2095     {
2096       GtkMenuItem *item;
2097       GtkWidget   *label;
2098
2099       item = GTK_MENU_ITEM (self->priv->edit_context);
2100       label = gtk_bin_get_child (GTK_BIN (item));
2101       gtk_label_set_text (GTK_LABEL (label), _("Contact"));
2102
2103       gtk_widget_show (self->priv->edit_context);
2104       gtk_widget_show (self->priv->edit_context_separator);
2105
2106       gtk_menu_item_set_submenu (item, submenu);
2107
2108       return;
2109     }
2110
2111   submenu = empathy_individual_view_get_group_menu (self->priv->individual_view);
2112   if (submenu)
2113     {
2114       GtkMenuItem *item;
2115       GtkWidget   *label;
2116
2117       item = GTK_MENU_ITEM (self->priv->edit_context);
2118       label = gtk_bin_get_child (GTK_BIN (item));
2119       gtk_label_set_text (GTK_LABEL (label), _("Group"));
2120
2121       gtk_widget_show (self->priv->edit_context);
2122       gtk_widget_show (self->priv->edit_context_separator);
2123
2124       gtk_menu_item_set_submenu (item, submenu);
2125
2126       return;
2127     }
2128
2129   gtk_widget_hide (self->priv->edit_context);
2130   gtk_widget_hide (self->priv->edit_context_separator);
2131
2132   return;
2133 }
2134 #endif
2135
2136 static void
2137 roster_window_edit_accounts_cb (GSimpleAction *action,
2138     GVariant *parameter,
2139     gpointer user_data)
2140 {
2141   empathy_accounts_dialog_show_application (gdk_screen_get_default (),
2142       NULL, FALSE, FALSE);
2143 }
2144
2145 static void
2146 roster_window_edit_blocked_contacts_cb (GSimpleAction *action,
2147     GVariant *parameter,
2148     gpointer user_data)
2149 {
2150   EmpathyRosterWindow *self = user_data;
2151   GtkWidget *dialog;
2152
2153   dialog = empathy_contact_blocking_dialog_new (GTK_WINDOW (self));
2154   gtk_widget_show (dialog);
2155
2156   g_signal_connect (dialog, "response",
2157       G_CALLBACK (gtk_widget_destroy), NULL);
2158 }
2159
2160 void
2161 empathy_roster_window_show_preferences (EmpathyRosterWindow *self,
2162     const gchar *tab)
2163 {
2164   if (self->priv->preferences == NULL)
2165     {
2166       self->priv->preferences = empathy_preferences_new (GTK_WINDOW (self),
2167                                                    self->priv->shell_running);
2168       g_object_add_weak_pointer (G_OBJECT (self->priv->preferences),
2169                (gpointer) &self->priv->preferences);
2170
2171       gtk_widget_show (self->priv->preferences);
2172     }
2173   else
2174     {
2175       gtk_window_present (GTK_WINDOW (self->priv->preferences));
2176     }
2177
2178   if (tab != NULL)
2179     empathy_preferences_show_tab (
2180       EMPATHY_PREFERENCES (self->priv->preferences), tab);
2181 }
2182
2183 static void
2184 roster_window_edit_preferences_cb (GSimpleAction *action,
2185     GVariant *parameter,
2186     gpointer user_data)
2187 {
2188   EmpathyRosterWindow *self = user_data;
2189
2190   empathy_roster_window_show_preferences (self, NULL);
2191 }
2192
2193 static void
2194 roster_window_help_about_cb (GSimpleAction *action,
2195     GVariant *parameter,
2196     gpointer user_data)
2197 {
2198   EmpathyRosterWindow *self = user_data;
2199
2200   empathy_about_dialog_new (GTK_WINDOW (self));
2201 }
2202
2203 static void
2204 roster_window_help_debug_cb (GSimpleAction *action,
2205     GVariant *parameter,
2206     gpointer user_data)
2207 {
2208   empathy_launch_program (BIN_DIR, "empathy-debugger", NULL);
2209 }
2210
2211 static void
2212 roster_window_help_contents_cb (GSimpleAction *action,
2213     GVariant *parameter,
2214     gpointer user_data)
2215 {
2216   EmpathyRosterWindow *self = user_data;
2217
2218   empathy_url_show (GTK_WIDGET (self), "help:empathy");
2219 }
2220
2221 static gboolean
2222 roster_window_throbber_button_press_event_cb (GtkWidget *throbber,
2223     GdkEventButton *event,
2224     EmpathyRosterWindow *self)
2225 {
2226   if (event->type != GDK_BUTTON_PRESS ||
2227       event->button != 1)
2228     return FALSE;
2229
2230   empathy_accounts_dialog_show_application (
2231       gtk_widget_get_screen (GTK_WIDGET (throbber)),
2232       NULL, FALSE, FALSE);
2233
2234   return FALSE;
2235 }
2236
2237 static void
2238 roster_window_account_removed_cb (TpAccountManager  *manager,
2239     TpAccount *account,
2240     EmpathyRosterWindow *self)
2241 {
2242   /* remove errors if any */
2243   roster_window_remove_error (self, account);
2244
2245   /* remove the balance action if required */
2246   roster_window_remove_balance_action (self, account);
2247 }
2248
2249 static void
2250 account_connection_notify_cb (TpAccount *account,
2251     GParamSpec *spec,
2252     EmpathyRosterWindow *self)
2253 {
2254   TpConnection *conn;
2255
2256   conn = tp_account_get_connection (account);
2257
2258   if (conn != NULL)
2259     {
2260       roster_window_setup_balance (self, account);
2261     }
2262   else
2263     {
2264       /* remove balance action if required */
2265       roster_window_remove_balance_action (self, account);
2266     }
2267 }
2268
2269 static void
2270 add_account (EmpathyRosterWindow *self,
2271     TpAccount *account)
2272 {
2273   gulong handler_id;
2274
2275   handler_id = GPOINTER_TO_UINT (g_hash_table_lookup (
2276     self->priv->status_changed_handlers, account));
2277
2278   /* connect signal only if it was not connected yet */
2279   if (handler_id != 0)
2280     return;
2281
2282   handler_id = g_signal_connect (account, "status-changed",
2283     G_CALLBACK (roster_window_connection_changed_cb), self);
2284
2285   g_hash_table_insert (self->priv->status_changed_handlers,
2286     account, GUINT_TO_POINTER (handler_id));
2287
2288   /* roster_window_setup_balance() relies on the TpConnection to be ready on
2289    * the TpAccount so we connect this signal as well. */
2290   tp_g_signal_connect_object (account, "notify::connection",
2291       G_CALLBACK (account_connection_notify_cb), self, 0);
2292
2293   roster_window_setup_balance (self, account);
2294 }
2295
2296 /* @account: if not %NULL, the only account which can be enabled */
2297 static void
2298 display_page_account_not_enabled (EmpathyRosterWindow *self,
2299     TpAccount *account)
2300 {
2301   if (account == NULL)
2302     {
2303       display_page_message (self,
2304           _("You need to enable one of your accounts to see contacts here."),
2305           TRUE, FALSE);
2306     }
2307   else
2308     {
2309       gchar *tmp;
2310
2311       /* translators: argument is an account name */
2312       tmp = g_strdup_printf (_("You need to enable %s to see contacts here."),
2313           tp_account_get_display_name (account));
2314
2315       display_page_message (self, tmp, TRUE, FALSE);
2316       g_free (tmp);
2317     }
2318 }
2319 static gboolean
2320 has_enabled_account (GList *accounts)
2321 {
2322   GList *l;
2323
2324   for (l = accounts; l != NULL; l = g_list_next (l))
2325     {
2326       TpAccount *account = l->data;
2327
2328       if (tp_account_is_enabled (account))
2329         return TRUE;
2330     }
2331
2332   return FALSE;
2333 }
2334
2335 static void
2336 set_notebook_page (EmpathyRosterWindow *self)
2337 {
2338   GList *accounts;
2339   guint len;
2340
2341   accounts = tp_account_manager_get_valid_accounts (
2342       self->priv->account_manager);
2343
2344   len = g_list_length (accounts);
2345
2346   if (len == 0)
2347     {
2348       /* No account */
2349       display_page_no_account (self);
2350       goto out;
2351     }
2352
2353   if (!has_enabled_account (accounts))
2354     {
2355       TpAccount *account = NULL;
2356
2357       /* Pass the account if there is only one which can be enabled */
2358       if (len == 1)
2359         account = accounts->data;
2360
2361       display_page_account_not_enabled (self, account);
2362       goto out;
2363     }
2364
2365   display_page_contact_list (self);
2366
2367 out:
2368   g_list_free (accounts);
2369 }
2370
2371 static void
2372 roster_window_account_validity_changed_cb (TpAccountManager  *manager,
2373     TpAccount *account,
2374     gboolean valid,
2375     EmpathyRosterWindow *self)
2376 {
2377   if (valid)
2378     add_account (self, account);
2379   else
2380     roster_window_account_removed_cb (manager, account, self);
2381
2382   set_notebook_page (self);
2383 }
2384
2385 #if 0
2386 static void
2387 roster_window_notify_show_offline_cb (GSettings *gsettings,
2388     const gchar *key,
2389     gpointer toggle_action)
2390 {
2391   gtk_toggle_action_set_active (toggle_action,
2392       g_settings_get_boolean (gsettings, key));
2393 }
2394 #endif
2395
2396 static void
2397 roster_window_connection_items_setup (EmpathyRosterWindow *self)
2398 {
2399   guint i;
2400   const gchar *actions_connected[] = {
2401       "room_join_new",
2402       "room_join_favorites",
2403       "chat_new_message",
2404       "chat_new_call",
2405       "chat_search_contacts",
2406       "chat_add_contact",
2407       "edit_blocked_contacts",
2408       "edit_search_contacts"
2409   };
2410
2411   for (i = 0; i < G_N_ELEMENTS (actions_connected); i++)
2412     {
2413       GAction *action;
2414
2415       action = g_action_map_lookup_action (G_ACTION_MAP (self),
2416           actions_connected[i]);
2417
2418       self->priv->actions_connected = g_list_prepend (
2419           self->priv->actions_connected, action);
2420     }
2421 }
2422
2423 static void
2424 account_enabled_cb (TpAccountManager *manager,
2425     TpAccount *account,
2426     EmpathyRosterWindow *self)
2427 {
2428   set_notebook_page (self);
2429 }
2430
2431 static void
2432 account_disabled_cb (TpAccountManager *manager,
2433     TpAccount *account,
2434     EmpathyRosterWindow *self)
2435 {
2436   set_notebook_page (self);
2437 }
2438
2439 static void
2440 account_manager_prepared_cb (GObject *source_object,
2441     GAsyncResult *result,
2442     gpointer user_data)
2443 {
2444   GList *accounts, *j;
2445   TpAccountManager *manager = TP_ACCOUNT_MANAGER (source_object);
2446   EmpathyRosterWindow *self = user_data;
2447   GError *error = NULL;
2448
2449   if (!tp_proxy_prepare_finish (manager, result, &error))
2450     {
2451       DEBUG ("Failed to prepare account manager: %s", error->message);
2452       g_error_free (error);
2453       return;
2454     }
2455
2456   accounts = tp_account_manager_get_valid_accounts (self->priv->account_manager);
2457   for (j = accounts; j != NULL; j = j->next)
2458     {
2459       TpAccount *account = TP_ACCOUNT (j->data);
2460
2461       add_account (self, account);
2462     }
2463
2464   g_signal_connect (manager, "account-validity-changed",
2465       G_CALLBACK (roster_window_account_validity_changed_cb), self);
2466   tp_g_signal_connect_object (manager, "account-disabled",
2467       G_CALLBACK (account_disabled_cb), self, 0);
2468   tp_g_signal_connect_object (manager, "account-enabled",
2469       G_CALLBACK (account_enabled_cb), self, 0);
2470
2471   roster_window_update_status (self);
2472
2473   set_notebook_page (self);
2474
2475   g_list_free (accounts);
2476 }
2477
2478 void
2479 empathy_roster_window_set_shell_running (EmpathyRosterWindow *self,
2480     gboolean shell_running)
2481 {
2482   if (self->priv->shell_running == shell_running)
2483     return;
2484
2485   self->priv->shell_running = shell_running;
2486   g_object_notify (G_OBJECT (self), "shell-running");
2487 }
2488
2489 static GObject *
2490 empathy_roster_window_constructor (GType type,
2491     guint n_construct_params,
2492     GObjectConstructParam *construct_params)
2493 {
2494   static GObject *window = NULL;
2495
2496   if (window != NULL)
2497     return g_object_ref (window);
2498
2499   window = G_OBJECT_CLASS (empathy_roster_window_parent_class)->constructor (
2500     type, n_construct_params, construct_params);
2501
2502   g_object_add_weak_pointer (window, (gpointer) &window);
2503
2504   return window;
2505 }
2506
2507 static GActionEntry menubar_entries[] = {
2508   { "chat_new_message", roster_window_chat_new_message_cb, NULL, NULL, NULL },
2509   { "chat_new_call", roster_window_chat_new_call_cb, NULL, NULL, NULL },
2510   { "chat_add_contact", roster_window_chat_add_contact_cb, NULL, NULL, NULL },
2511   { "chat_search_contacts", roster_window_chat_search_contacts_cb, NULL, NULL, NULL },
2512   { "chat_quit", roster_window_chat_quit_cb, NULL, NULL, NULL },
2513
2514   { "edit_accounts", roster_window_edit_accounts_cb, NULL, NULL, NULL },
2515   { "edit_search_contacts", roster_window_edit_search_contacts_cb, NULL, NULL, NULL },
2516   { "edit_blocked_contacts", roster_window_edit_blocked_contacts_cb, NULL, NULL, NULL },
2517   { "edit_preferences", roster_window_edit_preferences_cb, NULL, NULL, NULL },
2518
2519   { "view_show_offline", activate_toggle, NULL, "false", roster_window_view_show_offline_cb },
2520   /* TODO */
2521   { "view_history", roster_window_view_history_cb, NULL, NULL, NULL },
2522   { "view_show_ft_manager", roster_window_view_show_ft_manager, NULL, NULL, NULL },
2523   { "view_show_map", roster_window_view_show_map_cb, NULL, NULL, NULL },
2524
2525   { "room_join_new", roster_window_room_join_new_cb, NULL, NULL, NULL },
2526   { "room_join_favorites", roster_window_room_join_favorites_cb, NULL, NULL, NULL },
2527   { "room_manage_favorites", roster_window_room_manage_favorites_cb, NULL, NULL, NULL },
2528
2529   { "help_contents", roster_window_help_contents_cb, NULL, NULL, NULL },
2530   { "help_debug", roster_window_help_debug_cb, NULL, NULL, NULL },
2531   { "help_about", roster_window_help_about_cb, NULL, NULL, NULL },
2532 };
2533
2534 static void
2535 empathy_roster_window_set_property (GObject *object,
2536     guint property_id,
2537     const GValue *value,
2538     GParamSpec *pspec)
2539 {
2540   EmpathyRosterWindow *self = EMPATHY_ROSTER_WINDOW (object);
2541
2542   switch (property_id)
2543     {
2544       case PROP_SHELL_RUNNING:
2545         self->priv->shell_running = g_value_get_boolean (value);
2546         break;
2547       default:
2548         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2549         break;
2550     }
2551 }
2552
2553 static void
2554 empathy_roster_window_get_property (GObject    *object,
2555     guint property_id,
2556     GValue *value,
2557     GParamSpec *pspec)
2558 {
2559   EmpathyRosterWindow *self = EMPATHY_ROSTER_WINDOW (object);
2560
2561   switch (property_id)
2562     {
2563       case PROP_SHELL_RUNNING:
2564         g_value_set_boolean (value, self->priv->shell_running);
2565         break;
2566       default:
2567         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2568         break;
2569     }
2570 }
2571
2572 static void
2573 empathy_roster_window_constructed (GObject *self)
2574 {
2575   G_OBJECT_CLASS (empathy_roster_window_parent_class)->constructed (self);
2576
2577   gtk_application_window_set_show_menubar (GTK_APPLICATION_WINDOW (self),
2578       FALSE);
2579 }
2580
2581 static void
2582 empathy_roster_window_class_init (EmpathyRosterWindowClass *klass)
2583 {
2584   GObjectClass *object_class = G_OBJECT_CLASS (klass);
2585   GParamSpec *pspec;
2586
2587   object_class->finalize = empathy_roster_window_finalize;
2588   object_class->constructor = empathy_roster_window_constructor;
2589   object_class->constructed = empathy_roster_window_constructed;
2590
2591   object_class->set_property = empathy_roster_window_set_property;
2592   object_class->get_property = empathy_roster_window_get_property;
2593
2594   pspec = g_param_spec_boolean ("shell-running",
2595       "Shell running",
2596       "Whether the Shell is running or not",
2597       FALSE,
2598       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
2599   g_object_class_install_property (object_class, PROP_SHELL_RUNNING, pspec);
2600
2601   g_type_class_add_private (object_class, sizeof (EmpathyRosterWindowPriv));
2602 }
2603
2604 static void
2605 show_contacts_loading (EmpathyRosterWindow *self)
2606 {
2607   display_page_message (self, NULL, FALSE, TRUE);
2608
2609   gtk_spinner_start (GTK_SPINNER (self->priv->spinner_loading));
2610 }
2611
2612 static void
2613 hide_contacts_loading (EmpathyRosterWindow *self)
2614 {
2615   gtk_spinner_stop (GTK_SPINNER (self->priv->spinner_loading));
2616
2617   set_notebook_page (self);
2618 }
2619
2620 static void
2621 contacts_loaded_cb (EmpathyIndividualManager *manager,
2622     EmpathyRosterWindow *self)
2623 {
2624   hide_contacts_loading (self);
2625 }
2626
2627 static void
2628 roster_window_menu_closed_cb (GtkWidget *button,
2629     GtkMenu *menu)
2630 {
2631   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), FALSE);
2632 }
2633
2634 static void
2635 roster_window_menu_position_func (GtkMenu *menu,
2636     int *x,
2637     int *y,
2638     gboolean *push_in,
2639     gpointer user_data)
2640 {
2641   GtkWidget *button = user_data;
2642   GtkAllocation allocation;
2643   int w = gtk_widget_get_allocated_width (GTK_WIDGET (menu));
2644
2645   gtk_widget_get_allocation (button, &allocation);
2646   gdk_window_get_root_coords (gtk_widget_get_window (button),
2647       allocation.x + allocation.width - w,
2648       allocation.y + allocation.height,
2649       x, y);
2650
2651   *push_in = TRUE;
2652 }
2653
2654 static gboolean
2655 roster_window_menu_button_press_cb (GtkWidget *button,
2656     GdkEventButton *event,
2657     EmpathyRosterWindow *self)
2658 {
2659   if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
2660     {
2661       GtkWidget *menu = gtk_menu_new_from_model (
2662           G_MENU_MODEL (self->priv->menubuttonmodel));
2663
2664       g_signal_connect (menu, "selection-done",
2665           G_CALLBACK (gtk_widget_destroy), NULL);
2666       gtk_menu_attach_to_widget (GTK_MENU (menu), button,
2667           roster_window_menu_closed_cb);
2668       gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
2669           roster_window_menu_position_func, button,
2670           event->button, event->time);
2671
2672       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
2673
2674       return TRUE;
2675     }
2676
2677   return FALSE;
2678 }
2679
2680 static void
2681 empathy_roster_window_init (EmpathyRosterWindow *self)
2682 {
2683   GtkBuilder *gui;
2684   GtkWidget *sw;
2685   gchar *filename;
2686   GtkTreeModel *model;
2687   GtkWidget *search_vbox;
2688   GtkWidget *button;
2689
2690   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2691       EMPATHY_TYPE_ROSTER_WINDOW, EmpathyRosterWindowPriv);
2692
2693   self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2694   self->priv->gsettings_contacts = g_settings_new (EMPATHY_PREFS_CONTACTS_SCHEMA);
2695
2696   self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2697
2698   gtk_window_set_title (GTK_WINDOW (self), _("Contact List"));
2699   gtk_window_set_role (GTK_WINDOW (self), "contact_list");
2700   gtk_window_set_default_size (GTK_WINDOW (self), 225, 325);
2701
2702   /* don't finalize the widget on delete-event, just hide it */
2703   g_signal_connect (self, "delete-event",
2704     G_CALLBACK (gtk_widget_hide_on_delete), NULL);
2705
2706   /* Set up interface */
2707   filename = empathy_file_lookup ("empathy-roster-window.ui", "src");
2708   gui = empathy_builder_get_file (filename,
2709       "main_vbox", &self->priv->main_vbox,
2710       "balance_vbox", &self->priv->balance_vbox,
2711       "errors_vbox", &self->priv->errors_vbox,
2712       "auth_vbox", &self->priv->auth_vbox,
2713       "search_vbox", &search_vbox,
2714       "presence_toolbar", &self->priv->presence_toolbar,
2715       "notebook", &self->priv->notebook,
2716       "no_entry_label", &self->priv->no_entry_label,
2717       "roster_scrolledwindow", &sw,
2718       "button_account_settings", &self->priv->button_account_settings,
2719       "spinner_loading", &self->priv->spinner_loading,
2720       NULL);
2721   g_free (filename);
2722
2723   gtk_container_add (GTK_CONTAINER (self), self->priv->main_vbox);
2724   gtk_widget_show (self->priv->main_vbox);
2725
2726   g_signal_connect (self, "key-press-event",
2727       G_CALLBACK (roster_window_key_press_event_cb), NULL);
2728
2729   //g_object_ref (self->priv->ui_manager);
2730   g_object_unref (gui);
2731
2732   self->priv->account_manager = tp_account_manager_dup ();
2733
2734   tp_proxy_prepare_async (self->priv->account_manager, NULL,
2735       account_manager_prepared_cb, self);
2736
2737   self->priv->errors = g_hash_table_new_full (g_direct_hash, g_direct_equal,
2738       g_object_unref, NULL);
2739
2740   self->priv->auths = g_hash_table_new (NULL, NULL);
2741
2742   self->priv->status_changed_handlers = g_hash_table_new_full (g_direct_hash,
2743       g_direct_equal, NULL, NULL);
2744
2745   self->priv->topup_menu_items = g_hash_table_new_full (NULL, NULL,
2746       g_object_unref, g_object_unref);
2747
2748   /* set up menus */
2749   g_action_map_add_action_entries (G_ACTION_MAP (self),
2750       menubar_entries, G_N_ELEMENTS (menubar_entries), self);
2751
2752   filename = empathy_file_lookup ("empathy-roster-window-menubar.ui", "src");
2753   gui = empathy_builder_get_file (filename,
2754       "menubutton", &self->priv->menubuttonmodel,
2755       "rooms", &self->priv->rooms_section,
2756       NULL);
2757   g_free (filename);
2758
2759   g_object_ref (self->priv->rooms_section);
2760
2761   /* Disable map if built without champlain */
2762 #ifndef HAVE_LIBCHAMPLAIN
2763     {
2764       GAction *view_show_map;
2765
2766       view_show_map = g_action_map_lookup_action (G_ACTION_MAP (self),
2767           "view_show_map");
2768       g_simple_action_set_enabled (G_SIMPLE_ACTION (view_show_map), FALSE);
2769     }
2770 #endif
2771
2772   /* Set up connection related actions. */
2773   roster_window_connection_items_setup (self);
2774   roster_window_favorite_chatroom_menu_setup (self);
2775
2776   /* FIXME: display accelerators in menu */
2777   /* FIXME: make menu appear on <Alt> */
2778
2779   g_object_unref (gui);
2780
2781   /* Set up contact list. */
2782   empathy_status_presets_get_all ();
2783
2784   /* Set up presence chooser */
2785   self->priv->presence_chooser = empathy_presence_chooser_new ();
2786   gtk_widget_show (self->priv->presence_chooser);
2787   gtk_box_pack_start (GTK_BOX (self->priv->presence_toolbar),
2788       self->priv->presence_chooser,
2789       TRUE, TRUE, 0);
2790
2791   /* Set up the throbber */
2792   self->priv->throbber = gtk_spinner_new ();
2793   gtk_widget_set_size_request (self->priv->throbber, 16, -1);
2794   gtk_widget_set_events (self->priv->throbber, GDK_BUTTON_PRESS_MASK);
2795   g_signal_connect (self->priv->throbber, "button-press-event",
2796     G_CALLBACK (roster_window_throbber_button_press_event_cb),
2797     self);
2798   gtk_box_pack_start (GTK_BOX (self->priv->presence_toolbar),
2799       self->priv->throbber,
2800       FALSE, TRUE, 0);
2801
2802   /* Set up the menu */
2803
2804   button = gtk_toggle_button_new ();
2805   gtk_button_set_image (GTK_BUTTON (button),
2806       gtk_image_new_from_icon_name ("user-available-symbolic",
2807         GTK_ICON_SIZE_SMALL_TOOLBAR));
2808   gtk_widget_show (button);
2809   gtk_box_pack_start (GTK_BOX (self->priv->presence_toolbar),
2810       button,
2811       FALSE, TRUE, 0);
2812
2813   g_signal_connect (button, "button-press-event",
2814       G_CALLBACK (roster_window_menu_button_press_cb), self);
2815
2816   /* XXX: this class is designed to live for the duration of the program,
2817    * so it's got a race condition between its signal handlers and its
2818    * finalization. The class is planned to be removed, so we won't fix
2819    * this before then. */
2820   self->priv->individual_manager = empathy_individual_manager_dup_singleton ();
2821
2822   if (!empathy_individual_manager_get_contacts_loaded (
2823         self->priv->individual_manager))
2824     {
2825       show_contacts_loading (self);
2826
2827       tp_g_signal_connect_object (self->priv->individual_manager,
2828           "contacts-loaded", G_CALLBACK (contacts_loaded_cb), self, 0);
2829     }
2830
2831   self->priv->individual_store = EMPATHY_INDIVIDUAL_STORE (
2832       empathy_individual_store_manager_new (self->priv->individual_manager));
2833
2834   /* For the moment, we disallow Persona drops onto the roster contact list
2835    * (e.g. from things such as the EmpathyPersonaView in the linking dialogue).
2836    * No code is hooked up to do anything on a Persona drop, so allowing them
2837    * would achieve nothing except confusion. */
2838   self->priv->individual_view = empathy_individual_view_new (
2839       self->priv->individual_store,
2840       /* EmpathyIndividualViewFeatureFlags */
2841       EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE |
2842       EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2843       EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE |
2844       EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE |
2845       EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE |
2846       EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP |
2847       EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG |
2848       EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP |
2849       EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL |
2850       EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP,
2851       /* EmpathyIndividualFeatureFlags  */
2852       EMPATHY_INDIVIDUAL_FEATURE_CHAT |
2853       EMPATHY_INDIVIDUAL_FEATURE_CALL |
2854       EMPATHY_INDIVIDUAL_FEATURE_EDIT |
2855       EMPATHY_INDIVIDUAL_FEATURE_INFO |
2856       EMPATHY_INDIVIDUAL_FEATURE_LOG |
2857       EMPATHY_INDIVIDUAL_FEATURE_SMS |
2858       EMPATHY_INDIVIDUAL_FEATURE_CALL_PHONE);
2859
2860   gtk_widget_show (GTK_WIDGET (self->priv->individual_view));
2861   gtk_container_add (GTK_CONTAINER (sw),
2862       GTK_WIDGET (self->priv->individual_view));
2863   g_signal_connect (self->priv->individual_view, "row-activated",
2864      G_CALLBACK (roster_window_row_activated_cb), self);
2865
2866   /* Set up search bar */
2867   self->priv->search_bar = empathy_live_search_new (
2868       GTK_WIDGET (self->priv->individual_view));
2869   empathy_individual_view_set_live_search (self->priv->individual_view,
2870       EMPATHY_LIVE_SEARCH (self->priv->search_bar));
2871   gtk_box_pack_start (GTK_BOX (search_vbox), self->priv->search_bar,
2872       FALSE, TRUE, 0);
2873
2874   g_signal_connect_swapped (self, "map",
2875       G_CALLBACK (gtk_widget_grab_focus), self->priv->individual_view);
2876
2877   /* Connect to proper signals to check if contact list is empty or not */
2878   model = gtk_tree_view_get_model (GTK_TREE_VIEW (self->priv->individual_view));
2879   self->priv->empty = TRUE;
2880   g_signal_connect (model, "row-inserted",
2881       G_CALLBACK (roster_window_row_inserted_cb), self);
2882   g_signal_connect (model, "row-deleted",
2883       G_CALLBACK (roster_window_row_deleted_cb), self);
2884
2885   /* Load user-defined accelerators. */
2886   roster_window_accels_load ();
2887
2888   gtk_window_set_default_size (GTK_WINDOW (self), -1, 600);
2889   /* Set window size. */
2890   empathy_geometry_bind (GTK_WINDOW (self), GEOMETRY_NAME);
2891
2892   /* Enable event handling */
2893   self->priv->call_observer = empathy_call_observer_dup_singleton ();
2894   self->priv->event_manager = empathy_event_manager_dup_singleton ();
2895
2896   g_signal_connect (self->priv->event_manager, "event-added",
2897       G_CALLBACK (roster_window_event_added_cb), self);
2898   g_signal_connect (self->priv->event_manager, "event-removed",
2899       G_CALLBACK (roster_window_event_removed_cb), self);
2900   g_signal_connect (self->priv->account_manager, "account-validity-changed",
2901       G_CALLBACK (roster_window_account_validity_changed_cb), self);
2902   g_signal_connect (self->priv->account_manager, "account-removed",
2903       G_CALLBACK (roster_window_account_removed_cb), self);
2904   g_signal_connect (self->priv->account_manager, "account-disabled",
2905       G_CALLBACK (roster_window_account_disabled_cb), self);
2906
2907   /* Show offline ? */
2908   /*
2909   show_offline = g_settings_get_boolean (self->priv->gsettings_ui,
2910       EMPATHY_PREFS_UI_SHOW_OFFLINE);
2911
2912   g_signal_connect (self->priv->gsettings_ui,
2913       "changed::" EMPATHY_PREFS_UI_SHOW_OFFLINE,
2914       G_CALLBACK (roster_window_notify_show_offline_cb), show_offline_widget);
2915
2916   gtk_toggle_action_set_active (show_offline_widget, show_offline);
2917       */
2918
2919   /* Show protocol ? */
2920   /*
2921   g_signal_connect (self->priv->gsettings_ui,
2922       "changed::" EMPATHY_PREFS_UI_SHOW_PROTOCOLS,
2923       G_CALLBACK (roster_window_notify_show_protocols_cb), self);
2924
2925   roster_window_notify_show_protocols_cb (self->priv->gsettings_ui,
2926       EMPATHY_PREFS_UI_SHOW_PROTOCOLS, self);
2927       */
2928
2929   /* Sort by name / by status ? */
2930   /*
2931   g_signal_connect (self->priv->gsettings_contacts,
2932       "changed::" EMPATHY_PREFS_CONTACTS_SORT_CRITERIUM,
2933       G_CALLBACK (roster_window_notify_sort_contact_cb), self);
2934
2935   roster_window_notify_sort_contact_cb (self->priv->gsettings_contacts,
2936       EMPATHY_PREFS_CONTACTS_SORT_CRITERIUM, self);
2937       */
2938
2939   /* Contacts list size */
2940   /*
2941   g_signal_connect (self->priv->gsettings_ui,
2942       "changed::" EMPATHY_PREFS_UI_COMPACT_CONTACT_LIST,
2943       G_CALLBACK (roster_window_notify_contact_list_size_cb), self);
2944
2945   g_signal_connect (self->priv->gsettings_ui,
2946       "changed::" EMPATHY_PREFS_UI_SHOW_AVATARS,
2947       G_CALLBACK (roster_window_notify_contact_list_size_cb),
2948       self);
2949       */
2950
2951   /*
2952   roster_window_notify_contact_list_size_cb (self->priv->gsettings_ui,
2953       EMPATHY_PREFS_UI_SHOW_AVATARS, self);
2954       */
2955
2956   g_signal_connect (self->priv->button_account_settings, "clicked",
2957       G_CALLBACK (button_account_settings_clicked_cb), self);
2958 }
2959
2960 GtkWidget *
2961 empathy_roster_window_new (GtkApplication *app)
2962 {
2963   return g_object_new (EMPATHY_TYPE_ROSTER_WINDOW,
2964       "application", app,
2965       NULL);
2966 }