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