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