]> git.0d.be Git - empathy.git/blob - src/empathy-chat-window.c
move the individual mgr reference to empathy-chat-window
[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_drag_drop (GtkWidget *widget,
1919     GdkDragContext *context,
1920     int x,
1921     int y,
1922     guint time_,
1923     EmpathyChatWindow *self)
1924 {
1925   GdkAtom target;
1926
1927   target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
1928   if (target == GDK_NONE)
1929     target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
1930
1931   if (target != GDK_NONE)
1932     {
1933       gtk_drag_get_data (widget, context, target, time_);
1934       return TRUE;
1935     }
1936
1937   return FALSE;
1938 }
1939
1940 static gboolean
1941 chat_window_drag_motion (GtkWidget *widget,
1942     GdkDragContext *context,
1943     int x,
1944     int y,
1945     guint time_,
1946     EmpathyChatWindow *self)
1947 {
1948   GdkAtom target;
1949
1950   target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
1951
1952   if (target != GDK_NONE)
1953     {
1954       /* This is a file drag. Ensure the contact is online and set the
1955          drag type to COPY. Note that it's possible that the tab will
1956          be switched by GTK+ after a timeout from drag_motion without
1957          getting another drag_motion to disable the drop. You have
1958          to hold your mouse really still.
1959        */
1960       EmpathyContact *contact;
1961
1962       contact = empathy_chat_get_remote_contact (self->priv->current_chat);
1963
1964       /* contact is NULL for multi-user chats. We don't do
1965        * file transfers to MUCs. We also don't send files
1966        * to offline contacts or contacts that don't support
1967        * file transfer.
1968        */
1969       if ((contact == NULL) || !empathy_contact_is_online (contact))
1970         {
1971           gdk_drag_status (context, 0, time_);
1972           return FALSE;
1973         }
1974
1975       if (!(empathy_contact_get_capabilities (contact)
1976            & EMPATHY_CAPABILITIES_FT))
1977         {
1978           gdk_drag_status (context, 0, time_);
1979           return FALSE;
1980         }
1981
1982       gdk_drag_status (context, GDK_ACTION_COPY, time_);
1983       return TRUE;
1984     }
1985
1986   target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
1987   if (target != GDK_NONE)
1988     {
1989       /* This is a drag of a contact from a contact list. Set to COPY.
1990          FIXME: If this drag is to a MUC window, it invites the user.
1991          Otherwise, it opens a chat. Should we use a different drag
1992          type for invites? Should we allow ASK?
1993        */
1994       gdk_drag_status (context, GDK_ACTION_COPY, time_);
1995       return TRUE;
1996     }
1997
1998   return FALSE;
1999 }
2000
2001 static void
2002 drag_data_received_individual_id (EmpathyChatWindow *self,
2003     GtkWidget *widget,
2004     GdkDragContext *context,
2005     int x,
2006     int y,
2007     GtkSelectionData *selection,
2008     guint info,
2009     guint time_)
2010 {
2011   const gchar *id;
2012   FolksIndividual *individual;
2013   EmpathyTpChat *chat;
2014   TpContact *tp_contact;
2015   TpConnection *conn;
2016   EmpathyContact *contact;
2017
2018   id = (const gchar *) gtk_selection_data_get_data (selection);
2019
2020   DEBUG ("DND invididual %s", id);
2021
2022   if (self->priv->current_chat == NULL)
2023     goto out;
2024
2025   chat = empathy_chat_get_tp_chat (self->priv->current_chat);
2026   if (chat == NULL)
2027     goto out;
2028
2029   if (!empathy_tp_chat_can_add_contact (chat))
2030     {
2031       DEBUG ("Can't invite contact to %s",
2032           tp_proxy_get_object_path (chat));
2033       goto out;
2034     }
2035
2036   individual = empathy_individual_manager_lookup_member (
2037           self->priv->individual_mgr, id);
2038   if (individual == NULL)
2039     {
2040       DEBUG ("Failed to find individual %s", id);
2041       goto out;
2042     }
2043
2044   conn = tp_channel_get_connection ((TpChannel *) chat);
2045   tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2046   if (tp_contact == NULL)
2047     {
2048       DEBUG ("Can't find a TpContact on connection %s for %s",
2049           tp_proxy_get_object_path (conn), id);
2050       goto out;
2051     }
2052
2053   DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2054       tp_channel_get_identifier ((TpChannel *) chat));
2055
2056   contact = empathy_contact_dup_from_tp_contact (tp_contact);
2057   empathy_tp_chat_add (chat, contact, NULL);
2058   g_object_unref (contact);
2059
2060 out:
2061   gtk_drag_finish (context, TRUE, FALSE, time_);
2062 }
2063
2064 static void
2065 chat_window_drag_data_received (GtkWidget *widget,
2066     GdkDragContext *context,
2067     int x,
2068     int y,
2069     GtkSelectionData *selection,
2070     guint info,
2071     guint time_,
2072     EmpathyChatWindow *self)
2073 {
2074   if (info == DND_DRAG_TYPE_CONTACT_ID)
2075     {
2076       EmpathyChat *chat = NULL;
2077       EmpathyChatWindow *old_window;
2078       TpAccount *account = NULL;
2079       EmpathyClientFactory *factory;
2080       const gchar *id;
2081       gchar **strv;
2082       const gchar *account_id;
2083       const gchar *contact_id;
2084
2085       id = (const gchar*) gtk_selection_data_get_data (selection);
2086
2087       factory = empathy_client_factory_dup ();
2088
2089       DEBUG ("DND contact from roster with id:'%s'", id);
2090
2091       strv = g_strsplit (id, ":", 2);
2092       if (g_strv_length (strv) == 2)
2093         {
2094           account_id = strv[0];
2095           contact_id = strv[1];
2096
2097           account = tp_simple_client_factory_ensure_account (
2098               TP_SIMPLE_CLIENT_FACTORY (factory), account_id, NULL, NULL);
2099
2100           g_object_unref (factory);
2101           if (account != NULL)
2102             chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2103         }
2104
2105       if (account == NULL)
2106         {
2107           g_strfreev (strv);
2108           gtk_drag_finish (context, FALSE, FALSE, time_);
2109           return;
2110         }
2111
2112       if (!chat)
2113         {
2114           empathy_chat_with_contact_id (account, contact_id,
2115               empathy_get_current_action_time (), NULL, NULL);
2116
2117           g_strfreev (strv);
2118           return;
2119         }
2120
2121       g_strfreev (strv);
2122
2123       old_window = chat_window_find_chat (chat);
2124       if (old_window)
2125         {
2126           if (old_window == self)
2127             {
2128               gtk_drag_finish (context, TRUE, FALSE, time_);
2129               return;
2130             }
2131
2132           empathy_chat_window_move_chat (old_window, self, chat);
2133         }
2134       else
2135         {
2136           empathy_chat_window_add_chat (self, chat);
2137         }
2138
2139       /* Added to take care of any outstanding chat events */
2140       empathy_chat_window_present_chat (chat,
2141           TP_USER_ACTION_TIME_NOT_USER_ACTION);
2142
2143       /* We should return TRUE to remove the data when doing
2144        * GDK_ACTION_MOVE, but we don't here otherwise it has
2145        * weird consequences, and we handle that internally
2146        * anyway with add_chat () and remove_chat ().
2147        */
2148       gtk_drag_finish (context, TRUE, FALSE, time_);
2149     }
2150   else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
2151     {
2152       drag_data_received_individual_id (self, widget, context, x, y,
2153           selection, info, time_);
2154     }
2155   else if (info == DND_DRAG_TYPE_URI_LIST)
2156     {
2157       EmpathyContact *contact;
2158       const gchar *data;
2159
2160       contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2161
2162       /* contact is NULL when current_chat is a multi-user chat.
2163        * We don't do file transfers to MUCs, so just cancel the drag.
2164        */
2165       if (contact == NULL)
2166         {
2167           gtk_drag_finish (context, TRUE, FALSE, time_);
2168           return;
2169         }
2170
2171       data = (const gchar *) gtk_selection_data_get_data (selection);
2172       empathy_send_file_from_uri_list (contact, data);
2173
2174       gtk_drag_finish (context, TRUE, FALSE, time_);
2175     }
2176   else if (info == DND_DRAG_TYPE_TAB)
2177     {
2178       EmpathyChat **chat;
2179       EmpathyChatWindow *old_window = NULL;
2180
2181       DEBUG ("DND tab");
2182
2183       chat = (void *) gtk_selection_data_get_data (selection);
2184       old_window = chat_window_find_chat (*chat);
2185
2186       if (old_window)
2187         {
2188           self->priv->dnd_same_window = (old_window == self);
2189
2190           DEBUG ("DND tab (within same window: %s)",
2191             self->priv->dnd_same_window ? "Yes" : "No");
2192         }
2193     }
2194   else
2195     {
2196       DEBUG ("DND from unknown source");
2197       gtk_drag_finish (context, FALSE, FALSE, time_);
2198     }
2199 }
2200
2201 static void
2202 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2203     guint num_chats_in_manager,
2204     EmpathyChatWindow *self)
2205 {
2206   gtk_action_set_sensitive (self->priv->menu_tabs_undo_close_tab,
2207       num_chats_in_manager > 0);
2208 }
2209
2210 static void
2211 chat_window_finalize (GObject *object)
2212 {
2213   EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2214
2215   DEBUG ("Finalized: %p", object);
2216
2217   g_object_unref (self->priv->ui_manager);
2218   g_object_unref (self->priv->chatroom_manager);
2219   g_object_unref (self->priv->notify_mgr);
2220   g_object_unref (self->priv->gsettings_chat);
2221   g_object_unref (self->priv->gsettings_notif);
2222   g_object_unref (self->priv->gsettings_ui);
2223   g_object_unref (self->priv->sound_mgr);
2224   g_clear_object (&self->priv->individual_mgr);
2225
2226   if (self->priv->notification != NULL)
2227     {
2228       notify_notification_close (self->priv->notification, NULL);
2229       self->priv->notification = NULL;
2230     }
2231
2232   if (self->priv->contact_targets)
2233     gtk_target_list_unref (self->priv->contact_targets);
2234
2235   if (self->priv->file_targets)
2236     gtk_target_list_unref (self->priv->file_targets);
2237
2238   if (self->priv->chat_manager)
2239     {
2240       g_signal_handler_disconnect (self->priv->chat_manager,
2241                  self->priv->chat_manager_chats_changed_id);
2242       g_object_unref (self->priv->chat_manager);
2243       self->priv->chat_manager = NULL;
2244     }
2245
2246   chat_windows = g_list_remove (chat_windows, self);
2247   gtk_widget_destroy (self->priv->dialog);
2248
2249   G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2250 }
2251
2252 static void
2253 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2254 {
2255   GObjectClass *object_class = G_OBJECT_CLASS (klass);
2256
2257   object_class->finalize = chat_window_finalize;
2258
2259   g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2260 }
2261
2262 static void
2263 empathy_chat_window_init (EmpathyChatWindow *self)
2264 {
2265   GtkBuilder *gui;
2266   GtkAccelGroup *accel_group;
2267   GClosure *closure;
2268   GtkWidget *menu;
2269   GtkWidget *submenu;
2270   guint i;
2271   GtkWidget *chat_vbox;
2272   gchar *filename;
2273   EmpathySmileyManager *smiley_manager;
2274
2275   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2276     EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2277
2278   filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2279   gui = empathy_builder_get_file (filename,
2280       "chat_window", &self->priv->dialog,
2281       "chat_vbox", &chat_vbox,
2282       "ui_manager", &self->priv->ui_manager,
2283       "menu_conv_insert_smiley", &self->priv->menu_conv_insert_smiley,
2284       "menu_conv_favorite", &self->priv->menu_conv_favorite,
2285       "menu_conv_always_urgent", &self->priv->menu_conv_always_urgent,
2286       "menu_conv_toggle_contacts", &self->priv->menu_conv_toggle_contacts,
2287       "menu_edit_cut", &self->priv->menu_edit_cut,
2288       "menu_edit_copy", &self->priv->menu_edit_copy,
2289       "menu_edit_paste", &self->priv->menu_edit_paste,
2290       "menu_edit_find", &self->priv->menu_edit_find,
2291       "menu_tabs_next", &self->priv->menu_tabs_next,
2292       "menu_tabs_prev", &self->priv->menu_tabs_prev,
2293       "menu_tabs_undo_close_tab", &self->priv->menu_tabs_undo_close_tab,
2294       "menu_tabs_left", &self->priv->menu_tabs_left,
2295       "menu_tabs_right", &self->priv->menu_tabs_right,
2296        "menu_tabs_detach", &self->priv->menu_tabs_detach,
2297       NULL);
2298   g_free (filename);
2299
2300   empathy_builder_connect (gui, self,
2301       "menu_conv", "activate", chat_window_conv_activate_cb,
2302       "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2303       "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2304       "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2305       "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2306       "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2307       "menu_conv_close", "activate", chat_window_close_activate_cb,
2308       "menu_edit", "activate", chat_window_edit_activate_cb,
2309       "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2310       "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2311       "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2312       "menu_edit_find", "activate", chat_window_find_activate_cb,
2313       "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2314       "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2315       "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2316       "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2317       "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2318       "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2319       "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2320       "menu_help_about", "activate", chat_window_help_about_activate_cb,
2321       NULL);
2322
2323   g_object_ref (self->priv->ui_manager);
2324   g_object_unref (gui);
2325
2326   empathy_set_css_provider (GTK_WIDGET (self->priv->dialog));
2327
2328   self->priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2329   self->priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2330   self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2331   self->priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2332
2333   /* Keep the individual manager alive so we won't fetch everything from Folks
2334    * each time we need to use it. The individual manager (and so Folks) is
2335    * needed to know to which FolksIndividual a TpContact belongs, including:
2336    * - empathy_chat_get_contact_menu: to list all the personas of the contact
2337    * - empathy_display_individual_info: to invoke gnome-contacts with the
2338    *   FolksIndividual.id of the contact
2339    * - drag_data_received_individual_id: to find the individual associated
2340    *   with the ID we received from the DnD in order to invite him.
2341    */
2342   self->priv->individual_mgr = empathy_individual_manager_dup_singleton ();
2343
2344   self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2345
2346   self->priv->notebook = gtk_notebook_new ();
2347
2348   g_signal_connect (self->priv->notebook, "create-window",
2349       G_CALLBACK (notebook_create_window_cb), self);
2350
2351   gtk_notebook_set_group_name (GTK_NOTEBOOK (self->priv->notebook),
2352     "EmpathyChatWindow");
2353   gtk_notebook_set_scrollable (GTK_NOTEBOOK (self->priv->notebook), TRUE);
2354   gtk_notebook_popup_enable (GTK_NOTEBOOK (self->priv->notebook));
2355   gtk_box_pack_start (GTK_BOX (chat_vbox), self->priv->notebook, TRUE, TRUE, 0);
2356   gtk_widget_show (self->priv->notebook);
2357
2358   /* Set up accels */
2359   accel_group = gtk_accel_group_new ();
2360   gtk_window_add_accel_group (GTK_WINDOW (self->priv->dialog), accel_group);
2361
2362   for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
2363     {
2364       closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb), self,
2365           NULL);
2366
2367       gtk_accel_group_connect (accel_group, tab_accel_keys[i], GDK_MOD1_MASK, 0,
2368           closure);
2369     }
2370
2371   g_object_unref (accel_group);
2372
2373   /* Set up drag target lists */
2374   self->priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2375       G_N_ELEMENTS (drag_types_dest_contact));
2376
2377   self->priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2378       G_N_ELEMENTS (drag_types_dest_file));
2379
2380   /* Set up smiley menu */
2381   smiley_manager = empathy_smiley_manager_dup_singleton ();
2382   submenu = empathy_smiley_menu_new (smiley_manager,
2383       chat_window_insert_smiley_activate_cb, self);
2384
2385   menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
2386     "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2387   gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2388   g_object_unref (smiley_manager);
2389
2390   /* Set up signals we can't do with ui file since we may need to
2391    * block/unblock them at some later stage.
2392    */
2393
2394   g_signal_connect (self->priv->dialog, "delete_event",
2395       G_CALLBACK (chat_window_delete_event_cb), self);
2396   g_signal_connect (self->priv->dialog, "focus_in_event",
2397       G_CALLBACK (chat_window_focus_in_event_cb), self);
2398   g_signal_connect_after (self->priv->notebook, "switch_page",
2399       G_CALLBACK (chat_window_page_switched_cb), self);
2400   g_signal_connect (self->priv->notebook, "page_added",
2401       G_CALLBACK (chat_window_page_added_cb), self);
2402   g_signal_connect (self->priv->notebook, "page_removed",
2403       G_CALLBACK (chat_window_page_removed_cb), self);
2404
2405   /* Set up drag and drop */
2406   gtk_drag_dest_set (GTK_WIDGET (self->priv->notebook),
2407       GTK_DEST_DEFAULT_HIGHLIGHT,
2408       drag_types_dest,
2409       G_N_ELEMENTS (drag_types_dest),
2410       GDK_ACTION_MOVE | GDK_ACTION_COPY);
2411
2412   /* connect_after to allow GtkNotebook's built-in tab switching */
2413   g_signal_connect_after (self->priv->notebook, "drag-motion",
2414       G_CALLBACK (chat_window_drag_motion), self);
2415   g_signal_connect (self->priv->notebook, "drag-data-received",
2416       G_CALLBACK (chat_window_drag_data_received), self);
2417   g_signal_connect (self->priv->notebook, "drag-drop",
2418       G_CALLBACK (chat_window_drag_drop), self);
2419
2420   chat_windows = g_list_prepend (chat_windows, self);
2421
2422   /* Set up private details */
2423   self->priv->chats = NULL;
2424   self->priv->current_chat = NULL;
2425   self->priv->notification = NULL;
2426
2427   self->priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2428
2429   self->priv->chat_manager = empathy_chat_manager_dup_singleton ();
2430   self->priv->chat_manager_chats_changed_id = g_signal_connect (
2431       self->priv->chat_manager, "closed-chats-changed",
2432       G_CALLBACK (chat_window_chat_manager_chats_changed_cb), self);
2433
2434   chat_window_chat_manager_chats_changed_cb (self->priv->chat_manager,
2435       empathy_chat_manager_get_num_closed_chats (self->priv->chat_manager), self);
2436 }
2437
2438 /* Returns the window to open a new tab in if there is a suitable window,
2439  * otherwise, returns NULL indicating that a new window should be added.
2440  */
2441 static EmpathyChatWindow *
2442 empathy_chat_window_get_default (gboolean room)
2443 {
2444   GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2445   GList *l;
2446   gboolean separate_windows = TRUE;
2447
2448   separate_windows = g_settings_get_boolean (gsettings,
2449       EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2450
2451   g_object_unref (gsettings);
2452
2453   if (separate_windows)
2454     /* Always create a new window */
2455     return NULL;
2456
2457   for (l = chat_windows; l; l = l->next)
2458     {
2459       EmpathyChatWindow *chat_window;
2460       guint nb_rooms, nb_private;
2461
2462       chat_window = l->data;
2463
2464       empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2465
2466       /* Skip the window if there aren't any rooms in it */
2467       if (room && nb_rooms == 0)
2468         continue;
2469
2470       /* Skip the window if there aren't any 1-1 chats in it */
2471       if (!room && nb_private == 0)
2472         continue;
2473
2474       return chat_window;
2475     }
2476
2477   return NULL;
2478 }
2479
2480 static void
2481 empathy_chat_window_add_chat (EmpathyChatWindow *self,
2482     EmpathyChat *chat)
2483 {
2484   GtkWidget *label;
2485   GtkWidget *popup_label;
2486   GtkWidget *child;
2487   GValue value = { 0, };
2488
2489   g_return_if_fail (self != NULL);
2490   g_return_if_fail (EMPATHY_IS_CHAT (chat));
2491
2492   /* Reference the chat object */
2493   g_object_ref (chat);
2494
2495   /* If this window has just been created, position it */
2496   if (self->priv->chats == NULL)
2497     {
2498       const gchar *name = "chat-window";
2499       gboolean separate_windows;
2500
2501       separate_windows = g_settings_get_boolean (self->priv->gsettings_ui,
2502           EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2503
2504       if (empathy_chat_is_room (chat))
2505         name = "room-window";
2506
2507       if (separate_windows)
2508         {
2509           gint x, y;
2510
2511           /* Save current position of the window */
2512           gtk_window_get_position (GTK_WINDOW (self->priv->dialog), &x, &y);
2513
2514           /* First bind to the 'generic' name. So new window for which we didn't
2515           * save a geometry yet will have the geometry of the last saved
2516           * window (bgo #601191). */
2517           empathy_geometry_bind (GTK_WINDOW (self->priv->dialog), name);
2518
2519           /* Restore previous position of the window so the newly created window
2520           * won't be in the same position as the latest saved window and so
2521           * completely hide it. */
2522           gtk_window_move (GTK_WINDOW (self->priv->dialog), x, y);
2523
2524           /* Then bind it to the name of the contact/room so we'll save the
2525           * geometry specific to this window */
2526           name = empathy_chat_get_id (chat);
2527         }
2528
2529       empathy_geometry_bind (GTK_WINDOW (self->priv->dialog), name);
2530     }
2531
2532   child = GTK_WIDGET (chat);
2533   label = chat_window_create_label (self, chat, TRUE);
2534   popup_label = chat_window_create_label (self, chat, FALSE);
2535   gtk_widget_show (child);
2536
2537   g_signal_connect (chat, "notify::name",
2538       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2539   g_signal_connect (chat, "notify::subject",
2540       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2541   g_signal_connect (chat, "notify::remote-contact",
2542       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2543   g_signal_connect (chat, "notify::sms-channel",
2544       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2545   g_signal_connect (chat, "notify::n-messages-sending",
2546       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2547   g_signal_connect (chat, "notify::nb-unread-messages",
2548       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2549   chat_window_chat_notify_cb (chat);
2550
2551   gtk_notebook_append_page_menu (GTK_NOTEBOOK (self->priv->notebook), child, label,
2552       popup_label);
2553   gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2554   gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2555   g_value_init (&value, G_TYPE_BOOLEAN);
2556   g_value_set_boolean (&value, TRUE);
2557   gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2558       child, "tab-expand" , &value);
2559   gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2560       child,  "tab-fill" , &value);
2561   g_value_unset (&value);
2562
2563   DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2564 }
2565
2566 static void
2567 empathy_chat_window_remove_chat (EmpathyChatWindow *self,
2568     EmpathyChat *chat)
2569 {
2570   gint position;
2571   EmpathyContact *remote_contact;
2572   EmpathyChatManager *chat_manager;
2573
2574   g_return_if_fail (self != NULL);
2575   g_return_if_fail (EMPATHY_IS_CHAT (chat));
2576
2577   g_signal_handlers_disconnect_by_func (chat,
2578       chat_window_chat_notify_cb, NULL);
2579
2580   remote_contact = g_object_get_data (G_OBJECT (chat),
2581       "chat-window-remote-contact");
2582
2583   if (remote_contact)
2584     {
2585       g_signal_handlers_disconnect_by_func (remote_contact,
2586           chat_window_update_chat_tab, chat);
2587     }
2588
2589   chat_manager = empathy_chat_manager_dup_singleton ();
2590   empathy_chat_manager_closed_chat (chat_manager, chat);
2591   g_object_unref (chat_manager);
2592
2593   position = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2594       GTK_WIDGET (chat));
2595   gtk_notebook_remove_page (GTK_NOTEBOOK (self->priv->notebook), position);
2596
2597   DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2598
2599   g_object_unref (chat);
2600 }
2601
2602 static void
2603 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2604     EmpathyChatWindow *new_window,
2605     EmpathyChat *chat)
2606 {
2607   GtkWidget *widget;
2608
2609   g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2610   g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2611   g_return_if_fail (EMPATHY_IS_CHAT (chat));
2612
2613   widget = GTK_WIDGET (chat);
2614
2615   DEBUG ("Chat moving with widget:%p (%d references)", widget,
2616       G_OBJECT (widget)->ref_count);
2617
2618   /* We reference here to make sure we don't loose the widget
2619    * and the EmpathyChat object during the move.
2620    */
2621   g_object_ref (chat);
2622   g_object_ref (widget);
2623
2624   empathy_chat_window_remove_chat (old_window, chat);
2625   empathy_chat_window_add_chat (new_window, chat);
2626
2627   g_object_unref (widget);
2628   g_object_unref (chat);
2629 }
2630
2631 static void
2632 empathy_chat_window_switch_to_chat (EmpathyChatWindow *self,
2633     EmpathyChat *chat)
2634 {
2635   gint page_num;
2636
2637   g_return_if_fail (self != NULL);
2638   g_return_if_fail (EMPATHY_IS_CHAT (chat));
2639
2640   page_num = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2641       GTK_WIDGET (chat));
2642
2643   gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
2644       page_num);
2645 }
2646
2647 EmpathyChat *
2648 empathy_chat_window_find_chat (TpAccount *account,
2649     const gchar *id,
2650     gboolean sms_channel)
2651 {
2652   GList *l;
2653
2654   g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2655
2656   for (l = chat_windows; l; l = l->next)
2657     {
2658       EmpathyChatWindow *window = l->data;
2659       GList *ll;
2660
2661       for (ll = window->priv->chats; ll; ll = ll->next)
2662         {
2663           EmpathyChat *chat;
2664
2665           chat = ll->data;
2666
2667           if (account == empathy_chat_get_account (chat) &&
2668               !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2669               sms_channel == empathy_chat_is_sms_channel (chat))
2670             return chat;
2671         }
2672     }
2673
2674   return NULL;
2675 }
2676
2677 EmpathyChatWindow *
2678 empathy_chat_window_present_chat (EmpathyChat *chat,
2679     gint64 timestamp)
2680 {
2681   EmpathyChatWindow *self;
2682   guint32 x_timestamp;
2683
2684   g_return_val_if_fail (EMPATHY_IS_CHAT (chat), NULL);
2685
2686   self = chat_window_find_chat (chat);
2687
2688   /* If the chat has no window, create one */
2689   if (self == NULL)
2690     {
2691       self = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2692       if (!self)
2693         {
2694           self = empathy_chat_window_new ();
2695
2696           /* we want to display the newly created window even if we
2697            * don't present it */
2698           gtk_widget_show (self->priv->dialog);
2699         }
2700
2701       empathy_chat_window_add_chat (self, chat);
2702     }
2703
2704   /* Don't force the window to show itself when it wasn't
2705    * an action by the user
2706    */
2707   if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2708     return self;
2709
2710   if (x_timestamp != GDK_CURRENT_TIME)
2711     {
2712       /* Don't present or switch tab if the action was earlier than the
2713        * last actions X time, accounting for overflow and the first ever
2714       * presentation */
2715
2716       if (self->priv->x_user_action_time != 0
2717         && X_EARLIER_OR_EQL (x_timestamp, self->priv->x_user_action_time))
2718         return self;
2719
2720       self->priv->x_user_action_time = x_timestamp;
2721     }
2722
2723   empathy_chat_window_switch_to_chat (self, chat);
2724
2725   /* Don't use empathy_window_present_with_time () which would move the window
2726    * to our current desktop but move to the window's desktop instead. This is
2727    * more coherent with Shell's 'app is ready' notication which moves the view
2728    * to the app desktop rather than moving the app itself. */
2729   empathy_move_to_window_desktop (GTK_WINDOW (self->priv->dialog), x_timestamp);
2730
2731   gtk_widget_grab_focus (chat->input_text_view);
2732   return self;
2733 }
2734
2735 static void
2736 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2737     guint *nb_rooms,
2738     guint *nb_private)
2739 {
2740   GList *l;
2741   guint _nb_rooms = 0, _nb_private = 0;
2742
2743   for (l = self->priv->chats; l != NULL; l = g_list_next (l))
2744     {
2745       if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2746         _nb_rooms++;
2747       else
2748         _nb_private++;
2749     }
2750
2751   if (nb_rooms != NULL)
2752     *nb_rooms = _nb_rooms;
2753   if (nb_private != NULL)
2754     *nb_private = _nb_private;
2755 }