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