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