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