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