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