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