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