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