]> git.0d.be Git - empathy.git/blob - src/empathy-main-window.c
Factor out main_window_setup_balance_create_action()
[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         GtkWidget              *balance_vbox;
150
151         guint                   size_timeout_id;
152
153         /* reffed TpAccount* => visible GtkInfoBar* */
154         GHashTable             *errors;
155
156         /* EmpathyEvent* => visible GtkInfoBar* */
157         GHashTable             *auths;
158
159         /* stores a mapping from TpAccount to Handler ID to prevent
160          * to listen more than once to the status-changed signal */
161         GHashTable             *status_changed_handlers;
162
163         /* Actions that are enabled when there are connected accounts */
164         GList                  *actions_connected;
165
166         /* The idle event source to migrate butterfly's logs */
167         guint butterfly_log_migration_members_changed_id;
168 };
169
170 static void
171 main_window_flash_stop (EmpathyMainWindow *window)
172 {
173         EmpathyMainWindowPriv *priv = GET_PRIV (window);
174
175         if (priv->flash_timeout_id == 0) {
176                 return;
177         }
178
179         DEBUG ("Stop flashing");
180         g_source_remove (priv->flash_timeout_id);
181         priv->flash_timeout_id = 0;
182         priv->flash_on = FALSE;
183 }
184
185 typedef struct {
186         EmpathyEvent       *event;
187         gboolean            on;
188         EmpathyMainWindow  *window;
189 } FlashForeachData;
190
191 static gboolean
192 main_window_flash_foreach (GtkTreeModel *model,
193                            GtkTreePath  *path,
194                            GtkTreeIter  *iter,
195                            gpointer      user_data)
196 {
197         FlashForeachData *data = (FlashForeachData *) user_data;
198         FolksIndividual *individual;
199         EmpathyContact   *contact;
200         const gchar      *icon_name;
201         GtkTreePath      *parent_path = NULL;
202         GtkTreeIter       parent_iter;
203         GdkPixbuf        *pixbuf = NULL;
204
205         gtk_tree_model_get (model, iter,
206                             EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL,
207                                 &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                                     GValueArray *balance)
820 {
821         TpAccount *account = g_object_get_data (G_OBJECT (action), "account");
822         int amount = 0;
823         guint scale = G_MAXINT32;
824         const char *currency = "";
825         char *str;
826
827         if (balance != NULL)
828                 tp_value_array_unpack (balance, 3,
829                         &amount,
830                         &scale,
831                         &currency);
832
833         if (amount == 0 &&
834             scale == G_MAXINT32 &&
835             tp_str_empty (currency)) {
836                 /* unknown balance */
837                 str = g_strdup_printf ("%s (--)",
838                         tp_account_get_display_name (account));
839         } else {
840                 char *money = empathy_format_currency (amount, scale, currency);
841
842                 str = g_strdup_printf ("%s (%s %s)",
843                         tp_account_get_display_name (account),
844                         currency, money);
845                 g_free (money);
846         }
847
848         gtk_action_set_label (action, str);
849         g_free (str);
850 }
851
852 static void
853 main_window_setup_balance_got_balance_props (TpProxy      *conn,
854                                              GHashTable   *props,
855                                              const GError *in_error,
856                                              gpointer      user_data,
857                                              GObject      *action)
858 {
859         GValueArray *balance = NULL;
860         const char *uri;
861
862         if (in_error != NULL) {
863                 DEBUG ("Failed to get account balance properties: %s",
864                         in_error->message);
865                 goto finally;
866         }
867
868         balance = tp_asv_get_boxed (props, "AccountBalance",
869                 TP_STRUCT_TYPE_CURRENCY_AMOUNT);
870         uri = tp_asv_get_string (props, "ManageCreditURI");
871
872         g_object_set_data_full (action, "manage-credit-uri",
873                 g_strdup (uri), g_free);
874         gtk_action_set_sensitive (GTK_ACTION (action), !tp_str_empty (uri));
875
876 finally:
877         main_window_balance_update_balance (GTK_ACTION (action), balance);
878 }
879
880 static void
881 main_window_balance_changed_cb (TpConnection      *conn,
882                                 const GValueArray *balance,
883                                 gpointer           user_data,
884                                 GObject           *action)
885 {
886         main_window_balance_update_balance (GTK_ACTION (action),
887                 (GValueArray *) balance);
888 }
889
890 static GtkAction *
891 main_window_setup_balance_create_action (EmpathyMainWindow *window,
892                                          TpAccount *account)
893 {
894         EmpathyMainWindowPriv *priv = GET_PRIV (window);
895         GtkAction *action;
896         char *name, *ui;
897         guint merge_id;
898         GError *error = NULL;
899
900         /* create the action group if required */
901         if (priv->balance_action_group == NULL) {
902                 priv->balance_action_group =
903                         gtk_action_group_new ("balance-action-group");
904
905                 gtk_ui_manager_insert_action_group (priv->ui_manager,
906                         priv->balance_action_group, -1);
907         }
908
909         /* create the action */
910         name = main_window_account_to_action_name (account);
911         action = gtk_action_new (name,
912                 tp_account_get_display_name (account),
913                 _("Top up account credit"),
914                 tp_account_get_icon_name (account));
915
916         g_object_set_data (G_OBJECT (action), "account", account);
917         g_signal_connect (action, "activate",
918                 G_CALLBACK (main_window_balance_activate_cb), window);
919
920         gtk_action_group_add_action (priv->balance_action_group, action);
921         g_object_unref (action);
922
923         ui = g_strdup_printf (
924                 "<ui>"
925                 " <menubar name='menubar'>"
926                 "  <menu action='view'>"
927                 "   <placeholder name='view_balance_placeholder'>"
928                 "    <menuitem action='%s'/>"
929                 "   </placeholder>"
930                 "  </menu>"
931                 " </menubar>"
932                 "</ui>",
933                 name);
934
935         merge_id = gtk_ui_manager_add_ui_from_string (priv->ui_manager,
936                 ui, -1, &error);
937         if (error != NULL) {
938                 DEBUG ("Failed to add balance UI for %s: %s",
939                         tp_account_get_display_name (account),
940                         error->message);
941                 g_error_free (error);
942         }
943
944         g_object_set_data (G_OBJECT (action),
945                 "merge-id", GUINT_TO_POINTER (merge_id));
946
947         g_free (name);
948         g_free (ui);
949
950         return action;
951 }
952
953 static void
954 main_window_setup_balance_conn_ready (GObject      *conn,
955                                       GAsyncResult *result,
956                                       gpointer      user_data)
957 {
958         EmpathyMainWindow *window = user_data;
959         TpAccount *account = g_object_get_data (conn, "account");
960         GtkAction *action;
961         GError *error = NULL;
962
963         if (!tp_proxy_prepare_finish (conn, result, &error)) {
964                 DEBUG ("Failed to prepare connection: %s", error->message);
965
966                 g_error_free (error);
967                 return;
968         }
969
970         if (!tp_proxy_has_interface_by_id (conn,
971                         TP_IFACE_QUARK_CONNECTION_INTERFACE_BALANCE)) {
972                 return;
973         }
974
975         DEBUG ("Setting up balance for acct: %s",
976                 tp_account_get_display_name (account));
977
978         /* create the action */
979         action = main_window_setup_balance_create_action (window, account);
980
981         /* request the current balance and monitor for any changes */
982         tp_cli_dbus_properties_call_get_all (conn, -1,
983                 TP_IFACE_CONNECTION_INTERFACE_BALANCE,
984                 main_window_setup_balance_got_balance_props,
985                 window, NULL, G_OBJECT (action));
986
987         tp_cli_connection_interface_balance_connect_to_balance_changed (
988                 TP_CONNECTION (conn), main_window_balance_changed_cb,
989                 window, NULL, G_OBJECT (action), NULL);
990 }
991
992 static void
993 main_window_setup_balance (EmpathyMainWindow *window,
994                            TpAccount         *account)
995 {
996         TpConnection *conn = tp_account_get_connection (account);
997
998         if (conn == NULL)
999                 return;
1000
1001         /* need to prepare the connection:
1002          * store the account on the connection */
1003         g_object_set_data (G_OBJECT (conn), "account", account);
1004         tp_proxy_prepare_async (conn, NULL,
1005                 main_window_setup_balance_conn_ready, window);
1006 }
1007
1008 static void
1009 main_window_connection_changed_cb (TpAccount  *account,
1010                                    guint       old_status,
1011                                    guint       current,
1012                                    guint       reason,
1013                                    gchar      *dbus_error_name,
1014                                    GHashTable *details,
1015                                    EmpathyMainWindow *window)
1016 {
1017         EmpathyMainWindowPriv *priv = GET_PRIV (window);
1018
1019         main_window_update_status (window);
1020
1021         if (current == TP_CONNECTION_STATUS_DISCONNECTED &&
1022             reason != TP_CONNECTION_STATUS_REASON_REQUESTED) {
1023                 main_window_error_display (window, account);
1024         }
1025
1026         if (current == TP_CONNECTION_STATUS_DISCONNECTED) {
1027                 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (window),
1028                                     EMPATHY_SOUND_ACCOUNT_DISCONNECTED);
1029
1030                 /* remove balance action if required */
1031                 if (priv->balance_action_group != NULL) {
1032                         GtkAction *action;
1033                         char *name;
1034
1035                         name = main_window_account_to_action_name (account);
1036
1037                         action = gtk_action_group_get_action (
1038                                 priv->balance_action_group, name);
1039
1040                         if (action != NULL) {
1041                                 guint merge_id;
1042
1043                                 DEBUG ("Removing action");
1044
1045                                 merge_id = GPOINTER_TO_UINT (g_object_get_data (
1046                                         G_OBJECT (action),
1047                                         "merge-id"));
1048
1049                                 gtk_ui_manager_remove_ui (priv->ui_manager,
1050                                         merge_id);
1051                                 gtk_action_group_remove_action (
1052                                         priv->balance_action_group, action);
1053                         }
1054
1055                         g_free (name);
1056                 }
1057         }
1058
1059         if (current == TP_CONNECTION_STATUS_CONNECTED) {
1060                 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (window),
1061                                     EMPATHY_SOUND_ACCOUNT_CONNECTED);
1062
1063                 /* Account connected without error, remove error message if any */
1064                 main_window_remove_error (window, account);
1065                 main_window_setup_balance (window, account);
1066         }
1067 }
1068
1069 static void
1070 main_window_accels_load (void)
1071 {
1072         gchar *filename;
1073
1074         filename = g_build_filename (g_get_user_config_dir (), PACKAGE_NAME, ACCELS_FILENAME, NULL);
1075         if (g_file_test (filename, G_FILE_TEST_EXISTS)) {
1076                 DEBUG ("Loading from:'%s'", filename);
1077                 gtk_accel_map_load (filename);
1078         }
1079
1080         g_free (filename);
1081 }
1082
1083 static void
1084 main_window_accels_save (void)
1085 {
1086         gchar *dir;
1087         gchar *file_with_path;
1088
1089         dir = g_build_filename (g_get_user_config_dir (), PACKAGE_NAME, NULL);
1090         g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR);
1091         file_with_path = g_build_filename (dir, ACCELS_FILENAME, NULL);
1092         g_free (dir);
1093
1094         DEBUG ("Saving to:'%s'", file_with_path);
1095         gtk_accel_map_save (file_with_path);
1096
1097         g_free (file_with_path);
1098 }
1099
1100 static void
1101 empathy_main_window_finalize (GObject *window)
1102 {
1103         EmpathyMainWindowPriv *priv = GET_PRIV (window);
1104         GHashTableIter iter;
1105         gpointer key, value;
1106
1107         /* Save user-defined accelerators. */
1108         main_window_accels_save ();
1109
1110         g_list_free (priv->actions_connected);
1111
1112         g_object_unref (priv->account_manager);
1113         g_object_unref (priv->individual_store);
1114         g_object_unref (priv->contact_manager);
1115         g_object_unref (priv->sound_mgr);
1116         g_hash_table_destroy (priv->errors);
1117         g_hash_table_destroy (priv->auths);
1118
1119         /* disconnect all handlers of status-changed signal */
1120         g_hash_table_iter_init (&iter, priv->status_changed_handlers);
1121         while (g_hash_table_iter_next (&iter, &key, &value))
1122                 g_signal_handler_disconnect (TP_ACCOUNT (key),
1123                                              GPOINTER_TO_UINT (value));
1124
1125         g_hash_table_destroy (priv->status_changed_handlers);
1126
1127         g_signal_handlers_disconnect_by_func (priv->event_manager,
1128                                               main_window_event_added_cb,
1129                                               window);
1130         g_signal_handlers_disconnect_by_func (priv->event_manager,
1131                                               main_window_event_removed_cb,
1132                                               window);
1133         g_object_unref (priv->call_observer);
1134         g_object_unref (priv->event_manager);
1135         g_object_unref (priv->ui_manager);
1136         g_object_unref (priv->chatroom_manager);
1137
1138         g_object_unref (priv->gsettings_ui);
1139         g_object_unref (priv->gsettings_contacts);
1140
1141         G_OBJECT_CLASS (empathy_main_window_parent_class)->finalize (window);
1142 }
1143
1144 static gboolean
1145 main_window_key_press_event_cb  (GtkWidget   *window,
1146                                  GdkEventKey *event,
1147                                  gpointer     user_data)
1148 {
1149         EmpathyChatManager *chat_manager;
1150
1151         if (event->keyval == GDK_KEY_T
1152             && event->state & GDK_SHIFT_MASK
1153             && event->state & GDK_CONTROL_MASK) {
1154                 chat_manager = empathy_chat_manager_dup_singleton ();
1155                 empathy_chat_manager_undo_closed_chat (chat_manager);
1156                 g_object_unref (chat_manager);
1157         }
1158         return FALSE;
1159 }
1160
1161 static void
1162 main_window_chat_quit_cb (GtkAction         *action,
1163                           EmpathyMainWindow *window)
1164 {
1165         gtk_main_quit ();
1166 }
1167
1168 static void
1169 main_window_view_history_cb (GtkAction         *action,
1170                              EmpathyMainWindow *window)
1171 {
1172         empathy_log_window_show (NULL, NULL, FALSE, GTK_WINDOW (window));
1173 }
1174
1175 static void
1176 main_window_chat_new_message_cb (GtkAction         *action,
1177                                  EmpathyMainWindow *window)
1178 {
1179         empathy_new_message_dialog_show (GTK_WINDOW (window));
1180 }
1181
1182 static void
1183 main_window_chat_new_call_cb (GtkAction         *action,
1184                               EmpathyMainWindow *window)
1185 {
1186         empathy_new_call_dialog_show (GTK_WINDOW (window));
1187 }
1188
1189 static void
1190 main_window_chat_add_contact_cb (GtkAction         *action,
1191                                  EmpathyMainWindow *window)
1192 {
1193         empathy_new_individual_dialog_show (GTK_WINDOW (window));
1194 }
1195
1196 static void
1197 main_window_chat_search_contacts_cb (GtkAction         *action,
1198                                      EmpathyMainWindow *window)
1199 {
1200         GtkWidget *dialog = empathy_contact_search_dialog_new (
1201                         GTK_WINDOW (window));
1202         gtk_widget_show (dialog);
1203 }
1204
1205 static void
1206 main_window_view_show_ft_manager (GtkAction         *action,
1207                                   EmpathyMainWindow *window)
1208 {
1209         empathy_ft_manager_show ();
1210 }
1211
1212 static void
1213 main_window_view_show_offline_cb (GtkToggleAction   *action,
1214                                   EmpathyMainWindow *window)
1215 {
1216         EmpathyMainWindowPriv *priv = GET_PRIV (window);
1217         gboolean current;
1218
1219         current = gtk_toggle_action_get_active (action);
1220         g_settings_set_boolean (priv->gsettings_ui,
1221                                 EMPATHY_PREFS_UI_SHOW_OFFLINE,
1222                                 current);
1223
1224         empathy_individual_view_set_show_offline (priv->individual_view,
1225                         current);
1226 }
1227
1228 static void
1229 main_window_notify_sort_contact_cb (GSettings         *gsettings,
1230                                     const gchar       *key,
1231                                     EmpathyMainWindow *window)
1232 {
1233         EmpathyMainWindowPriv *priv = GET_PRIV (window);
1234         gchar *str;
1235
1236         str = g_settings_get_string (gsettings, key);
1237
1238         if (str != NULL) {
1239                 GType       type;
1240                 GEnumClass *enum_class;
1241                 GEnumValue *enum_value;
1242
1243                 type = empathy_individual_store_sort_get_type ();
1244                 enum_class = G_ENUM_CLASS (g_type_class_peek (type));
1245                 enum_value = g_enum_get_value_by_nick (enum_class, str);
1246                 if (enum_value) {
1247                         /* By changing the value of the GtkRadioAction,
1248                            it emits a signal that calls main_window_view_sort_contacts_cb
1249                            which updates the contacts list */
1250                         gtk_radio_action_set_current_value (priv->sort_by_name,
1251                                                             enum_value->value);
1252                 } else {
1253                         g_warning ("Wrong value for sort_criterium configuration : %s", str);
1254                 }
1255                 g_free (str);
1256         }
1257 }
1258
1259 static void
1260 main_window_view_sort_contacts_cb (GtkRadioAction    *action,
1261                                    GtkRadioAction    *current,
1262                                    EmpathyMainWindow *window)
1263 {
1264         EmpathyMainWindowPriv *priv = GET_PRIV (window);
1265         EmpathyContactListStoreSort value;
1266         GSList      *group;
1267         GType        type;
1268         GEnumClass  *enum_class;
1269         GEnumValue  *enum_value;
1270
1271         value = gtk_radio_action_get_current_value (action);
1272         group = gtk_radio_action_get_group (action);
1273
1274         /* Get string from index */
1275         type = empathy_individual_store_sort_get_type ();
1276         enum_class = G_ENUM_CLASS (g_type_class_peek (type));
1277         enum_value = g_enum_get_value (enum_class, g_slist_index (group, current));
1278
1279         if (!enum_value) {
1280                 g_warning ("No GEnumValue for EmpathyContactListSort with GtkRadioAction index:%d",
1281                            g_slist_index (group, action));
1282         } else {
1283                 g_settings_set_string (priv->gsettings_contacts,
1284                                        EMPATHY_PREFS_CONTACTS_SORT_CRITERIUM,
1285                                        enum_value->value_nick);
1286         }
1287         empathy_individual_store_set_sort_criterium (priv->individual_store,
1288                         value);
1289 }
1290
1291 static void
1292 main_window_view_show_protocols_cb (GtkToggleAction   *action,
1293                                     EmpathyMainWindow *window)
1294 {
1295         EmpathyMainWindowPriv *priv = GET_PRIV (window);
1296         gboolean value;
1297
1298         value = gtk_toggle_action_get_active (action);
1299
1300         g_settings_set_boolean (priv->gsettings_ui,
1301                                 EMPATHY_PREFS_UI_SHOW_PROTOCOLS,
1302                                 value);
1303         empathy_individual_store_set_show_protocols (priv->individual_store,
1304                                                      value);
1305 }
1306
1307 /* Matches GtkRadioAction values set in empathy-main-window.ui */
1308 #define CONTACT_LIST_NORMAL_SIZE_WITH_AVATARS           0
1309 #define CONTACT_LIST_NORMAL_SIZE                        1
1310 #define CONTACT_LIST_COMPACT_SIZE                       2
1311
1312 static void
1313 main_window_view_contacts_list_size_cb (GtkRadioAction    *action,
1314                                         GtkRadioAction    *current,
1315                                         EmpathyMainWindow *window)
1316 {
1317         EmpathyMainWindowPriv *priv = GET_PRIV (window);
1318         GSettings *gsettings_ui;
1319         gint value;
1320
1321         value = gtk_radio_action_get_current_value (action);
1322         /* create a new GSettings, so we can delay the setting until both
1323          * values are set */
1324         gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
1325
1326         DEBUG ("radio button toggled, value = %i", value);
1327
1328         g_settings_delay (gsettings_ui);
1329         g_settings_set_boolean (gsettings_ui,
1330                                 EMPATHY_PREFS_UI_SHOW_AVATARS,
1331                                 value == CONTACT_LIST_NORMAL_SIZE_WITH_AVATARS);
1332
1333         g_settings_set_boolean (gsettings_ui,
1334                                 EMPATHY_PREFS_UI_COMPACT_CONTACT_LIST,
1335                                 value == CONTACT_LIST_COMPACT_SIZE);
1336         g_settings_apply (gsettings_ui);
1337
1338         /* FIXME: these enums probably have the wrong namespace */
1339         empathy_individual_store_set_show_avatars (priv->individual_store,
1340                         value == CONTACT_LIST_NORMAL_SIZE_WITH_AVATARS);
1341         empathy_individual_store_set_is_compact (priv->individual_store,
1342                         value == CONTACT_LIST_COMPACT_SIZE);
1343
1344         g_object_unref (gsettings_ui);
1345 }
1346
1347 static void main_window_notify_show_protocols_cb (GSettings         *gsettings,
1348                                                   const gchar       *key,
1349                                                   EmpathyMainWindow *window)
1350 {
1351         EmpathyMainWindowPriv *priv = GET_PRIV (window);
1352
1353         gtk_toggle_action_set_active (priv->show_protocols,
1354                         g_settings_get_boolean (gsettings,
1355                                 EMPATHY_PREFS_UI_SHOW_PROTOCOLS));
1356 }
1357
1358
1359 static void
1360 main_window_notify_contact_list_size_cb (GSettings         *gsettings,
1361                                          const gchar       *key,
1362                                          EmpathyMainWindow *window)
1363 {
1364         EmpathyMainWindowPriv *priv = GET_PRIV (window);
1365         gint value;
1366
1367         if (g_settings_get_boolean (gsettings,
1368                         EMPATHY_PREFS_UI_COMPACT_CONTACT_LIST)) {
1369                 value = CONTACT_LIST_COMPACT_SIZE;
1370         } else if (g_settings_get_boolean (gsettings,
1371                         EMPATHY_PREFS_UI_SHOW_AVATARS)) {
1372                 value = CONTACT_LIST_NORMAL_SIZE_WITH_AVATARS;
1373         } else {
1374                 value = CONTACT_LIST_NORMAL_SIZE;
1375         }
1376
1377         DEBUG ("setting changed, value = %i", value);
1378
1379         /* By changing the value of the GtkRadioAction,
1380            it emits a signal that calls main_window_view_contacts_list_size_cb
1381            which updates the contacts list */
1382         gtk_radio_action_set_current_value (priv->normal_with_avatars, value);
1383 }
1384
1385 static void
1386 main_window_edit_search_contacts_cb (GtkCheckMenuItem  *item,
1387                                      EmpathyMainWindow *window)
1388 {
1389         EmpathyMainWindowPriv *priv = GET_PRIV (window);
1390
1391         empathy_individual_view_start_search (priv->individual_view);
1392 }
1393
1394 static void
1395 main_window_view_show_map_cb (GtkCheckMenuItem  *item,
1396                               EmpathyMainWindow *window)
1397 {
1398 #ifdef HAVE_LIBCHAMPLAIN
1399         empathy_map_view_show ();
1400 #endif
1401 }
1402
1403 static void
1404 join_chatroom (EmpathyChatroom *chatroom,
1405                gint64 timestamp)
1406 {
1407         TpAccount      *account;
1408         const gchar    *room;
1409
1410         account = empathy_chatroom_get_account (chatroom);
1411         room = empathy_chatroom_get_room (chatroom);
1412
1413         DEBUG ("Requesting channel for '%s'", room);
1414         empathy_join_muc (account, room, timestamp);
1415 }
1416
1417 typedef struct
1418 {
1419         TpAccount *account;
1420         EmpathyChatroom *chatroom;
1421         gint64 timestamp;
1422         glong sig_id;
1423         guint timeout;
1424 } join_fav_account_sig_ctx;
1425
1426 static join_fav_account_sig_ctx *
1427 join_fav_account_sig_ctx_new (TpAccount *account,
1428                              EmpathyChatroom *chatroom,
1429                               gint64 timestamp)
1430 {
1431         join_fav_account_sig_ctx *ctx = g_slice_new0 (
1432                 join_fav_account_sig_ctx);
1433
1434         ctx->account = g_object_ref (account);
1435         ctx->chatroom = g_object_ref (chatroom);
1436         ctx->timestamp = timestamp;
1437         return ctx;
1438 }
1439
1440 static void
1441 join_fav_account_sig_ctx_free (join_fav_account_sig_ctx *ctx)
1442 {
1443         g_object_unref (ctx->account);
1444         g_object_unref (ctx->chatroom);
1445         g_slice_free (join_fav_account_sig_ctx, ctx);
1446 }
1447
1448 static void
1449 account_status_changed_cb (TpAccount  *account,
1450                            TpConnectionStatus old_status,
1451                            TpConnectionStatus new_status,
1452                            guint reason,
1453                            gchar *dbus_error_name,
1454                            GHashTable *details,
1455                            gpointer user_data)
1456 {
1457         join_fav_account_sig_ctx *ctx = user_data;
1458
1459         switch (new_status) {
1460                 case TP_CONNECTION_STATUS_DISCONNECTED:
1461                         /* Don't wait any longer */
1462                         goto finally;
1463                         break;
1464
1465                 case TP_CONNECTION_STATUS_CONNECTING:
1466                         /* Wait a bit */
1467                         return;
1468
1469                 case TP_CONNECTION_STATUS_CONNECTED:
1470                         /* We can join the room */
1471                         break;
1472
1473                 default:
1474                         g_assert_not_reached ();
1475         }
1476
1477         join_chatroom (ctx->chatroom, ctx->timestamp);
1478
1479 finally:
1480         g_source_remove (ctx->timeout);
1481         g_signal_handler_disconnect (account, ctx->sig_id);
1482 }
1483
1484 #define JOIN_FAVORITE_TIMEOUT 5
1485
1486 static gboolean
1487 join_favorite_timeout_cb (gpointer data)
1488 {
1489         join_fav_account_sig_ctx *ctx = data;
1490
1491         /* stop waiting for joining the favorite room */
1492         g_signal_handler_disconnect (ctx->account, ctx->sig_id);
1493         return FALSE;
1494 }
1495
1496 static void
1497 main_window_favorite_chatroom_join (EmpathyChatroom *chatroom)
1498 {
1499         TpAccount      *account;
1500
1501         account = empathy_chatroom_get_account (chatroom);
1502         if (tp_account_get_connection_status (account, NULL) !=
1503                                              TP_CONNECTION_STATUS_CONNECTED) {
1504                 join_fav_account_sig_ctx *ctx;
1505
1506                 ctx = join_fav_account_sig_ctx_new (account, chatroom,
1507                         gtk_get_current_event_time ());
1508
1509                 ctx->sig_id = g_signal_connect_data (account, "status-changed",
1510                         G_CALLBACK (account_status_changed_cb), ctx,
1511                         (GClosureNotify) join_fav_account_sig_ctx_free, 0);
1512
1513                 ctx->timeout = g_timeout_add_seconds (JOIN_FAVORITE_TIMEOUT,
1514                         join_favorite_timeout_cb, ctx);
1515                 return;
1516         }
1517
1518         join_chatroom (chatroom, gtk_get_current_event_time ());
1519 }
1520
1521 static void
1522 main_window_favorite_chatroom_menu_activate_cb (GtkMenuItem     *menu_item,
1523                                                 EmpathyChatroom *chatroom)
1524 {
1525         main_window_favorite_chatroom_join (chatroom);
1526 }
1527
1528 static void
1529 main_window_favorite_chatroom_menu_add (EmpathyMainWindow *window,
1530                                         EmpathyChatroom   *chatroom)
1531 {
1532         EmpathyMainWindowPriv *priv = GET_PRIV (window);
1533         GtkWidget   *menu_item;
1534         const gchar *name;
1535
1536         if (g_object_get_data (G_OBJECT (chatroom), "menu_item")) {
1537                 return;
1538         }
1539
1540         name = empathy_chatroom_get_name (chatroom);
1541         menu_item = gtk_menu_item_new_with_label (name);
1542         g_object_set_data (G_OBJECT (menu_item), "is_favorite",
1543                         GUINT_TO_POINTER (TRUE));
1544
1545         g_object_set_data (G_OBJECT (chatroom), "menu_item", menu_item);
1546         g_signal_connect (menu_item, "activate",
1547                           G_CALLBACK (main_window_favorite_chatroom_menu_activate_cb),
1548                           chatroom);
1549
1550         gtk_menu_shell_insert (GTK_MENU_SHELL (priv->room_menu),
1551                                menu_item, 4);
1552
1553         gtk_widget_show (menu_item);
1554 }
1555
1556 static void
1557 main_window_favorite_chatroom_menu_added_cb (EmpathyChatroomManager *manager,
1558                                              EmpathyChatroom        *chatroom,
1559                                              EmpathyMainWindow      *window)
1560 {
1561         EmpathyMainWindowPriv *priv = GET_PRIV (window);
1562
1563         main_window_favorite_chatroom_menu_add (window, chatroom);
1564         gtk_widget_show (priv->room_separator);
1565         gtk_action_set_sensitive (priv->room_join_favorites, TRUE);
1566 }
1567
1568 static void
1569 main_window_favorite_chatroom_menu_removed_cb (EmpathyChatroomManager *manager,
1570                                                EmpathyChatroom        *chatroom,
1571                                                EmpathyMainWindow      *window)
1572 {
1573         EmpathyMainWindowPriv *priv = GET_PRIV (window);
1574         GtkWidget *menu_item;
1575         GList *chatrooms;
1576
1577         menu_item = g_object_get_data (G_OBJECT (chatroom), "menu_item");
1578         g_object_set_data (G_OBJECT (chatroom), "menu_item", NULL);
1579         gtk_widget_destroy (menu_item);
1580
1581         chatrooms = empathy_chatroom_manager_get_chatrooms (priv->chatroom_manager, NULL);
1582         if (chatrooms) {
1583                 gtk_widget_show (priv->room_separator);
1584         } else {
1585                 gtk_widget_hide (priv->room_separator);
1586         }
1587
1588         gtk_action_set_sensitive (priv->room_join_favorites, chatrooms != NULL);
1589         g_list_free (chatrooms);
1590 }
1591
1592 static void
1593 main_window_favorite_chatroom_menu_setup (EmpathyMainWindow *window)
1594 {
1595         EmpathyMainWindowPriv *priv = GET_PRIV (window);
1596         GList *chatrooms, *l;
1597         GtkWidget *room;
1598
1599         priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
1600         chatrooms = empathy_chatroom_manager_get_chatrooms (
1601                 priv->chatroom_manager, NULL);
1602         room = gtk_ui_manager_get_widget (priv->ui_manager,
1603                 "/menubar/room");
1604         priv->room_menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (room));
1605         priv->room_separator = gtk_ui_manager_get_widget (priv->ui_manager,
1606                 "/menubar/room/room_separator");
1607
1608         for (l = chatrooms; l; l = l->next) {
1609                 main_window_favorite_chatroom_menu_add (window, l->data);
1610         }
1611
1612         if (!chatrooms) {
1613                 gtk_widget_hide (priv->room_separator);
1614         }
1615
1616         gtk_action_set_sensitive (priv->room_join_favorites, chatrooms != NULL);
1617
1618         g_signal_connect (priv->chatroom_manager, "chatroom-added",
1619                           G_CALLBACK (main_window_favorite_chatroom_menu_added_cb),
1620                           window);
1621         g_signal_connect (priv->chatroom_manager, "chatroom-removed",
1622                           G_CALLBACK (main_window_favorite_chatroom_menu_removed_cb),
1623                           window);
1624
1625         g_list_free (chatrooms);
1626 }
1627
1628 static void
1629 main_window_room_join_new_cb (GtkAction         *action,
1630                               EmpathyMainWindow *window)
1631 {
1632         empathy_new_chatroom_dialog_show (GTK_WINDOW (window));
1633 }
1634
1635 static void
1636 main_window_room_join_favorites_cb (GtkAction         *action,
1637                                     EmpathyMainWindow *window)
1638 {
1639         EmpathyMainWindowPriv *priv = GET_PRIV (window);
1640         GList *chatrooms, *l;
1641
1642         chatrooms = empathy_chatroom_manager_get_chatrooms (priv->chatroom_manager, NULL);
1643         for (l = chatrooms; l; l = l->next) {
1644                 main_window_favorite_chatroom_join (l->data);
1645         }
1646         g_list_free (chatrooms);
1647 }
1648
1649 static void
1650 main_window_room_manage_favorites_cb (GtkAction         *action,
1651                                       EmpathyMainWindow *window)
1652 {
1653         empathy_chatrooms_window_show (GTK_WINDOW (window));
1654 }
1655
1656 static void
1657 main_window_edit_cb (GtkAction         *action,
1658                      EmpathyMainWindow *window)
1659 {
1660         EmpathyMainWindowPriv *priv = GET_PRIV (window);
1661         GtkWidget *submenu;
1662
1663         /* FIXME: It should use the UIManager to merge the contact/group submenu */
1664         submenu = empathy_individual_view_get_individual_menu (
1665                         priv->individual_view);
1666         if (submenu) {
1667                 GtkMenuItem *item;
1668                 GtkWidget   *label;
1669
1670                 item = GTK_MENU_ITEM (priv->edit_context);
1671                 label = gtk_bin_get_child (GTK_BIN (item));
1672                 gtk_label_set_text (GTK_LABEL (label), _("Contact"));
1673
1674                 gtk_widget_show (priv->edit_context);
1675                 gtk_widget_show (priv->edit_context_separator);
1676
1677                 gtk_menu_item_set_submenu (item, submenu);
1678
1679                 return;
1680         }
1681
1682         submenu = empathy_individual_view_get_group_menu (
1683                         priv->individual_view);
1684         if (submenu) {
1685                 GtkMenuItem *item;
1686                 GtkWidget   *label;
1687
1688                 item = GTK_MENU_ITEM (priv->edit_context);
1689                 label = gtk_bin_get_child (GTK_BIN (item));
1690                 gtk_label_set_text (GTK_LABEL (label), _("Group"));
1691
1692                 gtk_widget_show (priv->edit_context);
1693                 gtk_widget_show (priv->edit_context_separator);
1694
1695                 gtk_menu_item_set_submenu (item, submenu);
1696
1697                 return;
1698         }
1699
1700         gtk_widget_hide (priv->edit_context);
1701         gtk_widget_hide (priv->edit_context_separator);
1702
1703         return;
1704 }
1705
1706 static void
1707 main_window_edit_accounts_cb (GtkAction         *action,
1708                               EmpathyMainWindow *window)
1709 {
1710         empathy_accounts_dialog_show_application (gdk_screen_get_default (),
1711                         NULL, FALSE, FALSE);
1712 }
1713
1714 static void
1715 main_window_edit_personal_information_cb (GtkAction         *action,
1716                                           EmpathyMainWindow *window)
1717 {
1718         empathy_contact_personal_dialog_show (GTK_WINDOW (window));
1719 }
1720
1721 static void
1722 main_window_edit_blocked_contacts_cb (GtkAction         *action,
1723                                       EmpathyMainWindow *window)
1724 {
1725         GtkWidget *dialog;
1726
1727         dialog = empathy_contact_blocking_dialog_new (GTK_WINDOW (window));
1728         gtk_widget_show (dialog);
1729         g_signal_connect (dialog, "response",
1730                         G_CALLBACK (gtk_widget_destroy), NULL);
1731 }
1732
1733 static void
1734 main_window_edit_preferences_cb (GtkAction         *action,
1735                                  EmpathyMainWindow *window)
1736 {
1737         EmpathyMainWindowPriv *priv = GET_PRIV (window);
1738
1739         if (priv->preferences == NULL) {
1740                 priv->preferences = empathy_preferences_new (GTK_WINDOW (window));
1741                 g_object_add_weak_pointer (G_OBJECT (priv->preferences),
1742                                            (gpointer) &priv->preferences);
1743
1744                 gtk_widget_show (priv->preferences);
1745         } else {
1746                 gtk_window_present (GTK_WINDOW (priv->preferences));
1747         }
1748 }
1749
1750 static void
1751 main_window_help_about_cb (GtkAction         *action,
1752                            EmpathyMainWindow *window)
1753 {
1754         empathy_about_dialog_new (GTK_WINDOW (window));
1755 }
1756
1757 static void
1758 main_window_help_debug_cb (GtkAction         *action,
1759                            EmpathyMainWindow *window)
1760 {
1761         GdkDisplay *display;
1762         GError *error = NULL;
1763         gchar *path;
1764         GAppInfo *app_info;
1765         GdkAppLaunchContext *context = NULL;
1766
1767         /* Try to run from source directory if possible */
1768         path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"), "src",
1769                         "empathy-debugger", NULL);
1770
1771         if (!g_file_test (path, G_FILE_TEST_EXISTS)) {
1772                 g_free (path);
1773                 path = g_build_filename (BIN_DIR, "empathy-debugger", NULL);
1774         }
1775
1776         app_info = g_app_info_create_from_commandline (path, NULL, 0, &error);
1777         if (app_info == NULL) {
1778                 DEBUG ("Failed to create app info: %s", error->message);
1779                 g_error_free (error);
1780                 goto out;
1781         }
1782
1783         display = gdk_display_get_default ();
1784         context = gdk_display_get_app_launch_context (display);
1785
1786         if (!g_app_info_launch (app_info, NULL, (GAppLaunchContext *) context,
1787                 &error)) {
1788                 g_warning ("Failed to open debug window: %s", error->message);
1789                 g_error_free (error);
1790                 goto out;
1791         }
1792
1793 out:
1794         tp_clear_object (&app_info);
1795         tp_clear_object (&context);
1796         g_free (path);
1797 }
1798
1799 static void
1800 main_window_help_contents_cb (GtkAction         *action,
1801                               EmpathyMainWindow *window)
1802 {
1803         empathy_url_show (GTK_WIDGET (window), "ghelp:empathy");
1804 }
1805
1806 static gboolean
1807 main_window_throbber_button_press_event_cb (GtkWidget         *throbber,
1808                                             GdkEventButton    *event,
1809                                             EmpathyMainWindow *window)
1810 {
1811         if (event->type != GDK_BUTTON_PRESS ||
1812             event->button != 1) {
1813                 return FALSE;
1814         }
1815
1816         empathy_accounts_dialog_show_application (
1817                         gtk_widget_get_screen (GTK_WIDGET (throbber)),
1818                         NULL, FALSE, FALSE);
1819
1820         return FALSE;
1821 }
1822
1823 static void
1824 main_window_account_removed_cb (TpAccountManager  *manager,
1825                                 TpAccount         *account,
1826                                 EmpathyMainWindow *window)
1827 {
1828         EmpathyMainWindowPriv *priv = GET_PRIV (window);
1829         GList *a;
1830
1831         a = tp_account_manager_get_valid_accounts (manager);
1832
1833         gtk_action_set_sensitive (priv->view_history,
1834                 g_list_length (a) > 0);
1835
1836         g_list_free (a);
1837
1838         /* remove errors if any */
1839         main_window_remove_error (window, account);
1840 }
1841
1842 static void
1843 main_window_account_validity_changed_cb (TpAccountManager  *manager,
1844                                          TpAccount         *account,
1845                                          gboolean           valid,
1846                                          EmpathyMainWindow *window)
1847 {
1848         EmpathyMainWindowPriv *priv = GET_PRIV (window);
1849
1850         if (valid) {
1851                 gulong handler_id;
1852                 handler_id = GPOINTER_TO_UINT (g_hash_table_lookup (
1853                         priv->status_changed_handlers, account));
1854
1855                 /* connect signal only if it was not connected yet */
1856                 if (handler_id == 0) {
1857                         handler_id = g_signal_connect (account,
1858                                 "status-changed",
1859                                 G_CALLBACK (main_window_connection_changed_cb),
1860                                 window);
1861                         g_hash_table_insert (priv->status_changed_handlers,
1862                                 account, GUINT_TO_POINTER (handler_id));
1863                 }
1864         }
1865
1866         main_window_account_removed_cb (manager, account, window);
1867 }
1868
1869 static void
1870 main_window_notify_show_offline_cb (GSettings   *gsettings,
1871                                     const gchar *key,
1872                                     gpointer     toggle_action)
1873 {
1874         gtk_toggle_action_set_active (toggle_action,
1875                         g_settings_get_boolean (gsettings, key));
1876 }
1877
1878 static void
1879 main_window_connection_items_setup (EmpathyMainWindow *window,
1880                                     GtkBuilder        *gui)
1881 {
1882         EmpathyMainWindowPriv *priv = GET_PRIV (window);
1883         GList         *list;
1884         GObject       *action;
1885         guint          i;
1886         const gchar *actions_connected[] = {
1887                 "room_join_new",
1888                 "room_join_favorites",
1889                 "chat_new_message",
1890                 "chat_new_call",
1891                 "chat_add_contact",
1892                 "edit_personal_information",
1893                 "edit_blocked_contacts"
1894         };
1895
1896         for (i = 0, list = NULL; i < G_N_ELEMENTS (actions_connected); i++) {
1897                 action = gtk_builder_get_object (gui, actions_connected[i]);
1898                 list = g_list_prepend (list, action);
1899         }
1900
1901         priv->actions_connected = list;
1902 }
1903
1904 static void
1905 account_manager_prepared_cb (GObject      *source_object,
1906                              GAsyncResult *result,
1907                              gpointer      user_data)
1908 {
1909         GList *accounts, *j;
1910         TpAccountManager *manager = TP_ACCOUNT_MANAGER (source_object);
1911         EmpathyMainWindow *window = user_data;
1912         EmpathyMainWindowPriv *priv = GET_PRIV (window);
1913         GError *error = NULL;
1914
1915         if (!tp_account_manager_prepare_finish (manager, result, &error)) {
1916                 DEBUG ("Failed to prepare account manager: %s", error->message);
1917                 g_error_free (error);
1918                 return;
1919         }
1920
1921         accounts = tp_account_manager_get_valid_accounts (priv->account_manager);
1922         for (j = accounts; j != NULL; j = j->next) {
1923                 TpAccount *account = TP_ACCOUNT (j->data);
1924                 gulong handler_id;
1925
1926                 handler_id = g_signal_connect (account, "status-changed",
1927                                   G_CALLBACK (main_window_connection_changed_cb),
1928                                   window);
1929                 g_hash_table_insert (priv->status_changed_handlers,
1930                                      account, GUINT_TO_POINTER (handler_id));
1931
1932                 main_window_setup_balance (window, account);
1933         }
1934
1935         g_signal_connect (manager, "account-validity-changed",
1936                           G_CALLBACK (main_window_account_validity_changed_cb),
1937                           window);
1938
1939         main_window_update_status (window);
1940
1941         /* Disable the "Previous Conversations" menu entry if there is no account */
1942         gtk_action_set_sensitive (priv->view_history,
1943                 g_list_length (accounts) > 0);
1944
1945         g_list_free (accounts);
1946 }
1947
1948 static void
1949 main_window_members_changed_cb (EmpathyContactList *list,
1950                                 EmpathyContact     *contact,
1951                                 EmpathyContact     *actor,
1952                                 guint               reason,
1953                                 gchar              *message,
1954                                 gboolean            is_member,
1955                                 EmpathyMainWindow  *window)
1956 {
1957         EmpathyMainWindowPriv *priv = GET_PRIV (window);
1958
1959         if (!is_member)
1960                 return;
1961
1962         if (!empathy_migrate_butterfly_logs (contact)) {
1963                 g_signal_handler_disconnect (list,
1964                         priv->butterfly_log_migration_members_changed_id);
1965                 priv->butterfly_log_migration_members_changed_id = 0;
1966         }
1967 }
1968
1969 static GObject *
1970 empathy_main_window_constructor (GType type,
1971                                  guint n_construct_params,
1972                                  GObjectConstructParam *construct_params)
1973 {
1974         static GObject *window = NULL;
1975
1976         if (window != NULL)
1977                 return g_object_ref (window);
1978
1979         window = G_OBJECT_CLASS (empathy_main_window_parent_class)->constructor (
1980                 type, n_construct_params, construct_params);
1981
1982         g_object_add_weak_pointer (window, (gpointer) &window);
1983
1984         return window;
1985 }
1986
1987 static void
1988 empathy_main_window_class_init (EmpathyMainWindowClass *klass)
1989 {
1990         GObjectClass *object_class = G_OBJECT_CLASS (klass);
1991
1992         object_class->finalize = empathy_main_window_finalize;
1993         object_class->constructor = empathy_main_window_constructor;
1994
1995         g_type_class_add_private (object_class, sizeof (EmpathyMainWindowPriv));
1996 }
1997
1998 static void
1999 empathy_main_window_init (EmpathyMainWindow *window)
2000 {
2001         EmpathyMainWindowPriv    *priv;
2002         EmpathyIndividualManager *individual_manager;
2003         GtkBuilder               *gui;
2004         GtkWidget                *sw;
2005         GtkToggleAction          *show_offline_widget;
2006         GtkAction                *show_map_widget;
2007         GtkToolItem              *item;
2008         gboolean                  show_offline;
2009         gchar                    *filename;
2010         GSList                   *l;
2011         GtkTreeModel             *model;
2012
2013         priv = window->priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
2014                         EMPATHY_TYPE_MAIN_WINDOW, EmpathyMainWindowPriv);
2015
2016         priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2017         priv->gsettings_contacts = g_settings_new (EMPATHY_PREFS_CONTACTS_SCHEMA);
2018
2019         priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2020
2021         gtk_window_set_title (GTK_WINDOW (window), _("Contact List"));
2022         gtk_window_set_role (GTK_WINDOW (window), "contact_list");
2023         gtk_window_set_default_size (GTK_WINDOW (window), 225, 325);
2024
2025         /* Set up interface */
2026         filename = empathy_file_lookup ("empathy-main-window.ui", "src");
2027         gui = empathy_builder_get_file (filename,
2028                                        "main_vbox", &priv->main_vbox,
2029                                        "balance_box", &priv->balance_vbox,
2030                                        "errors_vbox", &priv->errors_vbox,
2031                                        "auth_vbox", &priv->auth_vbox,
2032                                        "ui_manager", &priv->ui_manager,
2033                                        "view_show_offline", &show_offline_widget,
2034                                        "view_show_protocols", &priv->show_protocols,
2035                                        "view_sort_by_name", &priv->sort_by_name,
2036                                        "view_sort_by_status", &priv->sort_by_status,
2037                                        "view_normal_size_with_avatars", &priv->normal_with_avatars,
2038                                        "view_normal_size", &priv->normal_size,
2039                                        "view_compact_size", &priv->compact_size,
2040                                        "view_history", &priv->view_history,
2041                                        "view_show_map", &show_map_widget,
2042                                        "room_join_favorites", &priv->room_join_favorites,
2043                                        "presence_toolbar", &priv->presence_toolbar,
2044                                        "notebook", &priv->notebook,
2045                                        "no_entry_label", &priv->no_entry_label,
2046                                        "roster_scrolledwindow", &sw,
2047                                        NULL);
2048         g_free (filename);
2049
2050         gtk_container_add (GTK_CONTAINER (window), priv->main_vbox);
2051         gtk_widget_show (priv->main_vbox);
2052
2053         g_signal_connect (window, "key-press-event",
2054                           G_CALLBACK (main_window_key_press_event_cb), NULL);
2055
2056         empathy_builder_connect (gui, window,
2057                               "chat_quit", "activate", main_window_chat_quit_cb,
2058                               "chat_new_message", "activate", main_window_chat_new_message_cb,
2059                               "chat_new_call", "activate", main_window_chat_new_call_cb,
2060                               "view_history", "activate", main_window_view_history_cb,
2061                               "room_join_new", "activate", main_window_room_join_new_cb,
2062                               "room_join_favorites", "activate", main_window_room_join_favorites_cb,
2063                               "room_manage_favorites", "activate", main_window_room_manage_favorites_cb,
2064                               "chat_add_contact", "activate", main_window_chat_add_contact_cb,
2065                               "chat_search_contacts", "activate", main_window_chat_search_contacts_cb,
2066                               "view_show_ft_manager", "activate", main_window_view_show_ft_manager,
2067                               "view_show_offline", "toggled", main_window_view_show_offline_cb,
2068                               "view_show_protocols", "toggled", main_window_view_show_protocols_cb,
2069                               "view_sort_by_name", "changed", main_window_view_sort_contacts_cb,
2070                               "view_normal_size_with_avatars", "changed", main_window_view_contacts_list_size_cb,
2071                               "view_show_map", "activate", main_window_view_show_map_cb,
2072                               "edit", "activate", main_window_edit_cb,
2073                               "edit_accounts", "activate", main_window_edit_accounts_cb,
2074                               "edit_personal_information", "activate", main_window_edit_personal_information_cb,
2075                               "edit_blocked_contacts", "activate", main_window_edit_blocked_contacts_cb,
2076                               "edit_preferences", "activate", main_window_edit_preferences_cb,
2077                               "edit_search_contacts", "activate", main_window_edit_search_contacts_cb,
2078                               "help_about", "activate", main_window_help_about_cb,
2079                               "help_debug", "activate", main_window_help_debug_cb,
2080                               "help_contents", "activate", main_window_help_contents_cb,
2081                               NULL);
2082
2083         /* Set up connection related widgets. */
2084         main_window_connection_items_setup (window, gui);
2085
2086         g_object_ref (priv->ui_manager);
2087         g_object_unref (gui);
2088
2089 #ifndef HAVE_LIBCHAMPLAIN
2090         gtk_action_set_visible (show_map_widget, FALSE);
2091 #endif
2092
2093         priv->account_manager = tp_account_manager_dup ();
2094
2095         tp_account_manager_prepare_async (priv->account_manager, NULL,
2096                                           account_manager_prepared_cb, window);
2097
2098         priv->errors = g_hash_table_new_full (g_direct_hash,
2099                                               g_direct_equal,
2100                                               g_object_unref,
2101                                               NULL);
2102
2103         priv->auths = g_hash_table_new (NULL, NULL);
2104
2105         priv->status_changed_handlers = g_hash_table_new_full (g_direct_hash,
2106                                                                g_direct_equal,
2107                                                                NULL,
2108                                                                NULL);
2109
2110         /* Set up menu */
2111         main_window_favorite_chatroom_menu_setup (window);
2112
2113         priv->edit_context = gtk_ui_manager_get_widget (priv->ui_manager,
2114                 "/menubar/edit/edit_context");
2115         priv->edit_context_separator = gtk_ui_manager_get_widget (
2116                 priv->ui_manager,
2117                 "/menubar/edit/edit_context_separator");
2118         gtk_widget_hide (priv->edit_context);
2119         gtk_widget_hide (priv->edit_context_separator);
2120
2121         /* Set up contact list. */
2122         empathy_status_presets_get_all ();
2123
2124         /* Set up presence chooser */
2125         priv->presence_chooser = empathy_presence_chooser_new ();
2126         gtk_widget_show (priv->presence_chooser);
2127         item = gtk_tool_item_new ();
2128         gtk_widget_show (GTK_WIDGET (item));
2129         gtk_widget_set_size_request (priv->presence_chooser, 10, -1);
2130         gtk_container_add (GTK_CONTAINER (item), priv->presence_chooser);
2131         gtk_tool_item_set_is_important (item, TRUE);
2132         gtk_tool_item_set_expand (item, TRUE);
2133         gtk_toolbar_insert (GTK_TOOLBAR (priv->presence_toolbar), item, -1);
2134
2135         /* Set up the throbber */
2136         priv->throbber = gtk_spinner_new ();
2137         gtk_widget_set_size_request (priv->throbber, 16, -1);
2138         gtk_widget_set_tooltip_text (priv->throbber, _("Show and edit accounts"));
2139         gtk_widget_set_events (priv->throbber, GDK_BUTTON_PRESS_MASK);
2140         g_signal_connect (priv->throbber, "button-press-event",
2141                 G_CALLBACK (main_window_throbber_button_press_event_cb),
2142                 window);
2143         gtk_widget_show (priv->throbber);
2144
2145         item = gtk_tool_item_new ();
2146         gtk_container_set_border_width (GTK_CONTAINER (item), 6);
2147         gtk_toolbar_insert (GTK_TOOLBAR (priv->presence_toolbar), item, -1);
2148         gtk_container_add (GTK_CONTAINER (item), priv->throbber);
2149         priv->throbber_tool_item = GTK_WIDGET (item);
2150
2151         /* XXX: this class is designed to live for the duration of the program,
2152          * so it's got a race condition between its signal handlers and its
2153          * finalization. The class is planned to be removed, so we won't fix
2154          * this before then. */
2155         priv->contact_manager = EMPATHY_CONTACT_LIST (
2156                         empathy_contact_manager_dup_singleton ());
2157         individual_manager = empathy_individual_manager_dup_singleton ();
2158         priv->individual_store = empathy_individual_store_new (
2159                         individual_manager);
2160         g_object_unref (individual_manager);
2161
2162         /* For the moment, we disallow Persona drops onto the main contact list (e.g. from things such as
2163          * the EmpathyPersonaView in the linking dialogue). No code is hooked up to do anything on a Persona
2164          * drop, so allowing them would achieve nothing except confusion. */
2165         priv->individual_view = empathy_individual_view_new (
2166                         priv->individual_store,
2167                         EMPATHY_INDIVIDUAL_VIEW_FEATURE_ALL ^ EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP,
2168                         EMPATHY_INDIVIDUAL_FEATURE_ALL);
2169
2170         priv->butterfly_log_migration_members_changed_id = g_signal_connect (
2171                         priv->contact_manager, "members-changed",
2172                         G_CALLBACK (main_window_members_changed_cb), window);
2173
2174         gtk_widget_show (GTK_WIDGET (priv->individual_view));
2175         gtk_container_add (GTK_CONTAINER (sw),
2176                            GTK_WIDGET (priv->individual_view));
2177         g_signal_connect (priv->individual_view, "row-activated",
2178                           G_CALLBACK (main_window_row_activated_cb),
2179                           window);
2180
2181         /* Set up search bar */
2182         priv->search_bar = empathy_live_search_new (
2183                 GTK_WIDGET (priv->individual_view));
2184         empathy_individual_view_set_live_search (priv->individual_view,
2185                 EMPATHY_LIVE_SEARCH (priv->search_bar));
2186         gtk_box_pack_start (GTK_BOX (priv->main_vbox), priv->search_bar,
2187                 FALSE, TRUE, 0);
2188         g_signal_connect_swapped (window, "map",
2189                 G_CALLBACK (gtk_widget_grab_focus), priv->individual_view);
2190
2191         /* Connect to proper signals to check if contact list is empty or not */
2192         model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->individual_view));
2193         priv->empty = TRUE;
2194         g_signal_connect (model, "row-inserted",
2195                           G_CALLBACK (main_window_row_inserted_cb),
2196                           window);
2197         g_signal_connect (model, "row-deleted",
2198                           G_CALLBACK (main_window_row_deleted_cb),
2199                           window);
2200
2201         /* Load user-defined accelerators. */
2202         main_window_accels_load ();
2203
2204         /* Set window size. */
2205         empathy_geometry_bind (GTK_WINDOW (window), GEOMETRY_NAME);
2206
2207         /* Enable event handling */
2208         priv->call_observer = empathy_call_observer_dup_singleton ();
2209         priv->event_manager = empathy_event_manager_dup_singleton ();
2210
2211         g_signal_connect (priv->event_manager, "event-added",
2212                           G_CALLBACK (main_window_event_added_cb), window);
2213         g_signal_connect (priv->event_manager, "event-removed",
2214                           G_CALLBACK (main_window_event_removed_cb), window);
2215         g_signal_connect (priv->account_manager, "account-validity-changed",
2216                           G_CALLBACK (main_window_account_validity_changed_cb),
2217                           window);
2218         g_signal_connect (priv->account_manager, "account-removed",
2219                           G_CALLBACK (main_window_account_removed_cb),
2220                           window);
2221         g_signal_connect (priv->account_manager, "account-disabled",
2222                           G_CALLBACK (main_window_account_disabled_cb),
2223                           window);
2224
2225         l = empathy_event_manager_get_events (priv->event_manager);
2226         while (l) {
2227                 main_window_event_added_cb (priv->event_manager, l->data,
2228                                 window);
2229                 l = l->next;
2230         }
2231
2232         /* Show offline ? */
2233         show_offline = g_settings_get_boolean (priv->gsettings_ui,
2234                                                EMPATHY_PREFS_UI_SHOW_OFFLINE);
2235         g_signal_connect (priv->gsettings_ui,
2236                           "changed::" EMPATHY_PREFS_UI_SHOW_OFFLINE,
2237                           G_CALLBACK (main_window_notify_show_offline_cb),
2238                           show_offline_widget);
2239
2240         gtk_toggle_action_set_active (show_offline_widget, show_offline);
2241
2242         /* Show protocol ? */
2243         g_signal_connect (priv->gsettings_ui,
2244                           "changed::" EMPATHY_PREFS_UI_SHOW_PROTOCOLS,
2245                           G_CALLBACK (main_window_notify_show_protocols_cb),
2246                           window);
2247
2248         main_window_notify_show_protocols_cb (priv->gsettings_ui,
2249                                               EMPATHY_PREFS_UI_SHOW_PROTOCOLS,
2250                                               window);
2251
2252         /* Sort by name / by status ? */
2253         g_signal_connect (priv->gsettings_contacts,
2254                           "changed::" EMPATHY_PREFS_CONTACTS_SORT_CRITERIUM,
2255                           G_CALLBACK (main_window_notify_sort_contact_cb),
2256                           window);
2257
2258         main_window_notify_sort_contact_cb (priv->gsettings_contacts,
2259                                             EMPATHY_PREFS_CONTACTS_SORT_CRITERIUM,
2260                                             window);
2261
2262         /* Contacts list size */
2263         g_signal_connect (priv->gsettings_ui,
2264                           "changed::" EMPATHY_PREFS_UI_COMPACT_CONTACT_LIST,
2265                           G_CALLBACK (main_window_notify_contact_list_size_cb),
2266                           window);
2267         g_signal_connect (priv->gsettings_ui,
2268                           "changed::" EMPATHY_PREFS_UI_SHOW_AVATARS,
2269                           G_CALLBACK (main_window_notify_contact_list_size_cb),
2270                           window);
2271
2272         main_window_notify_contact_list_size_cb (priv->gsettings_ui,
2273                                                  EMPATHY_PREFS_UI_SHOW_AVATARS,
2274                                                  window);
2275 }
2276
2277 GtkWidget *
2278 empathy_main_window_dup (void)
2279 {
2280         return g_object_new (EMPATHY_TYPE_MAIN_WINDOW, NULL);
2281 }