]> git.0d.be Git - empathy.git/blob - src/empathy-chat-window.c
Fire a "part-command-entered" signal from libempathy-gtk/empathy-chat.c to be caught...
[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 void
1486 chat_window_command_part (EmpathyChat *chat,
1487                            GStrv        strv)
1488 {
1489         EmpathyChat *chat_to_be_parted;
1490
1491         if (strv[1] == NULL) {
1492                 empathy_tp_chat_leave (empathy_chat_get_tp_chat (chat), "");
1493                 return;
1494         }
1495         chat_to_be_parted = empathy_chat_window_find_chat (
1496                 empathy_chat_get_account (chat), strv[1]);
1497
1498         if (chat_to_be_parted != NULL) {
1499                 empathy_tp_chat_leave (empathy_chat_get_tp_chat (chat_to_be_parted),
1500                         strv[2]);
1501         } else {
1502                 empathy_tp_chat_leave (empathy_chat_get_tp_chat (chat), strv[1]);
1503         }
1504 }
1505
1506 static GtkNotebook *
1507 notebook_create_window_cb (GtkNotebook *source,
1508                          GtkWidget   *page,
1509                          gint         x,
1510                          gint         y,
1511                          gpointer     user_data)
1512 {
1513         EmpathyChatWindowPriv *priv;
1514         EmpathyChatWindow     *window, *new_window;
1515         EmpathyChat           *chat;
1516
1517         chat = EMPATHY_CHAT (page);
1518         window = chat_window_find_chat (chat);
1519
1520         new_window = empathy_chat_window_new ();
1521         priv = GET_PRIV (new_window);
1522
1523         DEBUG ("Detach hook called");
1524
1525         empathy_chat_window_move_chat (window, new_window, chat);
1526
1527         gtk_widget_show (priv->dialog);
1528         gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1529
1530         return NULL;
1531 }
1532
1533 static void
1534 chat_window_page_switched_cb (GtkNotebook      *notebook,
1535                               GtkWidget         *child,
1536                               gint              page_num,
1537                               EmpathyChatWindow *window)
1538 {
1539         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1540         EmpathyChat           *chat = EMPATHY_CHAT (child);
1541
1542         DEBUG ("Page switched");
1543
1544         if (priv->page_added) {
1545                 priv->page_added = FALSE;
1546                 empathy_chat_scroll_down (chat);
1547         }
1548         else if (priv->current_chat == chat) {
1549                 return;
1550         }
1551
1552         priv->current_chat = chat;
1553         empathy_chat_messages_read (chat);
1554
1555         chat_window_update_chat_tab (chat);
1556 }
1557
1558 static void
1559 chat_window_page_added_cb (GtkNotebook      *notebook,
1560                            GtkWidget        *child,
1561                            guint             page_num,
1562                            EmpathyChatWindow *window)
1563 {
1564         EmpathyChatWindowPriv *priv;
1565         EmpathyChat           *chat;
1566
1567         priv = GET_PRIV (window);
1568
1569         /* If we just received DND to the same window, we don't want
1570          * to do anything here like removing the tab and then readding
1571          * it, so we return here and in "page-added".
1572          */
1573         if (priv->dnd_same_window) {
1574                 DEBUG ("Page added (back to the same window)");
1575                 priv->dnd_same_window = FALSE;
1576                 return;
1577         }
1578
1579         DEBUG ("Page added");
1580
1581         /* Get chat object */
1582         chat = EMPATHY_CHAT (child);
1583
1584         /* Connect chat signals for this window */
1585         g_signal_connect (chat, "composing",
1586                           G_CALLBACK (chat_window_composing_cb),
1587                           window);
1588         g_signal_connect (chat, "new-message",
1589                           G_CALLBACK (chat_window_new_message_cb),
1590                           window);
1591         g_signal_connect (chat, "part-command-entered",
1592                           G_CALLBACK (chat_window_command_part),
1593                           NULL);
1594         g_signal_connect (chat, "notify::tp-chat",
1595                           G_CALLBACK (chat_window_update_chat_tab),
1596                           window);
1597
1598         /* Set flag so we know to perform some special operations on
1599          * switch page due to the new page being added.
1600          */
1601         priv->page_added = TRUE;
1602
1603         /* Get list of chats up to date */
1604         priv->chats = g_list_append (priv->chats, chat);
1605
1606         chat_window_update_chat_tab (chat);
1607 }
1608
1609 static void
1610 chat_window_page_removed_cb (GtkNotebook      *notebook,
1611                              GtkWidget        *child,
1612                              guint             page_num,
1613                              EmpathyChatWindow *window)
1614 {
1615         EmpathyChatWindowPriv *priv;
1616         EmpathyChat           *chat;
1617
1618         priv = GET_PRIV (window);
1619
1620         /* If we just received DND to the same window, we don't want
1621          * to do anything here like removing the tab and then readding
1622          * it, so we return here and in "page-added".
1623          */
1624         if (priv->dnd_same_window) {
1625                 DEBUG ("Page removed (and will be readded to same window)");
1626                 return;
1627         }
1628
1629         DEBUG ("Page removed");
1630
1631         /* Get chat object */
1632         chat = EMPATHY_CHAT (child);
1633
1634         /* Disconnect all signal handlers for this chat and this window */
1635         g_signal_handlers_disconnect_by_func (chat,
1636                                               G_CALLBACK (chat_window_composing_cb),
1637                                               window);
1638         g_signal_handlers_disconnect_by_func (chat,
1639                                               G_CALLBACK (chat_window_new_message_cb),
1640                                               window);
1641         g_signal_handlers_disconnect_by_func (chat,
1642                                               G_CALLBACK (chat_window_update_chat_tab),
1643                                               window);
1644
1645         /* Keep list of chats up to date */
1646         priv->chats = g_list_remove (priv->chats, chat);
1647         empathy_chat_messages_read (chat);
1648
1649         if (priv->chats == NULL) {
1650                 g_object_unref (window);
1651         } else {
1652                 chat_window_update (window, TRUE);
1653         }
1654 }
1655
1656 static gboolean
1657 chat_window_focus_in_event_cb (GtkWidget        *widget,
1658                                GdkEvent         *event,
1659                                EmpathyChatWindow *window)
1660 {
1661         EmpathyChatWindowPriv *priv;
1662
1663         priv = GET_PRIV (window);
1664
1665         empathy_chat_messages_read (priv->current_chat);
1666
1667         chat_window_set_urgency_hint (window, FALSE);
1668
1669         /* Update the title, since we now mark all unread messages as read. */
1670         chat_window_update_chat_tab_full (priv->current_chat, FALSE);
1671
1672         return FALSE;
1673 }
1674
1675 static gboolean
1676 chat_window_drag_drop (GtkWidget        *widget,
1677                          GdkDragContext   *context,
1678                          int               x,
1679                          int               y,
1680                          guint             time_,
1681                          EmpathyChatWindow *window)
1682 {
1683         GdkAtom target;
1684         EmpathyChatWindowPriv *priv;
1685
1686         priv = GET_PRIV (window);
1687
1688         target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1689         if (target == GDK_NONE)
1690                 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1691
1692         if (target != GDK_NONE) {
1693                 gtk_drag_get_data (widget, context, target, time_);
1694                 return TRUE;
1695         }
1696
1697         return FALSE;
1698 }
1699
1700 static gboolean
1701 chat_window_drag_motion (GtkWidget        *widget,
1702                          GdkDragContext   *context,
1703                          int               x,
1704                          int               y,
1705                          guint             time_,
1706                          EmpathyChatWindow *window)
1707 {
1708         GdkAtom target;
1709         EmpathyChatWindowPriv *priv;
1710
1711         priv = GET_PRIV (window);
1712
1713         target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1714         if (target != GDK_NONE) {
1715                 /* This is a file drag.  Ensure the contact is online and set the
1716                    drag type to COPY.  Note that it's possible that the tab will
1717                    be switched by GTK+ after a timeout from drag_motion without
1718                    getting another drag_motion to disable the drop.  You have
1719                    to hold your mouse really still.
1720                  */
1721                 EmpathyContact *contact;
1722
1723                 priv = GET_PRIV (window);
1724                 contact = empathy_chat_get_remote_contact (priv->current_chat);
1725                 /* contact is NULL for multi-user chats.  We don't do
1726                  * file transfers to MUCs.  We also don't send files
1727                  * to offline contacts or contacts that don't support
1728                  * file transfer.
1729                  */
1730                 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1731                         gdk_drag_status (context, 0, time_);
1732                         return FALSE;
1733                 }
1734                 if (!(empathy_contact_get_capabilities (contact)
1735                            & EMPATHY_CAPABILITIES_FT)) {
1736                         gdk_drag_status (context, 0, time_);
1737                         return FALSE;
1738                 }
1739                 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1740                 return TRUE;
1741         }
1742
1743         target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1744         if (target != GDK_NONE) {
1745                 /* This is a drag of a contact from a contact list.  Set to COPY.
1746                    FIXME: If this drag is to a MUC window, it invites the user.
1747                    Otherwise, it opens a chat.  Should we use a different drag
1748                    type for invites?  Should we allow ASK?
1749                  */
1750                 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1751                 return TRUE;
1752         }
1753
1754         return FALSE;
1755 }
1756
1757 static void
1758 chat_window_drag_data_received (GtkWidget        *widget,
1759                                 GdkDragContext   *context,
1760                                 int               x,
1761                                 int               y,
1762                                 GtkSelectionData *selection,
1763                                 guint             info,
1764                                 guint             time_,
1765                                 EmpathyChatWindow *window)
1766 {
1767         if (info == DND_DRAG_TYPE_CONTACT_ID) {
1768                 EmpathyChat           *chat = NULL;
1769                 EmpathyChatWindow     *old_window;
1770                 TpAccount             *account = NULL;
1771                 TpAccountManager      *account_manager;
1772                 const gchar           *id;
1773                 gchar                **strv;
1774                 const gchar           *account_id;
1775                 const gchar           *contact_id;
1776
1777                 id = (const gchar*) gtk_selection_data_get_data (selection);
1778
1779                 /* FIXME: Perhaps should be sure that the account manager is
1780                  * prepared before calling _ensure_account on it. */
1781                 account_manager = tp_account_manager_dup ();
1782
1783                 DEBUG ("DND contact from roster with id:'%s'", id);
1784
1785                 strv = g_strsplit (id, ":", 2);
1786                 if (g_strv_length (strv) == 2) {
1787                         account_id = strv[0];
1788                         contact_id = strv[1];
1789                         account =
1790                                 tp_account_manager_ensure_account (account_manager, account_id);
1791                         if (account != NULL)
1792                                 chat = empathy_chat_window_find_chat (account, contact_id);
1793                 }
1794
1795                 if (account == NULL) {
1796                         g_strfreev (strv);
1797                         gtk_drag_finish (context, FALSE, FALSE, time_);
1798                         return;
1799                 }
1800
1801                 if (!chat) {
1802                         empathy_chat_with_contact_id (
1803                                 account, contact_id, gtk_get_current_event_time ());
1804
1805                         g_strfreev (strv);
1806                         return;
1807                 }
1808                 g_object_unref (account_manager);
1809                 g_strfreev (strv);
1810
1811                 old_window = chat_window_find_chat (chat);
1812                 if (old_window) {
1813                         if (old_window == window) {
1814                                 gtk_drag_finish (context, TRUE, FALSE, time_);
1815                                 return;
1816                         }
1817
1818                         empathy_chat_window_move_chat (old_window, window, chat);
1819                 } else {
1820                         empathy_chat_window_add_chat (window, chat);
1821                 }
1822
1823                 /* Added to take care of any outstanding chat events */
1824                 empathy_chat_window_present_chat (chat,
1825                         TP_USER_ACTION_TIME_NOT_USER_ACTION);
1826
1827                 /* We should return TRUE to remove the data when doing
1828                  * GDK_ACTION_MOVE, but we don't here otherwise it has
1829                  * weird consequences, and we handle that internally
1830                  * anyway with add_chat () and remove_chat ().
1831                  */
1832                 gtk_drag_finish (context, TRUE, FALSE, time_);
1833         }
1834         else if (info == DND_DRAG_TYPE_URI_LIST) {
1835                 EmpathyChatWindowPriv *priv;
1836                 EmpathyContact *contact;
1837                 const gchar *data;
1838
1839                 priv = GET_PRIV (window);
1840                 contact = empathy_chat_get_remote_contact (priv->current_chat);
1841
1842                 /* contact is NULL when current_chat is a multi-user chat.
1843                  * We don't do file transfers to MUCs, so just cancel the drag.
1844                  */
1845                 if (contact == NULL) {
1846                         gtk_drag_finish (context, TRUE, FALSE, time_);
1847                         return;
1848                 }
1849
1850                 data = (const gchar *) gtk_selection_data_get_data (selection);
1851                 empathy_send_file_from_uri_list (contact, data);
1852
1853                 gtk_drag_finish (context, TRUE, FALSE, time_);
1854         }
1855         else if (info == DND_DRAG_TYPE_TAB) {
1856                 EmpathyChat        **chat;
1857                 EmpathyChatWindow   *old_window = NULL;
1858
1859                 DEBUG ("DND tab");
1860
1861                 chat = (void *) gtk_selection_data_get_data (selection);
1862                 old_window = chat_window_find_chat (*chat);
1863
1864                 if (old_window) {
1865                         EmpathyChatWindowPriv *priv;
1866
1867                         priv = GET_PRIV (window);
1868                         priv->dnd_same_window = (old_window == window);
1869                         DEBUG ("DND tab (within same window: %s)",
1870                                 priv->dnd_same_window ? "Yes" : "No");
1871                 }
1872         } else {
1873                 DEBUG ("DND from unknown source");
1874                 gtk_drag_finish (context, FALSE, FALSE, time_);
1875         }
1876 }
1877
1878 static void
1879 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
1880                                            guint num_chats_in_manager,
1881                                            EmpathyChatWindow *window)
1882 {
1883         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1884
1885         gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
1886                                   num_chats_in_manager > 0);
1887 }
1888
1889 static void
1890 chat_window_finalize (GObject *object)
1891 {
1892         EmpathyChatWindow     *window;
1893         EmpathyChatWindowPriv *priv;
1894
1895         window = EMPATHY_CHAT_WINDOW (object);
1896         priv = GET_PRIV (window);
1897
1898         DEBUG ("Finalized: %p", object);
1899
1900         g_object_unref (priv->ui_manager);
1901         g_object_unref (priv->chatroom_manager);
1902         g_object_unref (priv->notify_mgr);
1903         g_object_unref (priv->gsettings_chat);
1904         g_object_unref (priv->gsettings_notif);
1905         g_object_unref (priv->gsettings_ui);
1906         g_object_unref (priv->sound_mgr);
1907
1908         if (priv->notification != NULL) {
1909                 notify_notification_close (priv->notification, NULL);
1910                 priv->notification = NULL;
1911         }
1912
1913         if (priv->contact_targets) {
1914                 gtk_target_list_unref (priv->contact_targets);
1915         }
1916         if (priv->file_targets) {
1917                 gtk_target_list_unref (priv->file_targets);
1918         }
1919
1920         if (priv->chat_manager) {
1921                 g_signal_handler_disconnect (priv->chat_manager,
1922                                              priv->chat_manager_chats_changed_id);
1923                 g_object_unref (priv->chat_manager);
1924                 priv->chat_manager = NULL;
1925         }
1926
1927         chat_windows = g_list_remove (chat_windows, window);
1928         gtk_widget_destroy (priv->dialog);
1929
1930         G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
1931 }
1932
1933 static void
1934 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
1935 {
1936         GObjectClass *object_class = G_OBJECT_CLASS (klass);
1937
1938         object_class->finalize = chat_window_finalize;
1939
1940         g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
1941 }
1942
1943 static void
1944 empathy_chat_window_init (EmpathyChatWindow *window)
1945 {
1946         GtkBuilder            *gui;
1947         GtkAccelGroup         *accel_group;
1948         GClosure              *closure;
1949         GtkWidget             *menu;
1950         GtkWidget             *submenu;
1951         guint                  i;
1952         GtkWidget             *chat_vbox;
1953         gchar                 *filename;
1954         EmpathySmileyManager  *smiley_manager;
1955         EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
1956                 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
1957
1958         window->priv = priv;
1959         filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
1960         gui = empathy_builder_get_file (filename,
1961                                        "chat_window", &priv->dialog,
1962                                        "chat_vbox", &chat_vbox,
1963                                        "ui_manager", &priv->ui_manager,
1964                                        "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
1965                                        "menu_conv_favorite", &priv->menu_conv_favorite,
1966                                        "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
1967                                        "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
1968                                        "menu_edit_cut", &priv->menu_edit_cut,
1969                                        "menu_edit_copy", &priv->menu_edit_copy,
1970                                        "menu_edit_paste", &priv->menu_edit_paste,
1971                                        "menu_edit_find", &priv->menu_edit_find,
1972                                        "menu_tabs_next", &priv->menu_tabs_next,
1973                                        "menu_tabs_prev", &priv->menu_tabs_prev,
1974                                        "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
1975                                        "menu_tabs_left", &priv->menu_tabs_left,
1976                                        "menu_tabs_right", &priv->menu_tabs_right,
1977                                        "menu_tabs_detach", &priv->menu_tabs_detach,
1978                                        NULL);
1979         g_free (filename);
1980
1981         empathy_builder_connect (gui, window,
1982                               "menu_conv", "activate", chat_window_conv_activate_cb,
1983                               "menu_conv_clear", "activate", chat_window_clear_activate_cb,
1984                               "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
1985                               "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
1986                               "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
1987                               "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
1988                               "menu_conv_close", "activate", chat_window_close_activate_cb,
1989                               "menu_edit", "activate", chat_window_edit_activate_cb,
1990                               "menu_edit_cut", "activate", chat_window_cut_activate_cb,
1991                               "menu_edit_copy", "activate", chat_window_copy_activate_cb,
1992                               "menu_edit_paste", "activate", chat_window_paste_activate_cb,
1993                               "menu_edit_find", "activate", chat_window_find_activate_cb,
1994                               "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
1995                               "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
1996                               "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
1997                               "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
1998                               "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
1999                               "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2000                               "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2001                               "menu_help_about", "activate", chat_window_help_about_activate_cb,
2002                               NULL);
2003
2004         g_object_ref (priv->ui_manager);
2005         g_object_unref (gui);
2006
2007         priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2008         priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2009         priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2010         priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2011
2012         priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2013
2014         priv->notebook = gtk_notebook_new ();
2015
2016         g_signal_connect (priv->notebook, "create-window",
2017                 G_CALLBACK (notebook_create_window_cb), window);
2018
2019         gtk_notebook_set_group_name (GTK_NOTEBOOK (priv->notebook),
2020                 "EmpathyChatWindow");
2021         gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
2022         gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
2023         gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
2024         gtk_widget_show (priv->notebook);
2025
2026         /* Set up accels */
2027         accel_group = gtk_accel_group_new ();
2028         gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
2029
2030         for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
2031                 closure =  g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
2032                                            window,
2033                                            NULL);
2034                 gtk_accel_group_connect (accel_group,
2035                                          tab_accel_keys[i],
2036                                          GDK_MOD1_MASK,
2037                                          0,
2038                                          closure);
2039         }
2040
2041         g_object_unref (accel_group);
2042
2043         /* Set up drag target lists */
2044         priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2045                                                      G_N_ELEMENTS (drag_types_dest_contact));
2046         priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2047                                                   G_N_ELEMENTS (drag_types_dest_file));
2048
2049         /* Set up smiley menu */
2050         smiley_manager = empathy_smiley_manager_dup_singleton ();
2051         submenu = empathy_smiley_menu_new (smiley_manager,
2052                                            chat_window_insert_smiley_activate_cb,
2053                                            window);
2054         menu = gtk_ui_manager_get_widget (priv->ui_manager,
2055                 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2056         gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2057         g_object_unref (smiley_manager);
2058
2059         /* Set up signals we can't do with ui file since we may need to
2060          * block/unblock them at some later stage.
2061          */
2062
2063         g_signal_connect (priv->dialog,
2064                           "delete_event",
2065                           G_CALLBACK (chat_window_delete_event_cb),
2066                           window);
2067         g_signal_connect (priv->dialog,
2068                           "focus_in_event",
2069                           G_CALLBACK (chat_window_focus_in_event_cb),
2070                           window);
2071         g_signal_connect_after (priv->notebook,
2072                                 "switch_page",
2073                                 G_CALLBACK (chat_window_page_switched_cb),
2074                                 window);
2075         g_signal_connect (priv->notebook,
2076                           "page_added",
2077                           G_CALLBACK (chat_window_page_added_cb),
2078                           window);
2079         g_signal_connect (priv->notebook,
2080                           "page_removed",
2081                           G_CALLBACK (chat_window_page_removed_cb),
2082                           window);
2083
2084         /* Set up drag and drop */
2085         gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
2086                            GTK_DEST_DEFAULT_HIGHLIGHT,
2087                            drag_types_dest,
2088                            G_N_ELEMENTS (drag_types_dest),
2089                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
2090
2091         /* connect_after to allow GtkNotebook's built-in tab switching */
2092         g_signal_connect_after (priv->notebook,
2093                                 "drag-motion",
2094                                 G_CALLBACK (chat_window_drag_motion),
2095                                 window);
2096         g_signal_connect (priv->notebook,
2097                           "drag-data-received",
2098                           G_CALLBACK (chat_window_drag_data_received),
2099                           window);
2100         g_signal_connect (priv->notebook,
2101                           "drag-drop",
2102                           G_CALLBACK (chat_window_drag_drop),
2103                           window);
2104
2105         chat_windows = g_list_prepend (chat_windows, window);
2106
2107         /* Set up private details */
2108         priv->chats = NULL;
2109         priv->current_chat = NULL;
2110         priv->notification = NULL;
2111
2112         priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2113
2114         priv->chat_manager = empathy_chat_manager_dup_singleton ();
2115         priv->chat_manager_chats_changed_id =
2116                 g_signal_connect (priv->chat_manager, "closed-chats-changed",
2117                                   G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2118                                   window);
2119
2120         chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2121                                                    empathy_chat_manager_get_num_closed_chats (priv->chat_manager),
2122                                                    window);
2123 }
2124
2125 static GtkWidget *
2126 empathy_chat_window_get_dialog (EmpathyChatWindow *window)
2127 {
2128         EmpathyChatWindowPriv *priv;
2129
2130         g_return_val_if_fail (window != NULL, NULL);
2131
2132         priv = GET_PRIV (window);
2133
2134         return priv->dialog;
2135 }
2136
2137 /* Returns the window to open a new tab in if there is a suitable window,
2138  * otherwise, returns NULL indicating that a new window should be added.
2139  */
2140 static EmpathyChatWindow *
2141 empathy_chat_window_get_default (gboolean room)
2142 {
2143         GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2144         GList    *l;
2145         gboolean  separate_windows = TRUE;
2146
2147         separate_windows = g_settings_get_boolean (gsettings,
2148                         EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2149
2150         g_object_unref (gsettings);
2151
2152         if (separate_windows) {
2153                 /* Always create a new window */
2154                 return NULL;
2155         }
2156
2157         for (l = chat_windows; l; l = l->next) {
2158                 EmpathyChatWindowPriv *priv;
2159                 EmpathyChatWindow *chat_window;
2160                 GtkWidget         *dialog;
2161                 guint nb_rooms, nb_private;
2162
2163                 chat_window = l->data;
2164                 priv = GET_PRIV (chat_window);
2165
2166                 dialog = empathy_chat_window_get_dialog (chat_window);
2167
2168                 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2169
2170                 /* Skip the window if there aren't any rooms in it */
2171                 if (room && nb_rooms == 0)
2172                         continue;
2173
2174                 /* Skip the window if there aren't any 1-1 chats in it */
2175                 if (!room && nb_private == 0)
2176                         continue;
2177
2178                 /* Found a window on this desktop, make it visible if necessary */
2179                 if (!empathy_window_get_is_visible (GTK_WINDOW (dialog)))
2180                         empathy_window_present (GTK_WINDOW (dialog));
2181                 return chat_window;
2182         }
2183
2184         return NULL;
2185 }
2186
2187 static void
2188 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2189                               EmpathyChat       *chat)
2190 {
2191         EmpathyChatWindowPriv *priv;
2192         GtkWidget             *label;
2193         GtkWidget             *popup_label;
2194         GtkWidget             *child;
2195         GValue                value = { 0, };
2196
2197         g_return_if_fail (window != NULL);
2198         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2199
2200         priv = GET_PRIV (window);
2201
2202         /* Reference the chat object */
2203         g_object_ref (chat);
2204
2205         /* If this window has just been created, position it */
2206         if (priv->chats == NULL) {
2207                 const gchar *name = "chat-window";
2208                 gboolean     separate_windows;
2209
2210                 separate_windows = g_settings_get_boolean (priv->gsettings_ui,
2211                                 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2212
2213                 if (empathy_chat_is_room (chat))
2214                         name = "room-window";
2215
2216                 if (separate_windows) {
2217                         gint x, y;
2218
2219                         /* Save current position of the window */
2220                         gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
2221
2222                         /* First bind to the 'generic' name. So new window for which we didn't
2223                         * save a geometry yet will have the geometry of the last saved
2224                         * window (bgo #601191). */
2225                         empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2226
2227                         /* Restore previous position of the window so the newly created window
2228                         * won't be in the same position as the latest saved window and so
2229                         * completely hide it. */
2230                         gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
2231
2232                         /* Then bind it to the name of the contact/room so we'll save the
2233                         * geometry specific to this window */
2234                         name = empathy_chat_get_id (chat);
2235                 }
2236
2237                 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2238         }
2239
2240         child = GTK_WIDGET (chat);
2241         label = chat_window_create_label (window, chat, TRUE);
2242         popup_label = chat_window_create_label (window, chat, FALSE);
2243         gtk_widget_show (child);
2244
2245         g_signal_connect (chat, "notify::name",
2246                           G_CALLBACK (chat_window_chat_notify_cb),
2247                           NULL);
2248         g_signal_connect (chat, "notify::subject",
2249                           G_CALLBACK (chat_window_chat_notify_cb),
2250                           NULL);
2251         g_signal_connect (chat, "notify::remote-contact",
2252                           G_CALLBACK (chat_window_chat_notify_cb),
2253                           NULL);
2254         chat_window_chat_notify_cb (chat);
2255
2256         gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2257         gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2258         gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2259         g_value_init (&value, G_TYPE_BOOLEAN);
2260         g_value_set_boolean (&value, TRUE);
2261         gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2262                                           child, "tab-expand" , &value);
2263         gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2264                                           child,  "tab-fill" , &value);
2265         g_value_unset (&value);
2266
2267         DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2268 }
2269
2270 static void
2271 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2272                                  EmpathyChat       *chat)
2273 {
2274         EmpathyChatWindowPriv *priv;
2275         gint                   position;
2276         EmpathyContact        *remote_contact;
2277         EmpathyChatManager    *chat_manager;
2278
2279         g_return_if_fail (window != NULL);
2280         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2281
2282         priv = GET_PRIV (window);
2283
2284         g_signal_handlers_disconnect_by_func (chat,
2285                                               chat_window_chat_notify_cb,
2286                                               NULL);
2287         remote_contact = g_object_get_data (G_OBJECT (chat),
2288                                             "chat-window-remote-contact");
2289         if (remote_contact) {
2290                 g_signal_handlers_disconnect_by_func (remote_contact,
2291                                                       chat_window_update_chat_tab,
2292                                                       chat);
2293         }
2294
2295         chat_manager = empathy_chat_manager_dup_singleton ();
2296         empathy_chat_manager_closed_chat (chat_manager, chat);
2297         g_object_unref (chat_manager);
2298
2299         position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2300                                           GTK_WIDGET (chat));
2301         gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2302
2303         DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2304
2305         g_object_unref (chat);
2306 }
2307
2308 static void
2309 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2310                                EmpathyChatWindow *new_window,
2311                                EmpathyChat       *chat)
2312 {
2313         GtkWidget *widget;
2314
2315         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2316         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2317         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2318
2319         widget = GTK_WIDGET (chat);
2320
2321         DEBUG ("Chat moving with widget:%p (%d references)", widget,
2322                 G_OBJECT (widget)->ref_count);
2323
2324         /* We reference here to make sure we don't loose the widget
2325          * and the EmpathyChat object during the move.
2326          */
2327         g_object_ref (chat);
2328         g_object_ref (widget);
2329
2330         empathy_chat_window_remove_chat (old_window, chat);
2331         empathy_chat_window_add_chat (new_window, chat);
2332
2333         g_object_unref (widget);
2334         g_object_unref (chat);
2335 }
2336
2337 static void
2338 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2339                                     EmpathyChat       *chat)
2340 {
2341         EmpathyChatWindowPriv *priv;
2342         gint                  page_num;
2343
2344         g_return_if_fail (window != NULL);
2345         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2346
2347         priv = GET_PRIV (window);
2348
2349         page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2350                                           GTK_WIDGET (chat));
2351         gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2352                                        page_num);
2353 }
2354
2355 EmpathyChat *
2356 empathy_chat_window_find_chat (TpAccount   *account,
2357                                const gchar *id)
2358 {
2359         GList *l;
2360
2361         g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2362
2363         for (l = chat_windows; l; l = l->next) {
2364                 EmpathyChatWindowPriv *priv;
2365                 EmpathyChatWindow     *window;
2366                 GList                *ll;
2367
2368                 window = l->data;
2369                 priv = GET_PRIV (window);
2370
2371                 for (ll = priv->chats; ll; ll = ll->next) {
2372                         EmpathyChat *chat;
2373
2374                         chat = ll->data;
2375
2376                         if (account == empathy_chat_get_account (chat) &&
2377                             !tp_strdiff (id, empathy_chat_get_id (chat))) {
2378                                 return chat;
2379                         }
2380                 }
2381         }
2382
2383         return NULL;
2384 }
2385
2386 void
2387 empathy_chat_window_present_chat (EmpathyChat *chat,
2388                                   gint64 timestamp)
2389 {
2390         EmpathyChatWindow     *window;
2391         EmpathyChatWindowPriv *priv;
2392         guint32 x_timestamp;
2393
2394         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2395
2396         window = chat_window_find_chat (chat);
2397
2398         /* If the chat has no window, create one */
2399         if (window == NULL) {
2400                 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2401                 if (!window) {
2402                         window = empathy_chat_window_new ();
2403                 }
2404
2405                 empathy_chat_window_add_chat (window, chat);
2406         }
2407
2408         /* Don't force the window to show itself when it wasn't
2409          * an action by the user
2410          */
2411         if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2412                 return;
2413
2414         priv = GET_PRIV (window);
2415
2416         if (x_timestamp != GDK_CURRENT_TIME) {
2417                 /* Don't present or switch tab if the action was earlier than the
2418                  * last actions X time, accounting for overflow and the first ever
2419                 * presentation */
2420
2421                 if (priv->x_user_action_time != 0
2422                         && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2423                         return;
2424
2425                 priv->x_user_action_time = x_timestamp;
2426         }
2427
2428         empathy_chat_window_switch_to_chat (window, chat);
2429         empathy_window_present_with_time (GTK_WINDOW (priv->dialog),
2430           x_timestamp);
2431
2432         gtk_widget_grab_focus (chat->input_text_view);
2433 }
2434
2435 static void
2436 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2437                                guint *nb_rooms,
2438                                guint *nb_private)
2439 {
2440         EmpathyChatWindowPriv *priv = GET_PRIV (self);
2441         GList *l;
2442         guint _nb_rooms = 0, _nb_private = 0;
2443
2444         for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2445                 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2446                         _nb_rooms++;
2447                 else
2448                         _nb_private++;
2449         }
2450
2451         if (nb_rooms != NULL)
2452                 *nb_rooms = _nb_rooms;
2453         if (nb_private != NULL)
2454                 *nb_private = _nb_private;
2455 }