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