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