]> git.0d.be Git - empathy.git/blob - src/empathy-chat-window.c
Reorder header inclusions accordingly to the Telepathy coding style
[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 <gtk/gtk.h>
32 #include <glib/gi18n.h>
33
34 #include "empathy-about-dialog.h"
35 #include "empathy-chat-manager.h"
36 #include "empathy-chatroom-manager.h"
37 #include "empathy-client-factory.h"
38 #include "empathy-geometry.h"
39 #include "empathy-gsettings.h"
40 #include "empathy-images.h"
41 #include "empathy-individual-manager.h"
42 #include "empathy-invite-participant-dialog.h"
43 #include "empathy-notify-manager.h"
44 #include "empathy-request-util.h"
45 #include "empathy-smiley-manager.h"
46 #include "empathy-sound-manager.h"
47 #include "empathy-ui-utils.h"
48 #include "empathy-utils.h"
49
50 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
51 #include "empathy-debug.h"
52
53 /* Macro to compare guint32 X timestamps, while accounting for wrapping around
54  */
55 #define X_EARLIER_OR_EQL(t1, t2) \
56   ((t1 <= t2 && ((t2 - t1) < G_MAXUINT32/2))  \
57     || (t1 >= t2 && (t1 - t2) > (G_MAXUINT32/2)) \
58   )
59
60 enum
61 {
62   PROP_INDIVIDUAL_MGR = 1
63 };
64
65 struct _EmpathyChatWindowPriv
66 {
67   EmpathyChat *current_chat;
68   GList *chats;
69   gboolean page_added;
70   gboolean dnd_same_window;
71   EmpathyChatroomManager *chatroom_manager;
72   EmpathyNotifyManager *notify_mgr;
73   EmpathyIndividualManager *individual_mgr;
74   GtkWidget *notebook;
75   NotifyNotification *notification;
76
77   GtkTargetList *contact_targets;
78   GtkTargetList *file_targets;
79
80   EmpathyChatManager *chat_manager;
81   gulong chat_manager_chats_changed_id;
82
83   /* Menu items. */
84   GtkUIManager *ui_manager;
85   GtkAction *menu_conv_insert_smiley;
86   GtkAction *menu_conv_favorite;
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
1090   /* Favorite room menu */
1091   is_room = empathy_chat_is_room (self->priv->current_chat);
1092   if (is_room)
1093     {
1094       const gchar *room;
1095       TpAccount *account;
1096       gboolean found = FALSE;
1097       EmpathyChatroom *chatroom;
1098
1099       room = empathy_chat_get_id (self->priv->current_chat);
1100       account = empathy_chat_get_account (self->priv->current_chat);
1101       chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1102           account, room);
1103
1104       if (chatroom != NULL)
1105         found = empathy_chatroom_is_favorite (chatroom);
1106
1107       DEBUG ("This room %s favorite", found ? "is" : "is not");
1108       gtk_toggle_action_set_active (
1109         GTK_TOGGLE_ACTION (self->priv->menu_conv_favorite), found);
1110
1111       if (chatroom != NULL)
1112         found = empathy_chatroom_is_always_urgent (chatroom);
1113
1114       gtk_toggle_action_set_active (
1115           GTK_TOGGLE_ACTION (self->priv->menu_conv_always_urgent), found);
1116     }
1117
1118   gtk_action_set_visible (self->priv->menu_conv_favorite, is_room);
1119   gtk_action_set_visible (self->priv->menu_conv_always_urgent, is_room);
1120
1121   /* Show contacts menu */
1122   g_object_get (self->priv->current_chat,
1123       "remote-contact", &remote_contact,
1124       "show-contacts", &active,
1125       NULL);
1126
1127   if (remote_contact == NULL)
1128     {
1129       gtk_toggle_action_set_active (
1130         GTK_TOGGLE_ACTION (self->priv->menu_conv_toggle_contacts), active);
1131     }
1132
1133   gtk_action_set_visible (self->priv->menu_conv_toggle_contacts,
1134       (remote_contact == NULL));
1135
1136   if (remote_contact != NULL)
1137     g_object_unref (remote_contact);
1138 }
1139
1140 static void
1141 chat_window_clear_activate_cb (GtkAction *action,
1142     EmpathyChatWindow *self)
1143 {
1144   empathy_chat_clear (self->priv->current_chat);
1145 }
1146
1147 static void
1148 chat_window_favorite_toggled_cb (GtkToggleAction *toggle_action,
1149     EmpathyChatWindow *self)
1150 {
1151   gboolean active;
1152   TpAccount *account;
1153   gchar *name;
1154   const gchar *room;
1155   EmpathyChatroom *chatroom;
1156
1157   active = gtk_toggle_action_get_active (toggle_action);
1158   account = empathy_chat_get_account (self->priv->current_chat);
1159   room = empathy_chat_get_id (self->priv->current_chat);
1160   name = empathy_chat_dup_name (self->priv->current_chat);
1161
1162   chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1163       account, room, name);
1164
1165   empathy_chatroom_set_favorite (chatroom, active);
1166   g_object_unref (chatroom);
1167   g_free (name);
1168 }
1169
1170 static void
1171 chat_window_always_urgent_toggled_cb (GtkToggleAction *toggle_action,
1172     EmpathyChatWindow *self)
1173 {
1174   gboolean active;
1175   TpAccount *account;
1176   gchar *name;
1177   const gchar *room;
1178   EmpathyChatroom *chatroom;
1179
1180   active = gtk_toggle_action_get_active (toggle_action);
1181   account = empathy_chat_get_account (self->priv->current_chat);
1182   room = empathy_chat_get_id (self->priv->current_chat);
1183   name = empathy_chat_dup_name (self->priv->current_chat);
1184
1185   chatroom = empathy_chatroom_manager_ensure_chatroom (self->priv->chatroom_manager,
1186       account, room, name);
1187
1188   empathy_chatroom_set_always_urgent (chatroom, active);
1189   g_object_unref (chatroom);
1190   g_free (name);
1191 }
1192
1193 static void
1194 chat_window_contacts_toggled_cb (GtkToggleAction *toggle_action,
1195     EmpathyChatWindow *self)
1196 {
1197   gboolean active;
1198
1199   active = gtk_toggle_action_get_active (toggle_action);
1200
1201   empathy_chat_set_show_contacts (self->priv->current_chat, active);
1202 }
1203
1204 static void
1205 chat_window_invite_participant_activate_cb (GtkAction *action,
1206     EmpathyChatWindow *self)
1207 {
1208   GtkWidget *dialog;
1209   EmpathyTpChat *tp_chat;
1210   int response;
1211
1212   g_return_if_fail (self->priv->current_chat != NULL);
1213
1214   tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
1215
1216   dialog = empathy_invite_participant_dialog_new (
1217       GTK_WINDOW (self), tp_chat);
1218
1219   gtk_widget_show (dialog);
1220
1221   response = gtk_dialog_run (GTK_DIALOG (dialog));
1222
1223   if (response == GTK_RESPONSE_ACCEPT)
1224     {
1225       TpContact *tp_contact;
1226       EmpathyContact *contact;
1227
1228       tp_contact = empathy_invite_participant_dialog_get_selected (
1229         EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1230       if (tp_contact == NULL)
1231         goto out;
1232
1233       contact = empathy_contact_dup_from_tp_contact (tp_contact);
1234
1235       empathy_tp_chat_add (tp_chat, contact, _("Inviting you to this room"));
1236
1237       g_object_unref (contact);
1238     }
1239
1240 out:
1241   gtk_widget_destroy (dialog);
1242 }
1243
1244 static void
1245 chat_window_close_activate_cb (GtkAction *action,
1246     EmpathyChatWindow *self)
1247 {
1248   g_return_if_fail (self->priv->current_chat != NULL);
1249
1250   maybe_close_chat (self, self->priv->current_chat);
1251 }
1252
1253 static void
1254 chat_window_edit_activate_cb (GtkAction *action,
1255     EmpathyChatWindow *self)
1256 {
1257   GtkClipboard *clipboard;
1258   GtkTextBuffer *buffer;
1259   gboolean text_available;
1260
1261   g_return_if_fail (self->priv->current_chat != NULL);
1262
1263   if (!empathy_chat_get_tp_chat (self->priv->current_chat))
1264     {
1265       gtk_action_set_sensitive (self->priv->menu_edit_copy, FALSE);
1266       gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1267       gtk_action_set_sensitive (self->priv->menu_edit_paste, FALSE);
1268       return;
1269     }
1270
1271   buffer = gtk_text_view_get_buffer (
1272       GTK_TEXT_VIEW (self->priv->current_chat->input_text_view));
1273
1274   if (gtk_text_buffer_get_has_selection (buffer))
1275     {
1276       gtk_action_set_sensitive (self->priv->menu_edit_copy, TRUE);
1277       gtk_action_set_sensitive (self->priv->menu_edit_cut, TRUE);
1278     }
1279   else
1280     {
1281       gboolean selection;
1282
1283       selection = empathy_theme_adium_get_has_selection (
1284           self->priv->current_chat->view);
1285
1286       gtk_action_set_sensitive (self->priv->menu_edit_cut, FALSE);
1287       gtk_action_set_sensitive (self->priv->menu_edit_copy, selection);
1288     }
1289
1290   clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1291   text_available = gtk_clipboard_wait_is_text_available (clipboard);
1292   gtk_action_set_sensitive (self->priv->menu_edit_paste, text_available);
1293 }
1294
1295 static void
1296 chat_window_cut_activate_cb (GtkAction *action,
1297     EmpathyChatWindow *self)
1298 {
1299   g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1300
1301   empathy_chat_cut (self->priv->current_chat);
1302 }
1303
1304 static void
1305 chat_window_copy_activate_cb (GtkAction *action,
1306     EmpathyChatWindow *self)
1307 {
1308   g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1309
1310   empathy_chat_copy (self->priv->current_chat);
1311 }
1312
1313 static void
1314 chat_window_paste_activate_cb (GtkAction *action,
1315     EmpathyChatWindow *self)
1316 {
1317   g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1318
1319   empathy_chat_paste (self->priv->current_chat);
1320 }
1321
1322 static void
1323 chat_window_find_activate_cb (GtkAction *action,
1324     EmpathyChatWindow *self)
1325 {
1326   g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (self));
1327
1328   empathy_chat_find (self->priv->current_chat);
1329 }
1330
1331 static void
1332 chat_window_tabs_next_activate_cb (GtkAction *action,
1333     EmpathyChatWindow *self)
1334 {
1335   gint index_, numPages;
1336   gboolean wrap_around;
1337
1338   g_object_get (gtk_settings_get_default (),
1339       "gtk-keynav-wrap-around", &wrap_around,
1340       NULL);
1341
1342   index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1343   numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1344
1345   if (index_ == (numPages - 1) && wrap_around)
1346     {
1347       gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), 0);
1348       return;
1349     }
1350
1351   gtk_notebook_next_page (GTK_NOTEBOOK (self->priv->notebook));
1352 }
1353
1354 static void
1355 chat_window_tabs_previous_activate_cb (GtkAction *action,
1356     EmpathyChatWindow *self)
1357 {
1358   gint index_, numPages;
1359   gboolean wrap_around;
1360
1361   g_object_get (gtk_settings_get_default (),
1362       "gtk-keynav-wrap-around", &wrap_around,
1363       NULL);
1364
1365   index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1366   numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1367
1368   if (index_ <= 0 && wrap_around)
1369     {
1370       gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
1371           numPages - 1);
1372       return;
1373     }
1374
1375   gtk_notebook_prev_page (GTK_NOTEBOOK (self->priv->notebook));
1376 }
1377
1378 static void
1379 chat_window_tabs_undo_close_tab_activate_cb (GtkAction *action,
1380     EmpathyChatWindow *self)
1381 {
1382   empathy_chat_manager_undo_closed_chat (self->priv->chat_manager,
1383       empathy_get_current_action_time ());
1384 }
1385
1386 static void
1387 chat_window_tabs_left_activate_cb (GtkAction *action,
1388     EmpathyChatWindow *self)
1389 {
1390   EmpathyChat *chat;
1391   gint index_, num_pages;
1392
1393   chat = self->priv->current_chat;
1394   index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1395   if (index_ <= 0)
1396     return;
1397
1398   gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1399       index_ - 1);
1400
1401   num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1402   chat_window_menu_context_update (self, num_pages);
1403 }
1404
1405 static void
1406 chat_window_tabs_right_activate_cb (GtkAction *action,
1407     EmpathyChatWindow *self)
1408 {
1409   EmpathyChat *chat;
1410   gint index_, num_pages;
1411
1412   chat = self->priv->current_chat;
1413   index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (self->priv->notebook));
1414
1415   gtk_notebook_reorder_child (GTK_NOTEBOOK (self->priv->notebook), GTK_WIDGET (chat),
1416       index_ + 1);
1417
1418   num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (self->priv->notebook));
1419   chat_window_menu_context_update (self, num_pages);
1420 }
1421
1422 static EmpathyChatWindow *
1423 empathy_chat_window_new (void)
1424 {
1425   return g_object_new (EMPATHY_TYPE_CHAT_WINDOW,
1426       "default-width", 580,
1427       "default-height", 480,
1428       "title", _("Chat"),
1429       "role", "chat",
1430       NULL);
1431 }
1432
1433 static void
1434 chat_window_detach_activate_cb (GtkAction *action,
1435     EmpathyChatWindow *self)
1436 {
1437   EmpathyChatWindow *new_window;
1438   EmpathyChat *chat;
1439
1440   chat = self->priv->current_chat;
1441   new_window = empathy_chat_window_new ();
1442
1443   empathy_chat_window_move_chat (self, new_window, chat);
1444
1445   gtk_widget_show (GTK_WIDGET (new_window));
1446 }
1447
1448 static void
1449 chat_window_help_contents_activate_cb (GtkAction *action,
1450     EmpathyChatWindow *self)
1451 {
1452   empathy_url_show (GTK_WIDGET (self), "help:empathy");
1453 }
1454
1455 static void
1456 chat_window_help_about_activate_cb (GtkAction *action,
1457     EmpathyChatWindow *self)
1458 {
1459   empathy_about_dialog_new (GTK_WINDOW (self));
1460 }
1461
1462 static gboolean
1463 chat_window_delete_event_cb (GtkWidget *dialog,
1464     GdkEvent *event,
1465     EmpathyChatWindow *self)
1466 {
1467   EmpathyChat *chat = NULL;
1468   guint n_rooms = 0;
1469   GList *l;
1470
1471   DEBUG ("Delete event received");
1472
1473   for (l = self->priv->chats; l != NULL; l = l->next)
1474     {
1475       if (chat_needs_close_confirmation (l->data))
1476         {
1477           chat = l->data;
1478           n_rooms++;
1479         }
1480     }
1481
1482   if (n_rooms > 0)
1483     {
1484       confirm_close (self, TRUE, n_rooms, (n_rooms == 1 ? chat : NULL));
1485     }
1486   else
1487     {
1488       remove_all_chats (self);
1489     }
1490
1491   return TRUE;
1492 }
1493
1494 static void
1495 chat_window_composing_cb (EmpathyChat *chat,
1496     gboolean is_composing,
1497     EmpathyChatWindow *self)
1498 {
1499   chat_window_update_chat_tab (chat);
1500 }
1501
1502 static void
1503 chat_window_set_urgency_hint (EmpathyChatWindow *self,
1504     gboolean urgent)
1505 {
1506   gtk_window_set_urgency_hint (GTK_WINDOW (self), urgent);
1507 }
1508
1509 static void
1510 chat_window_notification_closed_cb (NotifyNotification *notify,
1511     EmpathyChatWindow *self)
1512 {
1513   g_object_unref (notify);
1514   if (self->priv->notification == notify)
1515     self->priv->notification = NULL;
1516 }
1517
1518 static void
1519 chat_window_show_or_update_notification (EmpathyChatWindow *self,
1520     EmpathyMessage *message,
1521     EmpathyChat *chat)
1522 {
1523   EmpathyContact *sender;
1524   const gchar *header;
1525   char *escaped;
1526   const char *body;
1527   GdkPixbuf *pixbuf;
1528   gboolean res, has_x_canonical_append;
1529   NotifyNotification *notification = self->priv->notification;
1530
1531   if (!empathy_notify_manager_notification_is_enabled (self->priv->notify_mgr))
1532     return;
1533
1534   res = g_settings_get_boolean (self->priv->gsettings_notif,
1535       EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1536
1537   if (!res)
1538     return;
1539
1540   sender = empathy_message_get_sender (message);
1541   header = empathy_contact_get_alias (sender);
1542   body = empathy_message_get_body (message);
1543   escaped = g_markup_escape_text (body, -1);
1544
1545   has_x_canonical_append = empathy_notify_manager_has_capability (
1546     self->priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1547
1548   if (notification != NULL && !has_x_canonical_append)
1549     {
1550       /* if the notification server supports x-canonical-append, it is
1551          better to not use notify_notification_update to avoid
1552          overwriting the current notification message */
1553       notify_notification_update (notification,
1554                 header, escaped, NULL);
1555     }
1556   else
1557     {
1558       /* if the notification server supports x-canonical-append,
1559          the hint will be added, so that the message from the
1560          just created notification will be automatically appended
1561          to an existing notification with the same title.
1562          In this way the previous message will not be lost: the new
1563          message will appear below it, in the same notification */
1564       const gchar *category = empathy_chat_is_room (chat)
1565         ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1566         : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1567
1568       notification = empathy_notify_manager_create_notification (header,
1569           escaped, NULL);
1570
1571       if (self->priv->notification == NULL)
1572         self->priv->notification = notification;
1573
1574       tp_g_signal_connect_object (notification, "closed",
1575             G_CALLBACK (chat_window_notification_closed_cb), self, 0);
1576
1577       if (has_x_canonical_append)
1578         {
1579           /* We have to set a not empty string to keep libnotify happy */
1580           notify_notification_set_hint_string (notification,
1581             EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1582         }
1583
1584       notify_notification_set_hint (notification,
1585           EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY, g_variant_new_string (category));
1586     }
1587
1588   pixbuf = empathy_notify_manager_get_pixbuf_for_notification (self->priv->notify_mgr,
1589     sender, EMPATHY_IMAGE_NEW_MESSAGE);
1590
1591   if (pixbuf != NULL)
1592     {
1593       notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1594       g_object_unref (pixbuf);
1595     }
1596
1597   notify_notification_show (notification, NULL);
1598
1599   g_free (escaped);
1600 }
1601
1602 static gboolean
1603 empathy_chat_window_has_focus (EmpathyChatWindow *self)
1604 {
1605   gboolean has_focus;
1606
1607   g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (self), FALSE);
1608
1609   g_object_get (self, "has-toplevel-focus", &has_focus, NULL);
1610
1611   return has_focus;
1612 }
1613
1614 static void
1615 chat_window_new_message_cb (EmpathyChat *chat,
1616     EmpathyMessage *message,
1617     gboolean pending,
1618     gboolean should_highlight,
1619     EmpathyChatWindow *self)
1620 {
1621   gboolean has_focus;
1622   gboolean needs_urgency;
1623   EmpathyContact *sender;
1624
1625   has_focus = empathy_chat_window_has_focus (self);
1626
1627   /* - if we're the sender, we play the sound if it's specified in the
1628    *   preferences and we're not away.
1629    * - if we receive a message, we play the sound if it's specified in the
1630    *   preferences and the window does not have focus on the chat receiving
1631    *   the message.
1632    */
1633
1634   sender = empathy_message_get_sender (message);
1635
1636   if (empathy_contact_is_user (sender))
1637     {
1638       empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self),
1639           EMPATHY_SOUND_MESSAGE_OUTGOING);
1640       return;
1641     }
1642
1643   if (has_focus && self->priv->current_chat == chat)
1644     {
1645       /* window and tab are focused so consider the message to be read */
1646
1647       /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1648       empathy_chat_messages_read (chat);
1649       return;
1650     }
1651
1652   /* Update the chat tab if this is the first unread message */
1653   if (empathy_chat_get_nb_unread_messages (chat) == 1)
1654     {
1655       chat_window_update_chat_tab (chat);
1656     }
1657
1658   /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1659    * If empathy_chat_get_remote_contact () returns NULL, that means it's
1660    * an unamed MUC (msn-like).
1661    * In case of a MUC, we set urgency if either:
1662    *   a) the chatroom's always_urgent property is TRUE
1663    *   b) the message contains our alias
1664    */
1665   if (empathy_chat_is_room (chat))
1666     {
1667       TpAccount *account;
1668       const gchar *room;
1669       EmpathyChatroom *chatroom;
1670
1671       account = empathy_chat_get_account (chat);
1672       room = empathy_chat_get_id (chat);
1673
1674       chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1675           account, room);
1676
1677       if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom))
1678         needs_urgency = TRUE;
1679       else
1680         needs_urgency = should_highlight;
1681     }
1682   else
1683     {
1684       needs_urgency = TRUE;
1685     }
1686
1687   if (needs_urgency)
1688     {
1689       if (!has_focus)
1690         chat_window_set_urgency_hint (self, TRUE);
1691
1692       /* Pending messages have already been displayed and notified in the
1693       * approver, so we don't display a notification and play a sound
1694       * for those */
1695       if (!pending)
1696         {
1697           empathy_sound_manager_play (self->priv->sound_mgr,
1698               GTK_WIDGET (self), EMPATHY_SOUND_MESSAGE_INCOMING);
1699
1700           chat_window_show_or_update_notification (self, message, chat);
1701         }
1702     }
1703
1704   /* update the number of unread messages and the window icon */
1705   chat_window_title_update (self);
1706   chat_window_icon_update (self, TRUE);
1707 }
1708
1709 static void
1710 chat_window_command_part (EmpathyChat *chat,
1711     GStrv strv)
1712 {
1713   EmpathyChat *chat_to_be_parted;
1714   EmpathyTpChat *tp_chat = NULL;
1715
1716   if (strv[1] == NULL)
1717     {
1718       /* No chatroom ID specified */
1719       tp_chat = empathy_chat_get_tp_chat (chat);
1720
1721       if (tp_chat)
1722         empathy_tp_chat_leave (tp_chat, "");
1723
1724       return;
1725     }
1726
1727   chat_to_be_parted = empathy_chat_window_find_chat (
1728     empathy_chat_get_account (chat), strv[1], FALSE);
1729
1730   if (chat_to_be_parted != NULL)
1731     {
1732       /* Found a chatroom matching the specified ID */
1733       tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1734
1735       if (tp_chat)
1736         empathy_tp_chat_leave (tp_chat, strv[2]);
1737     }
1738   else
1739     {
1740       gchar *message;
1741
1742       /* Going by the syntax of PART command:
1743        *
1744        * /PART [<chatroom-ID>] [<reason>]
1745        *
1746        * Chatroom-ID is not a must to specify a reason.
1747        * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1748        * MUC then the current chatroom should be parted and srtv[1] should
1749        * be treated as part of the optional part-message. */
1750       message = g_strconcat (strv[1], " ", strv[2], NULL);
1751       tp_chat = empathy_chat_get_tp_chat (chat);
1752
1753       if (tp_chat)
1754         empathy_tp_chat_leave (tp_chat, message);
1755
1756       g_free (message);
1757     }
1758 }
1759
1760 static GtkNotebook *
1761 notebook_create_window_cb (GtkNotebook *source,
1762     GtkWidget *page,
1763     gint x,
1764     gint y,
1765     gpointer user_data)
1766 {
1767   EmpathyChatWindow *window, *new_window;
1768   EmpathyChat *chat;
1769
1770   chat = EMPATHY_CHAT (page);
1771   window = chat_window_find_chat (chat);
1772
1773   new_window = empathy_chat_window_new ();
1774
1775   DEBUG ("Detach hook called");
1776
1777   empathy_chat_window_move_chat (window, new_window, chat);
1778
1779   gtk_widget_show (GTK_WIDGET (new_window));
1780   gtk_window_move (GTK_WINDOW (new_window), x, y);
1781
1782   return NULL;
1783 }
1784
1785 static void
1786 chat_window_page_switched_cb (GtkNotebook *notebook,
1787     GtkWidget *child,
1788     gint page_num,
1789     EmpathyChatWindow *self)
1790 {
1791   EmpathyChat *chat = EMPATHY_CHAT (child);
1792
1793   DEBUG ("Page switched");
1794
1795   if (self->priv->page_added)
1796     {
1797       self->priv->page_added = FALSE;
1798       empathy_chat_scroll_down (chat);
1799     }
1800   else if (self->priv->current_chat == chat)
1801     {
1802       return;
1803     }
1804
1805   self->priv->current_chat = chat;
1806   empathy_chat_messages_read (chat);
1807
1808   chat_window_update_chat_tab (chat);
1809 }
1810
1811 static void
1812 chat_window_page_added_cb (GtkNotebook *notebook,
1813     GtkWidget *child,
1814     guint page_num,
1815     EmpathyChatWindow *self)
1816 {
1817   EmpathyChat *chat;
1818
1819   /* If we just received DND to the same window, we don't want
1820    * to do anything here like removing the tab and then readding
1821    * it, so we return here and in "page-added".
1822    */
1823   if (self->priv->dnd_same_window)
1824     {
1825       DEBUG ("Page added (back to the same window)");
1826       self->priv->dnd_same_window = FALSE;
1827       return;
1828     }
1829
1830   DEBUG ("Page added");
1831
1832   /* Get chat object */
1833   chat = EMPATHY_CHAT (child);
1834
1835   /* Connect chat signals for this window */
1836   g_signal_connect (chat, "composing",
1837       G_CALLBACK (chat_window_composing_cb), self);
1838   g_signal_connect (chat, "new-message",
1839       G_CALLBACK (chat_window_new_message_cb), self);
1840   g_signal_connect (chat, "part-command-entered",
1841       G_CALLBACK (chat_window_command_part), NULL);
1842   g_signal_connect (chat, "notify::tp-chat",
1843       G_CALLBACK (chat_window_update_chat_tab), self);
1844
1845   /* Set flag so we know to perform some special operations on
1846    * switch page due to the new page being added.
1847    */
1848   self->priv->page_added = TRUE;
1849
1850   /* Get list of chats up to date */
1851   self->priv->chats = g_list_append (self->priv->chats, chat);
1852
1853   chat_window_update_chat_tab (chat);
1854 }
1855
1856 static void
1857 chat_window_page_removed_cb (GtkNotebook *notebook,
1858     GtkWidget *child,
1859     guint page_num,
1860     EmpathyChatWindow *self)
1861 {
1862   EmpathyChat *chat;
1863
1864   /* If we just received DND to the same window, we don't want
1865    * to do anything here like removing the tab and then readding
1866    * it, so we return here and in "page-added".
1867    */
1868   if (self->priv->dnd_same_window)
1869     {
1870       DEBUG ("Page removed (and will be readded to same window)");
1871       return;
1872     }
1873
1874   DEBUG ("Page removed");
1875
1876   /* Get chat object */
1877   chat = EMPATHY_CHAT (child);
1878
1879   /* Disconnect all signal handlers for this chat and this window */
1880   g_signal_handlers_disconnect_by_func (chat,
1881       G_CALLBACK (chat_window_composing_cb), self);
1882   g_signal_handlers_disconnect_by_func (chat,
1883       G_CALLBACK (chat_window_new_message_cb), self);
1884   g_signal_handlers_disconnect_by_func (chat,
1885       G_CALLBACK (chat_window_update_chat_tab), self);
1886
1887   /* Keep list of chats up to date */
1888   self->priv->chats = g_list_remove (self->priv->chats, chat);
1889   empathy_chat_messages_read (chat);
1890
1891   if (self->priv->chats == NULL)
1892     {
1893       gtk_widget_destroy (GTK_WIDGET (self));
1894     }
1895   else
1896     {
1897       chat_window_update (self, TRUE);
1898     }
1899 }
1900
1901 static gboolean
1902 chat_window_focus_in_event_cb (GtkWidget *widget,
1903     GdkEvent *event,
1904     EmpathyChatWindow *self)
1905 {
1906   empathy_chat_messages_read (self->priv->current_chat);
1907
1908   chat_window_set_urgency_hint (self, FALSE);
1909
1910   /* Update the title, since we now mark all unread messages as read. */
1911   chat_window_update_chat_tab_full (self->priv->current_chat, FALSE);
1912
1913   return FALSE;
1914 }
1915
1916 static void
1917 contacts_loaded_cb (EmpathyIndividualManager *mgr,
1918     EmpathyChatWindow *self)
1919 {
1920   chat_window_contact_menu_update (self);
1921 }
1922
1923 static gboolean
1924 chat_window_focus_out_event_cb (GtkWidget *widget,
1925     GdkEvent *event,
1926     EmpathyChatWindow *self)
1927 {
1928   if (self->priv->individual_mgr != NULL)
1929     return FALSE;
1930
1931   /* Keep the individual manager alive so we won't fetch everything from Folks
1932    * each time we need to use it. Loading FolksAggregator can takes quite a
1933    * while (if user has a huge LDAP abook for example) and it blocks
1934    * the mainloop during most of this loading. We workaround this by loading
1935    * it when the chat window has been unfocused and so, hopefully, not impact
1936    * the reactivity of the chat window too much.
1937    *
1938    * The individual manager (and so Folks) is needed to know to which
1939    * FolksIndividual a TpContact belongs, including:
1940    * - empathy_chat_get_contact_menu: to list all the personas of the contact
1941    * - empathy_display_individual_info: to invoke gnome-contacts with the
1942    *   FolksIndividual.id of the contact
1943    * - drag_data_received_individual_id: to find the individual associated
1944    *   with the ID we received from the DnD in order to invite him.
1945    */
1946   self->priv->individual_mgr = empathy_individual_manager_dup_singleton ();
1947
1948   if (!empathy_individual_manager_get_contacts_loaded (
1949       self->priv->individual_mgr))
1950     {
1951       /* We want to update the contact menu when Folks is loaded so we can
1952        * list all the personas of the contact. */
1953       tp_g_signal_connect_object (self->priv->individual_mgr, "contacts-loaded",
1954           G_CALLBACK (contacts_loaded_cb), self, 0);
1955     }
1956
1957   g_object_notify (G_OBJECT (self), "individual-manager");
1958
1959   return FALSE;
1960 }
1961
1962 static gboolean
1963 chat_window_drag_drop (GtkWidget *widget,
1964     GdkDragContext *context,
1965     int x,
1966     int y,
1967     guint time_,
1968     EmpathyChatWindow *self)
1969 {
1970   GdkAtom target;
1971
1972   target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
1973   if (target == GDK_NONE)
1974     target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
1975
1976   if (target != GDK_NONE)
1977     {
1978       gtk_drag_get_data (widget, context, target, time_);
1979       return TRUE;
1980     }
1981
1982   return FALSE;
1983 }
1984
1985 static gboolean
1986 chat_window_drag_motion (GtkWidget *widget,
1987     GdkDragContext *context,
1988     int x,
1989     int y,
1990     guint time_,
1991     EmpathyChatWindow *self)
1992 {
1993   GdkAtom target;
1994
1995   target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
1996
1997   if (target != GDK_NONE)
1998     {
1999       /* This is a file drag. Ensure the contact is online and set the
2000          drag type to COPY. Note that it's possible that the tab will
2001          be switched by GTK+ after a timeout from drag_motion without
2002          getting another drag_motion to disable the drop. You have
2003          to hold your mouse really still.
2004        */
2005       EmpathyContact *contact;
2006
2007       contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2008
2009       /* contact is NULL for multi-user chats. We don't do
2010        * file transfers to MUCs. We also don't send files
2011        * to offline contacts or contacts that don't support
2012        * file transfer.
2013        */
2014       if ((contact == NULL) || !empathy_contact_is_online (contact))
2015         {
2016           gdk_drag_status (context, 0, time_);
2017           return FALSE;
2018         }
2019
2020       if (!(empathy_contact_get_capabilities (contact)
2021            & EMPATHY_CAPABILITIES_FT))
2022         {
2023           gdk_drag_status (context, 0, time_);
2024           return FALSE;
2025         }
2026
2027       gdk_drag_status (context, GDK_ACTION_COPY, time_);
2028       return TRUE;
2029     }
2030
2031   target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
2032   if (target != GDK_NONE)
2033     {
2034       /* This is a drag of a contact from a contact list. Set to COPY.
2035          FIXME: If this drag is to a MUC window, it invites the user.
2036          Otherwise, it opens a chat. Should we use a different drag
2037          type for invites? Should we allow ASK?
2038        */
2039       gdk_drag_status (context, GDK_ACTION_COPY, time_);
2040       return TRUE;
2041     }
2042
2043   return FALSE;
2044 }
2045
2046 static void
2047 drag_data_received_individual_id (EmpathyChatWindow *self,
2048     GtkWidget *widget,
2049     GdkDragContext *context,
2050     int x,
2051     int y,
2052     GtkSelectionData *selection,
2053     guint info,
2054     guint time_)
2055 {
2056   const gchar *id;
2057   FolksIndividual *individual;
2058   EmpathyTpChat *chat;
2059   TpContact *tp_contact;
2060   TpConnection *conn;
2061   EmpathyContact *contact;
2062
2063   id = (const gchar *) gtk_selection_data_get_data (selection);
2064
2065   DEBUG ("DND invididual %s", id);
2066
2067   if (self->priv->current_chat == NULL)
2068     goto out;
2069
2070   chat = empathy_chat_get_tp_chat (self->priv->current_chat);
2071   if (chat == NULL)
2072     goto out;
2073
2074   if (!empathy_tp_chat_can_add_contact (chat))
2075     {
2076       DEBUG ("Can't invite contact to %s",
2077           tp_proxy_get_object_path (chat));
2078       goto out;
2079     }
2080
2081   if (self->priv->individual_mgr == NULL)
2082     /* Not likely as we have to focus out the chat window in order to start
2083      * the DnD but best to be safe. */
2084     goto out;
2085
2086   individual = empathy_individual_manager_lookup_member (
2087           self->priv->individual_mgr, id);
2088   if (individual == NULL)
2089     {
2090       DEBUG ("Failed to find individual %s", id);
2091       goto out;
2092     }
2093
2094   conn = tp_channel_get_connection ((TpChannel *) chat);
2095   tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2096   if (tp_contact == NULL)
2097     {
2098       DEBUG ("Can't find a TpContact on connection %s for %s",
2099           tp_proxy_get_object_path (conn), id);
2100       goto out;
2101     }
2102
2103   DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2104       tp_channel_get_identifier ((TpChannel *) chat));
2105
2106   contact = empathy_contact_dup_from_tp_contact (tp_contact);
2107   empathy_tp_chat_add (chat, contact, NULL);
2108   g_object_unref (contact);
2109
2110 out:
2111   gtk_drag_finish (context, TRUE, FALSE, time_);
2112 }
2113
2114 static void
2115 chat_window_drag_data_received (GtkWidget *widget,
2116     GdkDragContext *context,
2117     int x,
2118     int y,
2119     GtkSelectionData *selection,
2120     guint info,
2121     guint time_,
2122     EmpathyChatWindow *self)
2123 {
2124   if (info == DND_DRAG_TYPE_CONTACT_ID)
2125     {
2126       EmpathyChat *chat = NULL;
2127       EmpathyChatWindow *old_window;
2128       TpAccount *account = NULL;
2129       EmpathyClientFactory *factory;
2130       const gchar *id;
2131       gchar **strv;
2132       const gchar *account_id;
2133       const gchar *contact_id;
2134
2135       id = (const gchar*) gtk_selection_data_get_data (selection);
2136
2137       factory = empathy_client_factory_dup ();
2138
2139       DEBUG ("DND contact from roster with id:'%s'", id);
2140
2141       strv = g_strsplit (id, ":", 2);
2142       if (g_strv_length (strv) == 2)
2143         {
2144           account_id = strv[0];
2145           contact_id = strv[1];
2146
2147           account = tp_simple_client_factory_ensure_account (
2148               TP_SIMPLE_CLIENT_FACTORY (factory), account_id, NULL, NULL);
2149
2150           g_object_unref (factory);
2151           if (account != NULL)
2152             chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2153         }
2154
2155       if (account == NULL)
2156         {
2157           g_strfreev (strv);
2158           gtk_drag_finish (context, FALSE, FALSE, time_);
2159           return;
2160         }
2161
2162       if (!chat)
2163         {
2164           empathy_chat_with_contact_id (account, contact_id,
2165               empathy_get_current_action_time (), NULL, NULL);
2166
2167           g_strfreev (strv);
2168           return;
2169         }
2170
2171       g_strfreev (strv);
2172
2173       old_window = chat_window_find_chat (chat);
2174       if (old_window)
2175         {
2176           if (old_window == self)
2177             {
2178               gtk_drag_finish (context, TRUE, FALSE, time_);
2179               return;
2180             }
2181
2182           empathy_chat_window_move_chat (old_window, self, chat);
2183         }
2184       else
2185         {
2186           empathy_chat_window_add_chat (self, chat);
2187         }
2188
2189       /* Added to take care of any outstanding chat events */
2190       empathy_chat_window_present_chat (chat,
2191           TP_USER_ACTION_TIME_NOT_USER_ACTION);
2192
2193       /* We should return TRUE to remove the data when doing
2194        * GDK_ACTION_MOVE, but we don't here otherwise it has
2195        * weird consequences, and we handle that internally
2196        * anyway with add_chat () and remove_chat ().
2197        */
2198       gtk_drag_finish (context, TRUE, FALSE, time_);
2199     }
2200   else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
2201     {
2202       drag_data_received_individual_id (self, widget, context, x, y,
2203           selection, info, time_);
2204     }
2205   else if (info == DND_DRAG_TYPE_URI_LIST)
2206     {
2207       EmpathyContact *contact;
2208       const gchar *data;
2209
2210       contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2211
2212       /* contact is NULL when current_chat is a multi-user chat.
2213        * We don't do file transfers to MUCs, so just cancel the drag.
2214        */
2215       if (contact == NULL)
2216         {
2217           gtk_drag_finish (context, TRUE, FALSE, time_);
2218           return;
2219         }
2220
2221       data = (const gchar *) gtk_selection_data_get_data (selection);
2222       empathy_send_file_from_uri_list (contact, data);
2223
2224       gtk_drag_finish (context, TRUE, FALSE, time_);
2225     }
2226   else if (info == DND_DRAG_TYPE_TAB)
2227     {
2228       EmpathyChat **chat;
2229       EmpathyChatWindow *old_window = NULL;
2230
2231       DEBUG ("DND tab");
2232
2233       chat = (void *) gtk_selection_data_get_data (selection);
2234       old_window = chat_window_find_chat (*chat);
2235
2236       if (old_window)
2237         {
2238           self->priv->dnd_same_window = (old_window == self);
2239
2240           DEBUG ("DND tab (within same window: %s)",
2241             self->priv->dnd_same_window ? "Yes" : "No");
2242         }
2243     }
2244   else
2245     {
2246       DEBUG ("DND from unknown source");
2247       gtk_drag_finish (context, FALSE, FALSE, time_);
2248     }
2249 }
2250
2251 static void
2252 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2253     guint num_chats_in_manager,
2254     EmpathyChatWindow *self)
2255 {
2256   gtk_action_set_sensitive (self->priv->menu_tabs_undo_close_tab,
2257       num_chats_in_manager > 0);
2258 }
2259
2260 static void
2261 chat_window_finalize (GObject *object)
2262 {
2263   EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2264
2265   DEBUG ("Finalized: %p", object);
2266
2267   g_object_unref (self->priv->ui_manager);
2268   g_object_unref (self->priv->chatroom_manager);
2269   g_object_unref (self->priv->notify_mgr);
2270   g_object_unref (self->priv->gsettings_chat);
2271   g_object_unref (self->priv->gsettings_notif);
2272   g_object_unref (self->priv->gsettings_ui);
2273   g_object_unref (self->priv->sound_mgr);
2274   g_clear_object (&self->priv->individual_mgr);
2275
2276   if (self->priv->notification != NULL)
2277     {
2278       notify_notification_close (self->priv->notification, NULL);
2279       self->priv->notification = NULL;
2280     }
2281
2282   if (self->priv->contact_targets)
2283     gtk_target_list_unref (self->priv->contact_targets);
2284
2285   if (self->priv->file_targets)
2286     gtk_target_list_unref (self->priv->file_targets);
2287
2288   if (self->priv->chat_manager)
2289     {
2290       g_signal_handler_disconnect (self->priv->chat_manager,
2291                  self->priv->chat_manager_chats_changed_id);
2292       g_object_unref (self->priv->chat_manager);
2293       self->priv->chat_manager = NULL;
2294     }
2295
2296   chat_windows = g_list_remove (chat_windows, self);
2297
2298   G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2299 }
2300
2301 static void
2302 chat_window_get_property (GObject *object,
2303     guint property_id,
2304     GValue *value,
2305     GParamSpec *pspec)
2306 {
2307   EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2308
2309   switch (property_id)
2310     {
2311       case PROP_INDIVIDUAL_MGR:
2312         g_value_set_object (value, self->priv->individual_mgr);
2313       default:
2314         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2315         break;
2316     }
2317 }
2318
2319 static void
2320 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2321 {
2322   GObjectClass *object_class = G_OBJECT_CLASS (klass);
2323   GParamSpec *spec;
2324
2325   object_class->get_property = chat_window_get_property;
2326   object_class->finalize = chat_window_finalize;
2327
2328   spec = g_param_spec_object ("individual-manager", "individual-manager",
2329       "EmpathyIndividualManager",
2330       EMPATHY_TYPE_INDIVIDUAL_MANAGER,
2331       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
2332   g_object_class_install_property (object_class, PROP_INDIVIDUAL_MGR, spec);
2333
2334   g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2335 }
2336
2337 static void
2338 empathy_chat_window_init (EmpathyChatWindow *self)
2339 {
2340   GtkBuilder *gui;
2341   GtkAccelGroup *accel_group;
2342   GClosure *closure;
2343   GtkWidget *menu;
2344   GtkWidget *submenu;
2345   guint i;
2346   GtkWidget *chat_vbox;
2347   gchar *filename;
2348   EmpathySmileyManager *smiley_manager;
2349
2350   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2351     EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2352
2353   filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2354   gui = empathy_builder_get_file (filename,
2355       "chat_vbox", &chat_vbox,
2356       "ui_manager", &self->priv->ui_manager,
2357       "menu_conv_insert_smiley", &self->priv->menu_conv_insert_smiley,
2358       "menu_conv_favorite", &self->priv->menu_conv_favorite,
2359       "menu_conv_always_urgent", &self->priv->menu_conv_always_urgent,
2360       "menu_conv_toggle_contacts", &self->priv->menu_conv_toggle_contacts,
2361       "menu_edit_cut", &self->priv->menu_edit_cut,
2362       "menu_edit_copy", &self->priv->menu_edit_copy,
2363       "menu_edit_paste", &self->priv->menu_edit_paste,
2364       "menu_edit_find", &self->priv->menu_edit_find,
2365       "menu_tabs_next", &self->priv->menu_tabs_next,
2366       "menu_tabs_prev", &self->priv->menu_tabs_prev,
2367       "menu_tabs_undo_close_tab", &self->priv->menu_tabs_undo_close_tab,
2368       "menu_tabs_left", &self->priv->menu_tabs_left,
2369       "menu_tabs_right", &self->priv->menu_tabs_right,
2370        "menu_tabs_detach", &self->priv->menu_tabs_detach,
2371       NULL);
2372   g_free (filename);
2373
2374   empathy_builder_connect (gui, self,
2375       "menu_conv", "activate", chat_window_conv_activate_cb,
2376       "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2377       "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2378       "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2379       "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2380       "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2381       "menu_conv_close", "activate", chat_window_close_activate_cb,
2382       "menu_edit", "activate", chat_window_edit_activate_cb,
2383       "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2384       "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2385       "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2386       "menu_edit_find", "activate", chat_window_find_activate_cb,
2387       "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2388       "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2389       "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2390       "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2391       "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2392       "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2393       "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2394       "menu_help_about", "activate", chat_window_help_about_activate_cb,
2395       NULL);
2396
2397   empathy_set_css_provider (GTK_WIDGET (self));
2398
2399   self->priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2400   self->priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2401   self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2402   self->priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2403
2404   self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2405
2406   self->priv->notebook = gtk_notebook_new ();
2407
2408   g_signal_connect (self->priv->notebook, "create-window",
2409       G_CALLBACK (notebook_create_window_cb), self);
2410
2411   gtk_container_add (GTK_CONTAINER (self), chat_vbox);
2412
2413   gtk_notebook_set_group_name (GTK_NOTEBOOK (self->priv->notebook),
2414     "EmpathyChatWindow");
2415   gtk_notebook_set_scrollable (GTK_NOTEBOOK (self->priv->notebook), TRUE);
2416   gtk_notebook_popup_enable (GTK_NOTEBOOK (self->priv->notebook));
2417   gtk_box_pack_start (GTK_BOX (chat_vbox), self->priv->notebook, TRUE, TRUE, 0);
2418   gtk_widget_show (self->priv->notebook);
2419
2420   /* Set up accels */
2421   accel_group = gtk_accel_group_new ();
2422   gtk_window_add_accel_group (GTK_WINDOW (self), accel_group);
2423
2424   for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
2425     {
2426       closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb), self,
2427           NULL);
2428
2429       gtk_accel_group_connect (accel_group, tab_accel_keys[i], GDK_MOD1_MASK, 0,
2430           closure);
2431     }
2432
2433   g_object_unref (accel_group);
2434
2435   /* Set up drag target lists */
2436   self->priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2437       G_N_ELEMENTS (drag_types_dest_contact));
2438
2439   self->priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2440       G_N_ELEMENTS (drag_types_dest_file));
2441
2442   /* Set up smiley menu */
2443   smiley_manager = empathy_smiley_manager_dup_singleton ();
2444   submenu = empathy_smiley_menu_new (smiley_manager,
2445       chat_window_insert_smiley_activate_cb, self);
2446
2447   menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
2448     "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2449   gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2450   g_object_unref (smiley_manager);
2451
2452   /* Set up signals we can't do with ui file since we may need to
2453    * block/unblock them at some later stage.
2454    */
2455
2456   g_signal_connect (self, "delete_event",
2457       G_CALLBACK (chat_window_delete_event_cb), self);
2458   g_signal_connect (self, "focus_in_event",
2459       G_CALLBACK (chat_window_focus_in_event_cb), self);
2460   g_signal_connect (self, "focus_out_event",
2461       G_CALLBACK (chat_window_focus_out_event_cb), self);
2462   g_signal_connect_after (self->priv->notebook, "switch_page",
2463       G_CALLBACK (chat_window_page_switched_cb), self);
2464   g_signal_connect (self->priv->notebook, "page_added",
2465       G_CALLBACK (chat_window_page_added_cb), self);
2466   g_signal_connect (self->priv->notebook, "page_removed",
2467       G_CALLBACK (chat_window_page_removed_cb), self);
2468
2469   /* Set up drag and drop */
2470   gtk_drag_dest_set (GTK_WIDGET (self->priv->notebook),
2471       GTK_DEST_DEFAULT_HIGHLIGHT,
2472       drag_types_dest,
2473       G_N_ELEMENTS (drag_types_dest),
2474       GDK_ACTION_MOVE | GDK_ACTION_COPY);
2475
2476   /* connect_after to allow GtkNotebook's built-in tab switching */
2477   g_signal_connect_after (self->priv->notebook, "drag-motion",
2478       G_CALLBACK (chat_window_drag_motion), self);
2479   g_signal_connect (self->priv->notebook, "drag-data-received",
2480       G_CALLBACK (chat_window_drag_data_received), self);
2481   g_signal_connect (self->priv->notebook, "drag-drop",
2482       G_CALLBACK (chat_window_drag_drop), self);
2483
2484   chat_windows = g_list_prepend (chat_windows, self);
2485
2486   /* Set up private details */
2487   self->priv->chats = NULL;
2488   self->priv->current_chat = NULL;
2489   self->priv->notification = NULL;
2490
2491   self->priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2492
2493   self->priv->chat_manager = empathy_chat_manager_dup_singleton ();
2494   self->priv->chat_manager_chats_changed_id = g_signal_connect (
2495       self->priv->chat_manager, "closed-chats-changed",
2496       G_CALLBACK (chat_window_chat_manager_chats_changed_cb), self);
2497
2498   chat_window_chat_manager_chats_changed_cb (self->priv->chat_manager,
2499       empathy_chat_manager_get_num_closed_chats (self->priv->chat_manager), self);
2500
2501   g_object_ref (self->priv->ui_manager);
2502   g_object_unref (gui);
2503 }
2504
2505 /* Returns the window to open a new tab in if there is a suitable window,
2506  * otherwise, returns NULL indicating that a new window should be added.
2507  */
2508 static EmpathyChatWindow *
2509 empathy_chat_window_get_default (gboolean room)
2510 {
2511   GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2512   GList *l;
2513   gboolean separate_windows = TRUE;
2514
2515   separate_windows = g_settings_get_boolean (gsettings,
2516       EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2517
2518   g_object_unref (gsettings);
2519
2520   if (separate_windows)
2521     /* Always create a new window */
2522     return NULL;
2523
2524   for (l = chat_windows; l; l = l->next)
2525     {
2526       EmpathyChatWindow *chat_window;
2527       guint nb_rooms, nb_private;
2528
2529       chat_window = l->data;
2530
2531       empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2532
2533       /* Skip the window if there aren't any rooms in it */
2534       if (room && nb_rooms == 0)
2535         continue;
2536
2537       /* Skip the window if there aren't any 1-1 chats in it */
2538       if (!room && nb_private == 0)
2539         continue;
2540
2541       return chat_window;
2542     }
2543
2544   return NULL;
2545 }
2546
2547 static void
2548 empathy_chat_window_add_chat (EmpathyChatWindow *self,
2549     EmpathyChat *chat)
2550 {
2551   GtkWidget *label;
2552   GtkWidget *popup_label;
2553   GtkWidget *child;
2554   GValue value = { 0, };
2555
2556   g_return_if_fail (self != NULL);
2557   g_return_if_fail (EMPATHY_IS_CHAT (chat));
2558
2559   /* Reference the chat object */
2560   g_object_ref (chat);
2561
2562   /* If this window has just been created, position it */
2563   if (self->priv->chats == NULL)
2564     {
2565       const gchar *name = "chat-window";
2566       gboolean separate_windows;
2567
2568       separate_windows = g_settings_get_boolean (self->priv->gsettings_ui,
2569           EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2570
2571       if (empathy_chat_is_room (chat))
2572         name = "room-window";
2573
2574       if (separate_windows)
2575         {
2576           gint x, y;
2577
2578           /* Save current position of the window */
2579           gtk_window_get_position (GTK_WINDOW (self), &x, &y);
2580
2581           /* First bind to the 'generic' name. So new window for which we didn't
2582           * save a geometry yet will have the geometry of the last saved
2583           * window (bgo #601191). */
2584           empathy_geometry_bind (GTK_WINDOW (self), name);
2585
2586           /* Restore previous position of the window so the newly created window
2587           * won't be in the same position as the latest saved window and so
2588           * completely hide it. */
2589           gtk_window_move (GTK_WINDOW (self), x, y);
2590
2591           /* Then bind it to the name of the contact/room so we'll save the
2592           * geometry specific to this window */
2593           name = empathy_chat_get_id (chat);
2594         }
2595
2596       empathy_geometry_bind (GTK_WINDOW (self), name);
2597     }
2598
2599   child = GTK_WIDGET (chat);
2600   label = chat_window_create_label (self, chat, TRUE);
2601   popup_label = chat_window_create_label (self, chat, FALSE);
2602   gtk_widget_show (child);
2603
2604   g_signal_connect (chat, "notify::name",
2605       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2606   g_signal_connect (chat, "notify::subject",
2607       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2608   g_signal_connect (chat, "notify::remote-contact",
2609       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2610   g_signal_connect (chat, "notify::sms-channel",
2611       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2612   g_signal_connect (chat, "notify::n-messages-sending",
2613       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2614   g_signal_connect (chat, "notify::nb-unread-messages",
2615       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2616   chat_window_chat_notify_cb (chat);
2617
2618   gtk_notebook_append_page_menu (GTK_NOTEBOOK (self->priv->notebook), child, label,
2619       popup_label);
2620   gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2621   gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2622   g_value_init (&value, G_TYPE_BOOLEAN);
2623   g_value_set_boolean (&value, TRUE);
2624   gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2625       child, "tab-expand" , &value);
2626   gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2627       child,  "tab-fill" , &value);
2628   g_value_unset (&value);
2629
2630   DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2631 }
2632
2633 static void
2634 empathy_chat_window_remove_chat (EmpathyChatWindow *self,
2635     EmpathyChat *chat)
2636 {
2637   gint position;
2638   EmpathyContact *remote_contact;
2639   EmpathyChatManager *chat_manager;
2640
2641   g_return_if_fail (self != NULL);
2642   g_return_if_fail (EMPATHY_IS_CHAT (chat));
2643
2644   g_signal_handlers_disconnect_by_func (chat,
2645       chat_window_chat_notify_cb, NULL);
2646
2647   remote_contact = g_object_get_data (G_OBJECT (chat),
2648       "chat-window-remote-contact");
2649
2650   if (remote_contact)
2651     {
2652       g_signal_handlers_disconnect_by_func (remote_contact,
2653           chat_window_update_chat_tab, chat);
2654     }
2655
2656   chat_manager = empathy_chat_manager_dup_singleton ();
2657   empathy_chat_manager_closed_chat (chat_manager, chat);
2658   g_object_unref (chat_manager);
2659
2660   position = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2661       GTK_WIDGET (chat));
2662   gtk_notebook_remove_page (GTK_NOTEBOOK (self->priv->notebook), position);
2663
2664   DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2665
2666   g_object_unref (chat);
2667 }
2668
2669 static void
2670 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2671     EmpathyChatWindow *new_window,
2672     EmpathyChat *chat)
2673 {
2674   GtkWidget *widget;
2675
2676   g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2677   g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2678   g_return_if_fail (EMPATHY_IS_CHAT (chat));
2679
2680   widget = GTK_WIDGET (chat);
2681
2682   DEBUG ("Chat moving with widget:%p (%d references)", widget,
2683       G_OBJECT (widget)->ref_count);
2684
2685   /* We reference here to make sure we don't loose the widget
2686    * and the EmpathyChat object during the move.
2687    */
2688   g_object_ref (chat);
2689   g_object_ref (widget);
2690
2691   empathy_chat_window_remove_chat (old_window, chat);
2692   empathy_chat_window_add_chat (new_window, chat);
2693
2694   g_object_unref (widget);
2695   g_object_unref (chat);
2696 }
2697
2698 static void
2699 empathy_chat_window_switch_to_chat (EmpathyChatWindow *self,
2700     EmpathyChat *chat)
2701 {
2702   gint page_num;
2703
2704   g_return_if_fail (self != NULL);
2705   g_return_if_fail (EMPATHY_IS_CHAT (chat));
2706
2707   page_num = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2708       GTK_WIDGET (chat));
2709
2710   gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
2711       page_num);
2712 }
2713
2714 EmpathyChat *
2715 empathy_chat_window_find_chat (TpAccount *account,
2716     const gchar *id,
2717     gboolean sms_channel)
2718 {
2719   GList *l;
2720
2721   g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2722
2723   for (l = chat_windows; l; l = l->next)
2724     {
2725       EmpathyChatWindow *window = l->data;
2726       GList *ll;
2727
2728       for (ll = window->priv->chats; ll; ll = ll->next)
2729         {
2730           EmpathyChat *chat;
2731
2732           chat = ll->data;
2733
2734           if (account == empathy_chat_get_account (chat) &&
2735               !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2736               sms_channel == empathy_chat_is_sms_channel (chat))
2737             return chat;
2738         }
2739     }
2740
2741   return NULL;
2742 }
2743
2744 EmpathyChatWindow *
2745 empathy_chat_window_present_chat (EmpathyChat *chat,
2746     gint64 timestamp)
2747 {
2748   EmpathyChatWindow *self;
2749   guint32 x_timestamp;
2750
2751   g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2752
2753   self = chat_window_find_chat (chat);
2754
2755   /* If the chat has no window, create one */
2756   if (self == NULL)
2757     {
2758       self = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2759       if (!self)
2760         {
2761           self = empathy_chat_window_new ();
2762
2763           /* we want to display the newly created window even if we
2764            * don't present it */
2765           gtk_widget_show (GTK_WIDGET (self));
2766         }
2767
2768       empathy_chat_window_add_chat (self, chat);
2769     }
2770
2771   /* Don't force the window to show itself when it wasn't
2772    * an action by the user
2773    */
2774   if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2775     return self;
2776
2777   if (x_timestamp != GDK_CURRENT_TIME)
2778     {
2779       /* Don't present or switch tab if the action was earlier than the
2780        * last actions X time, accounting for overflow and the first ever
2781       * presentation */
2782
2783       if (self->priv->x_user_action_time != 0
2784         && X_EARLIER_OR_EQL (x_timestamp, self->priv->x_user_action_time))
2785         return self;
2786
2787       self->priv->x_user_action_time = x_timestamp;
2788     }
2789
2790   empathy_chat_window_switch_to_chat (self, chat);
2791
2792   /* Don't use empathy_window_present_with_time () which would move the window
2793    * to our current desktop but move to the window's desktop instead. This is
2794    * more coherent with Shell's 'app is ready' notication which moves the view
2795    * to the app desktop rather than moving the app itself. */
2796   empathy_move_to_window_desktop (GTK_WINDOW (self), x_timestamp);
2797
2798   gtk_widget_grab_focus (chat->input_text_view);
2799   return self;
2800 }
2801
2802 static void
2803 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2804     guint *nb_rooms,
2805     guint *nb_private)
2806 {
2807   GList *l;
2808   guint _nb_rooms = 0, _nb_private = 0;
2809
2810   for (l = self->priv->chats; l != NULL; l = g_list_next (l))
2811     {
2812       if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2813         _nb_rooms++;
2814       else
2815         _nb_private++;
2816     }
2817
2818   if (nb_rooms != NULL)
2819     *nb_rooms = _nb_rooms;
2820   if (nb_private != NULL)
2821     *nb_private = _nb_private;
2822 }
2823
2824 EmpathyIndividualManager *
2825 empathy_chat_window_get_individual_manager (EmpathyChatWindow *self)
2826 {
2827   return self->priv->individual_mgr;
2828 }