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