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