]> git.0d.be Git - empathy.git/blob - src/empathy-chat-window.c
Show MUC highlight notifications in Gnome Shell
[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_contact_list_add (EMPATHY_CONTACT_LIST (tp_chat),
1192                                 contact, _("Inviting you to this room"));
1193
1194                 g_object_unref (contact);
1195         }
1196
1197 out:
1198         gtk_widget_destroy (dialog);
1199 }
1200
1201 static void
1202 chat_window_close_activate_cb (GtkAction         *action,
1203                                EmpathyChatWindow *window)
1204 {
1205         EmpathyChatWindowPriv *priv;
1206
1207         priv = GET_PRIV (window);
1208
1209         g_return_if_fail (priv->current_chat != NULL);
1210
1211         maybe_close_chat (window, priv->current_chat);
1212 }
1213
1214 static void
1215 chat_window_edit_activate_cb (GtkAction         *action,
1216                               EmpathyChatWindow *window)
1217 {
1218         EmpathyChatWindowPriv *priv;
1219         GtkClipboard         *clipboard;
1220         GtkTextBuffer        *buffer;
1221         gboolean              text_available;
1222
1223         priv = GET_PRIV (window);
1224
1225         g_return_if_fail (priv->current_chat != NULL);
1226
1227         if (!empathy_chat_get_tp_chat (priv->current_chat)) {
1228                 gtk_action_set_sensitive (priv->menu_edit_copy, FALSE);
1229                 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1230                 gtk_action_set_sensitive (priv->menu_edit_paste, FALSE);
1231                 return;
1232         }
1233
1234         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
1235         if (gtk_text_buffer_get_has_selection (buffer)) {
1236                 gtk_action_set_sensitive (priv->menu_edit_copy, TRUE);
1237                 gtk_action_set_sensitive (priv->menu_edit_cut, TRUE);
1238         } else {
1239                 gboolean selection;
1240
1241                 selection = empathy_chat_view_get_has_selection (priv->current_chat->view);
1242
1243                 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1244                 gtk_action_set_sensitive (priv->menu_edit_copy, selection);
1245         }
1246
1247         clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1248         text_available = gtk_clipboard_wait_is_text_available (clipboard);
1249         gtk_action_set_sensitive (priv->menu_edit_paste, text_available);
1250 }
1251
1252 static void
1253 chat_window_cut_activate_cb (GtkAction         *action,
1254                              EmpathyChatWindow *window)
1255 {
1256         EmpathyChatWindowPriv *priv;
1257
1258         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1259
1260         priv = GET_PRIV (window);
1261
1262         empathy_chat_cut (priv->current_chat);
1263 }
1264
1265 static void
1266 chat_window_copy_activate_cb (GtkAction         *action,
1267                               EmpathyChatWindow *window)
1268 {
1269         EmpathyChatWindowPriv *priv;
1270
1271         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1272
1273         priv = GET_PRIV (window);
1274
1275         empathy_chat_copy (priv->current_chat);
1276 }
1277
1278 static void
1279 chat_window_paste_activate_cb (GtkAction         *action,
1280                                EmpathyChatWindow *window)
1281 {
1282         EmpathyChatWindowPriv *priv;
1283
1284         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1285
1286         priv = GET_PRIV (window);
1287
1288         empathy_chat_paste (priv->current_chat);
1289 }
1290
1291 static void
1292 chat_window_find_activate_cb (GtkAction         *action,
1293                               EmpathyChatWindow *window)
1294 {
1295         EmpathyChatWindowPriv *priv;
1296
1297         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1298
1299         priv = GET_PRIV (window);
1300
1301         empathy_chat_find (priv->current_chat);
1302 }
1303
1304 static void
1305 chat_window_tabs_next_activate_cb (GtkAction         *action,
1306                                    EmpathyChatWindow *window)
1307 {
1308         EmpathyChatWindowPriv *priv;
1309         gint                  index_, numPages;
1310         gboolean              wrap_around;
1311
1312         priv = GET_PRIV (window);
1313
1314         g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1315                       &wrap_around, NULL);
1316
1317         index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1318         numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1319
1320         if (index_ == (numPages - 1) && wrap_around) {
1321                 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
1322                 return;
1323         }
1324
1325         gtk_notebook_next_page (GTK_NOTEBOOK (priv->notebook));
1326 }
1327
1328 static void
1329 chat_window_tabs_previous_activate_cb (GtkAction         *action,
1330                                    EmpathyChatWindow *window)
1331 {
1332         EmpathyChatWindowPriv *priv;
1333         gint                  index_, numPages;
1334         gboolean              wrap_around;
1335
1336         priv = GET_PRIV (window);
1337
1338         g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1339                       &wrap_around, NULL);
1340
1341         index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1342         numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1343
1344         if (index_ <= 0 && wrap_around) {
1345                 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
1346                 return;
1347         }
1348
1349         gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
1350 }
1351
1352 static void
1353 chat_window_tabs_undo_close_tab_activate_cb (GtkAction         *action,
1354                                              EmpathyChatWindow *window)
1355 {
1356         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1357         empathy_chat_manager_undo_closed_chat (priv->chat_manager,
1358                                                empathy_get_current_action_time ());
1359 }
1360
1361 static void
1362 chat_window_tabs_left_activate_cb (GtkAction         *action,
1363                                    EmpathyChatWindow *window)
1364 {
1365         EmpathyChatWindowPriv *priv;
1366         EmpathyChat           *chat;
1367         gint                  index_, num_pages;
1368
1369         priv = GET_PRIV (window);
1370
1371         chat = priv->current_chat;
1372         index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1373         if (index_ <= 0) {
1374                 return;
1375         }
1376
1377         gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1378                                     GTK_WIDGET (chat),
1379                                     index_ - 1);
1380
1381         num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1382         chat_window_menu_context_update (priv, num_pages);
1383 }
1384
1385 static void
1386 chat_window_tabs_right_activate_cb (GtkAction         *action,
1387                                     EmpathyChatWindow *window)
1388 {
1389         EmpathyChatWindowPriv *priv;
1390         EmpathyChat           *chat;
1391         gint                  index_, num_pages;
1392
1393         priv = GET_PRIV (window);
1394
1395         chat = priv->current_chat;
1396         index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1397
1398         gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1399                                     GTK_WIDGET (chat),
1400                                     index_ + 1);
1401
1402         num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1403         chat_window_menu_context_update (priv, num_pages);
1404 }
1405
1406 static EmpathyChatWindow *
1407 empathy_chat_window_new (void)
1408 {
1409         return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1410 }
1411
1412 static void
1413 chat_window_detach_activate_cb (GtkAction         *action,
1414                                 EmpathyChatWindow *window)
1415 {
1416         EmpathyChatWindowPriv *priv;
1417         EmpathyChatWindow     *new_window;
1418         EmpathyChat           *chat;
1419
1420         priv = GET_PRIV (window);
1421
1422         chat = priv->current_chat;
1423         new_window = empathy_chat_window_new ();
1424
1425         empathy_chat_window_move_chat (window, new_window, chat);
1426
1427         priv = GET_PRIV (new_window);
1428         gtk_widget_show (priv->dialog);
1429 }
1430
1431 static void
1432 chat_window_help_contents_activate_cb (GtkAction         *action,
1433                                        EmpathyChatWindow *window)
1434 {
1435         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1436
1437         empathy_url_show (priv->dialog, "help:empathy");
1438 }
1439
1440 static void
1441 chat_window_help_about_activate_cb (GtkAction         *action,
1442                                     EmpathyChatWindow *window)
1443 {
1444         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1445
1446         empathy_about_dialog_new (GTK_WINDOW (priv->dialog));
1447 }
1448
1449 static gboolean
1450 chat_window_delete_event_cb (GtkWidget        *dialog,
1451                              GdkEvent         *event,
1452                              EmpathyChatWindow *window)
1453 {
1454         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1455         EmpathyChat *chat = NULL;
1456         guint n_rooms = 0;
1457         GList *l;
1458
1459         DEBUG ("Delete event received");
1460
1461         for (l = priv->chats; l != NULL; l = l->next) {
1462                 if (chat_needs_close_confirmation (l->data)) {
1463                         chat = l->data;
1464                         n_rooms++;
1465                 }
1466         }
1467
1468         if (n_rooms > 0) {
1469                 confirm_close (window, TRUE, n_rooms,
1470                         (n_rooms == 1 ? chat : NULL));
1471         } else {
1472                 remove_all_chats (window);
1473         }
1474
1475         return TRUE;
1476 }
1477
1478 static void
1479 chat_window_composing_cb (EmpathyChat       *chat,
1480                           gboolean          is_composing,
1481                           EmpathyChatWindow *window)
1482 {
1483         chat_window_update_chat_tab (chat);
1484 }
1485
1486 static void
1487 chat_window_set_urgency_hint (EmpathyChatWindow *window,
1488                               gboolean          urgent)
1489 {
1490         EmpathyChatWindowPriv *priv;
1491
1492         priv = GET_PRIV (window);
1493
1494         gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
1495 }
1496
1497 static void
1498 chat_window_notification_closed_cb (NotifyNotification *notify,
1499                                     EmpathyChatWindow *self)
1500 {
1501         EmpathyChatWindowPriv *priv = GET_PRIV (self);
1502
1503         g_object_unref (notify);
1504         if (priv->notification == notify) {
1505                 priv->notification = NULL;
1506         }
1507 }
1508
1509 static void
1510 chat_window_show_or_update_notification (EmpathyChatWindow *window,
1511                                          EmpathyMessage *message,
1512                                          EmpathyChat *chat)
1513 {
1514         EmpathyContact *sender;
1515         const gchar *header;
1516         char *escaped;
1517         const char *body;
1518         GdkPixbuf *pixbuf;
1519         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1520         gboolean res, has_x_canonical_append;
1521         NotifyNotification *notification = priv->notification;
1522
1523         if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
1524                 return;
1525         } else {
1526                 res = g_settings_get_boolean (priv->gsettings_notif,
1527                                 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1528
1529                 if (!res) {
1530                         return;
1531                 }
1532         }
1533
1534         sender = empathy_message_get_sender (message);
1535         header = empathy_contact_get_alias (sender);
1536         body = empathy_message_get_body (message);
1537         escaped = g_markup_escape_text (body, -1);
1538         has_x_canonical_append = empathy_notify_manager_has_capability (
1539                 priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1540
1541         if (notification != NULL && !has_x_canonical_append) {
1542                 /* if the notification server supports x-canonical-append, it is
1543                    better to not use notify_notification_update to avoid
1544                    overwriting the current notification message */
1545                 notify_notification_update (notification,
1546                                             header, escaped, NULL);
1547         } else {
1548                 /* if the notification server supports x-canonical-append,
1549                    the hint will be added, so that the message from the
1550                    just created notification will be automatically appended
1551                    to an existing notification with the same title.
1552                    In this way the previous message will not be lost: the new
1553                    message will appear below it, in the same notification */
1554                 notification = notify_notification_new (header, escaped, NULL);
1555
1556                 if (priv->notification == NULL) {
1557                         priv->notification = notification;
1558                 }
1559
1560                 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1561
1562                 tp_g_signal_connect_object (notification, "closed",
1563                                   G_CALLBACK (chat_window_notification_closed_cb), window, 0);
1564
1565                 if (has_x_canonical_append) {
1566                         /* We have to set a not empty string to keep libnotify happy */
1567                         notify_notification_set_hint_string (notification,
1568                                 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1569                 }
1570
1571                 {
1572                         const gchar *category = empathy_chat_is_room (chat)
1573                                 ? "x-empathy.im.mentioned"
1574                                 : "im.received";
1575                         notify_notification_set_hint (notification,
1576                                 EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY,
1577                                 g_variant_new_string (category));
1578                 }
1579         }
1580
1581         pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
1582                 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1583
1584         if (pixbuf != NULL) {
1585                 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1586                 g_object_unref (pixbuf);
1587         }
1588
1589         notify_notification_show (notification, NULL);
1590
1591         g_free (escaped);
1592 }
1593
1594 static gboolean
1595 empathy_chat_window_has_focus (EmpathyChatWindow *window)
1596 {
1597         EmpathyChatWindowPriv *priv;
1598         gboolean              has_focus;
1599
1600         g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
1601
1602         priv = GET_PRIV (window);
1603
1604         g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1605
1606         return has_focus;
1607 }
1608
1609 static void
1610 chat_window_new_message_cb (EmpathyChat       *chat,
1611                             EmpathyMessage    *message,
1612                             gboolean pending,
1613                             gboolean should_highlight,
1614                             EmpathyChatWindow *window)
1615 {
1616         EmpathyChatWindowPriv *priv;
1617         gboolean              has_focus;
1618         gboolean              needs_urgency;
1619         EmpathyContact        *sender;
1620
1621         priv = GET_PRIV (window);
1622
1623         has_focus = empathy_chat_window_has_focus (window);
1624
1625         /* - if we're the sender, we play the sound if it's specified in the
1626          *   preferences and we're not away.
1627          * - if we receive a message, we play the sound if it's specified in the
1628          *   preferences and the window does not have focus on the chat receiving
1629          *   the message.
1630          */
1631
1632         sender = empathy_message_get_sender (message);
1633
1634         if (empathy_contact_is_user (sender)) {
1635                 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1636                                     EMPATHY_SOUND_MESSAGE_OUTGOING);
1637         }
1638
1639         if (has_focus && priv->current_chat == chat) {
1640                 /* window and tab are focused so consider the message to be read */
1641
1642                 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1643                 empathy_chat_messages_read (chat);
1644                 return;
1645         }
1646
1647         /* Update the chat tab if this is the first unread message */
1648         if (empathy_chat_get_nb_unread_messages (chat) == 1) {
1649                 chat_window_update_chat_tab (chat);
1650         }
1651
1652         /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1653          * If empathy_chat_get_remote_contact () returns NULL, that means it's
1654          * an unamed MUC (msn-like).
1655          * In case of a MUC, we set urgency if either:
1656          *   a) the chatroom's always_urgent property is TRUE
1657          *   b) the message contains our alias
1658          */
1659         if (empathy_chat_is_room (chat)) {
1660                 TpAccount             *account;
1661                 const gchar           *room;
1662                 EmpathyChatroom       *chatroom;
1663
1664                 account = empathy_chat_get_account (chat);
1665                 room = empathy_chat_get_id (chat);
1666
1667                 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1668                                                           account, room);
1669
1670                 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom)) {
1671                         needs_urgency = TRUE;
1672                 } else {
1673                         needs_urgency = should_highlight;
1674                 }
1675         } else {
1676                 needs_urgency = TRUE;
1677         }
1678
1679         if (needs_urgency) {
1680                 if (!has_focus) {
1681                         chat_window_set_urgency_hint (window, TRUE);
1682                 }
1683
1684                 /* Pending messages have already been displayed and notified in the
1685                 * approver, so we don't display a notification and play a sound for those */
1686                 if (!pending) {
1687                         empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1688                                     EMPATHY_SOUND_MESSAGE_INCOMING);
1689
1690                         chat_window_show_or_update_notification (window, message, chat);
1691                 }
1692         }
1693
1694         /* update the number of unread messages and the window icon */
1695         chat_window_title_update (priv);
1696         chat_window_icon_update (priv, TRUE);
1697 }
1698
1699 static void
1700 chat_window_command_part (EmpathyChat *chat,
1701                            GStrv        strv)
1702 {
1703         EmpathyChat *chat_to_be_parted;
1704         EmpathyTpChat *tp_chat = NULL;
1705
1706         if (strv[1] == NULL) {
1707                 /* No chatroom ID specified  */
1708                 tp_chat = empathy_chat_get_tp_chat (chat);
1709                 if (tp_chat)
1710                         empathy_tp_chat_leave (tp_chat, "");
1711                 return;
1712         }
1713         chat_to_be_parted = empathy_chat_window_find_chat (
1714                 empathy_chat_get_account (chat), strv[1], FALSE);
1715
1716         if (chat_to_be_parted != NULL) {
1717                 /* Found a chatroom matching the specified ID */
1718                 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1719                 if (tp_chat)
1720                         empathy_tp_chat_leave (tp_chat, strv[2]);
1721         } else {
1722                 gchar *message;
1723
1724                 /* Going by the syntax of PART command:
1725                  *
1726                  * /PART [<chatroom-ID>] [<reason>]
1727                  *
1728                  * Chatroom-ID is not a must to specify a reason.
1729                  * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1730                  * MUC then the current chatroom should be parted and srtv[1] should
1731                  * be treated as part of the optional part-message. */
1732                 message = g_strconcat (strv[1], " ", strv[2], NULL);
1733                 tp_chat = empathy_chat_get_tp_chat (chat);
1734                 if (tp_chat)
1735                         empathy_tp_chat_leave (tp_chat, message);
1736
1737                 g_free (message);
1738         }
1739 }
1740
1741 static GtkNotebook *
1742 notebook_create_window_cb (GtkNotebook *source,
1743                          GtkWidget   *page,
1744                          gint         x,
1745                          gint         y,
1746                          gpointer     user_data)
1747 {
1748         EmpathyChatWindowPriv *priv;
1749         EmpathyChatWindow     *window, *new_window;
1750         EmpathyChat           *chat;
1751
1752         chat = EMPATHY_CHAT (page);
1753         window = chat_window_find_chat (chat);
1754
1755         new_window = empathy_chat_window_new ();
1756         priv = GET_PRIV (new_window);
1757
1758         DEBUG ("Detach hook called");
1759
1760         empathy_chat_window_move_chat (window, new_window, chat);
1761
1762         gtk_widget_show (priv->dialog);
1763         gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1764
1765         return NULL;
1766 }
1767
1768 static void
1769 chat_window_page_switched_cb (GtkNotebook      *notebook,
1770                               GtkWidget         *child,
1771                               gint              page_num,
1772                               EmpathyChatWindow *window)
1773 {
1774         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1775         EmpathyChat           *chat = EMPATHY_CHAT (child);
1776
1777         DEBUG ("Page switched");
1778
1779         if (priv->page_added) {
1780                 priv->page_added = FALSE;
1781                 empathy_chat_scroll_down (chat);
1782         }
1783         else if (priv->current_chat == chat) {
1784                 return;
1785         }
1786
1787         priv->current_chat = chat;
1788         empathy_chat_messages_read (chat);
1789
1790         chat_window_update_chat_tab (chat);
1791 }
1792
1793 static void
1794 chat_window_page_added_cb (GtkNotebook      *notebook,
1795                            GtkWidget        *child,
1796                            guint             page_num,
1797                            EmpathyChatWindow *window)
1798 {
1799         EmpathyChatWindowPriv *priv;
1800         EmpathyChat           *chat;
1801
1802         priv = GET_PRIV (window);
1803
1804         /* If we just received DND to the same window, we don't want
1805          * to do anything here like removing the tab and then readding
1806          * it, so we return here and in "page-added".
1807          */
1808         if (priv->dnd_same_window) {
1809                 DEBUG ("Page added (back to the same window)");
1810                 priv->dnd_same_window = FALSE;
1811                 return;
1812         }
1813
1814         DEBUG ("Page added");
1815
1816         /* Get chat object */
1817         chat = EMPATHY_CHAT (child);
1818
1819         /* Connect chat signals for this window */
1820         g_signal_connect (chat, "composing",
1821                           G_CALLBACK (chat_window_composing_cb),
1822                           window);
1823         g_signal_connect (chat, "new-message",
1824                           G_CALLBACK (chat_window_new_message_cb),
1825                           window);
1826         g_signal_connect (chat, "part-command-entered",
1827                           G_CALLBACK (chat_window_command_part),
1828                           NULL);
1829         g_signal_connect (chat, "notify::tp-chat",
1830                           G_CALLBACK (chat_window_update_chat_tab),
1831                           window);
1832
1833         /* Set flag so we know to perform some special operations on
1834          * switch page due to the new page being added.
1835          */
1836         priv->page_added = TRUE;
1837
1838         /* Get list of chats up to date */
1839         priv->chats = g_list_append (priv->chats, chat);
1840
1841         chat_window_update_chat_tab (chat);
1842 }
1843
1844 static void
1845 chat_window_page_removed_cb (GtkNotebook      *notebook,
1846                              GtkWidget        *child,
1847                              guint             page_num,
1848                              EmpathyChatWindow *window)
1849 {
1850         EmpathyChatWindowPriv *priv;
1851         EmpathyChat           *chat;
1852
1853         priv = GET_PRIV (window);
1854
1855         /* If we just received DND to the same window, we don't want
1856          * to do anything here like removing the tab and then readding
1857          * it, so we return here and in "page-added".
1858          */
1859         if (priv->dnd_same_window) {
1860                 DEBUG ("Page removed (and will be readded to same window)");
1861                 return;
1862         }
1863
1864         DEBUG ("Page removed");
1865
1866         /* Get chat object */
1867         chat = EMPATHY_CHAT (child);
1868
1869         /* Disconnect all signal handlers for this chat and this window */
1870         g_signal_handlers_disconnect_by_func (chat,
1871                                               G_CALLBACK (chat_window_composing_cb),
1872                                               window);
1873         g_signal_handlers_disconnect_by_func (chat,
1874                                               G_CALLBACK (chat_window_new_message_cb),
1875                                               window);
1876         g_signal_handlers_disconnect_by_func (chat,
1877                                               G_CALLBACK (chat_window_update_chat_tab),
1878                                               window);
1879
1880         /* Keep list of chats up to date */
1881         priv->chats = g_list_remove (priv->chats, chat);
1882         empathy_chat_messages_read (chat);
1883
1884         if (priv->chats == NULL) {
1885                 g_object_unref (window);
1886         } else {
1887                 chat_window_update (window, TRUE);
1888         }
1889 }
1890
1891 static gboolean
1892 chat_window_focus_in_event_cb (GtkWidget        *widget,
1893                                GdkEvent         *event,
1894                                EmpathyChatWindow *window)
1895 {
1896         EmpathyChatWindowPriv *priv;
1897
1898         priv = GET_PRIV (window);
1899
1900         empathy_chat_messages_read (priv->current_chat);
1901
1902         chat_window_set_urgency_hint (window, FALSE);
1903
1904         /* Update the title, since we now mark all unread messages as read. */
1905         chat_window_update_chat_tab_full (priv->current_chat, FALSE);
1906
1907         return FALSE;
1908 }
1909
1910 static gboolean
1911 chat_window_drag_drop (GtkWidget        *widget,
1912                          GdkDragContext   *context,
1913                          int               x,
1914                          int               y,
1915                          guint             time_,
1916                          EmpathyChatWindow *window)
1917 {
1918         GdkAtom target;
1919         EmpathyChatWindowPriv *priv;
1920
1921         priv = GET_PRIV (window);
1922
1923         target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1924         if (target == GDK_NONE)
1925                 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1926
1927         if (target != GDK_NONE) {
1928                 gtk_drag_get_data (widget, context, target, time_);
1929                 return TRUE;
1930         }
1931
1932         return FALSE;
1933 }
1934
1935 static gboolean
1936 chat_window_drag_motion (GtkWidget        *widget,
1937                          GdkDragContext   *context,
1938                          int               x,
1939                          int               y,
1940                          guint             time_,
1941                          EmpathyChatWindow *window)
1942 {
1943         GdkAtom target;
1944         EmpathyChatWindowPriv *priv;
1945
1946         priv = GET_PRIV (window);
1947
1948         target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1949         if (target != GDK_NONE) {
1950                 /* This is a file drag.  Ensure the contact is online and set the
1951                    drag type to COPY.  Note that it's possible that the tab will
1952                    be switched by GTK+ after a timeout from drag_motion without
1953                    getting another drag_motion to disable the drop.  You have
1954                    to hold your mouse really still.
1955                  */
1956                 EmpathyContact *contact;
1957
1958                 priv = GET_PRIV (window);
1959                 contact = empathy_chat_get_remote_contact (priv->current_chat);
1960                 /* contact is NULL for multi-user chats.  We don't do
1961                  * file transfers to MUCs.  We also don't send files
1962                  * to offline contacts or contacts that don't support
1963                  * file transfer.
1964                  */
1965                 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1966                         gdk_drag_status (context, 0, time_);
1967                         return FALSE;
1968                 }
1969                 if (!(empathy_contact_get_capabilities (contact)
1970                            & EMPATHY_CAPABILITIES_FT)) {
1971                         gdk_drag_status (context, 0, time_);
1972                         return FALSE;
1973                 }
1974                 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1975                 return TRUE;
1976         }
1977
1978         target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1979         if (target != GDK_NONE) {
1980                 /* This is a drag of a contact from a contact list.  Set to COPY.
1981                    FIXME: If this drag is to a MUC window, it invites the user.
1982                    Otherwise, it opens a chat.  Should we use a different drag
1983                    type for invites?  Should we allow ASK?
1984                  */
1985                 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1986                 return TRUE;
1987         }
1988
1989         return FALSE;
1990 }
1991
1992 static void
1993 drag_data_received_individual_id (EmpathyChatWindow *self,
1994                                   GtkWidget *widget,
1995                                   GdkDragContext *context,
1996                                   int x,
1997                                   int y,
1998                                   GtkSelectionData *selection,
1999                                   guint info,
2000                                   guint time_)
2001 {
2002         const gchar *id;
2003         EmpathyIndividualManager *manager = NULL;
2004         FolksIndividual *individual;
2005         EmpathyChatWindowPriv *priv = GET_PRIV (self);
2006         EmpathyTpChat *chat;
2007         TpContact *tp_contact;
2008         TpConnection *conn;
2009         EmpathyContact *contact;
2010
2011         id = (const gchar *) gtk_selection_data_get_data (selection);
2012
2013         DEBUG ("DND invididual %s", id);
2014
2015         if (priv->current_chat == NULL)
2016                 goto out;
2017
2018         chat = empathy_chat_get_tp_chat (priv->current_chat);
2019         if (chat == NULL)
2020                 goto out;
2021
2022         if (!empathy_tp_chat_can_add_contact (chat)) {
2023                 DEBUG ("Can't invite contact to %s",
2024                                 tp_proxy_get_object_path (chat));
2025                 goto out;
2026         }
2027
2028         manager = empathy_individual_manager_dup_singleton ();
2029
2030         individual = empathy_individual_manager_lookup_member (manager, id);
2031         if (individual == NULL) {
2032                 DEBUG ("Failed to find individual %s", id);
2033                 goto out;
2034         }
2035
2036         conn = tp_channel_borrow_connection ((TpChannel *) chat);
2037         tp_contact = empathy_get_tp_contact_for_individual (individual, conn);
2038         if (tp_contact == NULL) {
2039                 DEBUG ("Can't find a TpContact on connection %s for %s",
2040                                 tp_proxy_get_object_path (conn), id);
2041                 goto out;
2042         }
2043
2044         DEBUG ("Inviting %s to join %s", tp_contact_get_identifier (tp_contact),
2045                         tp_channel_get_identifier ((TpChannel *) chat));
2046
2047         contact = empathy_contact_dup_from_tp_contact (tp_contact);
2048         empathy_contact_list_add (EMPATHY_CONTACT_LIST (chat), contact, NULL);
2049         g_object_unref (contact);
2050
2051 out:
2052         gtk_drag_finish (context, TRUE, FALSE, time_);
2053         tp_clear_object (&manager);
2054 }
2055
2056 static void
2057 chat_window_drag_data_received (GtkWidget        *widget,
2058                                 GdkDragContext   *context,
2059                                 int               x,
2060                                 int               y,
2061                                 GtkSelectionData *selection,
2062                                 guint             info,
2063                                 guint             time_,
2064                                 EmpathyChatWindow *window)
2065 {
2066         if (info == DND_DRAG_TYPE_CONTACT_ID) {
2067                 EmpathyChat           *chat = NULL;
2068                 EmpathyChatWindow     *old_window;
2069                 TpAccount             *account = NULL;
2070                 EmpathyClientFactory  *factory;
2071                 const gchar           *id;
2072                 gchar                **strv;
2073                 const gchar           *account_id;
2074                 const gchar           *contact_id;
2075
2076                 id = (const gchar*) gtk_selection_data_get_data (selection);
2077
2078                 factory = empathy_client_factory_dup ();
2079
2080                 DEBUG ("DND contact from roster with id:'%s'", id);
2081
2082                 strv = g_strsplit (id, ":", 2);
2083                 if (g_strv_length (strv) == 2) {
2084                         account_id = strv[0];
2085                         contact_id = strv[1];
2086                         account =
2087                                 tp_simple_client_factory_ensure_account (
2088                                 TP_SIMPLE_CLIENT_FACTORY (factory), account_id,
2089                                  NULL, NULL);
2090
2091                         g_object_unref (factory);
2092                         if (account != NULL)
2093                                 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2094                 }
2095
2096                 if (account == NULL) {
2097                         g_strfreev (strv);
2098                         gtk_drag_finish (context, FALSE, FALSE, time_);
2099                         return;
2100                 }
2101
2102                 if (!chat) {
2103                         empathy_chat_with_contact_id (
2104                                 account, contact_id,
2105                                 empathy_get_current_action_time (),
2106                                 NULL, NULL);
2107
2108                         g_strfreev (strv);
2109                         return;
2110                 }
2111                 g_strfreev (strv);
2112
2113                 old_window = chat_window_find_chat (chat);
2114                 if (old_window) {
2115                         if (old_window == window) {
2116                                 gtk_drag_finish (context, TRUE, FALSE, time_);
2117                                 return;
2118                         }
2119
2120                         empathy_chat_window_move_chat (old_window, window, chat);
2121                 } else {
2122                         empathy_chat_window_add_chat (window, chat);
2123                 }
2124
2125                 /* Added to take care of any outstanding chat events */
2126                 empathy_chat_window_present_chat (chat,
2127                         TP_USER_ACTION_TIME_NOT_USER_ACTION);
2128
2129                 /* We should return TRUE to remove the data when doing
2130                  * GDK_ACTION_MOVE, but we don't here otherwise it has
2131                  * weird consequences, and we handle that internally
2132                  * anyway with add_chat () and remove_chat ().
2133                  */
2134                 gtk_drag_finish (context, TRUE, FALSE, time_);
2135         }
2136         else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID) {
2137                 drag_data_received_individual_id (window, widget, context, x, y,
2138                                 selection, info, time_);
2139         }
2140         else if (info == DND_DRAG_TYPE_URI_LIST) {
2141                 EmpathyChatWindowPriv *priv;
2142                 EmpathyContact *contact;
2143                 const gchar *data;
2144
2145                 priv = GET_PRIV (window);
2146                 contact = empathy_chat_get_remote_contact (priv->current_chat);
2147
2148                 /* contact is NULL when current_chat is a multi-user chat.
2149                  * We don't do file transfers to MUCs, so just cancel the drag.
2150                  */
2151                 if (contact == NULL) {
2152                         gtk_drag_finish (context, TRUE, FALSE, time_);
2153                         return;
2154                 }
2155
2156                 data = (const gchar *) gtk_selection_data_get_data (selection);
2157                 empathy_send_file_from_uri_list (contact, data);
2158
2159                 gtk_drag_finish (context, TRUE, FALSE, time_);
2160         }
2161         else if (info == DND_DRAG_TYPE_TAB) {
2162                 EmpathyChat        **chat;
2163                 EmpathyChatWindow   *old_window = NULL;
2164
2165                 DEBUG ("DND tab");
2166
2167                 chat = (void *) gtk_selection_data_get_data (selection);
2168                 old_window = chat_window_find_chat (*chat);
2169
2170                 if (old_window) {
2171                         EmpathyChatWindowPriv *priv;
2172
2173                         priv = GET_PRIV (window);
2174                         priv->dnd_same_window = (old_window == window);
2175                         DEBUG ("DND tab (within same window: %s)",
2176                                 priv->dnd_same_window ? "Yes" : "No");
2177                 }
2178         } else {
2179                 DEBUG ("DND from unknown source");
2180                 gtk_drag_finish (context, FALSE, FALSE, time_);
2181         }
2182 }
2183
2184 static void
2185 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2186                                            guint num_chats_in_manager,
2187                                            EmpathyChatWindow *window)
2188 {
2189         EmpathyChatWindowPriv *priv = GET_PRIV (window);
2190
2191         gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
2192                                   num_chats_in_manager > 0);
2193 }
2194
2195 static void
2196 chat_window_finalize (GObject *object)
2197 {
2198         EmpathyChatWindow     *window;
2199         EmpathyChatWindowPriv *priv;
2200
2201         window = EMPATHY_CHAT_WINDOW (object);
2202         priv = GET_PRIV (window);
2203
2204         DEBUG ("Finalized: %p", object);
2205
2206         g_object_unref (priv->ui_manager);
2207         g_object_unref (priv->chatroom_manager);
2208         g_object_unref (priv->notify_mgr);
2209         g_object_unref (priv->gsettings_chat);
2210         g_object_unref (priv->gsettings_notif);
2211         g_object_unref (priv->gsettings_ui);
2212         g_object_unref (priv->sound_mgr);
2213
2214         if (priv->notification != NULL) {
2215                 notify_notification_close (priv->notification, NULL);
2216                 priv->notification = NULL;
2217         }
2218
2219         if (priv->contact_targets) {
2220                 gtk_target_list_unref (priv->contact_targets);
2221         }
2222         if (priv->file_targets) {
2223                 gtk_target_list_unref (priv->file_targets);
2224         }
2225
2226         if (priv->chat_manager) {
2227                 g_signal_handler_disconnect (priv->chat_manager,
2228                                              priv->chat_manager_chats_changed_id);
2229                 g_object_unref (priv->chat_manager);
2230                 priv->chat_manager = NULL;
2231         }
2232
2233         chat_windows = g_list_remove (chat_windows, window);
2234         gtk_widget_destroy (priv->dialog);
2235
2236         G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2237 }
2238
2239 static void
2240 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2241 {
2242         GObjectClass *object_class = G_OBJECT_CLASS (klass);
2243
2244         object_class->finalize = chat_window_finalize;
2245
2246         g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2247 }
2248
2249 static void
2250 empathy_chat_window_init (EmpathyChatWindow *window)
2251 {
2252         GtkBuilder            *gui;
2253         GtkAccelGroup         *accel_group;
2254         GClosure              *closure;
2255         GtkWidget             *menu;
2256         GtkWidget             *submenu;
2257         guint                  i;
2258         GtkWidget             *chat_vbox;
2259         gchar                 *filename;
2260         EmpathySmileyManager  *smiley_manager;
2261         EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
2262                 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2263
2264         window->priv = priv;
2265         filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2266         gui = empathy_builder_get_file (filename,
2267                                        "chat_window", &priv->dialog,
2268                                        "chat_vbox", &chat_vbox,
2269                                        "ui_manager", &priv->ui_manager,
2270                                        "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
2271                                        "menu_conv_favorite", &priv->menu_conv_favorite,
2272                                        "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
2273                                        "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
2274                                        "menu_edit_cut", &priv->menu_edit_cut,
2275                                        "menu_edit_copy", &priv->menu_edit_copy,
2276                                        "menu_edit_paste", &priv->menu_edit_paste,
2277                                        "menu_edit_find", &priv->menu_edit_find,
2278                                        "menu_tabs_next", &priv->menu_tabs_next,
2279                                        "menu_tabs_prev", &priv->menu_tabs_prev,
2280                                        "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
2281                                        "menu_tabs_left", &priv->menu_tabs_left,
2282                                        "menu_tabs_right", &priv->menu_tabs_right,
2283                                        "menu_tabs_detach", &priv->menu_tabs_detach,
2284                                        NULL);
2285         g_free (filename);
2286
2287         empathy_builder_connect (gui, window,
2288                               "menu_conv", "activate", chat_window_conv_activate_cb,
2289                               "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2290                               "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2291                               "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2292                               "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2293                               "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2294                               "menu_conv_close", "activate", chat_window_close_activate_cb,
2295                               "menu_edit", "activate", chat_window_edit_activate_cb,
2296                               "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2297                               "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2298                               "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2299                               "menu_edit_find", "activate", chat_window_find_activate_cb,
2300                               "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2301                               "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2302                               "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2303                               "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2304                               "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2305                               "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2306                               "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2307                               "menu_help_about", "activate", chat_window_help_about_activate_cb,
2308                               NULL);
2309
2310         g_object_ref (priv->ui_manager);
2311         g_object_unref (gui);
2312
2313         priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2314         priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2315         priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2316         priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2317
2318         priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2319
2320         priv->notebook = gtk_notebook_new ();
2321
2322         g_signal_connect (priv->notebook, "create-window",
2323                 G_CALLBACK (notebook_create_window_cb), window);
2324
2325         gtk_notebook_set_group_name (GTK_NOTEBOOK (priv->notebook),
2326                 "EmpathyChatWindow");
2327         gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
2328         gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
2329         gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
2330         gtk_widget_show (priv->notebook);
2331
2332         /* Set up accels */
2333         accel_group = gtk_accel_group_new ();
2334         gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
2335
2336         for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
2337                 closure =  g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
2338                                            window,
2339                                            NULL);
2340                 gtk_accel_group_connect (accel_group,
2341                                          tab_accel_keys[i],
2342                                          GDK_MOD1_MASK,
2343                                          0,
2344                                          closure);
2345         }
2346
2347         g_object_unref (accel_group);
2348
2349         /* Set up drag target lists */
2350         priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2351                                                      G_N_ELEMENTS (drag_types_dest_contact));
2352         priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2353                                                   G_N_ELEMENTS (drag_types_dest_file));
2354
2355         /* Set up smiley menu */
2356         smiley_manager = empathy_smiley_manager_dup_singleton ();
2357         submenu = empathy_smiley_menu_new (smiley_manager,
2358                                            chat_window_insert_smiley_activate_cb,
2359                                            window);
2360         menu = gtk_ui_manager_get_widget (priv->ui_manager,
2361                 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2362         gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2363         g_object_unref (smiley_manager);
2364
2365         /* Set up signals we can't do with ui file since we may need to
2366          * block/unblock them at some later stage.
2367          */
2368
2369         g_signal_connect (priv->dialog,
2370                           "delete_event",
2371                           G_CALLBACK (chat_window_delete_event_cb),
2372                           window);
2373         g_signal_connect (priv->dialog,
2374                           "focus_in_event",
2375                           G_CALLBACK (chat_window_focus_in_event_cb),
2376                           window);
2377         g_signal_connect_after (priv->notebook,
2378                                 "switch_page",
2379                                 G_CALLBACK (chat_window_page_switched_cb),
2380                                 window);
2381         g_signal_connect (priv->notebook,
2382                           "page_added",
2383                           G_CALLBACK (chat_window_page_added_cb),
2384                           window);
2385         g_signal_connect (priv->notebook,
2386                           "page_removed",
2387                           G_CALLBACK (chat_window_page_removed_cb),
2388                           window);
2389
2390         /* Set up drag and drop */
2391         gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
2392                            GTK_DEST_DEFAULT_HIGHLIGHT,
2393                            drag_types_dest,
2394                            G_N_ELEMENTS (drag_types_dest),
2395                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
2396
2397         /* connect_after to allow GtkNotebook's built-in tab switching */
2398         g_signal_connect_after (priv->notebook,
2399                                 "drag-motion",
2400                                 G_CALLBACK (chat_window_drag_motion),
2401                                 window);
2402         g_signal_connect (priv->notebook,
2403                           "drag-data-received",
2404                           G_CALLBACK (chat_window_drag_data_received),
2405                           window);
2406         g_signal_connect (priv->notebook,
2407                           "drag-drop",
2408                           G_CALLBACK (chat_window_drag_drop),
2409                           window);
2410
2411         chat_windows = g_list_prepend (chat_windows, window);
2412
2413         /* Set up private details */
2414         priv->chats = NULL;
2415         priv->current_chat = NULL;
2416         priv->notification = NULL;
2417
2418         priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2419
2420         priv->chat_manager = empathy_chat_manager_dup_singleton ();
2421         priv->chat_manager_chats_changed_id =
2422                 g_signal_connect (priv->chat_manager, "closed-chats-changed",
2423                                   G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2424                                   window);
2425
2426         chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2427                                                    empathy_chat_manager_get_num_closed_chats (priv->chat_manager),
2428                                                    window);
2429 }
2430
2431 /* Returns the window to open a new tab in if there is a suitable window,
2432  * otherwise, returns NULL indicating that a new window should be added.
2433  */
2434 static EmpathyChatWindow *
2435 empathy_chat_window_get_default (gboolean room)
2436 {
2437         GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2438         GList    *l;
2439         gboolean  separate_windows = TRUE;
2440
2441         separate_windows = g_settings_get_boolean (gsettings,
2442                         EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2443
2444         g_object_unref (gsettings);
2445
2446         if (separate_windows) {
2447                 /* Always create a new window */
2448                 return NULL;
2449         }
2450
2451         for (l = chat_windows; l; l = l->next) {
2452                 EmpathyChatWindow *chat_window;
2453                 guint nb_rooms, nb_private;
2454
2455                 chat_window = l->data;
2456
2457                 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2458
2459                 /* Skip the window if there aren't any rooms in it */
2460                 if (room && nb_rooms == 0)
2461                         continue;
2462
2463                 /* Skip the window if there aren't any 1-1 chats in it */
2464                 if (!room && nb_private == 0)
2465                         continue;
2466
2467                 return chat_window;
2468         }
2469
2470         return NULL;
2471 }
2472
2473 static void
2474 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2475                               EmpathyChat       *chat)
2476 {
2477         EmpathyChatWindowPriv *priv;
2478         GtkWidget             *label;
2479         GtkWidget             *popup_label;
2480         GtkWidget             *child;
2481         GValue                value = { 0, };
2482
2483         g_return_if_fail (window != NULL);
2484         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2485
2486         priv = GET_PRIV (window);
2487
2488         /* Reference the chat object */
2489         g_object_ref (chat);
2490
2491         /* If this window has just been created, position it */
2492         if (priv->chats == NULL) {
2493                 const gchar *name = "chat-window";
2494                 gboolean     separate_windows;
2495
2496                 separate_windows = g_settings_get_boolean (priv->gsettings_ui,
2497                                 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2498
2499                 if (empathy_chat_is_room (chat))
2500                         name = "room-window";
2501
2502                 if (separate_windows) {
2503                         gint x, y;
2504
2505                         /* Save current position of the window */
2506                         gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
2507
2508                         /* First bind to the 'generic' name. So new window for which we didn't
2509                         * save a geometry yet will have the geometry of the last saved
2510                         * window (bgo #601191). */
2511                         empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2512
2513                         /* Restore previous position of the window so the newly created window
2514                         * won't be in the same position as the latest saved window and so
2515                         * completely hide it. */
2516                         gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
2517
2518                         /* Then bind it to the name of the contact/room so we'll save the
2519                         * geometry specific to this window */
2520                         name = empathy_chat_get_id (chat);
2521                 }
2522
2523                 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2524         }
2525
2526         child = GTK_WIDGET (chat);
2527         label = chat_window_create_label (window, chat, TRUE);
2528         popup_label = chat_window_create_label (window, chat, FALSE);
2529         gtk_widget_show (child);
2530
2531         g_signal_connect (chat, "notify::name",
2532                           G_CALLBACK (chat_window_chat_notify_cb),
2533                           NULL);
2534         g_signal_connect (chat, "notify::subject",
2535                           G_CALLBACK (chat_window_chat_notify_cb),
2536                           NULL);
2537         g_signal_connect (chat, "notify::remote-contact",
2538                           G_CALLBACK (chat_window_chat_notify_cb),
2539                           NULL);
2540         g_signal_connect (chat, "notify::sms-channel",
2541                           G_CALLBACK (chat_window_chat_notify_cb),
2542                           NULL);
2543         g_signal_connect (chat, "notify::n-messages-sending",
2544                           G_CALLBACK (chat_window_chat_notify_cb),
2545                           NULL);
2546         g_signal_connect (chat, "notify::nb-unread-messages",
2547                           G_CALLBACK (chat_window_chat_notify_cb),
2548                           NULL);
2549         chat_window_chat_notify_cb (chat);
2550
2551         gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2552         gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2553         gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2554         g_value_init (&value, G_TYPE_BOOLEAN);
2555         g_value_set_boolean (&value, TRUE);
2556         gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2557                                           child, "tab-expand" , &value);
2558         gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2559                                           child,  "tab-fill" , &value);
2560         g_value_unset (&value);
2561
2562         DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2563 }
2564
2565 static void
2566 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2567                                  EmpathyChat       *chat)
2568 {
2569         EmpathyChatWindowPriv *priv;
2570         gint                   position;
2571         EmpathyContact        *remote_contact;
2572         EmpathyChatManager    *chat_manager;
2573
2574         g_return_if_fail (window != NULL);
2575         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2576
2577         priv = GET_PRIV (window);
2578
2579         g_signal_handlers_disconnect_by_func (chat,
2580                                               chat_window_chat_notify_cb,
2581                                               NULL);
2582         remote_contact = g_object_get_data (G_OBJECT (chat),
2583                                             "chat-window-remote-contact");
2584         if (remote_contact) {
2585                 g_signal_handlers_disconnect_by_func (remote_contact,
2586                                                       chat_window_update_chat_tab,
2587                                                       chat);
2588         }
2589
2590         chat_manager = empathy_chat_manager_dup_singleton ();
2591         empathy_chat_manager_closed_chat (chat_manager, chat);
2592         g_object_unref (chat_manager);
2593
2594         position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2595                                           GTK_WIDGET (chat));
2596         gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2597
2598         DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2599
2600         g_object_unref (chat);
2601 }
2602
2603 static void
2604 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2605                                EmpathyChatWindow *new_window,
2606                                EmpathyChat       *chat)
2607 {
2608         GtkWidget *widget;
2609
2610         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2611         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2612         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2613
2614         widget = GTK_WIDGET (chat);
2615
2616         DEBUG ("Chat moving with widget:%p (%d references)", widget,
2617                 G_OBJECT (widget)->ref_count);
2618
2619         /* We reference here to make sure we don't loose the widget
2620          * and the EmpathyChat object during the move.
2621          */
2622         g_object_ref (chat);
2623         g_object_ref (widget);
2624
2625         empathy_chat_window_remove_chat (old_window, chat);
2626         empathy_chat_window_add_chat (new_window, chat);
2627
2628         g_object_unref (widget);
2629         g_object_unref (chat);
2630 }
2631
2632 static void
2633 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2634                                     EmpathyChat       *chat)
2635 {
2636         EmpathyChatWindowPriv *priv;
2637         gint                  page_num;
2638
2639         g_return_if_fail (window != NULL);
2640         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2641
2642         priv = GET_PRIV (window);
2643
2644         page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2645                                           GTK_WIDGET (chat));
2646         gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2647                                        page_num);
2648 }
2649
2650 EmpathyChat *
2651 empathy_chat_window_find_chat (TpAccount   *account,
2652                                const gchar *id,
2653                                gboolean     sms_channel)
2654 {
2655         GList *l;
2656
2657         g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2658
2659         for (l = chat_windows; l; l = l->next) {
2660                 EmpathyChatWindowPriv *priv;
2661                 EmpathyChatWindow     *window;
2662                 GList                *ll;
2663
2664                 window = l->data;
2665                 priv = GET_PRIV (window);
2666
2667                 for (ll = priv->chats; ll; ll = ll->next) {
2668                         EmpathyChat *chat;
2669
2670                         chat = ll->data;
2671
2672                         if (account == empathy_chat_get_account (chat) &&
2673                             !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2674                             sms_channel == empathy_chat_is_sms_channel (chat)) {
2675                                 return chat;
2676                         }
2677                 }
2678         }
2679
2680         return NULL;
2681 }
2682
2683 void
2684 empathy_chat_window_present_chat (EmpathyChat *chat,
2685                                   gint64 timestamp)
2686 {
2687         EmpathyChatWindow     *window;
2688         EmpathyChatWindowPriv *priv;
2689         guint32 x_timestamp;
2690
2691         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2692
2693         window = chat_window_find_chat (chat);
2694
2695         /* If the chat has no window, create one */
2696         if (window == NULL) {
2697                 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2698                 if (!window) {
2699                         window = empathy_chat_window_new ();
2700
2701                         /* we want to display the newly created window even if we don't present
2702                         * it */
2703                         priv = GET_PRIV (window);
2704                         gtk_widget_show (priv->dialog);
2705                 }
2706
2707                 empathy_chat_window_add_chat (window, chat);
2708         }
2709
2710         /* Don't force the window to show itself when it wasn't
2711          * an action by the user
2712          */
2713         if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2714                 return;
2715
2716         priv = GET_PRIV (window);
2717
2718         if (x_timestamp != GDK_CURRENT_TIME) {
2719                 /* Don't present or switch tab if the action was earlier than the
2720                  * last actions X time, accounting for overflow and the first ever
2721                 * presentation */
2722
2723                 if (priv->x_user_action_time != 0
2724                         && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2725                         return;
2726
2727                 priv->x_user_action_time = x_timestamp;
2728         }
2729
2730         empathy_chat_window_switch_to_chat (window, chat);
2731
2732         /* Don't use empathy_window_present_with_time () which would move the window
2733          * to our current desktop but move to the window's desktop instead. This is
2734          * more coherent with Shell's 'app is ready' notication which moves the view
2735          * to the app desktop rather than moving the app itself. */
2736         empathy_move_to_window_desktop (GTK_WINDOW (priv->dialog), x_timestamp);
2737
2738         gtk_widget_grab_focus (chat->input_text_view);
2739 }
2740
2741 static void
2742 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2743                                guint *nb_rooms,
2744                                guint *nb_private)
2745 {
2746         EmpathyChatWindowPriv *priv = GET_PRIV (self);
2747         GList *l;
2748         guint _nb_rooms = 0, _nb_private = 0;
2749
2750         for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2751                 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2752                         _nb_rooms++;
2753                 else
2754                         _nb_private++;
2755         }
2756
2757         if (nb_rooms != NULL)
2758                 *nb_rooms = _nb_rooms;
2759         if (nb_private != NULL)
2760                 *nb_private = _nb_private;
2761 }