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