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