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