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