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