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