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