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