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