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