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