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