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