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