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