]> git.0d.be Git - empathy.git/blob - src/empathy-chat-window.c
"Join Chat" and "Leave Chat" menu items for Conversation menu in MUCs
[empathy.git] / src / empathy-chat-window.c
1 /*
2  * Copyright (C) 2003-2007 Imendio AB
3  * Copyright (C) 2007-2012 Collabora Ltd.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA  02110-1301  USA
19  *
20  * Authors: Mikael Hallendal <micke@imendio.com>
21  *          Richard Hult <richard@imendio.com>
22  *          Martyn Russell <martyn@imendio.com>
23  *          Geert-Jan Van den Bogaerde <geertjan@gnome.org>
24  *          Xavier Claessens <xclaesse@gmail.com>
25  *          Rômulo Fernandes Machado <romulo@castorgroup.net>
26  */
27
28 #include "config.h"
29 #include "empathy-chat-window.h"
30
31 #include <glib/gi18n.h>
32
33 #include "empathy-about-dialog.h"
34 #include "empathy-chat-manager.h"
35 #include "empathy-chatroom-manager.h"
36 #include "empathy-client-factory.h"
37 #include "empathy-geometry.h"
38 #include "empathy-gsettings.h"
39 #include "empathy-images.h"
40 #include "empathy-invite-participant-dialog.h"
41 #include "empathy-notify-manager.h"
42 #include "empathy-request-util.h"
43 #include "empathy-smiley-manager.h"
44 #include "empathy-sound-manager.h"
45 #include "empathy-ui-utils.h"
46 #include "empathy-utils.h"
47
48 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
49 #include "empathy-debug.h"
50
51 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
52  */
53 #define X_EARLIER_OR_EQL(t1, t2) \
54   ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2))  \
55     || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
56   )
57
58 enum
59 {
60   PROP_INDIVIDUAL_MGR = 1
61 };
62
63 struct _EmpathyChatWindowPriv
64 {
65   EmpathyChat *current_chat;
66   GList *chats;
67   gboolean page_added;
68   gboolean dnd_same_window;
69   EmpathyChatroomManager *chatroom_manager;
70   EmpathyNotifyManager *notify_mgr;
71   EmpathyIndividualManager *individual_mgr;
72   GtkWidget *notebook;
73   NotifyNotification *notification;
74
75   GtkTargetList *contact_targets;
76   GtkTargetList *file_targets;
77
78   EmpathyChatManager *chat_manager;
79   gulong chat_manager_chats_changed_id;
80
81   /* Menu items. */
82   GtkUIManager *ui_manager;
83   GtkAction *menu_conv_insert_smiley;
84   GtkAction *menu_conv_favorite;
85   GtkAction *menu_conv_join_chat;
86   GtkAction *menu_conv_leave_chat;
87   GtkAction *menu_conv_always_urgent;
88   GtkAction *menu_conv_toggle_contacts;
89
90   GtkAction *menu_edit_cut;
91   GtkAction *menu_edit_copy;
92   GtkAction *menu_edit_paste;
93   GtkAction *menu_edit_find;
94
95   GtkAction *menu_tabs_next;
96   GtkAction *menu_tabs_prev;
97   GtkAction *menu_tabs_undo_close_tab;
98   GtkAction *menu_tabs_left;
99   GtkAction *menu_tabs_right;
100   GtkAction *menu_tabs_detach;
101
102   /* Last user action time we acted upon to show a tab */
103   guint32 x_user_action_time;
104
105   GSettings *gsettings_chat;
106   GSettings *gsettings_notif;
107   GSettings *gsettings_ui;
108
109   EmpathySoundManager *sound_mgr;
110
111   gboolean updating_menu;
112 };
113
114 static GList *chat_windows = NULL;
115
116 static const guint tab_accel_keys[] =
117 {
118   GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
119   GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
120 };
121
122 typedef enum
123 {
124   DND_DRAG_TYPE_CONTACT_ID,
125   DND_DRAG_TYPE_INDIVIDUAL_ID,
126   DND_DRAG_TYPE_URI_LIST,
127   DND_DRAG_TYPE_TAB
128 } DndDragType;
129
130 static const GtkTargetEntry drag_types_dest[] =
131 {
132   { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
133   { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
134   { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
135   /* FIXME: disabled because of bug #640513
136   { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
137   { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
138   */
139 };
140
141 static const GtkTargetEntry drag_types_dest_contact[] =
142 {
143   { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
144   { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
145 };
146
147 static const GtkTargetEntry drag_types_dest_file[] =
148 {
149   /* must be first to be prioritized, in order to receive the
150    * note's file path from Tomboy instead of an URI */
151   { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
152   { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
153 };
154
155 static void chat_window_update (EmpathyChatWindow *window,
156     gboolean update_contact_menu);
157
158 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
159     EmpathyChat *chat);
160
161 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
162     EmpathyChat *chat);
163
164 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
165     EmpathyChatWindow *new_window,
166     EmpathyChat *chat);
167
168 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
169     guint *nb_rooms,
170     guint *nb_private);
171
172 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, GTK_TYPE_WINDOW)
173
174 static void
175 chat_window_accel_cb (GtkAccelGroup *accelgroup,
176     GObject *object,
177     guint key,
178     GdkModifierType mod,
179     EmpathyChatWindow *self)
180 {
181   gint num = -1;
182   guint i;
183
184   for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
185     {
186       if (tab_accel_keys[i] == key)
187         {
188           num = i;
189           break;
190         }
191     }
192
193   if (num != -1)
194     gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), num);
195 }
196
197 static EmpathyChatWindow *
198 chat_window_find_chat (EmpathyChat *chat)
199 {
200   GList *l, *ll;
201
202   for (l = chat_windows; l; l = l->next)
203     {
204       EmpathyChatWindow *window = l->data;
205
206       ll = g_list_find (window->priv->chats, chat);
207       if (ll)
208         return l->data;
209     }
210
211   return NULL;
212 }
213
214 static void
215 remove_all_chats (EmpathyChatWindow *self)
216 {
217   g_object_ref (self);
218
219   while (self->priv->chats)
220     empathy_chat_window_remove_chat (self, self->priv->chats->data);
221
222   g_object_unref (self);
223 }
224
225 static void
226 confirm_close_response_cb (GtkWidget *dialog,
227     int response,
228     EmpathyChatWindow *window)
229 {
230   EmpathyChat *chat;
231
232   chat = g_object_get_data (G_OBJECT (dialog), "chat");
233
234   gtk_widget_destroy (dialog);
235
236   if (response != GTK_RESPONSE_ACCEPT)
237     return;
238
239   if (chat != NULL)
240     empathy_chat_window_remove_chat (window, chat);
241   else
242     remove_all_chats (window);
243 }
244
245 static void
246 confirm_close (EmpathyChatWindow *self,
247     gboolean close_window,
248     guint n_rooms,
249     EmpathyChat *chat)
250 {
251   GtkWidget *dialog;
252   gchar *primary, *secondary;
253
254   g_return_if_fail (n_rooms > 0);
255
256   if (n_rooms > 1)
257     g_return_if_fail (chat == NULL);
258   else
259     g_return_if_fail (chat != NULL);
260
261   /* If there are no chats in this window, how could we possibly have got
262    * here?
263    */
264   g_return_if_fail (self->priv->chats != NULL);
265
266   /* Treat closing a window which only has one tab exactly like closing
267    * that tab.
268    */
269   if (close_window && self->priv->chats->next == NULL)
270     {
271       close_window = FALSE;
272       chat = self->priv->chats->data;
273     }
274
275   if (close_window)
276     {
277       primary = g_strdup (_("Close this window?"));
278
279       if (n_rooms == 1)
280         {
281           gchar *chat_name = empathy_chat_dup_name (chat);
282           secondary = g_strdup_printf (
283             _("Closing this window will leave %s. You will "
284               "not receive any further messages until you "
285               "rejoin it."),
286             chat_name);
287           g_free (chat_name);
288         }
289       else
290         {
291           secondary = g_strdup_printf (
292             /* Note to translators: the number of chats will
293              * always be at least 2.
294              */
295             ngettext (
296               "Closing this window will leave a chat room. You will "
297               "not receive any further messages until you rejoin it.",
298               "Closing this window will leave %u chat rooms. You will "
299               "not receive any further messages until you rejoin them.",
300               n_rooms),
301             n_rooms);
302         }
303     }
304   else
305     {
306       gchar *chat_name = empathy_chat_dup_name (chat);
307       primary = g_strdup_printf (_("Leave %s?"), chat_name);
308       secondary = g_strdup (
309           _("You will not receive any further messages from this chat "
310             "room until you rejoin it."));
311       g_free (chat_name);
312     }
313
314   dialog = gtk_message_dialog_new (
315     GTK_WINDOW (self),
316     GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
317     GTK_MESSAGE_WARNING,
318     GTK_BUTTONS_CANCEL,
319     "%s", primary);
320
321   gtk_window_set_title (GTK_WINDOW (dialog), "");
322   g_object_set (dialog, "secondary-text", secondary, NULL);
323
324   g_free (primary);
325   g_free (secondary);
326
327   gtk_dialog_add_button (GTK_DIALOG (dialog),
328     close_window ? _("Close window") : _("Leave room"),
329     GTK_RESPONSE_ACCEPT);
330   gtk_dialog_set_default_response (GTK_DIALOG (dialog),
331     GTK_RESPONSE_ACCEPT);
332
333   if (!close_window)
334     g_object_set_data (G_OBJECT (dialog), "chat", chat);
335
336   g_signal_connect (dialog, "response",
337     G_CALLBACK (confirm_close_response_cb), self);
338
339   gtk_window_present (GTK_WINDOW (dialog));
340 }
341
342 /* Returns TRUE if we should check if the user really wants to leave. If it's
343  * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
344  * the user is actually in the room as opposed to having been kicked or gone
345  * offline or something), then we should check.
346  */
347 static gboolean
348 chat_needs_close_confirmation (EmpathyChat *chat)
349 {
350   return (empathy_chat_is_room (chat) &&
351       empathy_chat_get_tp_chat (chat) != NULL);
352 }
353
354 static void
355 maybe_close_chat (EmpathyChatWindow *window,
356     EmpathyChat *chat)
357 {
358   g_return_if_fail (chat != NULL);
359
360   if (chat_needs_close_confirmation (chat))
361     confirm_close (window, FALSE, 1, chat);
362   else
363     empathy_chat_window_remove_chat (window, chat);
364 }
365
366 static void
367 chat_window_close_clicked_cb (GtkAction *action,
368     EmpathyChat *chat)
369 {
370   EmpathyChatWindow *window;
371
372   window = chat_window_find_chat (chat);
373   maybe_close_chat (window, chat);
374 }
375
376 static void
377 chat_tab_style_updated_cb (GtkWidget *hbox,
378     gpointer user_data)
379 {
380   GtkWidget *button;
381   int char_width, h, w;
382   PangoContext *context;
383   const PangoFontDescription *font_desc;
384   PangoFontMetrics *metrics;
385
386   button = g_object_get_data (G_OBJECT (user_data),
387     "chat-window-tab-close-button");
388   context = gtk_widget_get_pango_context (hbox);
389
390   font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
391       GTK_STATE_FLAG_NORMAL);
392
393   metrics = pango_context_get_metrics (context, font_desc,
394     pango_context_get_language (context));
395   char_width = pango_font_metrics_get_approximate_char_width (metrics);
396   pango_font_metrics_unref (metrics);
397
398   gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
399       GTK_ICON_SIZE_MENU, &w, &h);
400
401   /* Request at least about 12 chars width plus at least space for the status
402    * image and the close button */
403   gtk_widget_set_size_request (hbox,
404     12 * PANGO_PIXELS (char_width) + 2 * w, -1);
405
406   gtk_widget_set_size_request (button, w, h);
407 }
408
409 static GtkWidget *
410 create_close_button (void)
411 {
412   GtkWidget *button, *image;
413   GtkStyleContext *context;
414
415   button = gtk_button_new ();
416
417   context = gtk_widget_get_style_context (button);
418   gtk_style_context_add_class (context, "empathy-tab-close-button");
419
420   gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
421   gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
422
423   /* We don't want focus/keynav for the button to avoid clutter, and
424    * Ctrl-W works anyway.
425    */
426   gtk_widget_set_can_focus (button, FALSE);
427   gtk_widget_set_can_default (button, FALSE);
428
429   image = gtk_image_new_from_icon_name ("window-close-symbolic",
430       GTK_ICON_SIZE_MENU);
431   gtk_widget_show (image);
432
433   gtk_container_add (GTK_CONTAINER (button), image);
434
435   return button;
436 }
437
438 static GtkWidget *
439 chat_window_create_label (EmpathyChatWindow *window,
440     EmpathyChat *chat,
441     gboolean is_tab_label)
442 {
443   GtkWidget *hbox;
444   GtkWidget *name_label;
445   GtkWidget *status_image;
446   GtkWidget *event_box;
447   GtkWidget *event_box_hbox;
448   PangoAttrList *attr_list;
449   PangoAttribute *attr;
450
451   /* The spacing between the button and the label. */
452   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
453
454   event_box = gtk_event_box_new ();
455   gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
456
457   name_label = gtk_label_new (NULL);
458   if (is_tab_label)
459     gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
460
461   attr_list = pango_attr_list_new ();
462   attr = pango_attr_scale_new (1/1.2);
463   attr->start_index = 0;
464   attr->end_index = -1;
465   pango_attr_list_insert (attr_list, attr);
466   gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
467   pango_attr_list_unref (attr_list);
468
469   gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
470   gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
471   g_object_set_data (G_OBJECT (chat),
472     is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
473     name_label);
474
475   status_image = gtk_image_new ();
476
477   /* Spacing between the icon and label. */
478   event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
479
480   gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
481   gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
482
483   g_object_set_data (G_OBJECT (chat),
484     is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
485     status_image);
486   g_object_set_data (G_OBJECT (chat),
487     is_tab_label ? "chat-window-tab-tooltip-widget" :
488       "chat-window-menu-tooltip-widget",
489     event_box);
490
491   gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
492   gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
493
494   if (is_tab_label)
495     {
496       GtkWidget *close_button;
497       GtkWidget *sending_spinner;
498
499       sending_spinner = gtk_spinner_new ();
500
501       gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
502         FALSE, FALSE, 0);
503       g_object_set_data (G_OBJECT (chat),
504         "chat-window-tab-sending-spinner",
505         sending_spinner);
506
507       close_button = create_close_button ();
508       g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button",
509           close_button);
510
511       gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
512
513       g_signal_connect (close_button,
514           "clicked",
515           G_CALLBACK (chat_window_close_clicked_cb), chat);
516
517       /* React to theme changes and also setup the size correctly. */
518       g_signal_connect (hbox, "style-updated",
519           G_CALLBACK (chat_tab_style_updated_cb), chat);
520     }
521
522   gtk_widget_show_all (hbox);
523
524   return hbox;
525 }
526
527 static void
528 _submenu_notify_visible_changed_cb (GObject *object,
529     GParamSpec *pspec,
530     gpointer userdata)
531 {
532   g_signal_handlers_disconnect_by_func (object,
533       _submenu_notify_visible_changed_cb, userdata);
534
535   chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
536 }
537
538 static void
539 chat_window_menu_context_update (EmpathyChatWindow *self,
540     gint num_pages)
541 {
542   gboolean first_page;
543   gboolean last_page;
544   gboolean wrap_around;
545   gboolean is_connected;
546   gint page_num;
547
548   page_num = gtk_notebook_get_current_page (
549       GTK_NOTEBOOK (self->priv->notebook));
550   first_page = (page_num == 0);
551   last_page = (page_num == (num_pages - 1));
552   g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
553       &wrap_around, NULL);
554   is_connected = empathy_chat_get_tp_chat (self->priv->current_chat) != NULL;
555
556   gtk_action_set_sensitive (self->priv->menu_tabs_next, (!last_page ||
557         wrap_around));
558   gtk_action_set_sensitive (self->priv->menu_tabs_prev, (!first_page ||
559         wrap_around));
560   gtk_action_set_sensitive (self->priv->menu_tabs_detach, num_pages > 1);
561   gtk_action_set_sensitive (self->priv->menu_tabs_left, !first_page);
562   gtk_action_set_sensitive (self->priv->menu_tabs_right, !last_page);
563   gtk_action_set_sensitive (self->priv->menu_conv_insert_smiley, is_connected);
564 }
565
566 static void
567 chat_window_conversation_menu_update (EmpathyChatWindow *self)
568 {
569   EmpathyTpChat *tp_chat;
570   TpConnection *connection;
571   GtkAction *action;
572   gboolean sensitive = FALSE;
573
574   g_return_if_fail (self->priv->current_chat != NULL);
575
576   action = gtk_ui_manager_get_action (self->priv->ui_manager,
577     "/chats_menubar/menu_conv/menu_conv_invite_participant");
578   tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
579
580   if (tp_chat != NULL)
581     {
582       connection = tp_channel_get_connection (TP_CHANNEL (tp_chat));
583
584       sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
585         (tp_connection_get_status (connection, NULL) ==
586          TP_CONNECTION_STATUS_CONNECTED);
587     }
588
589   gtk_action_set_sensitive (action, sensitive);
590 }
591
592 static void
593 chat_window_contact_menu_update (EmpathyChatWindow *self)
594 {
595   GtkWidget *menu, *submenu, *orig_submenu;
596
597   if (self->priv->updating_menu)
598     return;
599   self->priv->updating_menu = TRUE;
600
601   menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
602     "/chats_menubar/menu_contact");
603   orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
604
605   if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu))
606     {
607       submenu = empathy_chat_get_contact_menu (self->priv->current_chat);
608
609       if (submenu != NULL)
610         {
611           /* gtk_menu_attach_to_widget () doesn't behave nicely here */
612           g_object_set_data (G_OBJECT (submenu), "window", self);
613
614           gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
615           gtk_widget_show (menu);
616           gtk_widget_set_sensitive (menu, TRUE);
617         }
618       else
619         {
620           gtk_widget_set_sensitive (menu, FALSE);
621         }
622     }
623   else
624     {
625       tp_g_signal_connect_object (orig_submenu,
626           "notify::visible",
627           (GCallback)_submenu_notify_visible_changed_cb, self, 0);
628     }
629
630   self->priv->updating_menu = FALSE;
631 }
632
633 static guint
634 get_all_unread_messages (EmpathyChatWindow *self)
635 {
636   GList *l;
637   guint nb = 0;
638
639   for (l = self->priv->chats; l != NULL; l = g_list_next (l))
640     nb += empathy_chat_get_nb_unread_messages (EMPATHY_CHAT (l->data));
641
642   return nb;
643 }
644
645 static gchar *
646 get_window_title_name (EmpathyChatWindow *self)
647 {
648   gchar *active_name, *ret;
649   guint nb_chats;
650   guint current_unread_msgs;
651
652   nb_chats = g_list_length (self->priv->chats);
653   g_assert (nb_chats > 0);
654
655   active_name = empathy_chat_dup_name (self->priv->current_chat);
656
657   current_unread_msgs = empathy_chat_get_nb_unread_messages (
658       self->priv->current_chat);
659
660   if (nb_chats == 1)
661     {
662       /* only one tab */
663       if (current_unread_msgs == 0)
664         ret = g_strdup (active_name);
665       else
666         ret = g_strdup_printf (ngettext (
667           "%s (%d unread)",
668           "%s (%d unread)", current_unread_msgs),
669           active_name, current_unread_msgs);
670     }
671   else
672     {
673       guint nb_others = nb_chats - 1;
674       guint all_unread_msgs;
675
676       all_unread_msgs = get_all_unread_messages (self);
677
678       if (all_unread_msgs == 0)
679         {
680           /* no unread message */
681           ret = g_strdup_printf (ngettext (
682             "%s (and %u other)",
683             "%s (and %u others)", nb_others),
684             active_name, nb_others);
685         }
686       else if (all_unread_msgs == current_unread_msgs)
687         {
688           /* unread messages are in the current tab */
689           ret = g_strdup_printf (ngettext (
690             "%s (%d unread)",
691             "%s (%d unread)", current_unread_msgs),
692             active_name, current_unread_msgs);
693         }
694       else if (current_unread_msgs == 0)
695         {
696           /* unread messages are in other tabs */
697           ret = g_strdup_printf (ngettext (
698             "%s (%d unread from others)",
699             "%s (%d unread from others)",
700             all_unread_msgs),
701             active_name, all_unread_msgs);
702         }
703       else
704         {
705           /* unread messages are in all the tabs */
706           ret = g_strdup_printf (ngettext (
707             "%s (%d unread from all)",
708             "%s (%d unread from all)",
709             all_unread_msgs),
710             active_name, all_unread_msgs);
711         }
712     }
713
714   g_free (active_name);
715
716   return ret;
717 }
718
719 static void
720 chat_window_title_update (EmpathyChatWindow *self)
721 {
722   gchar *name;
723
724   name = get_window_title_name (self);
725   gtk_window_set_title (GTK_WINDOW (self), name);
726   g_free (name);
727 }
728
729 static void
730 chat_window_icon_update (EmpathyChatWindow *self,
731     gboolean new_messages)
732 {
733   GdkPixbuf *icon;
734   EmpathyContact *remote_contact;
735   gboolean avatar_in_icon;
736   guint n_chats;
737
738   n_chats = g_list_length (self->priv->chats);
739
740   /* Update window icon */
741   if (new_messages)
742     {
743       gtk_window_set_icon_name (GTK_WINDOW (self),
744           EMPATHY_IMAGE_MESSAGE);
745     }
746   else
747     {
748       avatar_in_icon = g_settings_get_boolean (self->priv->gsettings_chat,
749           EMPATHY_PREFS_CHAT_AVATAR_IN_ICON);
750
751       if (n_chats == 1 && avatar_in_icon)
752         {
753           remote_contact = empathy_chat_get_remote_contact (self->priv->current_chat);
754           icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact,
755               0, 0);
756           gtk_window_set_icon (GTK_WINDOW (self), icon);
757
758           if (icon != NULL)
759             g_object_unref (icon);
760         }
761       else
762         {
763           gtk_window_set_icon_name (GTK_WINDOW (self), NULL);
764         }
765     }
766 }
767
768 static void
769 chat_window_close_button_update (EmpathyChatWindow *self,
770     gint num_pages)
771 {
772   GtkWidget *chat;
773   GtkWidget *chat_close_button;
774   gint i;
775
776   if (num_pages == 1)
777     {
778       chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), 0);
779       chat_close_button = g_object_get_data (G_OBJECT (chat),
780           "chat-window-tab-close-button");
781       gtk_widget_hide (chat_close_button);
782     }
783   else
784     {
785       for (i=0; i<num_pages; i++)
786         {
787           chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (self->priv->notebook), i);
788           chat_close_button = g_object_get_data (G_OBJECT (chat),
789               "chat-window-tab-close-button");
790           gtk_widget_show (chat_close_button);
791         }
792     }
793 }
794
795 static void
796 chat_window_update (EmpathyChatWindow *self,
797     gboolean update_contact_menu)
798 {
799   gint num_pages;
800
801   num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
802
803   /* Update Tab menu */
804   chat_window_menu_context_update (self, num_pages);
805
806   chat_window_conversation_menu_update (self);
807
808   /* If this update is due to a focus-in event, we know the menu will be
809      the same as when we last left it, so no work to do. Besides, if we
810      swap out the menu on a focus-in, we may confuse any external global
811      menu watching. */
812   if (update_contact_menu)
813     {
814       chat_window_contact_menu_update (self);
815     }
816
817   chat_window_title_update (self);
818
819   chat_window_icon_update (self, get_all_unread_messages (self) > 0);
820
821   chat_window_close_button_update (self, num_pages);
822 }
823
824 static void
825 append_markup_printf (GString *string,
826     const char *format,
827     ...)
828 {
829   gchar *tmp;
830   va_list args;
831
832   va_start (args, format);
833
834   tmp = g_markup_vprintf_escaped (format, args);
835   g_string_append (string, tmp);
836   g_free (tmp);
837
838   va_end (args);
839 }
840
841 static void
842 chat_window_update_chat_tab_full (EmpathyChat *chat,
843     gboolean update_contact_menu)
844 {
845   EmpathyChatWindow *self;
846   EmpathyContact *remote_contact;
847   gchar *name;
848   const gchar *id;
849   TpAccount *account;
850   const gchar *subject;
851   const gchar *status = NULL;
852   GtkWidget *widget;
853   GString *tooltip;
854   gchar *markup;
855   const gchar *icon_name;
856   GtkWidget *tab_image;
857   GtkWidget *menu_image;
858   GtkWidget *sending_spinner;
859   guint nb_sending;
860
861   self = chat_window_find_chat (chat);
862   if (!self)
863     return;
864
865   /* Get information */
866   name = empathy_chat_dup_name (chat);
867   account = empathy_chat_get_account (chat);
868   subject = empathy_chat_get_subject (chat);
869   remote_contact = empathy_chat_get_remote_contact (chat);
870
871   DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, "
872       "remote_contact=%p",
873     name, tp_proxy_get_object_path (account), subject, remote_contact);
874
875   /* Update tab image */
876   if (empathy_chat_get_tp_chat (chat) == NULL)
877     {
878       /* No TpChat, we are disconnected */
879       icon_name = NULL;
880     }
881   else if (empathy_chat_get_nb_unread_messages (chat) > 0)
882     {
883       icon_name = EMPATHY_IMAGE_MESSAGE;
884     }
885   else if (remote_contact && empathy_chat_is_composing (chat))
886     {
887       icon_name = EMPATHY_IMAGE_TYPING;
888     }
889   else if (empathy_chat_is_sms_channel (chat))
890     {
891       icon_name = EMPATHY_IMAGE_SMS;
892     }
893   else if (remote_contact)
894     {
895       icon_name = empathy_icon_name_for_contact (remote_contact);
896     }
897   else
898     {
899       icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
900     }
901
902   tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
903   menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
904
905   if (icon_name != NULL)
906     {
907       gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name,
908           GTK_ICON_SIZE_MENU);
909       gtk_widget_show (tab_image);
910       gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name,
911           GTK_ICON_SIZE_MENU);
912       gtk_widget_show (menu_image);
913     }
914   else
915     {
916       gtk_widget_hide (tab_image);
917       gtk_widget_hide (menu_image);
918     }
919
920   /* Update the sending spinner */
921   nb_sending = empathy_chat_get_n_messages_sending (chat);
922   sending_spinner = g_object_get_data (G_OBJECT (chat),
923     "chat-window-tab-sending-spinner");
924
925   g_object_set (sending_spinner,
926     "active", nb_sending > 0,
927     "visible", nb_sending > 0,
928     NULL);
929
930   /* Update tab tooltip */
931   tooltip = g_string_new (NULL);
932
933   if (remote_contact)
934     {
935       id = empathy_contact_get_id (remote_contact);
936       status = empathy_contact_get_presence_message (remote_contact);
937     }
938   else
939     {
940       id = name;
941     }
942
943   if (empathy_chat_is_sms_channel (chat))
944     append_markup_printf (tooltip, "%s ", _("SMS:"));
945
946   append_markup_printf (tooltip, "<b>%s</b><small> (%s)</small>",
947       id, tp_account_get_display_name (account));
948
949   if (nb_sending > 0)
950     {
951       char *tmp = g_strdup_printf (
952         ngettext ("Sending %d message",
953             "Sending %d messages",
954             nb_sending),
955         nb_sending);
956
957       g_string_append (tooltip, "\n");
958       g_string_append (tooltip, tmp);
959
960       gtk_widget_set_tooltip_text (sending_spinner, tmp);
961       g_free (tmp);
962     }
963
964   if (!EMP_STR_EMPTY (status))
965     append_markup_printf (tooltip, "\n<i>%s</i>", status);
966
967   if (!EMP_STR_EMPTY (subject))
968     append_markup_printf (tooltip, "\n<b>%s</b> %s",
969         _("Topic:"), subject);
970
971   if (remote_contact && empathy_chat_is_composing (chat))
972     append_markup_printf (tooltip, "\n%s", _("Typing a message."));
973
974   if (remote_contact != NULL)
975     {
976       const gchar * const *types;
977
978       types = empathy_contact_get_client_types (remote_contact);
979       if (empathy_client_types_contains_mobile_device ((GStrv) types))
980         {
981           /* I'm on a mobile device ! */
982           gchar *tmp = name;
983
984           name = g_strdup_printf ("☎ %s", name);
985           g_free (tmp);
986         }
987     }
988
989   markup = g_string_free (tooltip, FALSE);
990   widget = g_object_get_data (G_OBJECT (chat),
991       "chat-window-tab-tooltip-widget");
992   gtk_widget_set_tooltip_markup (widget, markup);
993
994   widget = g_object_get_data (G_OBJECT (chat),
995       "chat-window-menu-tooltip-widget");
996   gtk_widget_set_tooltip_markup (widget, markup);
997   g_free (markup);
998
999   /* Update tab and menu label */
1000   if (empathy_chat_is_highlighted (chat))
1001     {
1002       markup = g_markup_printf_escaped (
1003         "<span color=\"red\" weight=\"bold\">%s</span>",
1004         name);
1005     }
1006   else
1007     {
1008       markup = g_markup_escape_text (name, -1);
1009     }
1010
1011   widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1012   gtk_label_set_markup (GTK_LABEL (widget), markup);
1013   widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
1014   gtk_label_set_markup (GTK_LABEL (widget), markup);
1015   g_free (markup);
1016
1017   /* Update the window if it's the current chat */
1018   if (self->priv->current_chat == chat)
1019     chat_window_update (self, update_contact_menu);
1020
1021   g_free (name);
1022 }
1023
1024 static void
1025 chat_window_update_chat_tab (EmpathyChat *chat)
1026 {
1027   chat_window_update_chat_tab_full (chat, TRUE);
1028 }
1029
1030 static void
1031 chat_window_chat_notify_cb (EmpathyChat *chat)
1032 {
1033   EmpathyChatWindow *window;
1034   EmpathyContact *old_remote_contact;
1035   EmpathyContact *remote_contact = NULL;
1036
1037   old_remote_contact = g_object_get_data (G_OBJECT (chat),
1038       "chat-window-remote-contact");
1039   remote_contact = empathy_chat_get_remote_contact (chat);
1040
1041   if (old_remote_contact != remote_contact)
1042     {
1043       /* The remote-contact associated with the chat changed, we need
1044        * to keep track of any change of that contact and update the
1045        * window each time. */
1046       if (remote_contact)
1047         g_signal_connect_swapped (remote_contact, "notify",
1048             G_CALLBACK (chat_window_update_chat_tab), chat);
1049
1050       if (old_remote_contact)
1051         g_signal_handlers_disconnect_by_func (old_remote_contact,
1052             chat_window_update_chat_tab, chat);
1053
1054       g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
1055           g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
1056     }
1057
1058   chat_window_update_chat_tab (chat);
1059
1060   window = chat_window_find_chat (chat);
1061   if (window != NULL)
1062     chat_window_update (window, FALSE);
1063 }
1064
1065 static void
1066 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
1067     EmpathySmiley *smiley,
1068     gpointer user_data)
1069 {
1070   EmpathyChatWindow *self = user_data;
1071   EmpathyChat *chat;
1072   GtkTextBuffer *buffer;
1073   GtkTextIter iter;
1074
1075   chat = self->priv->current_chat;
1076
1077   buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1078   gtk_text_buffer_get_end_iter (buffer, &iter);
1079   gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1080 }
1081
1082 static void
1083 chat_window_conv_activate_cb (GtkAction *action,
1084     EmpathyChatWindow *self)
1085 {
1086   gboolean is_room;
1087   gboolean active;
1088   EmpathyContact *remote_contact = NULL;
1089   gboolean disconnected;
1090
1091   /* Favorite room menu */
1092   is_room = empathy_chat_is_room (self->priv->current_chat);
1093   if (is_room)
1094     {
1095       const gchar *room;
1096       TpAccount *account;
1097       gboolean found = FALSE;
1098       EmpathyChatroom *chatroom;
1099
1100       room = empathy_chat_get_id (self->priv->current_chat);
1101       account = empathy_chat_get_account (self->priv->current_chat);
1102       chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1103           account, room);
1104
1105       if (chatroom != NULL)
1106         found = empathy_chatroom_is_favorite (chatroom);
1107
1108       DEBUG ("This room %s favorite", found ? "is" : "is not");
1109       gtk_toggle_action_set_active (
1110         GTK_TOGGLE_ACTION (self->priv->menu_conv_favorite), found);
1111
1112       if (chatroom != NULL)
1113         found = empathy_chatroom_is_always_urgent (chatroom);
1114
1115       gtk_toggle_action_set_active (
1116           GTK_TOGGLE_ACTION (self->priv->menu_conv_always_urgent), found);
1117     }
1118
1119   gtk_action_set_visible (self->priv->menu_conv_favorite, is_room);
1120   gtk_action_set_visible (self->priv->menu_conv_always_urgent, is_room);
1121
1122   /* Show contacts menu */
1123   g_object_get (self->priv->current_chat,
1124       "remote-contact", &remote_contact,
1125       "show-contacts", &active,
1126       NULL);
1127
1128   if (remote_contact == NULL)
1129     {
1130       gtk_toggle_action_set_active (
1131         GTK_TOGGLE_ACTION (self->priv->menu_conv_toggle_contacts), active);
1132     }
1133
1134   /* Menu-items to be visible for MUCs only */
1135   gtk_action_set_visible (self->priv->menu_conv_toggle_contacts,
1136       (remote_contact == NULL));
1137
1138   disconnected = (empathy_chat_get_tp_chat (self->priv->current_chat) == NULL);
1139   if (disconnected)
1140     {
1141       gtk_action_set_visible (self->priv->menu_conv_join_chat, TRUE);
1142       gtk_action_set_visible (self->priv->menu_conv_leave_chat, FALSE);
1143     }
1144   else
1145     {
1146       TpChannel *channel = NULL;
1147       TpHandle  self_handle = 0;
1148
1149       channel = (TpChannel *) (empathy_chat_get_tp_chat (
1150           self->priv->current_chat));
1151       self_handle = tp_contact_get_handle (tp_channel_group_get_self_contact (
1152           channel));
1153       /* There is sometimes a lag between the members-changed signal
1154          emitted on tp-chat and invalidated signal being emitted on the channel.
1155          Leave Chat menu-item should be sensitive only till our self-handle is
1156          a part of channel-members */
1157       gtk_action_set_visible (self->priv->menu_conv_leave_chat,
1158           self_handle != 0);
1159
1160       /* Join Chat is insensitive for a connected chat */
1161       gtk_action_set_visible (self->priv->menu_conv_join_chat, FALSE);
1162     }
1163
1164   if (remote_contact != NULL)
1165     g_object_unref (remote_contact);
1166 }
1167
1168 static void
1169 chat_window_clear_activate_cb (GtkAction *action,
1170     EmpathyChatWindow *self)
1171 {
1172   empathy_chat_clear (self->priv->current_chat);
1173 }
1174
1175 static void
1176 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1177     EmpathyChatWindow *self)
1178 {
1179   gboolean active;
1180   TpAccount *account;
1181   gchar *name;
1182   const gchar *room;
1183   EmpathyChatroom *chatroom;
1184
1185   active = gtk_toggle_action_get_active (toggle_action);
1186   account = empathy_chat_get_account (self->priv->current_chat);
1187   room = empathy_chat_get_id (self->priv->current_chat);
1188   name = empathy_chat_dup_name (self->priv->current_chat);
1189
1190   chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1191       account, room, name);
1192
1193   empathy_chatroom_set_favorite (chatroom, active);
1194   g_object_unref (chatroom);
1195   g_free (name);
1196 }
1197
1198 static void
1199 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1200     EmpathyChatWindow *self)
1201 {
1202   gboolean active;
1203   TpAccount *account;
1204   gchar *name;
1205   const gchar *room;
1206   EmpathyChatroom *chatroom;
1207
1208   active = gtk_toggle_action_get_active (toggle_action);
1209   account = empathy_chat_get_account (self->priv->current_chat);
1210   room = empathy_chat_get_id (self->priv->current_chat);
1211   name = empathy_chat_dup_name (self->priv->current_chat);
1212
1213   chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1214       account, room, name);
1215
1216   empathy_chatroom_set_always_urgent (chatroom, active);
1217   g_object_unref (chatroom);
1218   g_free (name);
1219 }
1220
1221 static void
1222 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1223     EmpathyChatWindow *self)
1224 {
1225   gboolean active;
1226
1227   active = gtk_toggle_action_get_active (toggle_action);
1228
1229   empathy_chat_set_show_contacts (self->priv->current_chat, active);
1230 }
1231
1232 static void
1233 chat_window_invite_participant_activate_cb (GtkAction *action,
1234     EmpathyChatWindow *self)
1235 {
1236   GtkWidget *dialog;
1237   EmpathyTpChat *tp_chat;
1238   int response;
1239
1240   g_return_if_fail (self->priv->current_chat != NULL);
1241
1242   tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1243
1244   dialog = empathy_invite_participant_dialog_new (
1245       GTK_WINDOW (self), tp_chat);
1246
1247   gtk_widget_show (dialog);
1248
1249   response = gtk_dialog_run (GTK_DIALOG (dialog));
1250
1251   if (response == GTK_RESPONSE_ACCEPT)
1252     {
1253       TpContact *tp_contact;
1254       EmpathyContact *contact;
1255
1256       tp_contact = empathy_invite_participant_dialog_get_selected (
1257         EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1258       if (tp_contact == NULL)
1259         goto out;
1260
1261       contact = empathy_contact_dup_from_tp_contact (tp_contact);
1262
1263       empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1264
1265       g_object_unref (contact);
1266     }
1267
1268 out:
1269   gtk_widget_destroy (dialog);
1270 }
1271
1272 static void
1273 chat_window_join_chat_activate_cb (GtkAction *action,
1274     EmpathyChatWindow *self)
1275 {
1276     g_return_if_fail (self->priv->current_chat != NULL);
1277
1278     empathy_chat_join_muc (self->priv->current_chat,
1279         empathy_chat_get_id (self->priv->current_chat));
1280 }
1281
1282 static void
1283 chat_window_leave_chat_activate_cb (GtkAction *action,
1284     EmpathyChatWindow *self)
1285 {
1286     EmpathyTpChat * tp_chat;
1287
1288     g_return_if_fail (self->priv->current_chat != NULL);
1289
1290     tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1291     if (tp_chat != NULL)
1292         empathy_tp_chat_leave (tp_chat, "");
1293 }
1294
1295 static void
1296 chat_window_close_activate_cb (GtkAction *action,
1297     EmpathyChatWindow *self)
1298 {
1299   g_return_if_fail (self->priv->current_chat != NULL);
1300
1301   maybe_close_chat (self, self->priv->current_chat);
1302 }
1303
1304 static void
1305 chat_window_edit_activate_cb (GtkAction *action,
1306     EmpathyChatWindow *self)
1307 {
1308   GtkClipboard *clipboard;
1309   GtkTextBuffer *buffer;
1310   gboolean text_available;
1311
1312   g_return_if_fail (self->priv->current_chat != NULL);
1313
1314   if (!empathy_chat_get_tp_chat (self->priv->current_chat))
1315     {
1316       gtk_action_set_sensitive (self->priv->menu_edit_copy, FALSE);
1317       gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1318       gtk_action_set_sensitive (self->priv->menu_edit_paste, FALSE);
1319       return;
1320     }
1321
1322   buffer = gtk_text_view_get_buffer (
1323       GTK_TEXT_VIEW (self->priv->current_chat->input_text_view));
1324
1325   if (gtk_text_buffer_get_has_selection (buffer))
1326     {
1327       gtk_action_set_sensitive (self->priv->menu_edit_copy, TRUE);
1328       gtk_action_set_sensitive (self->priv->menu_edit_cut, TRUE);
1329     }
1330   else
1331     {
1332       gboolean selection;
1333
1334       selection = empathy_theme_adium_get_has_selection (
1335           self->priv->current_chat->view);
1336
1337       gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1338       gtk_action_set_sensitive (self->priv->menu_edit_copy, selection);
1339     }
1340
1341   clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1342   text_available = gtk_clipboard_wait_is_text_available (clipboard);
1343   gtk_action_set_sensitive (self->priv->menu_edit_paste, text_available);
1344 }
1345
1346 static void
1347 chat_window_cut_activate_cb (GtkAction *action,
1348     EmpathyChatWindow *self)
1349 {
1350   g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1351
1352   empathy_chat_cut (self->priv->current_chat);
1353 }
1354
1355 static void
1356 chat_window_copy_activate_cb (GtkAction *action,
1357     EmpathyChatWindow *self)
1358 {
1359   g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1360
1361   empathy_chat_copy (self->priv->current_chat);
1362 }
1363
1364 static void
1365 chat_window_paste_activate_cb (GtkAction *action,
1366     EmpathyChatWindow *self)
1367 {
1368   g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1369
1370   empathy_chat_paste (self->priv->current_chat);
1371 }
1372
1373 static void
1374 chat_window_find_activate_cb (GtkAction *action,
1375     EmpathyChatWindow *self)
1376 {
1377   g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1378
1379   empathy_chat_find (self->priv->current_chat);
1380 }
1381
1382 static void
1383 chat_window_tabs_next_activate_cb (GtkAction *action,
1384     EmpathyChatWindow *self)
1385 {
1386   gint index_, numPages;
1387   gboolean wrap_around;
1388
1389   g_object_get (gtk_settings_get_default (),
1390       "gtk-keynav-wrap-around", &wrap_around,
1391       NULL);
1392
1393   index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1394   numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1395
1396   if (index_ == (numPages - 1) && wrap_around)
1397     {
1398       gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), 0);
1399       return;
1400     }
1401
1402   gtk_notebook_next_page (GTK_NOTEBOOK (self->priv->notebook));
1403 }
1404
1405 static void
1406 chat_window_tabs_previous_activate_cb (GtkAction *action,
1407     EmpathyChatWindow *self)
1408 {
1409   gint index_, numPages;
1410   gboolean wrap_around;
1411
1412   g_object_get (gtk_settings_get_default (),
1413       "gtk-keynav-wrap-around", &wrap_around,
1414       NULL);
1415
1416   index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1417   numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1418
1419   if (index_ <= 0 && wrap_around)
1420     {
1421       gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
1422           numPages - 1);
1423       return;
1424     }
1425
1426   gtk_notebook_prev_page (GTK_NOTEBOOK (self->priv->notebook));
1427 }
1428
1429 static void
1430 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1431     EmpathyChatWindow *self)
1432 {
1433   empathy_chat_manager_undo_closed_chat (self->priv->chat_manager,
1434       empathy_get_current_action_time ());
1435 }
1436
1437 static void
1438 chat_window_tabs_left_activate_cb (GtkAction *action,
1439     EmpathyChatWindow *self)
1440 {
1441   EmpathyChat *chat;
1442   gint index_, num_pages;
1443
1444   chat = self->priv->current_chat;
1445   index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1446   if (index_ <= 0)
1447     return;
1448
1449   gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1450       index_ - 1);
1451
1452   num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1453   chat_window_menu_context_update (self, num_pages);
1454 }
1455
1456 static void
1457 chat_window_tabs_right_activate_cb (GtkAction *action,
1458     EmpathyChatWindow *self)
1459 {
1460   EmpathyChat *chat;
1461   gint index_, num_pages;
1462
1463   chat = self->priv->current_chat;
1464   index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1465
1466   gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1467       index_ + 1);
1468
1469   num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1470   chat_window_menu_context_update (self, num_pages);
1471 }
1472
1473 static EmpathyChatWindow *
1474 empathy_chat_window_new (void)
1475 {
1476   return g_object_new (EMPATHY_TYPE_CHAT_WINDOW,
1477       "default-width", 580,
1478       "default-height", 480,
1479       "title", _("Chat"),
1480       "role", "chat",
1481       NULL);
1482 }
1483
1484 static void
1485 chat_window_detach_activate_cb (GtkAction *action,
1486     EmpathyChatWindow *self)
1487 {
1488   EmpathyChatWindow *new_window;
1489   EmpathyChat *chat;
1490
1491   chat = self->priv->current_chat;
1492   new_window = empathy_chat_window_new ();
1493
1494   empathy_chat_window_move_chat (self, new_window, chat);
1495
1496   gtk_widget_show (GTK_WIDGET (new_window));
1497 }
1498
1499 static void
1500 chat_window_help_contents_activate_cb (GtkAction *action,
1501     EmpathyChatWindow *self)
1502 {
1503   empathy_url_show (GTK_WIDGET (self), "help:empathy");
1504 }
1505
1506 static void
1507 chat_window_help_about_activate_cb (GtkAction *action,
1508     EmpathyChatWindow *self)
1509 {
1510   empathy_about_dialog_new (GTK_WINDOW (self));
1511 }
1512
1513 static gboolean
1514 chat_window_delete_event_cb (GtkWidget *dialog,
1515     GdkEvent *event,
1516     EmpathyChatWindow *self)
1517 {
1518   EmpathyChat *chat = NULL;
1519   guint n_rooms = 0;
1520   GList *l;
1521
1522   DEBUG ("Delete event received");
1523
1524   for (l = self->priv->chats; l != NULL; l = l->next)
1525     {
1526       if (chat_needs_close_confirmation (l->data))
1527         {
1528           chat = l->data;
1529           n_rooms++;
1530         }
1531     }
1532
1533   if (n_rooms > 0)
1534     {
1535       confirm_close (self, TRUE, n_rooms, (n_rooms == 1 ? chat : NULL));
1536     }
1537   else
1538     {
1539       remove_all_chats (self);
1540     }
1541
1542   return TRUE;
1543 }
1544
1545 static void
1546 chat_window_composing_cb (EmpathyChat *chat,
1547     gboolean is_composing,
1548     EmpathyChatWindow *self)
1549 {
1550   chat_window_update_chat_tab (chat);
1551 }
1552
1553 static void
1554 chat_window_set_urgency_hint (EmpathyChatWindow *self,
1555     gboolean urgent)
1556 {
1557   gtk_window_set_urgency_hint (GTK_WINDOW (self), urgent);
1558 }
1559
1560 static void
1561 chat_window_notification_closed_cb (NotifyNotification *notify,
1562     EmpathyChatWindow *self)
1563 {
1564   g_object_unref (notify);
1565   if (self->priv->notification == notify)
1566     self->priv->notification = NULL;
1567 }
1568
1569 static void
1570 chat_window_show_or_update_notification (EmpathyChatWindow *self,
1571     EmpathyMessage *message,
1572     EmpathyChat *chat)
1573 {
1574   EmpathyContact *sender;
1575   const gchar *header;
1576   char *escaped;
1577   const char *body;
1578   GdkPixbuf *pixbuf;
1579   gboolean res, has_x_canonical_append;
1580   NotifyNotification *notification = self->priv->notification;
1581
1582   if (!empathy_notify_manager_notification_is_enabled (self->priv->notify_mgr))
1583     return;
1584
1585   res = g_settings_get_boolean (self->priv->gsettings_notif,
1586       EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1587
1588   if (!res)
1589     return;
1590
1591   sender = empathy_message_get_sender (message);
1592   header = empathy_contact_get_alias (sender);
1593   body = empathy_message_get_body (message);
1594   escaped = g_markup_escape_text (body, -1);
1595
1596   has_x_canonical_append = empathy_notify_manager_has_capability (
1597     self->priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1598
1599   if (notification != NULL && !has_x_canonical_append)
1600     {
1601       /* if the notification server supports x-canonical-append, it is
1602          better to not use notify_notification_update to avoid
1603          overwriting the current notification message */
1604       notify_notification_update (notification,
1605                 header, escaped, NULL);
1606     }
1607   else
1608     {
1609       /* if the notification server supports x-canonical-append,
1610          the hint will be added, so that the message from the
1611          just created notification will be automatically appended
1612          to an existing notification with the same title.
1613          In this way the previous message will not be lost: the new
1614          message will appear below it, in the same notification */
1615       const gchar *category = empathy_chat_is_room (chat)
1616         ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1617         : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1618
1619       notification = empathy_notify_manager_create_notification (header,
1620           escaped, NULL);
1621
1622       if (self->priv->notification == NULL)
1623         self->priv->notification = notification;
1624
1625       tp_g_signal_connect_object (notification, "closed",
1626             G_CALLBACK (chat_window_notification_closed_cb), self, 0);
1627
1628       if (has_x_canonical_append)
1629         {
1630           /* We have to set a not empty string to keep libnotify happy */
1631           notify_notification_set_hint_string (notification,
1632             EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1633         }
1634
1635       notify_notification_set_hint (notification,
1636           EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY, g_variant_new_string (category));
1637     }
1638
1639   pixbuf = empathy_notify_manager_get_pixbuf_for_notification (self->priv->notify_mgr,
1640     sender, EMPATHY_IMAGE_NEW_MESSAGE);
1641
1642   if (pixbuf != NULL)
1643     {
1644       notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1645       g_object_unref (pixbuf);
1646     }
1647
1648   notify_notification_show (notification, NULL);
1649
1650   g_free (escaped);
1651 }
1652
1653 static gboolean
1654 empathy_chat_window_has_focus (EmpathyChatWindow *self)
1655 {
1656   gboolean has_focus;
1657
1658   g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (self), FALSE);
1659
1660   g_object_get (self, "has-toplevel-focus", &has_focus, NULL);
1661
1662   return has_focus;
1663 }
1664
1665 static void
1666 chat_window_new_message_cb (EmpathyChat *chat,
1667     EmpathyMessage *message,
1668     gboolean pending,
1669     gboolean should_highlight,
1670     EmpathyChatWindow *self)
1671 {
1672   gboolean has_focus;
1673   gboolean needs_urgency;
1674   EmpathyContact *sender;
1675
1676   has_focus = empathy_chat_window_has_focus (self);
1677
1678   /* - if we're the sender, we play the sound if it's specified in the
1679    *   preferences and we're not away.
1680    * - if we receive a message, we play the sound if it's specified in the
1681    *   preferences and the window does not have focus on the chat receiving
1682    *   the message.
1683    */
1684
1685   sender = empathy_message_get_sender (message);
1686
1687   if (empathy_contact_is_user (sender))
1688     {
1689       empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
1690           EMPATHY_SOUND_MESSAGE_OUTGOING);
1691       return;
1692     }
1693
1694   if (has_focus && self->priv->current_chat == chat)
1695     {
1696       /* window and tab are focused so consider the message to be read */
1697
1698       /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1699       empathy_chat_messages_read (chat);
1700       return;
1701     }
1702
1703   /* Update the chat tab if this is the first unread message */
1704   if (empathy_chat_get_nb_unread_messages (chat) == 1)
1705     {
1706       chat_window_update_chat_tab (chat);
1707     }
1708
1709   /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1710    * If empathy_chat_get_remote_contact () returns NULL, that means it's
1711    * an unamed MUC (msn-like).
1712    * In case of a MUC, we set urgency if either:
1713    *   a) the chatroom's always_urgent property is TRUE
1714    *   b) the message contains our alias
1715    */
1716   if (empathy_chat_is_room (chat))
1717     {
1718       TpAccount *account;
1719       const gchar *room;
1720       EmpathyChatroom *chatroom;
1721
1722       account = empathy_chat_get_account (chat);
1723       room = empathy_chat_get_id (chat);
1724
1725       chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1726           account, room);
1727
1728       if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom))
1729         needs_urgency = TRUE;
1730       else
1731         needs_urgency = should_highlight;
1732     }
1733   else
1734     {
1735       needs_urgency = TRUE;
1736     }
1737
1738   if (needs_urgency)
1739     {
1740       if (!has_focus)
1741         chat_window_set_urgency_hint (self, TRUE);
1742
1743       /* Pending messages have already been displayed and notified in the
1744       * approver, so we don't display a notification and play a sound
1745       * for those */
1746       if (!pending)
1747         {
1748           empathy_sound_manager_play (self->priv->sound_mgr,
1749               GTK_WIDGET (self), EMPATHY_SOUND_MESSAGE_INCOMING);
1750
1751           chat_window_show_or_update_notification (self, message, chat);
1752         }
1753     }
1754
1755   /* update the number of unread messages and the window icon */
1756   chat_window_title_update (self);
1757   chat_window_icon_update (self, TRUE);
1758 }
1759
1760 static void
1761 chat_window_command_part (EmpathyChat *chat,
1762     GStrv strv)
1763 {
1764   EmpathyChat *chat_to_be_parted;
1765   EmpathyTpChat *tp_chat = NULL;
1766
1767   if (strv[1] == NULL)
1768     {
1769       /* No chatroom ID specified */
1770       tp_chat = empathy_chat_get_tp_chat (chat);
1771
1772       if (tp_chat)
1773         empathy_tp_chat_leave (tp_chat, "");
1774
1775       return;
1776     }
1777
1778   chat_to_be_parted = empathy_chat_window_find_chat (
1779     empathy_chat_get_account (chat), strv[1], FALSE);
1780
1781   if (chat_to_be_parted != NULL)
1782     {
1783       /* Found a chatroom matching the specified ID */
1784       tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1785
1786       if (tp_chat)
1787         empathy_tp_chat_leave (tp_chat, strv[2]);
1788     }
1789   else
1790     {
1791       gchar *message;
1792
1793       /* Going by the syntax of PART command:
1794        *
1795        * /PART [<chatroom-ID>] [<reason>]
1796        *
1797        * Chatroom-ID is not a must to specify a reason.
1798        * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1799        * MUC then the current chatroom should be parted and srtv[1] should
1800        * be treated as part of the optional part-message. */
1801       message = g_strconcat (strv[1], " ", strv[2], NULL);
1802       tp_chat = empathy_chat_get_tp_chat (chat);
1803
1804       if (tp_chat)
1805         empathy_tp_chat_leave (tp_chat, message);
1806
1807       g_free (message);
1808     }
1809 }
1810
1811 static GtkNotebook *
1812 notebook_create_window_cb (GtkNotebook *source,
1813     GtkWidget *page,
1814     gint x,
1815     gint y,
1816     gpointer user_data)
1817 {
1818   EmpathyChatWindow *window, *new_window;
1819   EmpathyChat *chat;
1820
1821   chat = EMPATHY_CHAT (page);
1822   window = chat_window_find_chat (chat);
1823
1824   new_window = empathy_chat_window_new ();
1825
1826   DEBUG ("Detach hook called");
1827
1828   empathy_chat_window_move_chat (window, new_window, chat);
1829
1830   gtk_widget_show (GTK_WIDGET (new_window));
1831   gtk_window_move (GTK_WINDOW (new_window), x, y);
1832
1833   return NULL;
1834 }
1835
1836 static void
1837 chat_window_page_switched_cb (GtkNotebook *notebook,
1838     GtkWidget *child,
1839     gint page_num,
1840     EmpathyChatWindow *self)
1841 {
1842   EmpathyChat *chat = EMPATHY_CHAT (child);
1843
1844   DEBUG ("Page switched");
1845
1846   if (self->priv->page_added)
1847     {
1848       self->priv->page_added = FALSE;
1849       empathy_chat_scroll_down (chat);
1850     }
1851   else if (self->priv->current_chat == chat)
1852     {
1853       return;
1854     }
1855
1856   self->priv->current_chat = chat;
1857   empathy_chat_messages_read (chat);
1858
1859   chat_window_update_chat_tab (chat);
1860 }
1861
1862 static void
1863 chat_window_page_added_cb (GtkNotebook *notebook,
1864     GtkWidget *child,
1865     guint page_num,
1866     EmpathyChatWindow *self)
1867 {
1868   EmpathyChat *chat;
1869
1870   /* If we just received DND to the same window, we don't want
1871    * to do anything here like removing the tab and then readding
1872    * it, so we return here and in "page-added".
1873    */
1874   if (self->priv->dnd_same_window)
1875     {
1876       DEBUG ("Page added (back to the same window)");
1877       self->priv->dnd_same_window = FALSE;
1878       return;
1879     }
1880
1881   DEBUG ("Page added");
1882
1883   /* Get chat object */
1884   chat = EMPATHY_CHAT (child);
1885
1886   /* Connect chat signals for this window */
1887   g_signal_connect (chat, "composing",
1888       G_CALLBACK (chat_window_composing_cb), self);
1889   g_signal_connect (chat, "new-message",
1890       G_CALLBACK (chat_window_new_message_cb), self);
1891   g_signal_connect (chat, "part-command-entered",
1892       G_CALLBACK (chat_window_command_part), NULL);
1893   g_signal_connect (chat, "notify::tp-chat",
1894       G_CALLBACK (chat_window_update_chat_tab), self);
1895
1896   /* Set flag so we know to perform some special operations on
1897    * switch page due to the new page being added.
1898    */
1899   self->priv->page_added = TRUE;
1900
1901   /* Get list of chats up to date */
1902   self->priv->chats = g_list_append (self->priv->chats, chat);
1903
1904   chat_window_update_chat_tab (chat);
1905 }
1906
1907 static void
1908 chat_window_page_removed_cb (GtkNotebook *notebook,
1909     GtkWidget *child,
1910     guint page_num,
1911     EmpathyChatWindow *self)
1912 {
1913   EmpathyChat *chat;
1914
1915   /* If we just received DND to the same window, we don't want
1916    * to do anything here like removing the tab and then readding
1917    * it, so we return here and in "page-added".
1918    */
1919   if (self->priv->dnd_same_window)
1920     {
1921       DEBUG ("Page removed (and will be readded to same window)");
1922       return;
1923     }
1924
1925   DEBUG ("Page removed");
1926
1927   /* Get chat object */
1928   chat = EMPATHY_CHAT (child);
1929
1930   /* Disconnect all signal handlers for this chat and this window */
1931   g_signal_handlers_disconnect_by_func (chat,
1932       G_CALLBACK (chat_window_composing_cb), self);
1933   g_signal_handlers_disconnect_by_func (chat,
1934       G_CALLBACK (chat_window_new_message_cb), self);
1935   g_signal_handlers_disconnect_by_func (chat,
1936       G_CALLBACK (chat_window_update_chat_tab), self);
1937
1938   /* Keep list of chats up to date */
1939   self->priv->chats = g_list_remove (self->priv->chats, chat);
1940   empathy_chat_messages_read (chat);
1941
1942   if (self->priv->chats == NULL)
1943     {
1944       gtk_widget_destroy (GTK_WIDGET (self));
1945     }
1946   else
1947     {
1948       chat_window_update (self, TRUE);
1949     }
1950 }
1951
1952 static gboolean
1953 chat_window_focus_in_event_cb (GtkWidget *widget,
1954     GdkEvent *event,
1955     EmpathyChatWindow *self)
1956 {
1957   empathy_chat_messages_read (self->priv->current_chat);
1958
1959   chat_window_set_urgency_hint (self, FALSE);
1960
1961   /* Update the title, since we now mark all unread messages as read. */
1962   chat_window_update_chat_tab_full (self->priv->current_chat, FALSE);
1963
1964   return FALSE;
1965 }
1966
1967 static void
1968 contacts_loaded_cb (EmpathyIndividualManager *mgr,
1969     EmpathyChatWindow *self)
1970 {
1971   chat_window_contact_menu_update (self);
1972 }
1973
1974 static gboolean
1975 chat_window_focus_out_event_cb (GtkWidget *widget,
1976     GdkEvent *event,
1977     EmpathyChatWindow *self)
1978 {
1979   if (self->priv->individual_mgr != NULL)
1980     return FALSE;
1981
1982   /* Keep the individual manager alive so we won't fetch everything from Folks
1983    * each time we need to use it. Loading FolksAggregator can takes quite a
1984    * while (if user has a huge LDAP abook for example) and it blocks
1985    * the mainloop during most of this loading. We workaround this by loading
1986    * it when the chat window has been unfocused and so, hopefully, not impact
1987    * the reactivity of the chat window too much.
1988    *
1989    * The individual manager (and so Folks) is needed to know to which
1990    * FolksIndividual a TpContact belongs, including:
1991    * - empathy_chat_get_contact_menu: to list all the personas of the contact
1992    * - empathy_display_individual_info: to invoke gnome-contacts with the
1993    *   FolksIndividual.id of the contact
1994    * - drag_data_received_individual_id: to find the individual associated
1995    *   with the ID we received from the DnD in order to invite him.
1996    */
1997   self->priv->individual_mgr = empathy_individual_manager_dup_singleton ();
1998
1999   if (!empathy_individual_manager_get_contacts_loaded (
2000       self->priv->individual_mgr))
2001     {
2002       /* We want to update the contact menu when Folks is loaded so we can
2003        * list all the personas of the contact. */
2004       tp_g_signal_connect_object (self->priv->individual_mgr, "contacts-loaded",
2005           G_CALLBACK (contacts_loaded_cb), self, 0);
2006     }
2007
2008   g_object_notify (G_OBJECT (self), "individual-manager");
2009
2010   return FALSE;
2011 }
2012
2013 static gboolean
2014 chat_window_drag_drop (GtkWidget *widget,
2015     GdkDragContext *context,
2016     int x,
2017     int y,
2018     guint time_,
2019     EmpathyChatWindow *self)
2020 {
2021   GdkAtom target;
2022
2023   target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
2024   if (target == GDK_NONE)
2025     target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
2026
2027   if (target != GDK_NONE)
2028     {
2029       gtk_drag_get_data (widget, context, target, time_);
2030       return TRUE;
2031     }
2032
2033   return FALSE;
2034 }
2035
2036 static gboolean
2037 chat_window_drag_motion (GtkWidget *widget,
2038     GdkDragContext *context,
2039     int x,
2040     int y,
2041     guint time_,
2042     EmpathyChatWindow *self)
2043 {
2044   GdkAtom target;
2045
2046   target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
2047
2048   if (target != GDK_NONE)
2049     {
2050       /* This is a file drag. Ensure the contact is online and set the
2051          drag type to COPY. Note that it's possible that the tab will
2052          be switched by GTK+ after a timeout from drag_motion without
2053          getting another drag_motion to disable the drop. You have
2054          to hold your mouse really still.
2055        */
2056       EmpathyContact *contact;
2057
2058       contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2059
2060       /* contact is NULL for multi-user chats. We don't do
2061        * file transfers to MUCs. We also don't send files
2062        * to offline contacts or contacts that don't support
2063        * file transfer.
2064        */
2065       if ((contact == NULL) || !empathy_contact_is_online (contact))
2066         {
2067           gdk_drag_status (context, 0, time_);
2068           return FALSE;
2069         }
2070
2071       if (!(empathy_contact_get_capabilities (contact)
2072            & EMPATHY_CAPABILITIES_FT))
2073         {
2074           gdk_drag_status (context, 0, time_);
2075           return FALSE;
2076         }
2077
2078       gdk_drag_status (context, GDK_ACTION_COPY, time_);
2079       return TRUE;
2080     }
2081
2082   target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
2083   if (target != GDK_NONE)
2084     {
2085       /* This is a drag of a contact from a contact list. Set to COPY.
2086          FIXME: If this drag is to a MUC window, it invites the user.
2087          Otherwise, it opens a chat. Should we use a different drag
2088          type for invites? Should we allow ASK?
2089        */
2090       gdk_drag_status (context, GDK_ACTION_COPY, time_);
2091       return TRUE;
2092     }
2093
2094   return FALSE;
2095 }
2096
2097 static void
2098 drag_data_received_individual_id (EmpathyChatWindow *self,
2099     GtkWidget *widget,
2100     GdkDragContext *context,
2101     int x,
2102     int y,
2103     GtkSelectionData *selection,
2104     guint info,
2105     guint time_)
2106 {
2107   const gchar *id;
2108   FolksIndividual *individual;
2109   EmpathyTpChat *chat;
2110   TpContact *tp_contact;
2111   TpConnection *conn;
2112   EmpathyContact *contact;
2113
2114   id = (const gchar *) gtk_selection_data_get_data (selection);
2115
2116   DEBUG ("DND invididual %s", id);
2117
2118   if (self->priv->current_chat == NULL)
2119     goto out;
2120
2121   chat = empathy_chat_get_tp_chat (self->priv->current_chat);
2122   if (chat == NULL)
2123     goto out;
2124
2125   if (!empathy_tp_chat_can_add_contact (chat))
2126     {
2127       DEBUG ("Can't invite contact to %s",
2128           tp_proxy_get_object_path (chat));
2129       goto out;
2130     }
2131
2132   if (self->priv->individual_mgr == NULL)
2133     /* Not likely as we have to focus out the chat window in order to start
2134      * the DnD but best to be safe. */
2135     goto out;
2136
2137   individual = empathy_individual_manager_lookup_member (
2138           self->priv->individual_mgr, id);
2139   if (individual == NULL)
2140     {
2141       DEBUG ("Failed to find individual %s", id);
2142       goto out;
2143     }
2144
2145   conn = tp_channel_get_connection ((TpChannel *) chat);
2146   tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2147   if (tp_contact == NULL)
2148     {
2149       DEBUG ("Can't find a TpContact on connection %s for %s",
2150           tp_proxy_get_object_path (conn), id);
2151       goto out;
2152     }
2153
2154   DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2155       tp_channel_get_identifier ((TpChannel *) chat));
2156
2157   contact = empathy_contact_dup_from_tp_contact (tp_contact);
2158   empathy_tp_chat_add (chat, contact, NULL);
2159   g_object_unref (contact);
2160
2161 out:
2162   gtk_drag_finish (context, TRUE, FALSE, time_);
2163 }
2164
2165 static void
2166 chat_window_drag_data_received (GtkWidget *widget,
2167     GdkDragContext *context,
2168     int x,
2169     int y,
2170     GtkSelectionData *selection,
2171     guint info,
2172     guint time_,
2173     EmpathyChatWindow *self)
2174 {
2175   if (info == DND_DRAG_TYPE_CONTACT_ID)
2176     {
2177       EmpathyChat *chat = NULL;
2178       EmpathyChatWindow *old_window;
2179       TpAccount *account = NULL;
2180       EmpathyClientFactory *factory;
2181       const gchar *id;
2182       gchar **strv;
2183       const gchar *account_id;
2184       const gchar *contact_id;
2185
2186       id = (const gchar*) gtk_selection_data_get_data (selection);
2187
2188       factory = empathy_client_factory_dup ();
2189
2190       DEBUG ("DND contact from roster with id:'%s'", id);
2191
2192       strv = g_strsplit (id, ":", 2);
2193       if (g_strv_length (strv) == 2)
2194         {
2195           account_id = strv[0];
2196           contact_id = strv[1];
2197
2198           account = tp_simple_client_factory_ensure_account (
2199               TP_SIMPLE_CLIENT_FACTORY (factory), account_id, NULL, NULL);
2200
2201           g_object_unref (factory);
2202           if (account != NULL)
2203             chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2204         }
2205
2206       if (account == NULL)
2207         {
2208           g_strfreev (strv);
2209           gtk_drag_finish (context, FALSE, FALSE, time_);
2210           return;
2211         }
2212
2213       if (!chat)
2214         {
2215           empathy_chat_with_contact_id (account, contact_id,
2216               empathy_get_current_action_time (), NULL, NULL);
2217
2218           g_strfreev (strv);
2219           return;
2220         }
2221
2222       g_strfreev (strv);
2223
2224       old_window = chat_window_find_chat (chat);
2225       if (old_window)
2226         {
2227           if (old_window == self)
2228             {
2229               gtk_drag_finish (context, TRUE, FALSE, time_);
2230               return;
2231             }
2232
2233           empathy_chat_window_move_chat (old_window, self, chat);
2234         }
2235       else
2236         {
2237           empathy_chat_window_add_chat (self, chat);
2238         }
2239
2240       /* Added to take care of any outstanding chat events */
2241       empathy_chat_window_present_chat (chat,
2242           TP_USER_ACTION_TIME_NOT_USER_ACTION);
2243
2244       /* We should return TRUE to remove the data when doing
2245        * GDK_ACTION_MOVE, but we don't here otherwise it has
2246        * weird consequences, and we handle that internally
2247        * anyway with add_chat () and remove_chat ().
2248        */
2249       gtk_drag_finish (context, TRUE, FALSE, time_);
2250     }
2251   else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
2252     {
2253       drag_data_received_individual_id (self, widget, context, x, y,
2254           selection, info, time_);
2255     }
2256   else if (info == DND_DRAG_TYPE_URI_LIST)
2257     {
2258       EmpathyContact *contact;
2259       const gchar *data;
2260
2261       contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2262
2263       /* contact is NULL when current_chat is a multi-user chat.
2264        * We don't do file transfers to MUCs, so just cancel the drag.
2265        */
2266       if (contact == NULL)
2267         {
2268           gtk_drag_finish (context, TRUE, FALSE, time_);
2269           return;
2270         }
2271
2272       data = (const gchar *) gtk_selection_data_get_data (selection);
2273       empathy_send_file_from_uri_list (contact, data);
2274
2275       gtk_drag_finish (context, TRUE, FALSE, time_);
2276     }
2277   else if (info == DND_DRAG_TYPE_TAB)
2278     {
2279       EmpathyChat **chat;
2280       EmpathyChatWindow *old_window = NULL;
2281
2282       DEBUG ("DND tab");
2283
2284       chat = (void *) gtk_selection_data_get_data (selection);
2285       old_window = chat_window_find_chat (*chat);
2286
2287       if (old_window)
2288         {
2289           self->priv->dnd_same_window = (old_window == self);
2290
2291           DEBUG ("DND tab (within same window: %s)",
2292             self->priv->dnd_same_window ? "Yes" : "No");
2293         }
2294     }
2295   else
2296     {
2297       DEBUG ("DND from unknown source");
2298       gtk_drag_finish (context, FALSE, FALSE, time_);
2299     }
2300 }
2301
2302 static void
2303 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2304     guint num_chats_in_manager,
2305     EmpathyChatWindow *self)
2306 {
2307   gtk_action_set_sensitive (self->priv->menu_tabs_undo_close_tab,
2308       num_chats_in_manager > 0);
2309 }
2310
2311 static void
2312 chat_window_finalize (GObject *object)
2313 {
2314   EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2315
2316   DEBUG ("Finalized: %p", object);
2317
2318   g_object_unref (self->priv->ui_manager);
2319   g_object_unref (self->priv->chatroom_manager);
2320   g_object_unref (self->priv->notify_mgr);
2321   g_object_unref (self->priv->gsettings_chat);
2322   g_object_unref (self->priv->gsettings_notif);
2323   g_object_unref (self->priv->gsettings_ui);
2324   g_object_unref (self->priv->sound_mgr);
2325   g_clear_object (&self->priv->individual_mgr);
2326
2327   if (self->priv->notification != NULL)
2328     {
2329       notify_notification_close (self->priv->notification, NULL);
2330       self->priv->notification = NULL;
2331     }
2332
2333   if (self->priv->contact_targets)
2334     gtk_target_list_unref (self->priv->contact_targets);
2335
2336   if (self->priv->file_targets)
2337     gtk_target_list_unref (self->priv->file_targets);
2338
2339   if (self->priv->chat_manager)
2340     {
2341       g_signal_handler_disconnect (self->priv->chat_manager,
2342                  self->priv->chat_manager_chats_changed_id);
2343       g_object_unref (self->priv->chat_manager);
2344       self->priv->chat_manager = NULL;
2345     }
2346
2347   chat_windows = g_list_remove (chat_windows, self);
2348
2349   G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2350 }
2351
2352 static void
2353 chat_window_get_property (GObject *object,
2354     guint property_id,
2355     GValue *value,
2356     GParamSpec *pspec)
2357 {
2358   EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2359
2360   switch (property_id)
2361     {
2362       case PROP_INDIVIDUAL_MGR:
2363         g_value_set_object (value, self->priv->individual_mgr);
2364       default:
2365         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2366         break;
2367     }
2368 }
2369
2370 static void
2371 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2372 {
2373   GObjectClass *object_class = G_OBJECT_CLASS (klass);
2374   GParamSpec *spec;
2375
2376   object_class->get_property = chat_window_get_property;
2377   object_class->finalize = chat_window_finalize;
2378
2379   spec = g_param_spec_object ("individual-manager", "individual-manager",
2380       "EmpathyIndividualManager",
2381       EMPATHY_TYPE_INDIVIDUAL_MANAGER,
2382       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
2383   g_object_class_install_property (object_class, PROP_INDIVIDUAL_MGR, spec);
2384
2385   g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2386 }
2387
2388 static void
2389 empathy_chat_window_init (EmpathyChatWindow *self)
2390 {
2391   GtkBuilder *gui;
2392   GtkAccelGroup *accel_group;
2393   GClosure *closure;
2394   GtkWidget *menu;
2395   GtkWidget *submenu;
2396   guint i;
2397   GtkWidget *chat_vbox;
2398   gchar *filename;
2399   EmpathySmileyManager *smiley_manager;
2400
2401   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2402     EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2403
2404   filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2405   gui = empathy_builder_get_file (filename,
2406       "chat_vbox", &chat_vbox,
2407       "ui_manager", &self->priv->ui_manager,
2408       "menu_conv_insert_smiley", &self->priv->menu_conv_insert_smiley,
2409       "menu_conv_favorite", &self->priv->menu_conv_favorite,
2410       "menu_conv_join_chat", &self->priv->menu_conv_join_chat,
2411       "menu_conv_leave_chat", &self->priv->menu_conv_leave_chat,
2412       "menu_conv_always_urgent", &self->priv->menu_conv_always_urgent,
2413       "menu_conv_toggle_contacts", &self->priv->menu_conv_toggle_contacts,
2414       "menu_edit_cut", &self->priv->menu_edit_cut,
2415       "menu_edit_copy", &self->priv->menu_edit_copy,
2416       "menu_edit_paste", &self->priv->menu_edit_paste,
2417       "menu_edit_find", &self->priv->menu_edit_find,
2418       "menu_tabs_next", &self->priv->menu_tabs_next,
2419       "menu_tabs_prev", &self->priv->menu_tabs_prev,
2420       "menu_tabs_undo_close_tab", &self->priv->menu_tabs_undo_close_tab,
2421       "menu_tabs_left", &self->priv->menu_tabs_left,
2422       "menu_tabs_right", &self->priv->menu_tabs_right,
2423        "menu_tabs_detach", &self->priv->menu_tabs_detach,
2424       NULL);
2425   g_free (filename);
2426
2427   empathy_builder_connect (gui, self,
2428       "menu_conv", "activate", chat_window_conv_activate_cb,
2429       "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2430       "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2431       "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2432       "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2433       "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2434       "menu_conv_join_chat", "activate", chat_window_join_chat_activate_cb,
2435       "menu_conv_leave_chat", "activate", chat_window_leave_chat_activate_cb,
2436       "menu_conv_close", "activate", chat_window_close_activate_cb,
2437       "menu_edit", "activate", chat_window_edit_activate_cb,
2438       "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2439       "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2440       "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2441       "menu_edit_find", "activate", chat_window_find_activate_cb,
2442       "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2443       "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2444       "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2445       "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2446       "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2447       "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2448       "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2449       "menu_help_about", "activate", chat_window_help_about_activate_cb,
2450       NULL);
2451
2452   empathy_set_css_provider (GTK_WIDGET (self));
2453
2454   self->priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2455   self->priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2456   self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2457   self->priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2458
2459   self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2460
2461   self->priv->notebook = gtk_notebook_new ();
2462
2463   g_signal_connect (self->priv->notebook, "create-window",
2464       G_CALLBACK (notebook_create_window_cb), self);
2465
2466   gtk_container_add (GTK_CONTAINER (self), chat_vbox);
2467
2468   gtk_notebook_set_group_name (GTK_NOTEBOOK (self->priv->notebook),
2469     "EmpathyChatWindow");
2470   gtk_notebook_set_scrollable (GTK_NOTEBOOK (self->priv->notebook), TRUE);
2471   gtk_notebook_popup_enable (GTK_NOTEBOOK (self->priv->notebook));
2472   gtk_box_pack_start (GTK_BOX (chat_vbox), self->priv->notebook, TRUE, TRUE, 0);
2473   gtk_widget_show (self->priv->notebook);
2474
2475   /* Set up accels */
2476   accel_group = gtk_accel_group_new ();
2477   gtk_window_add_accel_group (GTK_WINDOW (self), accel_group);
2478
2479   for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
2480     {
2481       closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb), self,
2482           NULL);
2483
2484       gtk_accel_group_connect (accel_group, tab_accel_keys[i], GDK_MOD1_MASK, 0,
2485           closure);
2486     }
2487
2488   g_object_unref (accel_group);
2489
2490   /* Set up drag target lists */
2491   self->priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2492       G_N_ELEMENTS (drag_types_dest_contact));
2493
2494   self->priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2495       G_N_ELEMENTS (drag_types_dest_file));
2496
2497   /* Set up smiley menu */
2498   smiley_manager = empathy_smiley_manager_dup_singleton ();
2499   submenu = empathy_smiley_menu_new (smiley_manager,
2500       chat_window_insert_smiley_activate_cb, self);
2501
2502   menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
2503     "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2504   gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2505   g_object_unref (smiley_manager);
2506
2507   /* Set up signals we can't do with ui file since we may need to
2508    * block/unblock them at some later stage.
2509    */
2510
2511   g_signal_connect (self, "delete_event",
2512       G_CALLBACK (chat_window_delete_event_cb), self);
2513   g_signal_connect (self, "focus_in_event",
2514       G_CALLBACK (chat_window_focus_in_event_cb), self);
2515   g_signal_connect (self, "focus_out_event",
2516       G_CALLBACK (chat_window_focus_out_event_cb), self);
2517   g_signal_connect_after (self->priv->notebook, "switch_page",
2518       G_CALLBACK (chat_window_page_switched_cb), self);
2519   g_signal_connect (self->priv->notebook, "page_added",
2520       G_CALLBACK (chat_window_page_added_cb), self);
2521   g_signal_connect (self->priv->notebook, "page_removed",
2522       G_CALLBACK (chat_window_page_removed_cb), self);
2523
2524   /* Set up drag and drop */
2525   gtk_drag_dest_set (GTK_WIDGET (self->priv->notebook),
2526       GTK_DEST_DEFAULT_HIGHLIGHT,
2527       drag_types_dest,
2528       G_N_ELEMENTS (drag_types_dest),
2529       GDK_ACTION_MOVE | GDK_ACTION_COPY);
2530
2531   /* connect_after to allow GtkNotebook's built-in tab switching */
2532   g_signal_connect_after (self->priv->notebook, "drag-motion",
2533       G_CALLBACK (chat_window_drag_motion), self);
2534   g_signal_connect (self->priv->notebook, "drag-data-received",
2535       G_CALLBACK (chat_window_drag_data_received), self);
2536   g_signal_connect (self->priv->notebook, "drag-drop",
2537       G_CALLBACK (chat_window_drag_drop), self);
2538
2539   chat_windows = g_list_prepend (chat_windows, self);
2540
2541   /* Set up private details */
2542   self->priv->chats = NULL;
2543   self->priv->current_chat = NULL;
2544   self->priv->notification = NULL;
2545
2546   self->priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2547
2548   self->priv->chat_manager = empathy_chat_manager_dup_singleton ();
2549   self->priv->chat_manager_chats_changed_id = g_signal_connect (
2550       self->priv->chat_manager, "closed-chats-changed",
2551       G_CALLBACK (chat_window_chat_manager_chats_changed_cb), self);
2552
2553   chat_window_chat_manager_chats_changed_cb (self->priv->chat_manager,
2554       empathy_chat_manager_get_num_closed_chats (self->priv->chat_manager), self);
2555
2556   g_object_ref (self->priv->ui_manager);
2557   g_object_unref (gui);
2558 }
2559
2560 /* Returns the window to open a new tab in if there is a suitable window,
2561  * otherwise, returns NULL indicating that a new window should be added.
2562  */
2563 static EmpathyChatWindow *
2564 empathy_chat_window_get_default (gboolean room)
2565 {
2566   GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2567   GList *l;
2568   gboolean separate_windows = TRUE;
2569
2570   separate_windows = g_settings_get_boolean (gsettings,
2571       EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2572
2573   g_object_unref (gsettings);
2574
2575   if (separate_windows)
2576     /* Always create a new window */
2577     return NULL;
2578
2579   for (l = chat_windows; l; l = l->next)
2580     {
2581       EmpathyChatWindow *chat_window;
2582       guint nb_rooms, nb_private;
2583
2584       chat_window = l->data;
2585
2586       empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2587
2588       /* Skip the window if there aren't any rooms in it */
2589       if (room && nb_rooms == 0)
2590         continue;
2591
2592       /* Skip the window if there aren't any 1-1 chats in it */
2593       if (!room && nb_private == 0)
2594         continue;
2595
2596       return chat_window;
2597     }
2598
2599   return NULL;
2600 }
2601
2602 static void
2603 empathy_chat_window_add_chat (EmpathyChatWindow *self,
2604     EmpathyChat *chat)
2605 {
2606   GtkWidget *label;
2607   GtkWidget *popup_label;
2608   GtkWidget *child;
2609   GValue value = { 0, };
2610
2611   g_return_if_fail (self != NULL);
2612   g_return_if_fail (EMPATHY_IS_CHAT (chat));
2613
2614   /* Reference the chat object */
2615   g_object_ref (chat);
2616
2617   /* If this window has just been created, position it */
2618   if (self->priv->chats == NULL)
2619     {
2620       const gchar *name = "chat-window";
2621       gboolean separate_windows;
2622
2623       separate_windows = g_settings_get_boolean (self->priv->gsettings_ui,
2624           EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2625
2626       if (empathy_chat_is_room (chat))
2627         name = "room-window";
2628
2629       if (separate_windows)
2630         {
2631           gint x, y;
2632
2633           /* Save current position of the window */
2634           gtk_window_get_position (GTK_WINDOW (self), &x, &y);
2635
2636           /* First bind to the 'generic' name. So new window for which we didn't
2637           * save a geometry yet will have the geometry of the last saved
2638           * window (bgo #601191). */
2639           empathy_geometry_bind (GTK_WINDOW (self), name);
2640
2641           /* Restore previous position of the window so the newly created window
2642           * won't be in the same position as the latest saved window and so
2643           * completely hide it. */
2644           gtk_window_move (GTK_WINDOW (self), x, y);
2645
2646           /* Then bind it to the name of the contact/room so we'll save the
2647           * geometry specific to this window */
2648           name = empathy_chat_get_id (chat);
2649         }
2650
2651       empathy_geometry_bind (GTK_WINDOW (self), name);
2652     }
2653
2654   child = GTK_WIDGET (chat);
2655   label = chat_window_create_label (self, chat, TRUE);
2656   popup_label = chat_window_create_label (self, chat, FALSE);
2657   gtk_widget_show (child);
2658
2659   g_signal_connect (chat, "notify::name",
2660       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2661   g_signal_connect (chat, "notify::subject",
2662       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2663   g_signal_connect (chat, "notify::remote-contact",
2664       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2665   g_signal_connect (chat, "notify::sms-channel",
2666       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2667   g_signal_connect (chat, "notify::n-messages-sending",
2668       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2669   g_signal_connect (chat, "notify::nb-unread-messages",
2670       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2671   chat_window_chat_notify_cb (chat);
2672
2673   gtk_notebook_append_page_menu (GTK_NOTEBOOK (self->priv->notebook), child, label,
2674       popup_label);
2675   gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2676   gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2677   g_value_init (&value, G_TYPE_BOOLEAN);
2678   g_value_set_boolean (&value, TRUE);
2679   gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2680       child, "tab-expand" , &value);
2681   gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2682       child,  "tab-fill" , &value);
2683   g_value_unset (&value);
2684
2685   DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2686 }
2687
2688 static void
2689 empathy_chat_window_remove_chat (EmpathyChatWindow *self,
2690     EmpathyChat *chat)
2691 {
2692   gint position;
2693   EmpathyContact *remote_contact;
2694   EmpathyChatManager *chat_manager;
2695
2696   g_return_if_fail (self != NULL);
2697   g_return_if_fail (EMPATHY_IS_CHAT (chat));
2698
2699   g_signal_handlers_disconnect_by_func (chat,
2700       chat_window_chat_notify_cb, NULL);
2701
2702   remote_contact = g_object_get_data (G_OBJECT (chat),
2703       "chat-window-remote-contact");
2704
2705   if (remote_contact)
2706     {
2707       g_signal_handlers_disconnect_by_func (remote_contact,
2708           chat_window_update_chat_tab, chat);
2709     }
2710
2711   chat_manager = empathy_chat_manager_dup_singleton ();
2712   empathy_chat_manager_closed_chat (chat_manager, chat);
2713   g_object_unref (chat_manager);
2714
2715   position = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2716       GTK_WIDGET (chat));
2717   gtk_notebook_remove_page (GTK_NOTEBOOK (self->priv->notebook), position);
2718
2719   DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2720
2721   g_object_unref (chat);
2722 }
2723
2724 static void
2725 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2726     EmpathyChatWindow *new_window,
2727     EmpathyChat *chat)
2728 {
2729   GtkWidget *widget;
2730
2731   g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2732   g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2733   g_return_if_fail (EMPATHY_IS_CHAT (chat));
2734
2735   widget = GTK_WIDGET (chat);
2736
2737   DEBUG ("Chat moving with widget:%p (%d references)", widget,
2738       G_OBJECT (widget)->ref_count);
2739
2740   /* We reference here to make sure we don't loose the widget
2741    * and the EmpathyChat object during the move.
2742    */
2743   g_object_ref (chat);
2744   g_object_ref (widget);
2745
2746   empathy_chat_window_remove_chat (old_window, chat);
2747   empathy_chat_window_add_chat (new_window, chat);
2748
2749   g_object_unref (widget);
2750   g_object_unref (chat);
2751 }
2752
2753 static void
2754 empathy_chat_window_switch_to_chat (EmpathyChatWindow *self,
2755     EmpathyChat *chat)
2756 {
2757   gint page_num;
2758
2759   g_return_if_fail (self != NULL);
2760   g_return_if_fail (EMPATHY_IS_CHAT (chat));
2761
2762   page_num = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2763       GTK_WIDGET (chat));
2764
2765   gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
2766       page_num);
2767 }
2768
2769 EmpathyChat *
2770 empathy_chat_window_find_chat (TpAccount *account,
2771     const gchar *id,
2772     gboolean sms_channel)
2773 {
2774   GList *l;
2775
2776   g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2777
2778   for (l = chat_windows; l; l = l->next)
2779     {
2780       EmpathyChatWindow *window = l->data;
2781       GList *ll;
2782
2783       for (ll = window->priv->chats; ll; ll = ll->next)
2784         {
2785           EmpathyChat *chat;
2786
2787           chat = ll->data;
2788
2789           if (account == empathy_chat_get_account (chat) &&
2790               !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2791               sms_channel == empathy_chat_is_sms_channel (chat))
2792             return chat;
2793         }
2794     }
2795
2796   return NULL;
2797 }
2798
2799 EmpathyChatWindow *
2800 empathy_chat_window_present_chat (EmpathyChat *chat,
2801     gint64 timestamp)
2802 {
2803   EmpathyChatWindow *self;
2804   guint32 x_timestamp;
2805
2806   g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2807
2808   self = chat_window_find_chat (chat);
2809
2810   /* If the chat has no window, create one */
2811   if (self == NULL)
2812     {
2813       self = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2814       if (!self)
2815         {
2816           self = empathy_chat_window_new ();
2817
2818           /* we want to display the newly created window even if we
2819            * don't present it */
2820           gtk_widget_show (GTK_WIDGET (self));
2821         }
2822
2823       empathy_chat_window_add_chat (self, chat);
2824     }
2825
2826   /* Don't force the window to show itself when it wasn't
2827    * an action by the user
2828    */
2829   if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2830     return self;
2831
2832   if (x_timestamp != GDK_CURRENT_TIME)
2833     {
2834       /* Don't present or switch tab if the action was earlier than the
2835        * last actions X time, accounting for overflow and the first ever
2836       * presentation */
2837
2838       if (self->priv->x_user_action_time != 0
2839         && X_EARLIER_OR_EQL (x_timestamp, self->priv->x_user_action_time))
2840         return self;
2841
2842       self->priv->x_user_action_time = x_timestamp;
2843     }
2844
2845   empathy_chat_window_switch_to_chat (self, chat);
2846
2847   /* Don't use empathy_window_present_with_time () which would move the window
2848    * to our current desktop but move to the window's desktop instead. This is
2849    * more coherent with Shell's 'app is ready' notication which moves the view
2850    * to the app desktop rather than moving the app itself. */
2851   empathy_move_to_window_desktop (GTK_WINDOW (self), x_timestamp);
2852
2853   gtk_widget_grab_focus (chat->input_text_view);
2854   return self;
2855 }
2856
2857 static void
2858 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2859     guint *nb_rooms,
2860     guint *nb_private)
2861 {
2862   GList *l;
2863   guint _nb_rooms = 0, _nb_private = 0;
2864
2865   for (l = self->priv->chats; l != NULL; l = g_list_next (l))
2866     {
2867       if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2868         _nb_rooms++;
2869       else
2870         _nb_private++;
2871     }
2872
2873   if (nb_rooms != NULL)
2874     *nb_rooms = _nb_rooms;
2875   if (nb_private != NULL)
2876     *nb_private = _nb_private;
2877 }
2878
2879 EmpathyIndividualManager *
2880 empathy_chat_window_get_individual_manager (EmpathyChatWindow *self)
2881 {
2882   return self->priv->individual_mgr;
2883 }