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