]> git.0d.be Git - empathy.git/blob - src/empathy-roster-window.c
roster-window: migrate menu bar to GtkBuilder
[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 *throbber_tool_item;
125   GtkWidget *presence_toolbar;
126   GtkWidget *presence_chooser;
127   GtkWidget *errors_vbox;
128   GtkWidget *auth_vbox;
129   GtkWidget *search_bar;
130   GtkWidget *notebook;
131   GtkWidget *no_entry_label;
132   GtkWidget *button_account_settings;
133   GtkWidget *spinner_loading;
134
135   GtkToggleAction *show_protocols;
136   GtkRadioAction *sort_by_name;
137   GtkRadioAction *sort_by_status;
138   GtkRadioAction *normal_with_avatars;
139   GtkRadioAction *normal_size;
140   GtkRadioAction *compact_size;
141
142   GtkUIManager *ui_manager;
143   GtkAction *view_history;
144   GMenu *rooms_section;
145   GMenu *balance_section;
146   GAction *view_credit_action;
147   GtkWidget *edit_context;
148   GtkWidget *edit_context_separator;
149
150   GtkActionGroup *balance_action_group;
151   GtkWidget *balance_vbox;
152
153   guint size_timeout_id;
154
155   /* reffed TpAccount* => visible GtkInfoBar* */
156   GHashTable *errors;
157
158   /* EmpathyEvent* => visible GtkInfoBar* */
159   GHashTable *auths;
160
161   /* stores a mapping from TpAccount to Handler ID to prevent
162    * to listen more than once to the status-changed signal */
163   GHashTable *status_changed_handlers;
164
165   /* Actions that are enabled when there are connected accounts */
166   GList *actions_connected;
167
168   gboolean shell_running;
169
170   /* GAction => GMenuItem */
171   GHashTable *topup_menu_items;
172 };
173
174 static void
175 roster_window_flash_stop (EmpathyRosterWindow *self)
176 {
177   if (self->priv->flash_timeout_id == 0)
178     return;
179
180   DEBUG ("Stop flashing");
181   g_source_remove (self->priv->flash_timeout_id);
182   self->priv->flash_timeout_id = 0;
183   self->priv->flash_on = FALSE;
184 }
185
186 typedef struct
187 {
188   EmpathyEvent *event;
189   gboolean on;
190   EmpathyRosterWindow *self;
191 } FlashForeachData;
192
193 static gboolean
194 roster_window_flash_foreach (GtkTreeModel *model,
195     GtkTreePath *path,
196     GtkTreeIter *iter,
197     gpointer user_data)
198 {
199   FlashForeachData *data = (FlashForeachData *) user_data;
200   FolksIndividual *individual;
201   EmpathyContact *contact;
202   const gchar *icon_name;
203   GtkTreePath *parent_path = NULL;
204   GtkTreeIter parent_iter;
205   GdkPixbuf *pixbuf = NULL;
206
207   gtk_tree_model_get (model, iter,
208           EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
209           -1);
210
211   if (individual == NULL)
212     return FALSE;
213
214   contact = empathy_contact_dup_from_folks_individual (individual);
215   if (contact != data->event->contact)
216     goto out;
217
218   if (data->on)
219     {
220       icon_name = data->event->icon_name;
221       pixbuf = empathy_pixbuf_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
222     }
223   else
224     {
225       pixbuf = empathy_individual_store_get_individual_status_icon (
226               data->self->priv->individual_store,
227               individual);
228       if (pixbuf != NULL)
229         g_object_ref (pixbuf);
230     }
231
232   gtk_tree_store_set (GTK_TREE_STORE (model), iter,
233       EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, pixbuf,
234       -1);
235
236   /* To make sure the parent is shown correctly, we emit
237    * the row-changed signal on the parent so it prompts
238    * it to be refreshed by the filter func.
239    */
240   if (gtk_tree_model_iter_parent (model, &parent_iter, iter))
241     {
242       parent_path = gtk_tree_model_get_path (model, &parent_iter);
243     }
244
245   if (parent_path != NULL)
246     {
247       gtk_tree_model_row_changed (model, parent_path, &parent_iter);
248       gtk_tree_path_free (parent_path);
249     }
250
251 out:
252   g_object_unref (individual);
253   tp_clear_object (&contact);
254   tp_clear_object (&pixbuf);
255
256   return FALSE;
257 }
258
259 static gboolean
260 roster_window_flash_cb (EmpathyRosterWindow *self)
261 {
262   GtkTreeModel *model;
263   GSList *events, *l;
264   gboolean found_event = FALSE;
265   FlashForeachData  data;
266
267   self->priv->flash_on = !self->priv->flash_on;
268   data.on = self->priv->flash_on;
269   model = GTK_TREE_MODEL (self->priv->individual_store);
270
271   events = empathy_event_manager_get_events (self->priv->event_manager);
272   for (l = events; l; l = l->next)
273     {
274       data.event = l->data;
275       data.self = self;
276       if (!data.event->contact || !data.event->must_ack)
277         continue;
278
279       found_event = TRUE;
280       gtk_tree_model_foreach (model,
281           roster_window_flash_foreach,
282           &data);
283     }
284
285   if (!found_event)
286     roster_window_flash_stop (self);
287
288   return TRUE;
289 }
290
291 static void
292 roster_window_flash_start (EmpathyRosterWindow *self)
293 {
294   if (self->priv->flash_timeout_id != 0)
295     return;
296
297   DEBUG ("Start flashing");
298   self->priv->flash_timeout_id = g_timeout_add (FLASH_TIMEOUT,
299       (GSourceFunc) roster_window_flash_cb, self);
300
301   roster_window_flash_cb (self);
302 }
303
304 static void
305 roster_window_remove_auth (EmpathyRosterWindow *self,
306     EmpathyEvent *event)
307 {
308   GtkWidget *error_widget;
309
310   error_widget = g_hash_table_lookup (self->priv->auths, event);
311   if (error_widget != NULL)
312     {
313       gtk_widget_destroy (error_widget);
314       g_hash_table_remove (self->priv->auths, event);
315     }
316 }
317
318 static void
319 roster_window_auth_add_clicked_cb (GtkButton *button,
320     EmpathyRosterWindow *self)
321 {
322   EmpathyEvent *event;
323
324   event = g_object_get_data (G_OBJECT (button), "event");
325
326   empathy_event_approve (event);
327
328   roster_window_remove_auth (self, event);
329 }
330
331 static void
332 roster_window_auth_close_clicked_cb (GtkButton *button,
333     EmpathyRosterWindow *self)
334 {
335   EmpathyEvent *event;
336
337   event = g_object_get_data (G_OBJECT (button), "event");
338
339   empathy_event_decline (event);
340   roster_window_remove_auth (self, event);
341 }
342
343 static void
344 roster_window_auth_display (EmpathyRosterWindow *self,
345     EmpathyEvent *event)
346 {
347   TpAccount *account = event->account;
348   GtkWidget *info_bar;
349   GtkWidget *content_area;
350   GtkWidget *image;
351   GtkWidget *label;
352   GtkWidget *add_button;
353   GtkWidget *close_button;
354   GtkWidget *action_area;
355   GtkWidget *action_grid;
356   const gchar *icon_name;
357   gchar *str;
358
359   if (g_hash_table_lookup (self->priv->auths, event) != NULL)
360     return;
361
362   info_bar = gtk_info_bar_new ();
363   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_QUESTION);
364
365   gtk_widget_set_no_show_all (info_bar, TRUE);
366   gtk_box_pack_start (GTK_BOX (self->priv->auth_vbox), info_bar, FALSE, TRUE, 0);
367   gtk_widget_show (info_bar);
368
369   icon_name = tp_account_get_icon_name (account);
370   image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_SMALL_TOOLBAR);
371   gtk_widget_show (image);
372
373   str = g_markup_printf_escaped ("<b>%s</b>\n%s",
374       tp_account_get_display_name (account),
375       _("Password required"));
376
377   label = gtk_label_new (str);
378   gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
379   gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
380   gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
381   gtk_widget_show (label);
382
383   g_free (str);
384
385   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
386   gtk_box_pack_start (GTK_BOX (content_area), image, FALSE, FALSE, 0);
387   gtk_box_pack_start (GTK_BOX (content_area), label, TRUE, TRUE, 0);
388
389   image = gtk_image_new_from_stock (GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON);
390   add_button = gtk_button_new ();
391   gtk_button_set_image (GTK_BUTTON (add_button), image);
392   gtk_widget_set_tooltip_text (add_button, _("Provide Password"));
393   gtk_widget_show (add_button);
394
395   image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON);
396   close_button = gtk_button_new ();
397   gtk_button_set_image (GTK_BUTTON (close_button), image);
398   gtk_widget_set_tooltip_text (close_button, _("Disconnect"));
399   gtk_widget_show (close_button);
400
401   action_grid = gtk_grid_new ();
402   gtk_grid_set_column_spacing (GTK_GRID (action_grid), 6);
403   gtk_widget_show (action_grid);
404
405   action_area = gtk_info_bar_get_action_area (GTK_INFO_BAR (info_bar));
406   gtk_box_pack_start (GTK_BOX (action_area), action_grid, FALSE, FALSE, 0);
407
408   gtk_grid_attach (GTK_GRID (action_grid), add_button, 0, 0, 1, 1);
409   gtk_grid_attach (GTK_GRID (action_grid), close_button, 1, 0, 1, 1);
410
411   g_object_set_data_full (G_OBJECT (info_bar),
412       "event", event, NULL);
413   g_object_set_data_full (G_OBJECT (add_button),
414       "event", event, NULL);
415   g_object_set_data_full (G_OBJECT (close_button),
416       "event", event, NULL);
417
418   g_signal_connect (add_button, "clicked",
419       G_CALLBACK (roster_window_auth_add_clicked_cb), self);
420   g_signal_connect (close_button, "clicked",
421       G_CALLBACK (roster_window_auth_close_clicked_cb), self);
422
423   gtk_widget_show (self->priv->auth_vbox);
424
425   g_hash_table_insert (self->priv->auths, event, info_bar);
426 }
427
428 static void
429 modify_event_count (GtkTreeModel *model,
430     GtkTreeIter *iter,
431     EmpathyEvent *event,
432     gboolean increase)
433 {
434   FolksIndividual *individual;
435   EmpathyContact *contact;
436   guint count;
437
438   gtk_tree_model_get (model, iter,
439       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
440       EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &count,
441       -1);
442
443   if (individual == NULL)
444     return;
445
446   increase ? count++ : count--;
447
448   contact = empathy_contact_dup_from_folks_individual (individual);
449   if (contact == event->contact)
450     gtk_tree_store_set (GTK_TREE_STORE (model), iter,
451         EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, count, -1);
452
453   tp_clear_object (&contact);
454   g_object_unref (individual);
455 }
456
457 static gboolean
458 increase_event_count_foreach (GtkTreeModel *model,
459     GtkTreePath *path,
460     GtkTreeIter *iter,
461     gpointer user_data)
462 {
463   EmpathyEvent *event = user_data;
464
465   modify_event_count (model, iter, event, TRUE);
466
467   return FALSE;
468 }
469
470 static void
471 increase_event_count (EmpathyRosterWindow *self,
472     EmpathyEvent *event)
473 {
474   GtkTreeModel *model;
475
476   model = GTK_TREE_MODEL (self->priv->individual_store);
477
478   gtk_tree_model_foreach (model, increase_event_count_foreach, event);
479 }
480
481 static gboolean
482 decrease_event_count_foreach (GtkTreeModel *model,
483     GtkTreePath *path,
484     GtkTreeIter *iter,
485     gpointer user_data)
486 {
487   EmpathyEvent *event = user_data;
488
489   modify_event_count (model, iter, event, FALSE);
490
491   return FALSE;
492 }
493
494 static void
495 decrease_event_count (EmpathyRosterWindow *self,
496     EmpathyEvent *event)
497 {
498   GtkTreeModel *model;
499
500   model = GTK_TREE_MODEL (self->priv->individual_store);
501
502   gtk_tree_model_foreach (model, decrease_event_count_foreach, event);
503 }
504
505 static void
506 roster_window_event_added_cb (EmpathyEventManager *manager,
507     EmpathyEvent *event,
508     EmpathyRosterWindow *self)
509 {
510   if (event->contact)
511     {
512       increase_event_count (self, event);
513
514       roster_window_flash_start (self);
515     }
516   else if (event->type == EMPATHY_EVENT_TYPE_AUTH)
517     {
518       roster_window_auth_display (self, event);
519     }
520 }
521
522 static void
523 roster_window_event_removed_cb (EmpathyEventManager *manager,
524     EmpathyEvent *event,
525     EmpathyRosterWindow *self)
526 {
527   FlashForeachData data;
528
529   if (event->type == EMPATHY_EVENT_TYPE_AUTH)
530     {
531       roster_window_remove_auth (self, event);
532       return;
533     }
534
535   if (!event->contact)
536     return;
537
538   decrease_event_count (self, event);
539
540   data.on = FALSE;
541   data.event = event;
542   data.self = self;
543
544   gtk_tree_model_foreach (GTK_TREE_MODEL (self->priv->individual_store),
545       roster_window_flash_foreach,
546       &data);
547 }
548
549 static gboolean
550 roster_window_load_events_idle_cb (gpointer user_data)
551 {
552   EmpathyRosterWindow *self = user_data;
553   GSList *l;
554
555   l = empathy_event_manager_get_events (self->priv->event_manager);
556   while (l != NULL)
557     {
558       roster_window_event_added_cb (self->priv->event_manager, l->data,
559           self);
560       l = l->next;
561     }
562
563   return FALSE;
564 }
565
566 static void
567 roster_window_row_activated_cb (EmpathyIndividualView *view,
568     GtkTreePath *path,
569     GtkTreeViewColumn *col,
570     EmpathyRosterWindow *self)
571 {
572   EmpathyContact *contact = NULL;
573   FolksIndividual *individual;
574   GtkTreeModel *model;
575   GtkTreeIter iter;
576   GSList *events, *l;
577
578   model = gtk_tree_view_get_model (GTK_TREE_VIEW (self->priv->individual_view));
579   gtk_tree_model_get_iter (model, &iter, path);
580
581   gtk_tree_model_get (model, &iter, EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL,
582       &individual, -1);
583
584   if (individual != NULL)
585     contact = empathy_contact_dup_from_folks_individual (individual);
586
587   if (contact == NULL)
588     goto OUT;
589
590   /* If the contact has an event activate it, otherwise the
591    * default handler of row-activated will be called. */
592   events = empathy_event_manager_get_events (self->priv->event_manager);
593   for (l = events; l; l = l->next)
594     {
595       EmpathyEvent *event = l->data;
596
597       if (event->contact == contact)
598         {
599           DEBUG ("Activate event");
600           empathy_event_activate (event);
601
602           /* We don't want the default handler of this signal
603            * (e.g. open a chat) */
604           g_signal_stop_emission_by_name (view, "row-activated");
605           break;
606         }
607     }
608
609   g_object_unref (contact);
610 OUT:
611   tp_clear_object (&individual);
612 }
613
614 static void
615 button_account_settings_clicked_cb (GtkButton *button,
616     EmpathyRosterWindow *self)
617 {
618   empathy_accounts_dialog_show_application (gdk_screen_get_default (),
619       NULL, FALSE, FALSE);
620 }
621
622 static void
623 display_page_message (EmpathyRosterWindow *self,
624     const gchar *msg,
625     gboolean display_accounts_button,
626     gboolean display_spinner)
627 {
628   if (msg != NULL)
629     {
630       gchar *tmp;
631
632       tmp = g_strdup_printf ("<b><span size='xx-large'>%s</span></b>", msg);
633
634       gtk_label_set_markup (GTK_LABEL (self->priv->no_entry_label), tmp);
635       g_free (tmp);
636
637       gtk_label_set_line_wrap (GTK_LABEL (self->priv->no_entry_label), TRUE);
638       gtk_widget_show (self->priv->no_entry_label);
639     }
640   else
641     {
642       gtk_widget_hide (self->priv->no_entry_label);
643     }
644
645   gtk_widget_set_visible (self->priv->button_account_settings,
646       display_accounts_button);
647   gtk_widget_set_visible (self->priv->spinner_loading,
648       display_spinner);
649
650   gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
651       PAGE_MESSAGE);
652 }
653
654 static void
655 display_page_no_account (EmpathyRosterWindow *self)
656 {
657   display_page_message (self,
658       _("You need to setup an account to see contacts here."), TRUE, FALSE);
659 }
660
661 static void
662 roster_window_row_deleted_cb (GtkTreeModel *model,
663     GtkTreePath *path,
664     EmpathyRosterWindow *self)
665 {
666   GtkTreeIter help_iter;
667
668   if (!gtk_tree_model_get_iter_first (model, &help_iter))
669     {
670       self->priv->empty = TRUE;
671
672       if (empathy_individual_view_is_searching (self->priv->individual_view))
673         {
674           display_page_message (self, _("No match found"), FALSE, FALSE);
675         }
676     }
677 }
678
679 static void
680 display_page_contact_list (EmpathyRosterWindow *self)
681 {
682   if (!empathy_individual_manager_get_contacts_loaded (
683         self->priv->individual_manager))
684     /* We'll display the contact list once we're done loading */
685     return;
686
687   gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
688       PAGE_CONTACT_LIST);
689 }
690
691 static void
692 roster_window_row_inserted_cb (GtkTreeModel      *model,
693     GtkTreePath *path,
694     GtkTreeIter *iter,
695     EmpathyRosterWindow *self)
696 {
697   if (self->priv->empty)
698     {
699       self->priv->empty = FALSE;
700
701       display_page_contact_list (self);
702       gtk_widget_grab_focus (GTK_WIDGET (self->priv->individual_view));
703
704       /* The store is being filled, it will be done after an idle cb.
705        * So we can then get events. If we do that too soon, event's
706        * contact is not yet in the store and it won't get marked as
707        * having events. */
708       g_idle_add (roster_window_load_events_idle_cb, self);
709     }
710 }
711
712 static void
713 roster_window_remove_error (EmpathyRosterWindow *self,
714     TpAccount *account)
715 {
716   GtkWidget *error_widget;
717
718   error_widget = g_hash_table_lookup (self->priv->errors, account);
719   if (error_widget != NULL)
720     {
721       gtk_widget_destroy (error_widget);
722       g_hash_table_remove (self->priv->errors, account);
723     }
724 }
725
726 static void
727 roster_window_account_disabled_cb (TpAccountManager  *manager,
728     TpAccount *account,
729     EmpathyRosterWindow *self)
730 {
731   roster_window_remove_error (self, account);
732 }
733
734 static void
735 roster_window_error_retry_clicked_cb (GtkButton *button,
736     EmpathyRosterWindow *self)
737 {
738   TpAccount *account;
739
740   account = g_object_get_data (G_OBJECT (button), "account");
741   tp_account_reconnect_async (account, NULL, NULL);
742
743   roster_window_remove_error (self, account);
744 }
745
746 static void
747 roster_window_error_edit_clicked_cb (GtkButton *button,
748     EmpathyRosterWindow *self)
749 {
750   TpAccount *account;
751
752   account = g_object_get_data (G_OBJECT (button), "account");
753
754   empathy_accounts_dialog_show_application (
755       gtk_widget_get_screen (GTK_WIDGET (button)),
756       account, FALSE, FALSE);
757
758   roster_window_remove_error (self, account);
759 }
760
761 static void
762 roster_window_error_close_clicked_cb (GtkButton *button,
763     EmpathyRosterWindow *self)
764 {
765   TpAccount *account;
766
767   account = g_object_get_data (G_OBJECT (button), "account");
768   roster_window_remove_error (self, account);
769 }
770
771 static void
772 roster_window_error_upgrade_sw_clicked_cb (GtkButton *button,
773     EmpathyRosterWindow *self)
774 {
775   TpAccount *account;
776   GtkWidget *dialog;
777
778   account = g_object_get_data (G_OBJECT (button), "account");
779   roster_window_remove_error (self, account);
780
781   dialog = gtk_message_dialog_new (GTK_WINDOW (self),
782       GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
783       GTK_BUTTONS_OK,
784       _("Sorry, %s accounts can’t be used until your %s software is updated."),
785       tp_account_get_protocol (account),
786       tp_account_get_protocol (account));
787
788   g_signal_connect_swapped (dialog, "response",
789       G_CALLBACK (gtk_widget_destroy),
790       dialog);
791
792   gtk_widget_show (dialog);
793 }
794
795 static void
796 roster_window_upgrade_software_error (EmpathyRosterWindow *self,
797     TpAccount *account)
798 {
799   GtkWidget *info_bar;
800   GtkWidget *content_area;
801   GtkWidget *label;
802   GtkWidget *image;
803   GtkWidget *upgrade_button;
804   GtkWidget *close_button;
805   GtkWidget *action_area;
806   GtkWidget *action_grid;
807   gchar *str;
808   const gchar *icon_name;
809   const gchar *error_message;
810   gboolean user_requested;
811
812   error_message =
813     empathy_account_get_error_message (account, &user_requested);
814
815   if (user_requested)
816     return;
817
818   str = g_markup_printf_escaped ("<b>%s</b>\n%s",
819       tp_account_get_display_name (account),
820       error_message);
821
822   /* If there are other errors, remove them */
823   roster_window_remove_error (self, account);
824
825   info_bar = gtk_info_bar_new ();
826   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_ERROR);
827
828   gtk_widget_set_no_show_all (info_bar, TRUE);
829   gtk_box_pack_start (GTK_BOX (self->priv->errors_vbox), info_bar, FALSE, TRUE, 0);
830   gtk_widget_show (info_bar);
831
832   icon_name = tp_account_get_icon_name (account);
833   image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_SMALL_TOOLBAR);
834   gtk_widget_show (image);
835
836   label = gtk_label_new (str);
837   gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
838   gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
839   gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
840   gtk_widget_show (label);
841   g_free (str);
842
843   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
844   gtk_box_pack_start (GTK_BOX (content_area), image, FALSE, FALSE, 0);
845   gtk_box_pack_start (GTK_BOX (content_area), label, TRUE, TRUE, 0);
846
847   image = gtk_image_new_from_stock (GTK_STOCK_REFRESH, GTK_ICON_SIZE_BUTTON);
848   upgrade_button = gtk_button_new ();
849   gtk_button_set_image (GTK_BUTTON (upgrade_button), image);
850   gtk_widget_set_tooltip_text (upgrade_button, _("Update software..."));
851   gtk_widget_show (upgrade_button);
852
853   image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON);
854   close_button = gtk_button_new ();
855   gtk_button_set_image (GTK_BUTTON (close_button), image);
856   gtk_widget_set_tooltip_text (close_button, _("Close"));
857   gtk_widget_show (close_button);
858
859   action_grid = gtk_grid_new ();
860   gtk_grid_set_column_spacing (GTK_GRID (action_grid), 2);
861   gtk_widget_show (action_grid);
862
863   action_area = gtk_info_bar_get_action_area (GTK_INFO_BAR (info_bar));
864   gtk_box_pack_start (GTK_BOX (action_area), action_grid, FALSE, FALSE, 0);
865
866   gtk_grid_attach (GTK_GRID (action_grid), upgrade_button, 0, 0, 1, 1);
867   gtk_grid_attach (GTK_GRID (action_grid), close_button, 1, 0, 1, 1);
868
869   g_object_set_data (G_OBJECT (info_bar), "label", label);
870   g_object_set_data_full (G_OBJECT (info_bar),
871       "account", g_object_ref (account),
872       g_object_unref);
873   g_object_set_data_full (G_OBJECT (upgrade_button),
874       "account", g_object_ref (account),
875       g_object_unref);
876   g_object_set_data_full (G_OBJECT (close_button),
877       "account", g_object_ref (account),
878       g_object_unref);
879
880   g_signal_connect (upgrade_button, "clicked",
881       G_CALLBACK (roster_window_error_upgrade_sw_clicked_cb), self);
882   g_signal_connect (close_button, "clicked",
883       G_CALLBACK (roster_window_error_close_clicked_cb), self);
884
885   gtk_widget_set_tooltip_text (self->priv->errors_vbox, error_message);
886   gtk_widget_show (self->priv->errors_vbox);
887
888   g_hash_table_insert (self->priv->errors, g_object_ref (account), info_bar);
889 }
890
891 static void
892 roster_window_error_display (EmpathyRosterWindow *self,
893     TpAccount *account)
894 {
895   GtkWidget *info_bar;
896   GtkWidget *content_area;
897   GtkWidget *label;
898   GtkWidget *image;
899   GtkWidget *retry_button;
900   GtkWidget *edit_button;
901   GtkWidget *close_button;
902   GtkWidget *action_area;
903   GtkWidget *action_grid;
904   gchar *str;
905   const gchar *icon_name;
906   const gchar *error_message;
907   gboolean user_requested;
908
909   if (!tp_strdiff (TP_ERROR_STR_SOFTWARE_UPGRADE_REQUIRED,
910        tp_account_get_detailed_error (account, NULL)))
911     {
912       roster_window_upgrade_software_error (self, account);
913       return;
914     }
915
916   error_message = empathy_account_get_error_message (account, &user_requested);
917
918   if (user_requested)
919     return;
920
921   str = g_markup_printf_escaped ("<b>%s</b>\n%s",
922       tp_account_get_display_name (account), error_message);
923
924   info_bar = g_hash_table_lookup (self->priv->errors, account);
925   if (info_bar)
926     {
927       label = g_object_get_data (G_OBJECT (info_bar), "label");
928
929       /* Just set the latest error and return */
930       gtk_label_set_markup (GTK_LABEL (label), str);
931       g_free (str);
932
933       return;
934     }
935
936   info_bar = gtk_info_bar_new ();
937   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_ERROR);
938
939   gtk_widget_set_no_show_all (info_bar, TRUE);
940   gtk_box_pack_start (GTK_BOX (self->priv->errors_vbox), info_bar, FALSE, TRUE, 0);
941   gtk_widget_show (info_bar);
942
943   icon_name = tp_account_get_icon_name (account);
944   image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_SMALL_TOOLBAR);
945   gtk_widget_show (image);
946
947   label = gtk_label_new (str);
948   gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
949   gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
950   gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
951   gtk_widget_show (label);
952   g_free (str);
953
954   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
955   gtk_box_pack_start (GTK_BOX (content_area), image, FALSE, FALSE, 0);
956   gtk_box_pack_start (GTK_BOX (content_area), label, TRUE, TRUE, 0);
957
958   image = gtk_image_new_from_stock (GTK_STOCK_REFRESH, GTK_ICON_SIZE_BUTTON);
959   retry_button = gtk_button_new ();
960   gtk_button_set_image (GTK_BUTTON (retry_button), image);
961   gtk_widget_set_tooltip_text (retry_button, _("Reconnect"));
962   gtk_widget_show (retry_button);
963
964   image = gtk_image_new_from_stock (GTK_STOCK_EDIT, GTK_ICON_SIZE_BUTTON);
965   edit_button = gtk_button_new ();
966   gtk_button_set_image (GTK_BUTTON (edit_button), image);
967   gtk_widget_set_tooltip_text (edit_button, _("Edit Account"));
968   gtk_widget_show (edit_button);
969
970   image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON);
971   close_button = gtk_button_new ();
972   gtk_button_set_image (GTK_BUTTON (close_button), image);
973   gtk_widget_set_tooltip_text (close_button, _("Close"));
974   gtk_widget_show (close_button);
975
976   action_grid = gtk_grid_new ();
977   gtk_grid_set_column_spacing (GTK_GRID (action_grid), 2);
978   gtk_widget_show (action_grid);
979
980   action_area = gtk_info_bar_get_action_area (GTK_INFO_BAR (info_bar));
981   gtk_box_pack_start (GTK_BOX (action_area), action_grid, FALSE, FALSE, 0);
982
983   gtk_grid_attach (GTK_GRID (action_grid), retry_button, 0, 0, 1, 1);
984   gtk_grid_attach (GTK_GRID (action_grid), edit_button, 1, 0, 1, 1);
985   gtk_grid_attach (GTK_GRID (action_grid), close_button, 2, 0, 1, 1);
986
987   g_object_set_data (G_OBJECT (info_bar), "label", label);
988   g_object_set_data_full (G_OBJECT (info_bar),
989       "account", g_object_ref (account),
990       g_object_unref);
991   g_object_set_data_full (G_OBJECT (edit_button),
992       "account", g_object_ref (account),
993       g_object_unref);
994   g_object_set_data_full (G_OBJECT (close_button),
995       "account", g_object_ref (account),
996       g_object_unref);
997   g_object_set_data_full (G_OBJECT (retry_button),
998       "account", g_object_ref (account),
999       g_object_unref);
1000
1001   g_signal_connect (edit_button, "clicked",
1002       G_CALLBACK (roster_window_error_edit_clicked_cb), self);
1003   g_signal_connect (close_button, "clicked",
1004       G_CALLBACK (roster_window_error_close_clicked_cb), self);
1005   g_signal_connect (retry_button, "clicked",
1006       G_CALLBACK (roster_window_error_retry_clicked_cb), self);
1007
1008   gtk_widget_set_tooltip_text (self->priv->errors_vbox, error_message);
1009   gtk_widget_show (self->priv->errors_vbox);
1010
1011   g_hash_table_insert (self->priv->errors, g_object_ref (account), info_bar);
1012 }
1013
1014 static void
1015 roster_window_update_status (EmpathyRosterWindow *self)
1016 {
1017   gboolean connected, connecting;
1018   GList *l;
1019
1020   connected = empathy_account_manager_get_accounts_connected (&connecting);
1021
1022   /* Update the spinner state */
1023   if (connecting)
1024     {
1025       gtk_spinner_start (GTK_SPINNER (self->priv->throbber));
1026       gtk_widget_show (self->priv->throbber_tool_item);
1027     }
1028   else
1029     {
1030       gtk_spinner_stop (GTK_SPINNER (self->priv->throbber));
1031       gtk_widget_hide (self->priv->throbber_tool_item);
1032     }
1033
1034   /* Update widgets sensibility */
1035   for (l = self->priv->actions_connected; l; l = l->next)
1036     g_simple_action_set_enabled (l->data, connected);
1037 }
1038
1039 static char *
1040 account_to_topup_action_name (TpAccount *account,
1041     gboolean prefix)
1042 {
1043   char *r;
1044   gchar *result;
1045
1046   /* action names can't have '/' in them, replace it with '.' */
1047   r = g_strdup (tp_account_get_path_suffix (account));
1048   r = g_strdelimit (r, "/", '.');
1049
1050   result = g_strconcat (prefix ? "win." : "", "topup-", r, NULL);
1051   g_free (r);
1052
1053   return result;
1054 }
1055
1056 static void
1057 roster_window_balance_activate_cb (GtkAction *action,
1058     EmpathyRosterWindow *self)
1059 {
1060   const char *uri;
1061
1062   uri = g_object_get_data (G_OBJECT (action), "manage-credit-uri");
1063
1064   if (!tp_str_empty (uri))
1065     {
1066       DEBUG ("Top-up credit URI: %s", uri);
1067       empathy_url_show (GTK_WIDGET (self), uri);
1068     }
1069   else
1070     {
1071       DEBUG ("unknown protocol for top-up");
1072     }
1073 }
1074
1075 static void
1076 roster_window_balance_update_balance (EmpathyRosterWindow *self,
1077     TpAccount *account)
1078 {
1079   TpConnection *conn;
1080   GAction *action;
1081   GMenuItem *item;
1082   GtkWidget *label;
1083   int amount = 0;
1084   guint scale = G_MAXINT32;
1085   const gchar *currency = "";
1086   char *money;
1087   gchar *name;
1088
1089   g_print ("111111111\n");
1090   conn = tp_account_get_connection (account);
1091   if (conn == NULL)
1092     return;
1093   g_print ("2222222222\n");
1094
1095   name = account_to_topup_action_name (account, FALSE);
1096
1097   action = g_action_map_lookup_action (G_ACTION_MAP (self), name);
1098   g_free (name);
1099
1100   if (action == NULL)
1101     return;
1102   g_print ("333333333\n");
1103
1104   if (!tp_connection_get_balance (conn, &amount, &scale, &currency))
1105     return;
1106   g_print ("44444444444\n");
1107
1108   if (amount == 0 &&
1109       scale == G_MAXINT32 &&
1110       tp_str_empty (currency))
1111     {
1112       /* unknown balance */
1113       money = g_strdup ("--");
1114     }
1115   else
1116     {
1117       char *tmp = empathy_format_currency (amount, scale, currency);
1118
1119       money = g_strdup_printf ("%s %s", currency, tmp);
1120       g_free (tmp);
1121     }
1122
1123   item = g_hash_table_lookup (self->priv->topup_menu_items, action);
1124   g_print ("5555555 %p\n", item);
1125   if (item != NULL)
1126     {
1127       gchar *str;
1128
1129       /* Translators: this string will be something like:
1130        * Top up My Account ($1.23)..." */
1131       str = g_strdup_printf (_("Top up %s (%s)..."),
1132         tp_account_get_display_name (account), money);
1133
1134       g_print ("SET %s\n", str);
1135       g_menu_item_set_label (item, str);
1136       g_free (str);
1137     }
1138
1139   /* update the money label in the roster */
1140   label = g_object_get_data (G_OBJECT (action), "money-label");
1141
1142   gtk_label_set_text (GTK_LABEL (label), money);
1143   g_free (money);
1144 }
1145
1146 static void
1147 roster_window_balance_changed_cb (TpConnection *conn,
1148     guint balance,
1149     guint scale,
1150     const gchar *currency,
1151     EmpathyRosterWindow *self)
1152 {
1153   TpAccount *account;
1154
1155   account = tp_connection_get_account (conn);
1156   if (account == NULL)
1157     return;
1158
1159   roster_window_balance_update_balance (self, account);
1160 }
1161
1162 static GAction *
1163 roster_window_setup_balance_create_action (EmpathyRosterWindow *self,
1164     TpAccount *account)
1165 {
1166   GAction *action;
1167   gchar *name;
1168   GMenuItem *item;
1169
1170   /* create the action */
1171   name = account_to_topup_action_name (account, FALSE);
1172
1173   action = (GAction *) g_simple_action_new (name, NULL);
1174
1175   g_action_map_add_action (G_ACTION_MAP (self), action);
1176   g_free (name);
1177
1178   /* Add to the menu */
1179   name = account_to_topup_action_name (account, TRUE);
1180
1181   item = g_menu_item_new (_("Top up account credit"), name);
1182   g_print ("CREATE %p\n", item);
1183
1184   g_menu_append_item (self->priv->balance_section, item);
1185   g_menu_item_set_label (item, "mushroom");
1186
1187   /* Pass ownership of action to the hash table */
1188   g_hash_table_insert (self->priv->topup_menu_items,
1189       action, g_object_ref (item));
1190
1191   if (0) {
1192   g_object_bind_property (account, "icon-name", action, "icon-name",
1193       G_BINDING_SYNC_CREATE);
1194
1195   g_signal_connect (action, "activate",
1196       G_CALLBACK (roster_window_balance_activate_cb), self);
1197   }
1198
1199   g_free (name);
1200
1201   return action;
1202 }
1203
1204 static GtkWidget *
1205 roster_window_setup_balance_create_widget (EmpathyRosterWindow *self,
1206     GAction *action,
1207     TpAccount *account)
1208 {
1209 #if 0
1210   GtkWidget *hbox, *image, *label, *button;
1211
1212   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
1213
1214   /* protocol icon */
1215   image = gtk_image_new ();
1216   gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, TRUE, 0);
1217   g_object_bind_property (action, "icon-name", image, "icon-name",
1218       G_BINDING_SYNC_CREATE);
1219
1220   /* account name label */
1221   label = gtk_label_new ("");
1222   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1223   gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
1224   g_object_bind_property (account, "display-name", label, "label",
1225       G_BINDING_SYNC_CREATE);
1226
1227   /* balance label */
1228   label = gtk_label_new ("");
1229   gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
1230   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 0);
1231   g_object_set_data (G_OBJECT (action), "money-label", label);
1232
1233   /* top up button */
1234   button = gtk_button_new_with_label (_("Top Up..."));
1235   gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, TRUE, 0);
1236   g_signal_connect_swapped (button, "clicked",
1237       G_CALLBACK (gtk_action_activate), action);
1238
1239   gtk_box_pack_start (GTK_BOX (self->priv->balance_vbox), hbox, FALSE, TRUE, 0);
1240   gtk_widget_show_all (hbox);
1241
1242   /* tie the lifetime of the widget to the lifetime of the action */
1243   g_object_weak_ref (G_OBJECT (action),
1244       (GWeakNotify) gtk_widget_destroy, hbox);
1245
1246   return hbox;
1247 #endif
1248   return NULL;
1249 }
1250
1251 #define ACTION_SHOW_CREDIT_BALANCE "show-credit-balance"
1252
1253 static void
1254 add_view_credit_balance_item (EmpathyRosterWindow *self)
1255 {
1256   GMenuItem *item;
1257
1258   if (self->priv->view_credit_action != NULL)
1259     return;
1260
1261   self->priv->view_credit_action = (GAction *) g_simple_action_new (
1262       ACTION_SHOW_CREDIT_BALANCE, NULL);
1263
1264   /* TODO: connect sig */
1265   /* bind view_balance_show_in_roster */
1266   /*
1267   g_settings_bind (self->priv->gsettings_ui, "show-balance-in-roster",
1268       self->priv->view_balance_show_in_roster, "active",
1269       G_SETTINGS_BIND_DEFAULT);
1270   g_object_bind_property (self->priv->view_balance_show_in_roster, "active",
1271       self->priv->balance_vbox, "visible",
1272       G_BINDING_SYNC_CREATE);
1273       */
1274
1275   g_action_map_add_action (G_ACTION_MAP (self), self->priv->view_credit_action);
1276
1277   item = g_menu_item_new (_("Credit Balance"),
1278       "win." ACTION_SHOW_CREDIT_BALANCE);
1279
1280   g_menu_append_item (self->priv->balance_section, item);
1281 }
1282
1283 static void
1284 roster_window_setup_balance (EmpathyRosterWindow *self,
1285     TpAccount *account)
1286 {
1287   TpConnection *conn = tp_account_get_connection (account);
1288   GAction *action;
1289   const gchar *uri;
1290
1291   if (conn == NULL)
1292     return;
1293
1294   if (!tp_proxy_is_prepared (conn, TP_CONNECTION_FEATURE_BALANCE))
1295     return;
1296
1297   DEBUG ("Setting up balance for acct: %s",
1298       tp_account_get_display_name (account));
1299
1300   add_view_credit_balance_item (self);
1301
1302   /* create the action */
1303   action = roster_window_setup_balance_create_action (self, account);
1304
1305   if (action == NULL)
1306     return;
1307
1308   /* create the display widget */
1309   roster_window_setup_balance_create_widget (self, action, account);
1310
1311   /* check the current balance and monitor for any changes */
1312   uri = tp_connection_get_balance_uri (conn);
1313
1314   g_object_set_data_full (G_OBJECT (action), "manage-credit-uri",
1315       g_strdup (uri), g_free);
1316   gtk_action_set_sensitive (GTK_ACTION (action), !tp_str_empty (uri));
1317
1318   roster_window_balance_update_balance (self, account);
1319
1320   g_signal_connect (conn, "balance-changed",
1321       G_CALLBACK (roster_window_balance_changed_cb), self);
1322 }
1323
1324 static void
1325 remove_view_credit_balance_item (EmpathyRosterWindow *self)
1326 {
1327   gint i;
1328
1329   if (self->priv->view_credit_action == NULL)
1330     return;
1331
1332   for (i = 0; i < g_menu_model_get_n_items (
1333         G_MENU_MODEL (self->priv->balance_section)); i++)
1334     {
1335       const gchar *action;
1336
1337       if (g_menu_model_get_item_attribute (
1338             G_MENU_MODEL (self->priv->balance_section), i,
1339             G_MENU_ATTRIBUTE_ACTION, "s", &action))
1340         {
1341           if (!tp_strdiff (action, "win." ACTION_SHOW_CREDIT_BALANCE))
1342             {
1343               g_menu_remove (self->priv->balance_section, i);
1344               break;
1345             }
1346         }
1347     }
1348
1349   g_action_map_remove_action (G_ACTION_MAP (self),
1350       g_action_get_name (self->priv->view_credit_action));
1351
1352   g_clear_object (&self->priv->view_credit_action);
1353 }
1354
1355 static void
1356 roster_window_remove_balance_action (EmpathyRosterWindow *self,
1357     TpAccount *account)
1358 {
1359   /* Remove the "Credit Balance" entry if we just removed the last account
1360    * supporting balance and so that's the last entry in the section. */
1361   if (g_menu_model_get_n_items (G_MENU_MODEL (self->priv->balance_section)) <= 1)
1362     remove_view_credit_balance_item (self);
1363 #if 0
1364   GtkAction *action;
1365   char *name;
1366   GList *a;
1367
1368   name = roster_window_account_to_action_name (account);
1369
1370   action = gtk_action_group_get_action (
1371       self->priv->balance_action_group, name);
1372
1373   if (action != NULL)
1374     {
1375       guint merge_id;
1376
1377       DEBUG ("Removing action");
1378
1379       merge_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (action),
1380             "merge-id"));
1381
1382       gtk_ui_manager_remove_ui (self->priv->ui_manager,
1383           merge_id);
1384       gtk_action_group_remove_action (
1385           self->priv->balance_action_group, action);
1386     }
1387
1388   g_free (name);
1389
1390   a = gtk_action_group_list_actions (
1391       self->priv->balance_action_group);
1392
1393   gtk_action_set_visible (self->priv->view_balance_show_in_roster,
1394       g_list_length (a) > 0);
1395
1396   g_list_free (a);
1397 #endif
1398 }
1399
1400 static void
1401 roster_window_connection_changed_cb (TpAccount  *account,
1402     guint old_status,
1403     guint current,
1404     guint reason,
1405     gchar *dbus_error_name,
1406     GHashTable *details,
1407     EmpathyRosterWindow *self)
1408 {
1409   roster_window_update_status (self);
1410
1411   if (current == TP_CONNECTION_STATUS_DISCONNECTED &&
1412       reason != TP_CONNECTION_STATUS_REASON_REQUESTED)
1413     {
1414       roster_window_error_display (self, account);
1415     }
1416
1417   if (current == TP_CONNECTION_STATUS_DISCONNECTED)
1418     {
1419       empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
1420               EMPATHY_SOUND_ACCOUNT_DISCONNECTED);
1421     }
1422
1423   if (current == TP_CONNECTION_STATUS_CONNECTED)
1424     {
1425       empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
1426               EMPATHY_SOUND_ACCOUNT_CONNECTED);
1427
1428       /* Account connected without error, remove error message if any */
1429       roster_window_remove_error (self, account);
1430     }
1431 }
1432
1433 static void
1434 roster_window_accels_load (void)
1435 {
1436   gchar *filename;
1437
1438   filename = g_build_filename (g_get_user_config_dir (),
1439       PACKAGE_NAME, ACCELS_FILENAME, NULL);
1440   if (g_file_test (filename, G_FILE_TEST_EXISTS))
1441     {
1442       DEBUG ("Loading from:'%s'", filename);
1443       gtk_accel_map_load (filename);
1444     }
1445
1446   g_free (filename);
1447 }
1448
1449 static void
1450 roster_window_accels_save (void)
1451 {
1452   gchar *dir;
1453   gchar *file_with_path;
1454
1455   dir = g_build_filename (g_get_user_config_dir (), PACKAGE_NAME, NULL);
1456   g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
1457   file_with_path = g_build_filename (dir, ACCELS_FILENAME, NULL);
1458   g_free (dir);
1459
1460   DEBUG ("Saving to:'%s'", file_with_path);
1461   gtk_accel_map_save (file_with_path);
1462
1463   g_free (file_with_path);
1464 }
1465
1466 static void
1467 empathy_roster_window_finalize (GObject *window)
1468 {
1469   EmpathyRosterWindow *self = EMPATHY_ROSTER_WINDOW (window);
1470   GHashTableIter iter;
1471   gpointer key, value;
1472
1473   /* Save user-defined accelerators. */
1474   roster_window_accels_save ();
1475
1476   g_list_free (self->priv->actions_connected);
1477
1478   g_object_unref (self->priv->account_manager);
1479   g_object_unref (self->priv->individual_store);
1480   g_object_unref (self->priv->sound_mgr);
1481   g_hash_table_unref (self->priv->errors);
1482   g_hash_table_unref (self->priv->auths);
1483
1484   /* disconnect all handlers of status-changed signal */
1485   g_hash_table_iter_init (&iter, self->priv->status_changed_handlers);
1486   while (g_hash_table_iter_next (&iter, &key, &value))
1487     g_signal_handler_disconnect (TP_ACCOUNT (key), GPOINTER_TO_UINT (value));
1488
1489   g_hash_table_unref (self->priv->status_changed_handlers);
1490
1491   g_signal_handlers_disconnect_by_func (self->priv->event_manager,
1492       roster_window_event_added_cb, self);
1493   g_signal_handlers_disconnect_by_func (self->priv->event_manager,
1494       roster_window_event_removed_cb, self);
1495
1496   g_object_unref (self->priv->call_observer);
1497   g_object_unref (self->priv->event_manager);
1498   g_object_unref (self->priv->ui_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->rooms_section);
1506   g_object_unref (self->priv->balance_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   GList *a;
2243
2244   a = tp_account_manager_get_valid_accounts (manager);
2245
2246   gtk_action_set_sensitive (self->priv->view_history,
2247     g_list_length (a) > 0);
2248
2249   g_list_free (a);
2250
2251   /* remove errors if any */
2252   roster_window_remove_error (self, account);
2253
2254   /* remove the balance action if required */
2255   roster_window_remove_balance_action (self, account);
2256 }
2257
2258 static void
2259 account_connection_notify_cb (TpAccount *account,
2260     GParamSpec *spec,
2261     EmpathyRosterWindow *self)
2262 {
2263   TpConnection *conn;
2264
2265   conn = tp_account_get_connection (account);
2266
2267   if (conn != NULL)
2268     {
2269       roster_window_setup_balance (self, account);
2270     }
2271   else
2272     {
2273       /* remove balance action if required */
2274       roster_window_remove_balance_action (self, account);
2275     }
2276 }
2277
2278 static void
2279 add_account (EmpathyRosterWindow *self,
2280     TpAccount *account)
2281 {
2282   gulong handler_id;
2283
2284   handler_id = GPOINTER_TO_UINT (g_hash_table_lookup (
2285     self->priv->status_changed_handlers, account));
2286
2287   /* connect signal only if it was not connected yet */
2288   if (handler_id != 0)
2289     return;
2290
2291   handler_id = g_signal_connect (account, "status-changed",
2292     G_CALLBACK (roster_window_connection_changed_cb), self);
2293
2294   g_hash_table_insert (self->priv->status_changed_handlers,
2295     account, GUINT_TO_POINTER (handler_id));
2296
2297   /* roster_window_setup_balance() relies on the TpConnection to be ready on
2298    * the TpAccount so we connect this signal as well. */
2299   tp_g_signal_connect_object (account, "notify::connection",
2300       G_CALLBACK (account_connection_notify_cb), self, 0);
2301
2302   roster_window_setup_balance (self, account);
2303 }
2304
2305 /* @account: if not %NULL, the only account which can be enabled */
2306 static void
2307 display_page_account_not_enabled (EmpathyRosterWindow *self,
2308     TpAccount *account)
2309 {
2310   if (account == NULL)
2311     {
2312       display_page_message (self,
2313           _("You need to enable one of your accounts to see contacts here."),
2314           TRUE, FALSE);
2315     }
2316   else
2317     {
2318       gchar *tmp;
2319
2320       /* translators: argument is an account name */
2321       tmp = g_strdup_printf (_("You need to enable %s to see contacts here."),
2322           tp_account_get_display_name (account));
2323
2324       display_page_message (self, tmp, TRUE, FALSE);
2325       g_free (tmp);
2326     }
2327 }
2328 static gboolean
2329 has_enabled_account (GList *accounts)
2330 {
2331   GList *l;
2332
2333   for (l = accounts; l != NULL; l = g_list_next (l))
2334     {
2335       TpAccount *account = l->data;
2336
2337       if (tp_account_is_enabled (account))
2338         return TRUE;
2339     }
2340
2341   return FALSE;
2342 }
2343
2344 static void
2345 set_notebook_page (EmpathyRosterWindow *self)
2346 {
2347   GList *accounts;
2348   guint len;
2349
2350   accounts = tp_account_manager_get_valid_accounts (
2351       self->priv->account_manager);
2352
2353   len = g_list_length (accounts);
2354
2355   if (len == 0)
2356     {
2357       /* No account */
2358       display_page_no_account (self);
2359       goto out;
2360     }
2361
2362   if (!has_enabled_account (accounts))
2363     {
2364       TpAccount *account = NULL;
2365
2366       /* Pass the account if there is only one which can be enabled */
2367       if (len == 1)
2368         account = accounts->data;
2369
2370       display_page_account_not_enabled (self, account);
2371       goto out;
2372     }
2373
2374   display_page_contact_list (self);
2375
2376 out:
2377   g_list_free (accounts);
2378 }
2379
2380 static void
2381 roster_window_account_validity_changed_cb (TpAccountManager  *manager,
2382     TpAccount *account,
2383     gboolean valid,
2384     EmpathyRosterWindow *self)
2385 {
2386   if (valid)
2387     add_account (self, account);
2388   else
2389     roster_window_account_removed_cb (manager, account, self);
2390
2391   set_notebook_page (self);
2392 }
2393
2394 #if 0
2395 static void
2396 roster_window_notify_show_offline_cb (GSettings *gsettings,
2397     const gchar *key,
2398     gpointer toggle_action)
2399 {
2400   gtk_toggle_action_set_active (toggle_action,
2401       g_settings_get_boolean (gsettings, key));
2402 }
2403 #endif
2404
2405 static void
2406 roster_window_connection_items_setup (EmpathyRosterWindow *self,
2407     GtkBuilder *gui)
2408 {
2409   guint i;
2410   const gchar *actions_connected[] = {
2411       "room_join_new",
2412       "room_join_favorites",
2413       "chat_new_message",
2414       "chat_new_call",
2415       "chat_search_contacts",
2416       "chat_add_contact",
2417       "edit_blocked_contacts",
2418       "edit_search_contacts"
2419   };
2420
2421   for (i = 0; i < G_N_ELEMENTS (actions_connected); i++)
2422     {
2423       GAction *action;
2424
2425       action = g_action_map_lookup_action (G_ACTION_MAP (self),
2426           actions_connected[i]);
2427
2428       self->priv->actions_connected = g_list_prepend (
2429           self->priv->actions_connected, action);
2430     }
2431 }
2432
2433 static void
2434 account_enabled_cb (TpAccountManager *manager,
2435     TpAccount *account,
2436     EmpathyRosterWindow *self)
2437 {
2438   set_notebook_page (self);
2439 }
2440
2441 static void
2442 account_disabled_cb (TpAccountManager *manager,
2443     TpAccount *account,
2444     EmpathyRosterWindow *self)
2445 {
2446   set_notebook_page (self);
2447 }
2448
2449 static void
2450 account_manager_prepared_cb (GObject *source_object,
2451     GAsyncResult *result,
2452     gpointer user_data)
2453 {
2454   GList *accounts, *j;
2455   TpAccountManager *manager = TP_ACCOUNT_MANAGER (source_object);
2456   EmpathyRosterWindow *self = user_data;
2457   GError *error = NULL;
2458
2459   if (!tp_proxy_prepare_finish (manager, result, &error))
2460     {
2461       DEBUG ("Failed to prepare account manager: %s", error->message);
2462       g_error_free (error);
2463       return;
2464     }
2465
2466   accounts = tp_account_manager_get_valid_accounts (self->priv->account_manager);
2467   for (j = accounts; j != NULL; j = j->next)
2468     {
2469       TpAccount *account = TP_ACCOUNT (j->data);
2470
2471       add_account (self, account);
2472     }
2473
2474   g_signal_connect (manager, "account-validity-changed",
2475       G_CALLBACK (roster_window_account_validity_changed_cb), self);
2476   tp_g_signal_connect_object (manager, "account-disabled",
2477       G_CALLBACK (account_disabled_cb), self, 0);
2478   tp_g_signal_connect_object (manager, "account-enabled",
2479       G_CALLBACK (account_enabled_cb), self, 0);
2480
2481   roster_window_update_status (self);
2482
2483   /* Disable the "Previous Conversations" menu entry if there is no account */
2484   gtk_action_set_sensitive (self->priv->view_history,
2485       g_list_length (accounts) > 0);
2486
2487   set_notebook_page (self);
2488
2489   g_list_free (accounts);
2490 }
2491
2492 void
2493 empathy_roster_window_set_shell_running (EmpathyRosterWindow *self,
2494     gboolean shell_running)
2495 {
2496   if (self->priv->shell_running == shell_running)
2497     return;
2498
2499   self->priv->shell_running = shell_running;
2500   g_object_notify (G_OBJECT (self), "shell-running");
2501 }
2502
2503 static GObject *
2504 empathy_roster_window_constructor (GType type,
2505     guint n_construct_params,
2506     GObjectConstructParam *construct_params)
2507 {
2508   static GObject *window = NULL;
2509
2510   if (window != NULL)
2511     return g_object_ref (window);
2512
2513   window = G_OBJECT_CLASS (empathy_roster_window_parent_class)->constructor (
2514     type, n_construct_params, construct_params);
2515
2516   g_object_add_weak_pointer (window, (gpointer) &window);
2517
2518   return window;
2519 }
2520
2521 static GActionEntry menubar_entries[] = {
2522   { "chat_new_message", roster_window_chat_new_message_cb, NULL, NULL, NULL },
2523   { "chat_new_call", roster_window_chat_new_call_cb, NULL, NULL, NULL },
2524   { "chat_add_contact", roster_window_chat_add_contact_cb, NULL, NULL, NULL },
2525   { "chat_search_contacts", roster_window_chat_search_contacts_cb, NULL, NULL, NULL },
2526   { "chat_quit", roster_window_chat_quit_cb, NULL, NULL, NULL },
2527
2528   { "edit_accounts", roster_window_edit_accounts_cb, NULL, NULL, NULL },
2529   { "edit_search_contacts", roster_window_edit_search_contacts_cb, NULL, NULL, NULL },
2530   { "edit_blocked_contacts", roster_window_edit_blocked_contacts_cb, NULL, NULL, NULL },
2531   { "edit_preferences", roster_window_edit_preferences_cb, NULL, NULL, NULL },
2532
2533   { "view_show_offline", activate_toggle, NULL, "false", roster_window_view_show_offline_cb },
2534   /* TODO */
2535   { "view_history", roster_window_view_history_cb, NULL, NULL, NULL },
2536   { "view_show_ft_manager", roster_window_view_show_ft_manager, NULL, NULL, NULL },
2537   { "view_show_map", roster_window_view_show_map_cb, NULL, NULL, NULL },
2538
2539   { "room_join_new", roster_window_room_join_new_cb, NULL, NULL, NULL },
2540   { "room_join_favorites", roster_window_room_join_favorites_cb, NULL, NULL, NULL },
2541   { "room_manage_favorites", roster_window_room_manage_favorites_cb, NULL, NULL, NULL },
2542
2543   { "help_contents", roster_window_help_contents_cb, NULL, NULL, NULL },
2544   { "help_debug", roster_window_help_debug_cb, NULL, NULL, NULL },
2545   { "help_about", roster_window_help_about_cb, NULL, NULL, NULL },
2546 };
2547
2548 static void
2549 empathy_roster_window_constructed (GObject *object)
2550 {
2551   EmpathyRosterWindow *self = EMPATHY_ROSTER_WINDOW (object);
2552   gchar *filename;
2553   GtkBuilder *builder;
2554   GtkApplication *app;
2555   GMenu *menubar;
2556   /* TODO: <cassidy> desrt, yeah. So I should basically move all my menu code
2557    * from gtkappwin to gtkapp, just deal with it in the app itself and stop
2558    * caring about the appwin ? */
2559
2560   G_OBJECT_CLASS (empathy_roster_window_parent_class)->constructed (object);
2561
2562   app = gtk_window_get_application (GTK_WINDOW (self));
2563   g_return_if_fail (app != NULL);
2564
2565   g_action_map_add_action_entries (G_ACTION_MAP (self),
2566       menubar_entries, G_N_ELEMENTS (menubar_entries), self);
2567
2568   filename = empathy_file_lookup ("empathy-roster-window-menubar.ui", "src");
2569   builder = empathy_builder_get_file (filename,
2570       /*
2571       "ui_manager", &self->priv->ui_manager,
2572       "view_show_offline", &show_offline_widget,
2573       "view_show_protocols", &self->priv->show_protocols,
2574       "view_sort_by_name", &self->priv->sort_by_name,
2575       "view_sort_by_status", &self->priv->sort_by_status,
2576       "view_normal_size_with_avatars", &self->priv->normal_with_avatars,
2577       "view_normal_size", &self->priv->normal_size,
2578       "view_compact_size", &self->priv->compact_size,
2579       "view_history", &self->priv->view_history,
2580       "menubar", &menubar,
2581       */
2582       NULL);
2583   g_free (filename);
2584
2585   menubar = G_MENU (gtk_builder_get_object (builder, "menubar"));
2586
2587   gtk_application_set_menubar (app, G_MENU_MODEL(menubar));
2588
2589   /* Disable map if built without champlain */
2590 #ifndef HAVE_LIBCHAMPLAIN
2591     {
2592       GAction *view_show_map;
2593
2594       view_show_map = g_action_map_lookup_action (G_ACTION_MAP (self),
2595           "view_show_map");
2596       g_simple_action_set_enabled (G_SIMPLE_ACTION (view_show_map), FALSE);
2597     }
2598 #endif
2599
2600   /* Set up connection related actions. */
2601   roster_window_connection_items_setup (self, NULL);
2602
2603   /* Add rooms to the menu */
2604   self->priv->rooms_section = G_MENU (gtk_builder_get_object (builder,
2605         "rooms"));
2606   g_object_ref (self->priv->rooms_section);
2607
2608   self->priv->balance_section = G_MENU (gtk_builder_get_object (builder,
2609         "balance"));
2610   g_object_ref (self->priv->balance_section);
2611
2612   roster_window_favorite_chatroom_menu_setup (self);
2613
2614   g_object_unref (builder);
2615 }
2616
2617 static void
2618 empathy_roster_window_set_property (GObject *object,
2619     guint property_id,
2620     const GValue *value,
2621     GParamSpec *pspec)
2622 {
2623   EmpathyRosterWindow *self = EMPATHY_ROSTER_WINDOW (object);
2624
2625   switch (property_id)
2626     {
2627       case PROP_SHELL_RUNNING:
2628         self->priv->shell_running = g_value_get_boolean (value);
2629         break;
2630       default:
2631         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2632         break;
2633     }
2634 }
2635
2636 static void
2637 empathy_roster_window_get_property (GObject    *object,
2638     guint property_id,
2639     GValue *value,
2640     GParamSpec *pspec)
2641 {
2642   EmpathyRosterWindow *self = EMPATHY_ROSTER_WINDOW (object);
2643
2644   switch (property_id)
2645     {
2646       case PROP_SHELL_RUNNING:
2647         g_value_set_boolean (value, self->priv->shell_running);
2648         break;
2649       default:
2650         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2651         break;
2652     }
2653 }
2654
2655 static void
2656 empathy_roster_window_class_init (EmpathyRosterWindowClass *klass)
2657 {
2658   GObjectClass *object_class = G_OBJECT_CLASS (klass);
2659   GParamSpec *pspec;
2660
2661   object_class->finalize = empathy_roster_window_finalize;
2662   object_class->constructor = empathy_roster_window_constructor;
2663   object_class->constructed = empathy_roster_window_constructed;
2664
2665   object_class->set_property = empathy_roster_window_set_property;
2666   object_class->get_property = empathy_roster_window_get_property;
2667
2668   pspec = g_param_spec_boolean ("shell-running",
2669       "Shell running",
2670       "Whether the Shell is running or not",
2671       FALSE,
2672       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
2673   g_object_class_install_property (object_class, PROP_SHELL_RUNNING, pspec);
2674
2675   g_type_class_add_private (object_class, sizeof (EmpathyRosterWindowPriv));
2676 }
2677
2678 static void
2679 show_contacts_loading (EmpathyRosterWindow *self)
2680 {
2681   display_page_message (self, NULL, FALSE, TRUE);
2682
2683   gtk_spinner_start (GTK_SPINNER (self->priv->spinner_loading));
2684 }
2685
2686 static void
2687 hide_contacts_loading (EmpathyRosterWindow *self)
2688 {
2689   gtk_spinner_stop (GTK_SPINNER (self->priv->spinner_loading));
2690
2691   set_notebook_page (self);
2692 }
2693
2694 static void
2695 contacts_loaded_cb (EmpathyIndividualManager *manager,
2696     EmpathyRosterWindow *self)
2697 {
2698   hide_contacts_loading (self);
2699 }
2700
2701 static void
2702 empathy_roster_window_init (EmpathyRosterWindow *self)
2703 {
2704   GtkBuilder *gui;
2705   GtkWidget *sw;
2706   //GtkToggleAction *show_offline_widget;
2707   GtkToolItem *item;
2708   //gboolean show_offline;
2709   gchar *filename;
2710   GtkTreeModel *model;
2711   GtkWidget *search_vbox;
2712
2713   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2714       EMPATHY_TYPE_ROSTER_WINDOW, EmpathyRosterWindowPriv);
2715
2716   self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2717   self->priv->gsettings_contacts = g_settings_new (EMPATHY_PREFS_CONTACTS_SCHEMA);
2718
2719   self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2720
2721   gtk_window_set_title (GTK_WINDOW (self), _("Contact List"));
2722   gtk_window_set_role (GTK_WINDOW (self), "contact_list");
2723   gtk_window_set_default_size (GTK_WINDOW (self), 225, 325);
2724
2725   /* don't finalize the widget on delete-event, just hide it */
2726   g_signal_connect (self, "delete-event",
2727     G_CALLBACK (gtk_widget_hide_on_delete), NULL);
2728
2729   /* Set up interface */
2730   filename = empathy_file_lookup ("empathy-roster-window.ui", "src");
2731   gui = empathy_builder_get_file (filename,
2732       "main_vbox", &self->priv->main_vbox,
2733       "balance_vbox", &self->priv->balance_vbox,
2734       "errors_vbox", &self->priv->errors_vbox,
2735       "auth_vbox", &self->priv->auth_vbox,
2736       "search_vbox", &search_vbox,
2737       "presence_toolbar", &self->priv->presence_toolbar,
2738       "notebook", &self->priv->notebook,
2739       "no_entry_label", &self->priv->no_entry_label,
2740       "roster_scrolledwindow", &sw,
2741       "button_account_settings", &self->priv->button_account_settings,
2742       "spinner_loading", &self->priv->spinner_loading,
2743       NULL);
2744   g_free (filename);
2745
2746   gtk_container_add (GTK_CONTAINER (self), self->priv->main_vbox);
2747   gtk_widget_show (self->priv->main_vbox);
2748
2749   g_signal_connect (self, "key-press-event",
2750       G_CALLBACK (roster_window_key_press_event_cb), NULL);
2751
2752   //g_object_ref (self->priv->ui_manager);
2753   g_object_unref (gui);
2754
2755   self->priv->account_manager = tp_account_manager_dup ();
2756
2757   tp_proxy_prepare_async (self->priv->account_manager, NULL,
2758       account_manager_prepared_cb, self);
2759
2760   self->priv->errors = g_hash_table_new_full (g_direct_hash, g_direct_equal,
2761       g_object_unref, NULL);
2762
2763   self->priv->auths = g_hash_table_new (NULL, NULL);
2764
2765   self->priv->status_changed_handlers = g_hash_table_new_full (g_direct_hash,
2766       g_direct_equal, NULL, NULL);
2767
2768   self->priv->topup_menu_items = g_hash_table_new_full (NULL, NULL,
2769       g_object_unref, g_object_unref);
2770
2771   /*
2772   self->priv->edit_context = gtk_ui_manager_get_widget (self->priv->ui_manager,
2773       "/menubar/edit/edit_context");
2774   self->priv->edit_context_separator = gtk_ui_manager_get_widget (
2775       self->priv->ui_manager,
2776       "/menubar/edit/edit_context_separator");
2777   gtk_widget_hide (self->priv->edit_context);
2778   gtk_widget_hide (self->priv->edit_context_separator);
2779       */
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   item = gtk_tool_item_new ();
2788   gtk_widget_show (GTK_WIDGET (item));
2789   gtk_widget_set_size_request (self->priv->presence_chooser, 10, -1);
2790   gtk_container_add (GTK_CONTAINER (item), self->priv->presence_chooser);
2791   gtk_tool_item_set_is_important (item, TRUE);
2792   gtk_tool_item_set_expand (item, TRUE);
2793   gtk_toolbar_insert (GTK_TOOLBAR (self->priv->presence_toolbar), item, -1);
2794
2795   /* Set up the throbber */
2796   self->priv->throbber = gtk_spinner_new ();
2797   gtk_widget_set_size_request (self->priv->throbber, 16, -1);
2798   gtk_widget_set_events (self->priv->throbber, GDK_BUTTON_PRESS_MASK);
2799   g_signal_connect (self->priv->throbber, "button-press-event",
2800     G_CALLBACK (roster_window_throbber_button_press_event_cb),
2801     self);
2802   gtk_widget_show (self->priv->throbber);
2803
2804   item = gtk_tool_item_new ();
2805   gtk_container_set_border_width (GTK_CONTAINER (item), 6);
2806   gtk_toolbar_insert (GTK_TOOLBAR (self->priv->presence_toolbar), item, -1);
2807   gtk_container_add (GTK_CONTAINER (item), self->priv->throbber);
2808   self->priv->throbber_tool_item = GTK_WIDGET (item);
2809
2810   /* XXX: this class is designed to live for the duration of the program,
2811    * so it's got a race condition between its signal handlers and its
2812    * finalization. The class is planned to be removed, so we won't fix
2813    * this before then. */
2814   self->priv->individual_manager = empathy_individual_manager_dup_singleton ();
2815
2816   if (!empathy_individual_manager_get_contacts_loaded (
2817         self->priv->individual_manager))
2818     {
2819       show_contacts_loading (self);
2820
2821       tp_g_signal_connect_object (self->priv->individual_manager,
2822           "contacts-loaded", G_CALLBACK (contacts_loaded_cb), self, 0);
2823     }
2824
2825   self->priv->individual_store = EMPATHY_INDIVIDUAL_STORE (
2826       empathy_individual_store_manager_new (self->priv->individual_manager));
2827
2828   /* For the moment, we disallow Persona drops onto the roster contact list
2829    * (e.g. from things such as the EmpathyPersonaView in the linking dialogue).
2830    * No code is hooked up to do anything on a Persona drop, so allowing them
2831    * would achieve nothing except confusion. */
2832   self->priv->individual_view = empathy_individual_view_new (
2833       self->priv->individual_store,
2834       /* EmpathyIndividualViewFeatureFlags */
2835       EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE |
2836       EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2837       EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE |
2838       EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE |
2839       EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE |
2840       EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP |
2841       EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG |
2842       EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP |
2843       EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL |
2844       EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP,
2845       /* EmpathyIndividualFeatureFlags  */
2846       EMPATHY_INDIVIDUAL_FEATURE_CHAT |
2847       EMPATHY_INDIVIDUAL_FEATURE_CALL |
2848       EMPATHY_INDIVIDUAL_FEATURE_EDIT |
2849       EMPATHY_INDIVIDUAL_FEATURE_INFO |
2850       EMPATHY_INDIVIDUAL_FEATURE_LOG |
2851       EMPATHY_INDIVIDUAL_FEATURE_SMS |
2852       EMPATHY_INDIVIDUAL_FEATURE_CALL_PHONE);
2853
2854   gtk_widget_show (GTK_WIDGET (self->priv->individual_view));
2855   gtk_container_add (GTK_CONTAINER (sw),
2856       GTK_WIDGET (self->priv->individual_view));
2857   g_signal_connect (self->priv->individual_view, "row-activated",
2858      G_CALLBACK (roster_window_row_activated_cb), self);
2859
2860   /* Set up search bar */
2861   self->priv->search_bar = empathy_live_search_new (
2862       GTK_WIDGET (self->priv->individual_view));
2863   empathy_individual_view_set_live_search (self->priv->individual_view,
2864       EMPATHY_LIVE_SEARCH (self->priv->search_bar));
2865   gtk_box_pack_start (GTK_BOX (search_vbox), self->priv->search_bar,
2866       FALSE, TRUE, 0);
2867
2868   g_signal_connect_swapped (self, "map",
2869       G_CALLBACK (gtk_widget_grab_focus), self->priv->individual_view);
2870
2871   /* Connect to proper signals to check if contact list is empty or not */
2872   model = gtk_tree_view_get_model (GTK_TREE_VIEW (self->priv->individual_view));
2873   self->priv->empty = TRUE;
2874   g_signal_connect (model, "row-inserted",
2875       G_CALLBACK (roster_window_row_inserted_cb), self);
2876   g_signal_connect (model, "row-deleted",
2877       G_CALLBACK (roster_window_row_deleted_cb), self);
2878
2879   /* Load user-defined accelerators. */
2880   roster_window_accels_load ();
2881
2882   gtk_window_set_default_size (GTK_WINDOW (self), -1, 600);
2883   /* Set window size. */
2884   empathy_geometry_bind (GTK_WINDOW (self), GEOMETRY_NAME);
2885
2886   /* Enable event handling */
2887   self->priv->call_observer = empathy_call_observer_dup_singleton ();
2888   self->priv->event_manager = empathy_event_manager_dup_singleton ();
2889
2890   g_signal_connect (self->priv->event_manager, "event-added",
2891       G_CALLBACK (roster_window_event_added_cb), self);
2892   g_signal_connect (self->priv->event_manager, "event-removed",
2893       G_CALLBACK (roster_window_event_removed_cb), self);
2894   g_signal_connect (self->priv->account_manager, "account-validity-changed",
2895       G_CALLBACK (roster_window_account_validity_changed_cb), self);
2896   g_signal_connect (self->priv->account_manager, "account-removed",
2897       G_CALLBACK (roster_window_account_removed_cb), self);
2898   g_signal_connect (self->priv->account_manager, "account-disabled",
2899       G_CALLBACK (roster_window_account_disabled_cb), self);
2900
2901   /* Show offline ? */
2902   /*
2903   show_offline = g_settings_get_boolean (self->priv->gsettings_ui,
2904       EMPATHY_PREFS_UI_SHOW_OFFLINE);
2905
2906   g_signal_connect (self->priv->gsettings_ui,
2907       "changed::" EMPATHY_PREFS_UI_SHOW_OFFLINE,
2908       G_CALLBACK (roster_window_notify_show_offline_cb), show_offline_widget);
2909
2910   gtk_toggle_action_set_active (show_offline_widget, show_offline);
2911       */
2912
2913   /* Show protocol ? */
2914   /*
2915   g_signal_connect (self->priv->gsettings_ui,
2916       "changed::" EMPATHY_PREFS_UI_SHOW_PROTOCOLS,
2917       G_CALLBACK (roster_window_notify_show_protocols_cb), self);
2918
2919   roster_window_notify_show_protocols_cb (self->priv->gsettings_ui,
2920       EMPATHY_PREFS_UI_SHOW_PROTOCOLS, self);
2921       */
2922
2923   /* Sort by name / by status ? */
2924   /*
2925   g_signal_connect (self->priv->gsettings_contacts,
2926       "changed::" EMPATHY_PREFS_CONTACTS_SORT_CRITERIUM,
2927       G_CALLBACK (roster_window_notify_sort_contact_cb), self);
2928
2929   roster_window_notify_sort_contact_cb (self->priv->gsettings_contacts,
2930       EMPATHY_PREFS_CONTACTS_SORT_CRITERIUM, self);
2931       */
2932
2933   /* Contacts list size */
2934   /*
2935   g_signal_connect (self->priv->gsettings_ui,
2936       "changed::" EMPATHY_PREFS_UI_COMPACT_CONTACT_LIST,
2937       G_CALLBACK (roster_window_notify_contact_list_size_cb), self);
2938
2939   g_signal_connect (self->priv->gsettings_ui,
2940       "changed::" EMPATHY_PREFS_UI_SHOW_AVATARS,
2941       G_CALLBACK (roster_window_notify_contact_list_size_cb),
2942       self);
2943       */
2944
2945   /*
2946   roster_window_notify_contact_list_size_cb (self->priv->gsettings_ui,
2947       EMPATHY_PREFS_UI_SHOW_AVATARS, self);
2948       */
2949
2950   g_signal_connect (self->priv->button_account_settings, "clicked",
2951       G_CALLBACK (button_account_settings_clicked_cb), self);
2952 }
2953
2954 GtkWidget *
2955 empathy_roster_window_new (GtkApplication *app)
2956 {
2957   return g_object_new (EMPATHY_TYPE_ROSTER_WINDOW,
2958       "application", app,
2959       NULL);
2960 }