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