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