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