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