]> git.0d.be Git - empathy.git/blob - src/empathy-chat-window.c
include telepathy-glib.h
[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
117 static GList *chat_windows = NULL;
118
119 static const guint tab_accel_keys[] =
120 {
121   GDK_KEY_1, GDK_KEY_2, GDK_KEY_3, GDK_KEY_4, GDK_KEY_5,
122   GDK_KEY_6, GDK_KEY_7, GDK_KEY_8, GDK_KEY_9, GDK_KEY_0
123 };
124
125 typedef enum
126 {
127   DND_DRAG_TYPE_CONTACT_ID,
128   DND_DRAG_TYPE_INDIVIDUAL_ID,
129   DND_DRAG_TYPE_URI_LIST,
130   DND_DRAG_TYPE_TAB
131 } DndDragType;
132
133 static const GtkTargetEntry drag_types_dest[] =
134 {
135   { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
136   { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
137   { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
138   /* FIXME: disabled because of bug #640513
139   { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
140   { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
141   */
142 };
143
144 static const GtkTargetEntry drag_types_dest_contact[] =
145 {
146   { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
147   { "text/x-individual-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID },
148 };
149
150 static const GtkTargetEntry drag_types_dest_file[] =
151 {
152   /* must be first to be prioritized, in order to receive the
153    * note's file path from Tomboy instead of an URI */
154   { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
155   { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
156 };
157
158 static void chat_window_update (EmpathyChatWindow *window,
159     gboolean update_contact_menu);
160
161 static void empathy_chat_window_add_chat (EmpathyChatWindow *window,
162     EmpathyChat *chat);
163
164 static void empathy_chat_window_remove_chat (EmpathyChatWindow *window,
165     EmpathyChat *chat);
166
167 static void empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
168     EmpathyChatWindow *new_window,
169     EmpathyChat *chat);
170
171 static void empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
172     guint *nb_rooms,
173     guint *nb_private);
174
175 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT)
176
177 static void
178 chat_window_accel_cb (GtkAccelGroup *accelgroup,
179     GObject *object,
180     guint key,
181     GdkModifierType mod,
182     EmpathyChatWindow *self)
183 {
184   gint num = -1;
185   guint i;
186
187   for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
188     {
189       if (tab_accel_keys[i] == key)
190         {
191           num = i;
192           break;
193         }
194     }
195
196   if (num != -1)
197     gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook), num);
198 }
199
200 static EmpathyChatWindow *
201 chat_window_find_chat (EmpathyChat *chat)
202 {
203   GList *l, *ll;
204
205   for (l = chat_windows; l; l = l->next)
206     {
207       EmpathyChatWindow *window = l->data;
208
209       ll = g_list_find (window->priv->chats, chat);
210       if (ll)
211         return l->data;
212     }
213
214   return NULL;
215 }
216
217 static void
218 remove_all_chats (EmpathyChatWindow *self)
219 {
220   g_object_ref (self);
221
222   while (self->priv->chats)
223     empathy_chat_window_remove_chat (self, self->priv->chats->data);
224
225   g_object_unref (self);
226 }
227
228 static void
229 confirm_close_response_cb (GtkWidget *dialog,
230     int response,
231     EmpathyChatWindow *window)
232 {
233   EmpathyChat *chat;
234
235   chat = g_object_get_data (G_OBJECT (dialog), "chat");
236
237   gtk_widget_destroy (dialog);
238
239   if (response != GTK_RESPONSE_ACCEPT)
240     return;
241
242   if (chat != NULL)
243     empathy_chat_window_remove_chat (window, chat);
244   else
245     remove_all_chats (window);
246 }
247
248 static void
249 confirm_close (EmpathyChatWindow *self,
250     gboolean close_window,
251     guint n_rooms,
252     EmpathyChat *chat)
253 {
254   GtkWidget *dialog;
255   gchar *primary, *secondary;
256
257   g_return_if_fail (n_rooms > 0);
258
259   if (n_rooms > 1)
260     g_return_if_fail (chat == NULL);
261   else
262     g_return_if_fail (chat != NULL);
263
264   /* If there are no chats in this window, how could we possibly have got
265    * here?
266    */
267   g_return_if_fail (self->priv->chats != NULL);
268
269   /* Treat closing a window which only has one tab exactly like closing
270    * that tab.
271    */
272   if (close_window && self->priv->chats->next == NULL)
273     {
274       close_window = FALSE;
275       chat = self->priv->chats->data;
276     }
277
278   if (close_window)
279     {
280       primary = g_strdup (_("Close this window?"));
281
282       if (n_rooms == 1)
283         {
284           gchar *chat_name = empathy_chat_dup_name (chat);
285           secondary = g_strdup_printf (
286             _("Closing this window will leave %s. You will "
287               "not receive any further messages until you "
288               "rejoin it."),
289             chat_name);
290           g_free (chat_name);
291         }
292       else
293         {
294           secondary = g_strdup_printf (
295             /* Note to translators: the number of chats will
296              * always be at least 2.
297              */
298             ngettext (
299               "Closing this window will leave a chat room. You will "
300               "not receive any further messages until you rejoin it.",
301               "Closing this window will leave %u chat rooms. You will "
302               "not receive any further messages until you rejoin them.",
303               n_rooms),
304             n_rooms);
305         }
306     }
307   else
308     {
309       gchar *chat_name = empathy_chat_dup_name (chat);
310       primary = g_strdup_printf (_("Leave %s?"), chat_name);
311       secondary = g_strdup (
312           _("You will not receive any further messages from this chat "
313             "room until you rejoin it."));
314       g_free (chat_name);
315     }
316
317   dialog = gtk_message_dialog_new (
318     GTK_WINDOW (self->priv->dialog),
319     GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
320     GTK_MESSAGE_WARNING,
321     GTK_BUTTONS_CANCEL,
322     "%s", primary);
323
324   gtk_window_set_title (GTK_WINDOW (dialog), "");
325   g_object_set (dialog, "secondary-text", secondary, NULL);
326
327   g_free (primary);
328   g_free (secondary);
329
330   gtk_dialog_add_button (GTK_DIALOG (dialog),
331     close_window ? _("Close window") : _("Leave room"),
332     GTK_RESPONSE_ACCEPT);
333   gtk_dialog_set_default_response (GTK_DIALOG (dialog),
334     GTK_RESPONSE_ACCEPT);
335
336   if (!close_window)
337     g_object_set_data (G_OBJECT (dialog), "chat", chat);
338
339   g_signal_connect (dialog, "response",
340     G_CALLBACK (confirm_close_response_cb), self);
341
342   gtk_window_present (GTK_WINDOW (dialog));
343 }
344
345 /* Returns TRUE if we should check if the user really wants to leave. If it's
346  * a multi-user chat, and it has a TpChat (so there's an underlying channel, so
347  * the user is actually in the room as opposed to having been kicked or gone
348  * offline or something), then we should check.
349  */
350 static gboolean
351 chat_needs_close_confirmation (EmpathyChat *chat)
352 {
353   return (empathy_chat_is_room (chat) &&
354       empathy_chat_get_tp_chat (chat) != NULL);
355 }
356
357 static void
358 maybe_close_chat (EmpathyChatWindow *window,
359     EmpathyChat *chat)
360 {
361   g_return_if_fail (chat != NULL);
362
363   if (chat_needs_close_confirmation (chat))
364     confirm_close (window, FALSE, 1, chat);
365   else
366     empathy_chat_window_remove_chat (window, chat);
367 }
368
369 static void
370 chat_window_close_clicked_cb (GtkAction *action,
371     EmpathyChat *chat)
372 {
373   EmpathyChatWindow *window;
374
375   window = chat_window_find_chat (chat);
376   maybe_close_chat (window, chat);
377 }
378
379 static void
380 chat_tab_style_updated_cb (GtkWidget *hbox,
381     gpointer user_data)
382 {
383   GtkWidget *button;
384   int char_width, h, w;
385   PangoContext *context;
386   const PangoFontDescription *font_desc;
387   PangoFontMetrics *metrics;
388
389   button = g_object_get_data (G_OBJECT (user_data),
390     "chat-window-tab-close-button");
391   context = gtk_widget_get_pango_context (hbox);
392
393   font_desc = gtk_style_context_get_font (gtk_widget_get_style_context (hbox),
394       GTK_STATE_FLAG_NORMAL);
395
396   metrics = pango_context_get_metrics (context, font_desc,
397     pango_context_get_language (context));
398   char_width = pango_font_metrics_get_approximate_char_width (metrics);
399   pango_font_metrics_unref (metrics);
400
401   gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
402       GTK_ICON_SIZE_MENU, &w, &h);
403
404   /* Request at least about 12 chars width plus at least space for the status
405    * image and the close button */
406   gtk_widget_set_size_request (hbox,
407     12 * PANGO_PIXELS (char_width) + 2 * w, -1);
408
409   gtk_widget_set_size_request (button, w, h);
410 }
411
412 static GtkWidget *
413 create_close_button (void)
414 {
415   GtkWidget *button, *image;
416   GtkStyleContext *context;
417
418   button = gtk_button_new ();
419
420   context = gtk_widget_get_style_context (button);
421   gtk_style_context_add_class (context, "empathy-tab-close-button");
422
423   gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
424   gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
425
426   /* We don't want focus/keynav for the button to avoid clutter, and
427    * Ctrl-W works anyway.
428    */
429   gtk_widget_set_can_focus (button, FALSE);
430   gtk_widget_set_can_default (button, FALSE);
431
432   image = gtk_image_new_from_icon_name ("window-close-symbolic",
433       GTK_ICON_SIZE_MENU);
434   gtk_widget_show (image);
435
436   gtk_container_add (GTK_CONTAINER (button), image);
437
438   return button;
439 }
440
441 static GtkWidget *
442 chat_window_create_label (EmpathyChatWindow *window,
443     EmpathyChat *chat,
444     gboolean is_tab_label)
445 {
446   GtkWidget *hbox;
447   GtkWidget *name_label;
448   GtkWidget *status_image;
449   GtkWidget *event_box;
450   GtkWidget *event_box_hbox;
451   PangoAttrList *attr_list;
452   PangoAttribute *attr;
453
454   /* The spacing between the button and the label. */
455   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
456
457   event_box = gtk_event_box_new ();
458   gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
459
460   name_label = gtk_label_new (NULL);
461   if (is_tab_label)
462     gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
463
464   attr_list = pango_attr_list_new ();
465   attr = pango_attr_scale_new (1/1.2);
466   attr->start_index = 0;
467   attr->end_index = -1;
468   pango_attr_list_insert (attr_list, attr);
469   gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
470   pango_attr_list_unref (attr_list);
471
472   gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
473   gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
474   g_object_set_data (G_OBJECT (chat),
475     is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
476     name_label);
477
478   status_image = gtk_image_new ();
479
480   /* Spacing between the icon and label. */
481   event_box_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
482
483   gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
484   gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
485
486   g_object_set_data (G_OBJECT (chat),
487     is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
488     status_image);
489   g_object_set_data (G_OBJECT (chat),
490     is_tab_label ? "chat-window-tab-tooltip-widget" :
491       "chat-window-menu-tooltip-widget",
492     event_box);
493
494   gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
495   gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
496
497   if (is_tab_label)
498     {
499       GtkWidget *close_button;
500       GtkWidget *sending_spinner;
501
502       sending_spinner = gtk_spinner_new ();
503
504       gtk_box_pack_start (GTK_BOX (hbox), sending_spinner,
505         FALSE, FALSE, 0);
506       g_object_set_data (G_OBJECT (chat),
507         "chat-window-tab-sending-spinner",
508         sending_spinner);
509
510       close_button = create_close_button ();
511       g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button",
512           close_button);
513
514       gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
515
516       g_signal_connect (close_button,
517           "clicked",
518           G_CALLBACK (chat_window_close_clicked_cb), chat);
519
520       /* React to theme changes and also setup the size correctly. */
521       g_signal_connect (hbox, "style-updated",
522           G_CALLBACK (chat_tab_style_updated_cb), chat);
523     }
524
525   gtk_widget_show_all (hbox);
526
527   return hbox;
528 }
529
530 static void
531 _submenu_notify_visible_changed_cb (GObject *object,
532     GParamSpec *pspec,
533     gpointer userdata)
534 {
535   g_signal_handlers_disconnect_by_func (object,
536       _submenu_notify_visible_changed_cb, userdata);
537
538   chat_window_update (EMPATHY_CHAT_WINDOW (userdata), TRUE);
539 }
540
541 static void
542 chat_window_menu_context_update (EmpathyChatWindow *self,
543     gint num_pages)
544 {
545   gboolean first_page;
546   gboolean last_page;
547   gboolean wrap_around;
548   gboolean is_connected;
549   gint page_num;
550
551   page_num = gtk_notebook_get_current_page (
552       GTK_NOTEBOOK (self->priv->notebook));
553   first_page = (page_num == 0);
554   last_page = (page_num == (num_pages - 1));
555   g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
556       &wrap_around, NULL);
557   is_connected = empathy_chat_get_tp_chat (self->priv->current_chat) != NULL;
558
559   gtk_action_set_sensitive (self->priv->menu_tabs_next, (!last_page ||
560         wrap_around));
561   gtk_action_set_sensitive (self->priv->menu_tabs_prev, (!first_page ||
562         wrap_around));
563   gtk_action_set_sensitive (self->priv->menu_tabs_detach, num_pages > 1);
564   gtk_action_set_sensitive (self->priv->menu_tabs_left, !first_page);
565   gtk_action_set_sensitive (self->priv->menu_tabs_right, !last_page);
566   gtk_action_set_sensitive (self->priv->menu_conv_insert_smiley, is_connected);
567 }
568
569 static void
570 chat_window_conversation_menu_update (EmpathyChatWindow *self)
571 {
572   EmpathyTpChat *tp_chat;
573   TpConnection *connection;
574   GtkAction *action;
575   gboolean sensitive = FALSE;
576
577   g_return_if_fail (self->priv->current_chat != NULL);
578
579   action = gtk_ui_manager_get_action (self->priv->ui_manager,
580     "/chats_menubar/menu_conv/menu_conv_invite_participant");
581   tp_chat = empathy_chat_get_tp_chat (self->priv->current_chat);
582
583   if (tp_chat != NULL)
584     {
585       connection = tp_channel_get_connection (TP_CHANNEL (tp_chat));
586
587       sensitive = empathy_tp_chat_can_add_contact (tp_chat) &&
588         (tp_connection_get_status (connection, NULL) ==
589          TP_CONNECTION_STATUS_CONNECTED);
590     }
591
592   gtk_action_set_sensitive (action, sensitive);
593 }
594
595 static void
596 chat_window_contact_menu_update (EmpathyChatWindow *self,
597     EmpathyChatWindow *window)
598 {
599   GtkWidget *menu, *submenu, *orig_submenu;
600
601   menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
602     "/chats_menubar/menu_contact");
603   orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
604
605   if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu))
606     {
607       submenu = empathy_chat_get_contact_menu (self->priv->current_chat);
608
609       if (submenu != NULL)
610         {
611           /* gtk_menu_attach_to_widget () doesn't behave nicely here */
612           g_object_set_data (G_OBJECT (submenu), "window", self->priv->dialog);
613
614           gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
615           gtk_widget_show (menu);
616           gtk_widget_set_sensitive (menu, TRUE);
617         }
618       else
619         {
620           gtk_widget_set_sensitive (menu, FALSE);
621         }
622     }
623   else
624     {
625       tp_g_signal_connect_object (orig_submenu,
626           "notify::visible",
627           (GCallback)_submenu_notify_visible_changed_cb, window, 0);
628     }
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->priv->dialog), 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->priv->dialog),
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->priv->dialog), icon);
755
756           if (icon != NULL)
757             g_object_unref (icon);
758         }
759       else
760         {
761           gtk_window_set_icon_name (GTK_WINDOW (self->priv->dialog), 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, 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 (types != NULL && !tp_strdiff (types[0], "phone"))
978         {
979           /* I'm on a phone ! */
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->priv->dialog), 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 EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1424 }
1425
1426 static void
1427 chat_window_detach_activate_cb (GtkAction *action,
1428     EmpathyChatWindow *self)
1429 {
1430   EmpathyChatWindow *new_window;
1431   EmpathyChat *chat;
1432
1433   chat = self->priv->current_chat;
1434   new_window = empathy_chat_window_new ();
1435
1436   empathy_chat_window_move_chat (self, new_window, chat);
1437
1438   gtk_widget_show (new_window->priv->dialog);
1439 }
1440
1441 static void
1442 chat_window_help_contents_activate_cb (GtkAction *action,
1443     EmpathyChatWindow *self)
1444 {
1445   empathy_url_show (self->priv->dialog, "help:empathy");
1446 }
1447
1448 static void
1449 chat_window_help_about_activate_cb (GtkAction *action,
1450     EmpathyChatWindow *self)
1451 {
1452   empathy_about_dialog_new (GTK_WINDOW (self->priv->dialog));
1453 }
1454
1455 static gboolean
1456 chat_window_delete_event_cb (GtkWidget *dialog,
1457     GdkEvent *event,
1458     EmpathyChatWindow *self)
1459 {
1460   EmpathyChat *chat = NULL;
1461   guint n_rooms = 0;
1462   GList *l;
1463
1464   DEBUG ("Delete event received");
1465
1466   for (l = self->priv->chats; l != NULL; l = l->next)
1467     {
1468       if (chat_needs_close_confirmation (l->data))
1469         {
1470           chat = l->data;
1471           n_rooms++;
1472         }
1473     }
1474
1475   if (n_rooms > 0)
1476     {
1477       confirm_close (self, TRUE, n_rooms, (n_rooms == 1 ? chat : NULL));
1478     }
1479   else
1480     {
1481       remove_all_chats (self);
1482     }
1483
1484   return TRUE;
1485 }
1486
1487 static void
1488 chat_window_composing_cb (EmpathyChat *chat,
1489     gboolean is_composing,
1490     EmpathyChatWindow *self)
1491 {
1492   chat_window_update_chat_tab (chat);
1493 }
1494
1495 static void
1496 chat_window_set_urgency_hint (EmpathyChatWindow *self,
1497     gboolean urgent)
1498 {
1499   gtk_window_set_urgency_hint (GTK_WINDOW (self->priv->dialog), urgent);
1500 }
1501
1502 static void
1503 chat_window_notification_closed_cb (NotifyNotification *notify,
1504     EmpathyChatWindow *self)
1505 {
1506   g_object_unref (notify);
1507   if (self->priv->notification == notify)
1508     self->priv->notification = NULL;
1509 }
1510
1511 static void
1512 chat_window_show_or_update_notification (EmpathyChatWindow *self,
1513     EmpathyMessage *message,
1514     EmpathyChat *chat)
1515 {
1516   EmpathyContact *sender;
1517   const gchar *header;
1518   char *escaped;
1519   const char *body;
1520   GdkPixbuf *pixbuf;
1521   gboolean res, has_x_canonical_append;
1522   NotifyNotification *notification = self->priv->notification;
1523
1524   if (!empathy_notify_manager_notification_is_enabled (self->priv->notify_mgr))
1525     return;
1526
1527   res = g_settings_get_boolean (self->priv->gsettings_notif,
1528       EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1529
1530   if (!res)
1531     return;
1532
1533   sender = empathy_message_get_sender (message);
1534   header = empathy_contact_get_alias (sender);
1535   body = empathy_message_get_body (message);
1536   escaped = g_markup_escape_text (body, -1);
1537
1538   has_x_canonical_append = empathy_notify_manager_has_capability (
1539     self->priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1540
1541   if (notification != NULL && !has_x_canonical_append)
1542     {
1543       /* if the notification server supports x-canonical-append, it is
1544          better to not use notify_notification_update to avoid
1545          overwriting the current notification message */
1546       notify_notification_update (notification,
1547                 header, escaped, NULL);
1548     }
1549   else
1550     {
1551       /* if the notification server supports x-canonical-append,
1552          the hint will be added, so that the message from the
1553          just created notification will be automatically appended
1554          to an existing notification with the same title.
1555          In this way the previous message will not be lost: the new
1556          message will appear below it, in the same notification */
1557       const gchar *category = empathy_chat_is_room (chat)
1558         ? EMPATHY_NOTIFICATION_CATEGORY_MENTIONED
1559         : EMPATHY_NOTIFICATION_CATEGORY_CHAT;
1560       notification = notify_notification_new (header, escaped, NULL);
1561
1562       if (self->priv->notification == NULL)
1563         self->priv->notification = notification;
1564
1565       notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1566
1567       tp_g_signal_connect_object (notification, "closed",
1568             G_CALLBACK (chat_window_notification_closed_cb), self, 0);
1569
1570       if (has_x_canonical_append)
1571         {
1572           /* We have to set a not empty string to keep libnotify happy */
1573           notify_notification_set_hint_string (notification,
1574             EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1575         }
1576
1577       notify_notification_set_hint (notification,
1578           EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY, g_variant_new_string (category));
1579     }
1580
1581   pixbuf = empathy_notify_manager_get_pixbuf_for_notification (self->priv->notify_mgr,
1582     sender, EMPATHY_IMAGE_NEW_MESSAGE);
1583
1584   if (pixbuf != NULL)
1585     {
1586       notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1587       g_object_unref (pixbuf);
1588     }
1589
1590   notify_notification_show (notification, NULL);
1591
1592   g_free (escaped);
1593 }
1594
1595 static gboolean
1596 empathy_chat_window_has_focus (EmpathyChatWindow *self)
1597 {
1598   gboolean has_focus;
1599
1600   g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (self), FALSE);
1601
1602   g_object_get (self->priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1603
1604   return has_focus;
1605 }
1606
1607 static void
1608 chat_window_new_message_cb (EmpathyChat *chat,
1609     EmpathyMessage *message,
1610     gboolean pending,
1611     gboolean should_highlight,
1612     EmpathyChatWindow *self)
1613 {
1614   gboolean has_focus;
1615   gboolean needs_urgency;
1616   EmpathyContact *sender;
1617
1618   has_focus = empathy_chat_window_has_focus (self);
1619
1620   /* - if we're the sender, we play the sound if it's specified in the
1621    *   preferences and we're not away.
1622    * - if we receive a message, we play the sound if it's specified in the
1623    *   preferences and the window does not have focus on the chat receiving
1624    *   the message.
1625    */
1626
1627   sender = empathy_message_get_sender (message);
1628
1629   if (empathy_contact_is_user (sender))
1630     {
1631       empathy_sound_manager_play (self->priv->sound_mgr, GTK_WIDGET (self->priv->dialog),
1632           EMPATHY_SOUND_MESSAGE_OUTGOING);
1633       return;
1634     }
1635
1636   if (has_focus && self->priv->current_chat == chat)
1637     {
1638       /* window and tab are focused so consider the message to be read */
1639
1640       /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1641       empathy_chat_messages_read (chat);
1642       return;
1643     }
1644
1645   /* Update the chat tab if this is the first unread message */
1646   if (empathy_chat_get_nb_unread_messages (chat) == 1)
1647     {
1648       chat_window_update_chat_tab (chat);
1649     }
1650
1651   /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1652    * If empathy_chat_get_remote_contact () returns NULL, that means it's
1653    * an unamed MUC (msn-like).
1654    * In case of a MUC, we set urgency if either:
1655    *   a) the chatroom's always_urgent property is TRUE
1656    *   b) the message contains our alias
1657    */
1658   if (empathy_chat_is_room (chat))
1659     {
1660       TpAccount *account;
1661       const gchar *room;
1662       EmpathyChatroom *chatroom;
1663
1664       account = empathy_chat_get_account (chat);
1665       room = empathy_chat_get_id (chat);
1666
1667       chatroom = empathy_chatroom_manager_find (self->priv->chatroom_manager,
1668           account, room);
1669
1670       if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom))
1671         needs_urgency = TRUE;
1672       else
1673         needs_urgency = should_highlight;
1674     }
1675   else
1676     {
1677       needs_urgency = TRUE;
1678     }
1679
1680   if (needs_urgency)
1681     {
1682       if (!has_focus)
1683         chat_window_set_urgency_hint (self, TRUE);
1684
1685       /* Pending messages have already been displayed and notified in the
1686       * approver, so we don't display a notification and play a sound
1687       * for those */
1688       if (!pending)
1689         {
1690           empathy_sound_manager_play (self->priv->sound_mgr,
1691               GTK_WIDGET (self->priv->dialog), EMPATHY_SOUND_MESSAGE_INCOMING);
1692
1693           chat_window_show_or_update_notification (self, message, chat);
1694         }
1695     }
1696
1697   /* update the number of unread messages and the window icon */
1698   chat_window_title_update (self);
1699   chat_window_icon_update (self, TRUE);
1700 }
1701
1702 static void
1703 chat_window_command_part (EmpathyChat *chat,
1704     GStrv strv)
1705 {
1706   EmpathyChat *chat_to_be_parted;
1707   EmpathyTpChat *tp_chat = NULL;
1708
1709   if (strv[1] == NULL)
1710     {
1711       /* No chatroom ID specified */
1712       tp_chat = empathy_chat_get_tp_chat (chat);
1713
1714       if (tp_chat)
1715         empathy_tp_chat_leave (tp_chat, "");
1716
1717       return;
1718     }
1719
1720   chat_to_be_parted = empathy_chat_window_find_chat (
1721     empathy_chat_get_account (chat), strv[1], FALSE);
1722
1723   if (chat_to_be_parted != NULL)
1724     {
1725       /* Found a chatroom matching the specified ID */
1726       tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1727
1728       if (tp_chat)
1729         empathy_tp_chat_leave (tp_chat, strv[2]);
1730     }
1731   else
1732     {
1733       gchar *message;
1734
1735       /* Going by the syntax of PART command:
1736        *
1737        * /PART [<chatroom-ID>] [<reason>]
1738        *
1739        * Chatroom-ID is not a must to specify a reason.
1740        * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1741        * MUC then the current chatroom should be parted and srtv[1] should
1742        * be treated as part of the optional part-message. */
1743       message = g_strconcat (strv[1], " ", strv[2], NULL);
1744       tp_chat = empathy_chat_get_tp_chat (chat);
1745
1746       if (tp_chat)
1747         empathy_tp_chat_leave (tp_chat, message);
1748
1749       g_free (message);
1750     }
1751 }
1752
1753 static GtkNotebook *
1754 notebook_create_window_cb (GtkNotebook *source,
1755     GtkWidget *page,
1756     gint x,
1757     gint y,
1758     gpointer user_data)
1759 {
1760   EmpathyChatWindow *window, *new_window;
1761   EmpathyChat *chat;
1762
1763   chat = EMPATHY_CHAT (page);
1764   window = chat_window_find_chat (chat);
1765
1766   new_window = empathy_chat_window_new ();
1767
1768   DEBUG ("Detach hook called");
1769
1770   empathy_chat_window_move_chat (window, new_window, chat);
1771
1772   gtk_widget_show (new_window->priv->dialog);
1773   gtk_window_move (GTK_WINDOW (new_window->priv->dialog), x, y);
1774
1775   return NULL;
1776 }
1777
1778 static void
1779 chat_window_page_switched_cb (GtkNotebook *notebook,
1780     GtkWidget *child,
1781     gint page_num,
1782     EmpathyChatWindow *self)
1783 {
1784   EmpathyChat *chat = EMPATHY_CHAT (child);
1785
1786   DEBUG ("Page switched");
1787
1788   if (self->priv->page_added)
1789     {
1790       self->priv->page_added = FALSE;
1791       empathy_chat_scroll_down (chat);
1792     }
1793   else if (self->priv->current_chat == chat)
1794     {
1795       return;
1796     }
1797
1798   self->priv->current_chat = chat;
1799   empathy_chat_messages_read (chat);
1800
1801   chat_window_update_chat_tab (chat);
1802 }
1803
1804 static void
1805 chat_window_page_added_cb (GtkNotebook *notebook,
1806     GtkWidget *child,
1807     guint page_num,
1808     EmpathyChatWindow *self)
1809 {
1810   EmpathyChat *chat;
1811
1812   /* If we just received DND to the same window, we don't want
1813    * to do anything here like removing the tab and then readding
1814    * it, so we return here and in "page-added".
1815    */
1816   if (self->priv->dnd_same_window)
1817     {
1818       DEBUG ("Page added (back to the same window)");
1819       self->priv->dnd_same_window = FALSE;
1820       return;
1821     }
1822
1823   DEBUG ("Page added");
1824
1825   /* Get chat object */
1826   chat = EMPATHY_CHAT (child);
1827
1828   /* Connect chat signals for this window */
1829   g_signal_connect (chat, "composing",
1830       G_CALLBACK (chat_window_composing_cb), self);
1831   g_signal_connect (chat, "new-message",
1832       G_CALLBACK (chat_window_new_message_cb), self);
1833   g_signal_connect (chat, "part-command-entered",
1834       G_CALLBACK (chat_window_command_part), NULL);
1835   g_signal_connect (chat, "notify::tp-chat",
1836       G_CALLBACK (chat_window_update_chat_tab), self);
1837
1838   /* Set flag so we know to perform some special operations on
1839    * switch page due to the new page being added.
1840    */
1841   self->priv->page_added = TRUE;
1842
1843   /* Get list of chats up to date */
1844   self->priv->chats = g_list_append (self->priv->chats, chat);
1845
1846   chat_window_update_chat_tab (chat);
1847 }
1848
1849 static void
1850 chat_window_page_removed_cb (GtkNotebook *notebook,
1851     GtkWidget *child,
1852     guint page_num,
1853     EmpathyChatWindow *self)
1854 {
1855   EmpathyChat *chat;
1856
1857   /* If we just received DND to the same window, we don't want
1858    * to do anything here like removing the tab and then readding
1859    * it, so we return here and in "page-added".
1860    */
1861   if (self->priv->dnd_same_window)
1862     {
1863       DEBUG ("Page removed (and will be readded to same window)");
1864       return;
1865     }
1866
1867   DEBUG ("Page removed");
1868
1869   /* Get chat object */
1870   chat = EMPATHY_CHAT (child);
1871
1872   /* Disconnect all signal handlers for this chat and this window */
1873   g_signal_handlers_disconnect_by_func (chat,
1874       G_CALLBACK (chat_window_composing_cb), self);
1875   g_signal_handlers_disconnect_by_func (chat,
1876       G_CALLBACK (chat_window_new_message_cb), self);
1877   g_signal_handlers_disconnect_by_func (chat,
1878       G_CALLBACK (chat_window_update_chat_tab), self);
1879
1880   /* Keep list of chats up to date */
1881   self->priv->chats = g_list_remove (self->priv->chats, chat);
1882   empathy_chat_messages_read (chat);
1883
1884   if (self->priv->chats == NULL)
1885     {
1886       g_object_unref (self);
1887     }
1888   else
1889     {
1890       chat_window_update (self, TRUE);
1891     }
1892 }
1893
1894 static gboolean
1895 chat_window_focus_in_event_cb (GtkWidget *widget,
1896     GdkEvent *event,
1897     EmpathyChatWindow *self)
1898 {
1899   empathy_chat_messages_read (self->priv->current_chat);
1900
1901   chat_window_set_urgency_hint (self, FALSE);
1902
1903   /* Update the title, since we now mark all unread messages as read. */
1904   chat_window_update_chat_tab_full (self->priv->current_chat, FALSE);
1905
1906   return FALSE;
1907 }
1908
1909 static gboolean
1910 chat_window_drag_drop (GtkWidget *widget,
1911     GdkDragContext *context,
1912     int x,
1913     int y,
1914     guint time_,
1915     EmpathyChatWindow *self)
1916 {
1917   GdkAtom target;
1918
1919   target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
1920   if (target == GDK_NONE)
1921     target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
1922
1923   if (target != GDK_NONE)
1924     {
1925       gtk_drag_get_data (widget, context, target, time_);
1926       return TRUE;
1927     }
1928
1929   return FALSE;
1930 }
1931
1932 static gboolean
1933 chat_window_drag_motion (GtkWidget *widget,
1934     GdkDragContext *context,
1935     int x,
1936     int y,
1937     guint time_,
1938     EmpathyChatWindow *self)
1939 {
1940   GdkAtom target;
1941
1942   target = gtk_drag_dest_find_target (widget, context, self->priv->file_targets);
1943
1944   if (target != GDK_NONE)
1945     {
1946       /* This is a file drag. Ensure the contact is online and set the
1947          drag type to COPY. Note that it's possible that the tab will
1948          be switched by GTK+ after a timeout from drag_motion without
1949          getting another drag_motion to disable the drop. You have
1950          to hold your mouse really still.
1951        */
1952       EmpathyContact *contact;
1953
1954       contact = empathy_chat_get_remote_contact (self->priv->current_chat);
1955
1956       /* contact is NULL for multi-user chats. We don't do
1957        * file transfers to MUCs. We also don't send files
1958        * to offline contacts or contacts that don't support
1959        * file transfer.
1960        */
1961       if ((contact == NULL) || !empathy_contact_is_online (contact))
1962         {
1963           gdk_drag_status (context, 0, time_);
1964           return FALSE;
1965         }
1966
1967       if (!(empathy_contact_get_capabilities (contact)
1968            & EMPATHY_CAPABILITIES_FT))
1969         {
1970           gdk_drag_status (context, 0, time_);
1971           return FALSE;
1972         }
1973
1974       gdk_drag_status (context, GDK_ACTION_COPY, time_);
1975       return TRUE;
1976     }
1977
1978   target = gtk_drag_dest_find_target (widget, context, self->priv->contact_targets);
1979   if (target != GDK_NONE)
1980     {
1981       /* This is a drag of a contact from a contact list. Set to COPY.
1982          FIXME: If this drag is to a MUC window, it invites the user.
1983          Otherwise, it opens a chat. Should we use a different drag
1984          type for invites? Should we allow ASK?
1985        */
1986       gdk_drag_status (context, GDK_ACTION_COPY, time_);
1987       return TRUE;
1988     }
1989
1990   return FALSE;
1991 }
1992
1993 static void
1994 drag_data_received_individual_id (EmpathyChatWindow *self,
1995     GtkWidget *widget,
1996     GdkDragContext *context,
1997     int x,
1998     int y,
1999     GtkSelectionData *selection,
2000     guint info,
2001     guint time_)
2002 {
2003   const gchar *id;
2004   EmpathyIndividualManager *manager = NULL;
2005   FolksIndividual *individual;
2006   EmpathyTpChat *chat;
2007   TpContact *tp_contact;
2008   TpConnection *conn;
2009   EmpathyContact *contact;
2010
2011   id = (const gchar *) gtk_selection_data_get_data (selection);
2012
2013   DEBUG ("DND invididual %s", id);
2014
2015   if (self->priv->current_chat == NULL)
2016     goto out;
2017
2018   chat = empathy_chat_get_tp_chat (self->priv->current_chat);
2019   if (chat == NULL)
2020     goto out;
2021
2022   if (!empathy_tp_chat_can_add_contact (chat))
2023     {
2024       DEBUG ("Can't invite contact to %s",
2025           tp_proxy_get_object_path (chat));
2026       goto out;
2027     }
2028
2029   manager = empathy_individual_manager_dup_singleton ();
2030
2031   individual = empathy_individual_manager_lookup_member (manager, id);
2032   if (individual == NULL)
2033     {
2034       DEBUG ("Failed to find individual %s", id);
2035       goto out;
2036     }
2037
2038   conn = tp_channel_get_connection ((TpChannel *) chat);
2039   tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2040   if (tp_contact == NULL)
2041     {
2042       DEBUG ("Can't find a TpContact on connection %s for %s",
2043           tp_proxy_get_object_path (conn), id);
2044       goto out;
2045     }
2046
2047   DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2048       tp_channel_get_identifier ((TpChannel *) chat));
2049
2050   contact = empathy_contact_dup_from_tp_contact (tp_contact);
2051   empathy_tp_chat_add (chat, contact, NULL);
2052   g_object_unref (contact);
2053
2054 out:
2055   gtk_drag_finish (context, TRUE, FALSE, time_);
2056   tp_clear_object (&manager);
2057 }
2058
2059 static void
2060 chat_window_drag_data_received (GtkWidget *widget,
2061     GdkDragContext *context,
2062     int x,
2063     int y,
2064     GtkSelectionData *selection,
2065     guint info,
2066     guint time_,
2067     EmpathyChatWindow *self)
2068 {
2069   if (info == DND_DRAG_TYPE_CONTACT_ID)
2070     {
2071       EmpathyChat *chat = NULL;
2072       EmpathyChatWindow *old_window;
2073       TpAccount *account = NULL;
2074       EmpathyClientFactory *factory;
2075       const gchar *id;
2076       gchar **strv;
2077       const gchar *account_id;
2078       const gchar *contact_id;
2079
2080       id = (const gchar*) gtk_selection_data_get_data (selection);
2081
2082       factory = empathy_client_factory_dup ();
2083
2084       DEBUG ("DND contact from roster with id:'%s'", id);
2085
2086       strv = g_strsplit (id, ":", 2);
2087       if (g_strv_length (strv) == 2)
2088         {
2089           account_id = strv[0];
2090           contact_id = strv[1];
2091
2092           account = tp_simple_client_factory_ensure_account (
2093               TP_SIMPLE_CLIENT_FACTORY (factory), account_id, NULL, NULL);
2094
2095           g_object_unref (factory);
2096           if (account != NULL)
2097             chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2098         }
2099
2100       if (account == NULL)
2101         {
2102           g_strfreev (strv);
2103           gtk_drag_finish (context, FALSE, FALSE, time_);
2104           return;
2105         }
2106
2107       if (!chat)
2108         {
2109           empathy_chat_with_contact_id (account, contact_id,
2110               empathy_get_current_action_time (), NULL, NULL);
2111
2112           g_strfreev (strv);
2113           return;
2114         }
2115
2116       g_strfreev (strv);
2117
2118       old_window = chat_window_find_chat (chat);
2119       if (old_window)
2120         {
2121           if (old_window == self)
2122             {
2123               gtk_drag_finish (context, TRUE, FALSE, time_);
2124               return;
2125             }
2126
2127           empathy_chat_window_move_chat (old_window, self, chat);
2128         }
2129       else
2130         {
2131           empathy_chat_window_add_chat (self, chat);
2132         }
2133
2134       /* Added to take care of any outstanding chat events */
2135       empathy_chat_window_present_chat (chat,
2136           TP_USER_ACTION_TIME_NOT_USER_ACTION);
2137
2138       /* We should return TRUE to remove the data when doing
2139        * GDK_ACTION_MOVE, but we don't here otherwise it has
2140        * weird consequences, and we handle that internally
2141        * anyway with add_chat () and remove_chat ().
2142        */
2143       gtk_drag_finish (context, TRUE, FALSE, time_);
2144     }
2145   else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
2146     {
2147       drag_data_received_individual_id (self, widget, context, x, y,
2148           selection, info, time_);
2149     }
2150   else if (info == DND_DRAG_TYPE_URI_LIST)
2151     {
2152       EmpathyContact *contact;
2153       const gchar *data;
2154
2155       contact = empathy_chat_get_remote_contact (self->priv->current_chat);
2156
2157       /* contact is NULL when current_chat is a multi-user chat.
2158        * We don't do file transfers to MUCs, so just cancel the drag.
2159        */
2160       if (contact == NULL)
2161         {
2162           gtk_drag_finish (context, TRUE, FALSE, time_);
2163           return;
2164         }
2165
2166       data = (const gchar *) gtk_selection_data_get_data (selection);
2167       empathy_send_file_from_uri_list (contact, data);
2168
2169       gtk_drag_finish (context, TRUE, FALSE, time_);
2170     }
2171   else if (info == DND_DRAG_TYPE_TAB)
2172     {
2173       EmpathyChat **chat;
2174       EmpathyChatWindow *old_window = NULL;
2175
2176       DEBUG ("DND tab");
2177
2178       chat = (void *) gtk_selection_data_get_data (selection);
2179       old_window = chat_window_find_chat (*chat);
2180
2181       if (old_window)
2182         {
2183           self->priv->dnd_same_window = (old_window == self);
2184
2185           DEBUG ("DND tab (within same window: %s)",
2186             self->priv->dnd_same_window ? "Yes" : "No");
2187         }
2188     }
2189   else
2190     {
2191       DEBUG ("DND from unknown source");
2192       gtk_drag_finish (context, FALSE, FALSE, time_);
2193     }
2194 }
2195
2196 static void
2197 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2198     guint num_chats_in_manager,
2199     EmpathyChatWindow *self)
2200 {
2201   gtk_action_set_sensitive (self->priv->menu_tabs_undo_close_tab,
2202       num_chats_in_manager > 0);
2203 }
2204
2205 static void
2206 chat_window_finalize (GObject *object)
2207 {
2208   EmpathyChatWindow *self = EMPATHY_CHAT_WINDOW (object);
2209
2210   DEBUG ("Finalized: %p", object);
2211
2212   g_object_unref (self->priv->ui_manager);
2213   g_object_unref (self->priv->chatroom_manager);
2214   g_object_unref (self->priv->notify_mgr);
2215   g_object_unref (self->priv->gsettings_chat);
2216   g_object_unref (self->priv->gsettings_notif);
2217   g_object_unref (self->priv->gsettings_ui);
2218   g_object_unref (self->priv->sound_mgr);
2219
2220   if (self->priv->notification != NULL)
2221     {
2222       notify_notification_close (self->priv->notification, NULL);
2223       self->priv->notification = NULL;
2224     }
2225
2226   if (self->priv->contact_targets)
2227     gtk_target_list_unref (self->priv->contact_targets);
2228
2229   if (self->priv->file_targets)
2230     gtk_target_list_unref (self->priv->file_targets);
2231
2232   if (self->priv->chat_manager)
2233     {
2234       g_signal_handler_disconnect (self->priv->chat_manager,
2235                  self->priv->chat_manager_chats_changed_id);
2236       g_object_unref (self->priv->chat_manager);
2237       self->priv->chat_manager = NULL;
2238     }
2239
2240   chat_windows = g_list_remove (chat_windows, self);
2241   gtk_widget_destroy (self->priv->dialog);
2242
2243   G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2244 }
2245
2246 static void
2247 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2248 {
2249   GObjectClass *object_class = G_OBJECT_CLASS (klass);
2250
2251   object_class->finalize = chat_window_finalize;
2252
2253   g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2254 }
2255
2256 static void
2257 empathy_chat_window_init (EmpathyChatWindow *self)
2258 {
2259   GtkBuilder *gui;
2260   GtkAccelGroup *accel_group;
2261   GClosure *closure;
2262   GtkWidget *menu;
2263   GtkWidget *submenu;
2264   guint i;
2265   GtkWidget *chat_vbox;
2266   gchar *filename;
2267   EmpathySmileyManager *smiley_manager;
2268
2269   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
2270     EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2271
2272   filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2273   gui = empathy_builder_get_file (filename,
2274       "chat_window", &self->priv->dialog,
2275       "chat_vbox", &chat_vbox,
2276       "ui_manager", &self->priv->ui_manager,
2277       "menu_conv_insert_smiley", &self->priv->menu_conv_insert_smiley,
2278       "menu_conv_favorite", &self->priv->menu_conv_favorite,
2279       "menu_conv_always_urgent", &self->priv->menu_conv_always_urgent,
2280       "menu_conv_toggle_contacts", &self->priv->menu_conv_toggle_contacts,
2281       "menu_edit_cut", &self->priv->menu_edit_cut,
2282       "menu_edit_copy", &self->priv->menu_edit_copy,
2283       "menu_edit_paste", &self->priv->menu_edit_paste,
2284       "menu_edit_find", &self->priv->menu_edit_find,
2285       "menu_tabs_next", &self->priv->menu_tabs_next,
2286       "menu_tabs_prev", &self->priv->menu_tabs_prev,
2287       "menu_tabs_undo_close_tab", &self->priv->menu_tabs_undo_close_tab,
2288       "menu_tabs_left", &self->priv->menu_tabs_left,
2289       "menu_tabs_right", &self->priv->menu_tabs_right,
2290        "menu_tabs_detach", &self->priv->menu_tabs_detach,
2291       NULL);
2292   g_free (filename);
2293
2294   empathy_builder_connect (gui, self,
2295       "menu_conv", "activate", chat_window_conv_activate_cb,
2296       "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2297       "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2298       "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2299       "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2300       "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2301       "menu_conv_close", "activate", chat_window_close_activate_cb,
2302       "menu_edit", "activate", chat_window_edit_activate_cb,
2303       "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2304       "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2305       "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2306       "menu_edit_find", "activate", chat_window_find_activate_cb,
2307       "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2308       "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2309       "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2310       "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2311       "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2312       "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2313       "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2314       "menu_help_about", "activate", chat_window_help_about_activate_cb,
2315       NULL);
2316
2317   g_object_ref (self->priv->ui_manager);
2318   g_object_unref (gui);
2319
2320   empathy_set_css_provider (GTK_WIDGET (self->priv->dialog));
2321
2322   self->priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2323   self->priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2324   self->priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2325   self->priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2326
2327   self->priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2328
2329   self->priv->notebook = gtk_notebook_new ();
2330
2331   g_signal_connect (self->priv->notebook, "create-window",
2332       G_CALLBACK (notebook_create_window_cb), self);
2333
2334   gtk_notebook_set_group_name (GTK_NOTEBOOK (self->priv->notebook),
2335     "EmpathyChatWindow");
2336   gtk_notebook_set_scrollable (GTK_NOTEBOOK (self->priv->notebook), TRUE);
2337   gtk_notebook_popup_enable (GTK_NOTEBOOK (self->priv->notebook));
2338   gtk_box_pack_start (GTK_BOX (chat_vbox), self->priv->notebook, TRUE, TRUE, 0);
2339   gtk_widget_show (self->priv->notebook);
2340
2341   /* Set up accels */
2342   accel_group = gtk_accel_group_new ();
2343   gtk_window_add_accel_group (GTK_WINDOW (self->priv->dialog), accel_group);
2344
2345   for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++)
2346     {
2347       closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb), self,
2348           NULL);
2349
2350       gtk_accel_group_connect (accel_group, tab_accel_keys[i], GDK_MOD1_MASK, 0,
2351           closure);
2352     }
2353
2354   g_object_unref (accel_group);
2355
2356   /* Set up drag target lists */
2357   self->priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2358       G_N_ELEMENTS (drag_types_dest_contact));
2359
2360   self->priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2361       G_N_ELEMENTS (drag_types_dest_file));
2362
2363   /* Set up smiley menu */
2364   smiley_manager = empathy_smiley_manager_dup_singleton ();
2365   submenu = empathy_smiley_menu_new (smiley_manager,
2366       chat_window_insert_smiley_activate_cb, self);
2367
2368   menu = gtk_ui_manager_get_widget (self->priv->ui_manager,
2369     "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2370   gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2371   g_object_unref (smiley_manager);
2372
2373   /* Set up signals we can't do with ui file since we may need to
2374    * block/unblock them at some later stage.
2375    */
2376
2377   g_signal_connect (self->priv->dialog, "delete_event",
2378       G_CALLBACK (chat_window_delete_event_cb), self);
2379   g_signal_connect (self->priv->dialog, "focus_in_event",
2380       G_CALLBACK (chat_window_focus_in_event_cb), self);
2381   g_signal_connect_after (self->priv->notebook, "switch_page",
2382       G_CALLBACK (chat_window_page_switched_cb), self);
2383   g_signal_connect (self->priv->notebook, "page_added",
2384       G_CALLBACK (chat_window_page_added_cb), self);
2385   g_signal_connect (self->priv->notebook, "page_removed",
2386       G_CALLBACK (chat_window_page_removed_cb), self);
2387
2388   /* Set up drag and drop */
2389   gtk_drag_dest_set (GTK_WIDGET (self->priv->notebook),
2390       GTK_DEST_DEFAULT_HIGHLIGHT,
2391       drag_types_dest,
2392       G_N_ELEMENTS (drag_types_dest),
2393       GDK_ACTION_MOVE | GDK_ACTION_COPY);
2394
2395   /* connect_after to allow GtkNotebook's built-in tab switching */
2396   g_signal_connect_after (self->priv->notebook, "drag-motion",
2397       G_CALLBACK (chat_window_drag_motion), self);
2398   g_signal_connect (self->priv->notebook, "drag-data-received",
2399       G_CALLBACK (chat_window_drag_data_received), self);
2400   g_signal_connect (self->priv->notebook, "drag-drop",
2401       G_CALLBACK (chat_window_drag_drop), self);
2402
2403   chat_windows = g_list_prepend (chat_windows, self);
2404
2405   /* Set up private details */
2406   self->priv->chats = NULL;
2407   self->priv->current_chat = NULL;
2408   self->priv->notification = NULL;
2409
2410   self->priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2411
2412   self->priv->chat_manager = empathy_chat_manager_dup_singleton ();
2413   self->priv->chat_manager_chats_changed_id = g_signal_connect (
2414       self->priv->chat_manager, "closed-chats-changed",
2415       G_CALLBACK (chat_window_chat_manager_chats_changed_cb), self);
2416
2417   chat_window_chat_manager_chats_changed_cb (self->priv->chat_manager,
2418       empathy_chat_manager_get_num_closed_chats (self->priv->chat_manager), self);
2419 }
2420
2421 /* Returns the window to open a new tab in if there is a suitable window,
2422  * otherwise, returns NULL indicating that a new window should be added.
2423  */
2424 static EmpathyChatWindow *
2425 empathy_chat_window_get_default (gboolean room)
2426 {
2427   GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2428   GList *l;
2429   gboolean separate_windows = TRUE;
2430
2431   separate_windows = g_settings_get_boolean (gsettings,
2432       EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2433
2434   g_object_unref (gsettings);
2435
2436   if (separate_windows)
2437     /* Always create a new window */
2438     return NULL;
2439
2440   for (l = chat_windows; l; l = l->next)
2441     {
2442       EmpathyChatWindow *chat_window;
2443       guint nb_rooms, nb_private;
2444
2445       chat_window = l->data;
2446
2447       empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2448
2449       /* Skip the window if there aren't any rooms in it */
2450       if (room && nb_rooms == 0)
2451         continue;
2452
2453       /* Skip the window if there aren't any 1-1 chats in it */
2454       if (!room && nb_private == 0)
2455         continue;
2456
2457       return chat_window;
2458     }
2459
2460   return NULL;
2461 }
2462
2463 static void
2464 empathy_chat_window_add_chat (EmpathyChatWindow *self,
2465     EmpathyChat *chat)
2466 {
2467   GtkWidget *label;
2468   GtkWidget *popup_label;
2469   GtkWidget *child;
2470   GValue value = { 0, };
2471
2472   g_return_if_fail (self != NULL);
2473   g_return_if_fail (EMPATHY_IS_CHAT (chat));
2474
2475   /* Reference the chat object */
2476   g_object_ref (chat);
2477
2478   /* If this window has just been created, position it */
2479   if (self->priv->chats == NULL)
2480     {
2481       const gchar *name = "chat-window";
2482       gboolean separate_windows;
2483
2484       separate_windows = g_settings_get_boolean (self->priv->gsettings_ui,
2485           EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2486
2487       if (empathy_chat_is_room (chat))
2488         name = "room-window";
2489
2490       if (separate_windows)
2491         {
2492           gint x, y;
2493
2494           /* Save current position of the window */
2495           gtk_window_get_position (GTK_WINDOW (self->priv->dialog), &x, &y);
2496
2497           /* First bind to the 'generic' name. So new window for which we didn't
2498           * save a geometry yet will have the geometry of the last saved
2499           * window (bgo #601191). */
2500           empathy_geometry_bind (GTK_WINDOW (self->priv->dialog), name);
2501
2502           /* Restore previous position of the window so the newly created window
2503           * won't be in the same position as the latest saved window and so
2504           * completely hide it. */
2505           gtk_window_move (GTK_WINDOW (self->priv->dialog), x, y);
2506
2507           /* Then bind it to the name of the contact/room so we'll save the
2508           * geometry specific to this window */
2509           name = empathy_chat_get_id (chat);
2510         }
2511
2512       empathy_geometry_bind (GTK_WINDOW (self->priv->dialog), name);
2513     }
2514
2515   child = GTK_WIDGET (chat);
2516   label = chat_window_create_label (self, chat, TRUE);
2517   popup_label = chat_window_create_label (self, chat, FALSE);
2518   gtk_widget_show (child);
2519
2520   g_signal_connect (chat, "notify::name",
2521       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2522   g_signal_connect (chat, "notify::subject",
2523       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2524   g_signal_connect (chat, "notify::remote-contact",
2525       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2526   g_signal_connect (chat, "notify::sms-channel",
2527       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2528   g_signal_connect (chat, "notify::n-messages-sending",
2529       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2530   g_signal_connect (chat, "notify::nb-unread-messages",
2531       G_CALLBACK (chat_window_chat_notify_cb), NULL);
2532   chat_window_chat_notify_cb (chat);
2533
2534   gtk_notebook_append_page_menu (GTK_NOTEBOOK (self->priv->notebook), child, label,
2535       popup_label);
2536   gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2537   gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (self->priv->notebook), child, TRUE);
2538   g_value_init (&value, G_TYPE_BOOLEAN);
2539   g_value_set_boolean (&value, TRUE);
2540   gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2541       child, "tab-expand" , &value);
2542   gtk_container_child_set_property (GTK_CONTAINER (self->priv->notebook),
2543       child,  "tab-fill" , &value);
2544   g_value_unset (&value);
2545
2546   DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2547 }
2548
2549 static void
2550 empathy_chat_window_remove_chat (EmpathyChatWindow *self,
2551     EmpathyChat *chat)
2552 {
2553   gint position;
2554   EmpathyContact *remote_contact;
2555   EmpathyChatManager *chat_manager;
2556
2557   g_return_if_fail (self != NULL);
2558   g_return_if_fail (EMPATHY_IS_CHAT (chat));
2559
2560   g_signal_handlers_disconnect_by_func (chat,
2561       chat_window_chat_notify_cb, NULL);
2562
2563   remote_contact = g_object_get_data (G_OBJECT (chat),
2564       "chat-window-remote-contact");
2565
2566   if (remote_contact)
2567     {
2568       g_signal_handlers_disconnect_by_func (remote_contact,
2569           chat_window_update_chat_tab, chat);
2570     }
2571
2572   chat_manager = empathy_chat_manager_dup_singleton ();
2573   empathy_chat_manager_closed_chat (chat_manager, chat);
2574   g_object_unref (chat_manager);
2575
2576   position = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2577       GTK_WIDGET (chat));
2578   gtk_notebook_remove_page (GTK_NOTEBOOK (self->priv->notebook), position);
2579
2580   DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2581
2582   g_object_unref (chat);
2583 }
2584
2585 static void
2586 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2587     EmpathyChatWindow *new_window,
2588     EmpathyChat *chat)
2589 {
2590   GtkWidget *widget;
2591
2592   g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2593   g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2594   g_return_if_fail (EMPATHY_IS_CHAT (chat));
2595
2596   widget = GTK_WIDGET (chat);
2597
2598   DEBUG ("Chat moving with widget:%p (%d references)", widget,
2599       G_OBJECT (widget)->ref_count);
2600
2601   /* We reference here to make sure we don't loose the widget
2602    * and the EmpathyChat object during the move.
2603    */
2604   g_object_ref (chat);
2605   g_object_ref (widget);
2606
2607   empathy_chat_window_remove_chat (old_window, chat);
2608   empathy_chat_window_add_chat (new_window, chat);
2609
2610   g_object_unref (widget);
2611   g_object_unref (chat);
2612 }
2613
2614 static void
2615 empathy_chat_window_switch_to_chat (EmpathyChatWindow *self,
2616     EmpathyChat *chat)
2617 {
2618   gint page_num;
2619
2620   g_return_if_fail (self != NULL);
2621   g_return_if_fail (EMPATHY_IS_CHAT (chat));
2622
2623   page_num = gtk_notebook_page_num (GTK_NOTEBOOK (self->priv->notebook),
2624       GTK_WIDGET (chat));
2625
2626   gtk_notebook_set_current_page (GTK_NOTEBOOK (self->priv->notebook),
2627       page_num);
2628 }
2629
2630 EmpathyChat *
2631 empathy_chat_window_find_chat (TpAccount *account,
2632     const gchar *id,
2633     gboolean sms_channel)
2634 {
2635   GList *l;
2636
2637   g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2638
2639   for (l = chat_windows; l; l = l->next)
2640     {
2641       EmpathyChatWindow *window = l->data;
2642       GList *ll;
2643
2644       for (ll = window->priv->chats; ll; ll = ll->next)
2645         {
2646           EmpathyChat *chat;
2647
2648           chat = ll->data;
2649
2650           if (account == empathy_chat_get_account (chat) &&
2651               !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2652               sms_channel == empathy_chat_is_sms_channel (chat))
2653             return chat;
2654         }
2655     }
2656
2657   return NULL;
2658 }
2659
2660 void
2661 empathy_chat_window_present_chat (EmpathyChat *chat,
2662     gint64 timestamp)
2663 {
2664   EmpathyChatWindow *self;
2665   guint32 x_timestamp;
2666
2667   g_return_if_fail (EMPATHY_IS_CHAT (chat));
2668
2669   self = chat_window_find_chat (chat);
2670
2671   /* If the chat has no window, create one */
2672   if (self == NULL)
2673     {
2674       self = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2675       if (!self)
2676         {
2677           self = empathy_chat_window_new ();
2678
2679           /* we want to display the newly created window even if we
2680            * don't present it */
2681           gtk_widget_show (self->priv->dialog);
2682         }
2683
2684       empathy_chat_window_add_chat (self, chat);
2685     }
2686
2687   /* Don't force the window to show itself when it wasn't
2688    * an action by the user
2689    */
2690   if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2691     return;
2692
2693   if (x_timestamp != GDK_CURRENT_TIME)
2694     {
2695       /* Don't present or switch tab if the action was earlier than the
2696        * last actions X time, accounting for overflow and the first ever
2697       * presentation */
2698
2699       if (self->priv->x_user_action_time != 0
2700         && X_EARLIER_OR_EQL (x_timestamp, self->priv->x_user_action_time))
2701         return;
2702
2703       self->priv->x_user_action_time = x_timestamp;
2704     }
2705
2706   empathy_chat_window_switch_to_chat (self, chat);
2707
2708   /* Don't use empathy_window_present_with_time () which would move the window
2709    * to our current desktop but move to the window's desktop instead. This is
2710    * more coherent with Shell's 'app is ready' notication which moves the view
2711    * to the app desktop rather than moving the app itself. */
2712   empathy_move_to_window_desktop (GTK_WINDOW (self->priv->dialog), x_timestamp);
2713
2714   gtk_widget_grab_focus (chat->input_text_view);
2715 }
2716
2717 static void
2718 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2719     guint *nb_rooms,
2720     guint *nb_private)
2721 {
2722   GList *l;
2723   guint _nb_rooms = 0, _nb_private = 0;
2724
2725   for (l = self->priv->chats; l != NULL; l = g_list_next (l))
2726     {
2727       if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2728         _nb_rooms++;
2729       else
2730         _nb_private++;
2731     }
2732
2733   if (nb_rooms != NULL)
2734     *nb_rooms = _nb_rooms;
2735   if (nb_private != NULL)
2736     *nb_private = _nb_private;
2737 }