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