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