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