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