]> git.0d.be Git - empathy.git/blob - src/empathy-chat-window.c
Don't unhighlight chat tabs when more messages are received
[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         markup = g_string_free (tooltip, FALSE);
924         widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget");
925         gtk_widget_set_tooltip_markup (widget, markup);
926         widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-tooltip-widget");
927         gtk_widget_set_tooltip_markup (widget, markup);
928         g_free (markup);
929
930         /* Update tab and menu label */
931         if (empathy_chat_is_highlighted (chat)) {
932                 markup = g_markup_printf_escaped (
933                         "<span color=\"red\" weight=\"bold\">%s</span>",
934                         name);
935         } else {
936                 markup = g_markup_escape_text (name, -1);
937         }
938
939         widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
940         gtk_label_set_markup (GTK_LABEL (widget), markup);
941         widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
942         gtk_label_set_markup (GTK_LABEL (widget), markup);
943         g_free (markup);
944
945         /* Update the window if it's the current chat */
946         if (priv->current_chat == chat) {
947                 chat_window_update (window, update_contact_menu);
948         }
949
950         g_free (name);
951 }
952
953 static void
954 chat_window_update_chat_tab (EmpathyChat *chat)
955 {
956         chat_window_update_chat_tab_full (chat, TRUE);
957 }
958
959 static void
960 chat_window_chat_notify_cb (EmpathyChat *chat)
961 {
962         EmpathyChatWindow *window;
963         EmpathyContact *old_remote_contact;
964         EmpathyContact *remote_contact = NULL;
965
966         old_remote_contact = g_object_get_data (G_OBJECT (chat), "chat-window-remote-contact");
967         remote_contact = empathy_chat_get_remote_contact (chat);
968
969         if (old_remote_contact != remote_contact) {
970                 /* The remote-contact associated with the chat changed, we need
971                  * to keep track of any change of that contact and update the
972                  * window each time. */
973                 if (remote_contact) {
974                         g_signal_connect_swapped (remote_contact, "notify",
975                                                   G_CALLBACK (chat_window_update_chat_tab),
976                                                   chat);
977                 }
978                 if (old_remote_contact) {
979                         g_signal_handlers_disconnect_by_func (old_remote_contact,
980                                                               chat_window_update_chat_tab,
981                                                               chat);
982                 }
983
984                 g_object_set_data_full (G_OBJECT (chat), "chat-window-remote-contact",
985                                    g_object_ref (remote_contact), (GDestroyNotify) g_object_unref);
986         }
987
988         chat_window_update_chat_tab (chat);
989
990         window = chat_window_find_chat (chat);
991         if (window != NULL) {
992                 chat_window_update (window, FALSE);
993         }
994 }
995
996 static void
997 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
998                                        EmpathySmiley        *smiley,
999                                        gpointer              window)
1000 {
1001         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1002         EmpathyChat           *chat;
1003         GtkTextBuffer         *buffer;
1004         GtkTextIter            iter;
1005
1006         chat = priv->current_chat;
1007
1008         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
1009         gtk_text_buffer_get_end_iter (buffer, &iter);
1010         gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
1011 }
1012
1013 static void
1014 chat_window_conv_activate_cb (GtkAction         *action,
1015                               EmpathyChatWindow *window)
1016 {
1017         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1018         gboolean               is_room;
1019         gboolean               active;
1020         EmpathyContact        *remote_contact = NULL;
1021
1022         /* Favorite room menu */
1023         is_room = empathy_chat_is_room (priv->current_chat);
1024         if (is_room) {
1025                 const gchar *room;
1026                 TpAccount   *account;
1027                 gboolean     found = FALSE;
1028                 EmpathyChatroom *chatroom;
1029
1030                 room = empathy_chat_get_id (priv->current_chat);
1031                 account = empathy_chat_get_account (priv->current_chat);
1032                 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1033                                                        account, room);
1034                 if (chatroom != NULL)
1035                         found = empathy_chatroom_is_favorite (chatroom);
1036
1037                 DEBUG ("This room %s favorite", found ? "is" : "is not");
1038                 gtk_toggle_action_set_active (
1039                         GTK_TOGGLE_ACTION (priv->menu_conv_favorite), found);
1040
1041                 if (chatroom != NULL)
1042                         found = empathy_chatroom_is_always_urgent (chatroom);
1043
1044                 gtk_toggle_action_set_active (
1045                         GTK_TOGGLE_ACTION (priv->menu_conv_always_urgent),
1046                         found);
1047         }
1048         gtk_action_set_visible (priv->menu_conv_favorite, is_room);
1049         gtk_action_set_visible (priv->menu_conv_always_urgent, is_room);
1050
1051         /* Show contacts menu */
1052         g_object_get (priv->current_chat,
1053                       "remote-contact", &remote_contact,
1054                       "show-contacts", &active,
1055                       NULL);
1056         if (remote_contact == NULL) {
1057                 gtk_toggle_action_set_active (
1058                         GTK_TOGGLE_ACTION (priv->menu_conv_toggle_contacts),
1059                                            active);
1060         }
1061         gtk_action_set_visible (priv->menu_conv_toggle_contacts,
1062                                 (remote_contact == NULL));
1063         if (remote_contact != NULL) {
1064                 g_object_unref (remote_contact);
1065         }
1066 }
1067
1068 static void
1069 chat_window_clear_activate_cb (GtkAction         *action,
1070                                EmpathyChatWindow *window)
1071 {
1072         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1073
1074         empathy_chat_clear (priv->current_chat);
1075 }
1076
1077 static void
1078 chat_window_favorite_toggled_cb (GtkToggleAction   *toggle_action,
1079                                  EmpathyChatWindow *window)
1080 {
1081         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1082         gboolean               active;
1083         TpAccount             *account;
1084         gchar                 *name;
1085         const gchar           *room;
1086         EmpathyChatroom       *chatroom;
1087
1088         active = gtk_toggle_action_get_active (toggle_action);
1089         account = empathy_chat_get_account (priv->current_chat);
1090         room = empathy_chat_get_id (priv->current_chat);
1091         name = empathy_chat_dup_name (priv->current_chat);
1092
1093         chatroom = empathy_chatroom_manager_ensure_chatroom (
1094                      priv->chatroom_manager,
1095                      account,
1096                      room,
1097                      name);
1098
1099         empathy_chatroom_set_favorite (chatroom, active);
1100         g_object_unref (chatroom);
1101         g_free (name);
1102 }
1103
1104 static void
1105 chat_window_always_urgent_toggled_cb (GtkToggleAction   *toggle_action,
1106                                  EmpathyChatWindow *window)
1107 {
1108         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1109         gboolean               active;
1110         TpAccount             *account;
1111         gchar                 *name;
1112         const gchar           *room;
1113         EmpathyChatroom       *chatroom;
1114
1115         active = gtk_toggle_action_get_active (toggle_action);
1116         account = empathy_chat_get_account (priv->current_chat);
1117         room = empathy_chat_get_id (priv->current_chat);
1118         name = empathy_chat_dup_name (priv->current_chat);
1119
1120         chatroom = empathy_chatroom_manager_ensure_chatroom (
1121                      priv->chatroom_manager,
1122                      account,
1123                      room,
1124                      name);
1125
1126         empathy_chatroom_set_always_urgent (chatroom, active);
1127         g_object_unref (chatroom);
1128         g_free (name);
1129 }
1130
1131 static void
1132 chat_window_contacts_toggled_cb (GtkToggleAction   *toggle_action,
1133                                  EmpathyChatWindow *window)
1134 {
1135         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1136         gboolean               active;
1137
1138         active = gtk_toggle_action_get_active (toggle_action);
1139
1140         empathy_chat_set_show_contacts (priv->current_chat, active);
1141 }
1142
1143 static void
1144 chat_window_invite_participant_activate_cb (GtkAction         *action,
1145                                             EmpathyChatWindow *window)
1146 {
1147         EmpathyChatWindowPriv *priv;
1148         GtkWidget             *dialog;
1149         EmpathyTpChat         *tp_chat;
1150         int                    response;
1151
1152         priv = GET_PRIV (window);
1153
1154         g_return_if_fail (priv->current_chat != NULL);
1155
1156         tp_chat = empathy_chat_get_tp_chat (priv->current_chat);
1157
1158         dialog = empathy_invite_participant_dialog_new (
1159                         GTK_WINDOW (priv->dialog), tp_chat);
1160         gtk_widget_show (dialog);
1161
1162         response = gtk_dialog_run (GTK_DIALOG (dialog));
1163
1164         if (response == GTK_RESPONSE_ACCEPT) {
1165                 TpContact *tp_contact;
1166                 EmpathyContact *contact;
1167
1168                 tp_contact = empathy_invite_participant_dialog_get_selected (
1169                         EMPATHY_INVITE_PARTICIPANT_DIALOG (dialog));
1170                 if (tp_contact == NULL) goto out;
1171
1172                 contact = empathy_contact_dup_from_tp_contact (tp_contact);
1173
1174                 empathy_contact_list_add (EMPATHY_CONTACT_LIST (tp_chat),
1175                                 contact, _("Inviting you to this room"));
1176
1177                 g_object_unref (contact);
1178         }
1179
1180 out:
1181         gtk_widget_destroy (dialog);
1182 }
1183
1184 static void
1185 chat_window_close_activate_cb (GtkAction         *action,
1186                                EmpathyChatWindow *window)
1187 {
1188         EmpathyChatWindowPriv *priv;
1189
1190         priv = GET_PRIV (window);
1191
1192         g_return_if_fail (priv->current_chat != NULL);
1193
1194         maybe_close_chat (window, priv->current_chat);
1195 }
1196
1197 static void
1198 chat_window_edit_activate_cb (GtkAction         *action,
1199                               EmpathyChatWindow *window)
1200 {
1201         EmpathyChatWindowPriv *priv;
1202         GtkClipboard         *clipboard;
1203         GtkTextBuffer        *buffer;
1204         gboolean              text_available;
1205
1206         priv = GET_PRIV (window);
1207
1208         g_return_if_fail (priv->current_chat != NULL);
1209
1210         if (!empathy_chat_get_tp_chat (priv->current_chat)) {
1211                 gtk_action_set_sensitive (priv->menu_edit_copy, FALSE);
1212                 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1213                 gtk_action_set_sensitive (priv->menu_edit_paste, FALSE);
1214                 return;
1215         }
1216
1217         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
1218         if (gtk_text_buffer_get_has_selection (buffer)) {
1219                 gtk_action_set_sensitive (priv->menu_edit_copy, TRUE);
1220                 gtk_action_set_sensitive (priv->menu_edit_cut, TRUE);
1221         } else {
1222                 gboolean selection;
1223
1224                 selection = empathy_chat_view_get_has_selection (priv->current_chat->view);
1225
1226                 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
1227                 gtk_action_set_sensitive (priv->menu_edit_copy, selection);
1228         }
1229
1230         clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1231         text_available = gtk_clipboard_wait_is_text_available (clipboard);
1232         gtk_action_set_sensitive (priv->menu_edit_paste, text_available);
1233 }
1234
1235 static void
1236 chat_window_cut_activate_cb (GtkAction         *action,
1237                              EmpathyChatWindow *window)
1238 {
1239         EmpathyChatWindowPriv *priv;
1240
1241         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
1242
1243         priv = GET_PRIV (window);
1244
1245         empathy_chat_cut (priv->current_chat);
1246 }
1247
1248 static void
1249 chat_window_copy_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_copy (priv->current_chat);
1259 }
1260
1261 static void
1262 chat_window_paste_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_paste (priv->current_chat);
1272 }
1273
1274 static void
1275 chat_window_find_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_find (priv->current_chat);
1285 }
1286
1287 static void
1288 chat_window_tabs_next_activate_cb (GtkAction         *action,
1289                                    EmpathyChatWindow *window)
1290 {
1291         EmpathyChatWindowPriv *priv;
1292         gint                  index_, numPages;
1293         gboolean              wrap_around;
1294
1295         priv = GET_PRIV (window);
1296
1297         g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1298                       &wrap_around, NULL);
1299
1300         index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1301         numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1302
1303         if (index_ == (numPages - 1) && wrap_around) {
1304                 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
1305                 return;
1306         }
1307
1308         gtk_notebook_next_page (GTK_NOTEBOOK (priv->notebook));
1309 }
1310
1311 static void
1312 chat_window_tabs_previous_activate_cb (GtkAction         *action,
1313                                    EmpathyChatWindow *window)
1314 {
1315         EmpathyChatWindowPriv *priv;
1316         gint                  index_, numPages;
1317         gboolean              wrap_around;
1318
1319         priv = GET_PRIV (window);
1320
1321         g_object_get (gtk_settings_get_default (), "gtk-keynav-wrap-around",
1322                       &wrap_around, NULL);
1323
1324         index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1325         numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1326
1327         if (index_ <= 0 && wrap_around) {
1328                 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
1329                 return;
1330         }
1331
1332         gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
1333 }
1334
1335 static void
1336 chat_window_tabs_undo_close_tab_activate_cb (GtkAction         *action,
1337                                              EmpathyChatWindow *window)
1338 {
1339         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1340         empathy_chat_manager_undo_closed_chat (priv->chat_manager,
1341                                                empathy_get_current_action_time ());
1342 }
1343
1344 static void
1345 chat_window_tabs_left_activate_cb (GtkAction         *action,
1346                                    EmpathyChatWindow *window)
1347 {
1348         EmpathyChatWindowPriv *priv;
1349         EmpathyChat           *chat;
1350         gint                  index_, num_pages;
1351
1352         priv = GET_PRIV (window);
1353
1354         chat = priv->current_chat;
1355         index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1356         if (index_ <= 0) {
1357                 return;
1358         }
1359
1360         gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1361                                     GTK_WIDGET (chat),
1362                                     index_ - 1);
1363
1364         num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1365         chat_window_menu_context_update (priv, num_pages);
1366 }
1367
1368 static void
1369 chat_window_tabs_right_activate_cb (GtkAction         *action,
1370                                     EmpathyChatWindow *window)
1371 {
1372         EmpathyChatWindowPriv *priv;
1373         EmpathyChat           *chat;
1374         gint                  index_, num_pages;
1375
1376         priv = GET_PRIV (window);
1377
1378         chat = priv->current_chat;
1379         index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
1380
1381         gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
1382                                     GTK_WIDGET (chat),
1383                                     index_ + 1);
1384
1385         num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
1386         chat_window_menu_context_update (priv, num_pages);
1387 }
1388
1389 static EmpathyChatWindow *
1390 empathy_chat_window_new (void)
1391 {
1392         return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1393 }
1394
1395 static void
1396 chat_window_detach_activate_cb (GtkAction         *action,
1397                                 EmpathyChatWindow *window)
1398 {
1399         EmpathyChatWindowPriv *priv;
1400         EmpathyChatWindow     *new_window;
1401         EmpathyChat           *chat;
1402
1403         priv = GET_PRIV (window);
1404
1405         chat = priv->current_chat;
1406         new_window = empathy_chat_window_new ();
1407
1408         empathy_chat_window_move_chat (window, new_window, chat);
1409
1410         priv = GET_PRIV (new_window);
1411         gtk_widget_show (priv->dialog);
1412 }
1413
1414 static void
1415 chat_window_help_contents_activate_cb (GtkAction         *action,
1416                                        EmpathyChatWindow *window)
1417 {
1418         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1419
1420         empathy_url_show (priv->dialog, "ghelp:empathy");
1421 }
1422
1423 static void
1424 chat_window_help_about_activate_cb (GtkAction         *action,
1425                                     EmpathyChatWindow *window)
1426 {
1427         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1428
1429         empathy_about_dialog_new (GTK_WINDOW (priv->dialog));
1430 }
1431
1432 static gboolean
1433 chat_window_delete_event_cb (GtkWidget        *dialog,
1434                              GdkEvent         *event,
1435                              EmpathyChatWindow *window)
1436 {
1437         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1438         EmpathyChat *chat = NULL;
1439         guint n_rooms = 0;
1440         GList *l;
1441
1442         DEBUG ("Delete event received");
1443
1444         for (l = priv->chats; l != NULL; l = l->next) {
1445                 if (chat_needs_close_confirmation (l->data)) {
1446                         chat = l->data;
1447                         n_rooms++;
1448                 }
1449         }
1450
1451         if (n_rooms > 0) {
1452                 confirm_close (window, TRUE, n_rooms,
1453                         (n_rooms == 1 ? chat : NULL));
1454         } else {
1455                 remove_all_chats (window);
1456         }
1457
1458         return TRUE;
1459 }
1460
1461 static void
1462 chat_window_composing_cb (EmpathyChat       *chat,
1463                           gboolean          is_composing,
1464                           EmpathyChatWindow *window)
1465 {
1466         chat_window_update_chat_tab (chat);
1467 }
1468
1469 static void
1470 chat_window_set_urgency_hint (EmpathyChatWindow *window,
1471                               gboolean          urgent)
1472 {
1473         EmpathyChatWindowPriv *priv;
1474
1475         priv = GET_PRIV (window);
1476
1477         gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
1478 }
1479
1480 static void
1481 chat_window_notification_closed_cb (NotifyNotification *notify,
1482                                     EmpathyChatWindow *self)
1483 {
1484         EmpathyChatWindowPriv *priv = GET_PRIV (self);
1485
1486         g_object_unref (notify);
1487         if (priv->notification == notify) {
1488                 priv->notification = NULL;
1489         }
1490 }
1491
1492 static void
1493 chat_window_show_or_update_notification (EmpathyChatWindow *window,
1494                                          EmpathyMessage *message,
1495                                          EmpathyChat *chat)
1496 {
1497         EmpathyContact *sender;
1498         const gchar *header;
1499         char *escaped;
1500         const char *body;
1501         GdkPixbuf *pixbuf;
1502         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1503         gboolean res, has_x_canonical_append;
1504         NotifyNotification *notification = priv->notification;
1505
1506         if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
1507                 return;
1508         } else {
1509                 res = g_settings_get_boolean (priv->gsettings_notif,
1510                                 EMPATHY_PREFS_NOTIFICATIONS_FOCUS);
1511
1512                 if (!res) {
1513                         return;
1514                 }
1515         }
1516
1517         sender = empathy_message_get_sender (message);
1518         header = empathy_contact_get_alias (sender);
1519         body = empathy_message_get_body (message);
1520         escaped = g_markup_escape_text (body, -1);
1521         has_x_canonical_append = empathy_notify_manager_has_capability (
1522                 priv->notify_mgr, EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND);
1523
1524         if (notification != NULL && !has_x_canonical_append) {
1525                 /* if the notification server supports x-canonical-append, it is
1526                    better to not use notify_notification_update to avoid
1527                    overwriting the current notification message */
1528                 notify_notification_update (notification,
1529                                             header, escaped, NULL);
1530         } else {
1531                 /* if the notification server supports x-canonical-append,
1532                    the hint will be added, so that the message from the
1533                    just created notification will be automatically appended
1534                    to an existing notification with the same title.
1535                    In this way the previous message will not be lost: the new
1536                    message will appear below it, in the same notification */
1537                 notification = notify_notification_new (header, escaped, NULL);
1538
1539                 if (priv->notification == NULL) {
1540                         priv->notification = notification;
1541                 }
1542
1543                 notify_notification_set_timeout (notification, NOTIFY_EXPIRES_DEFAULT);
1544
1545                 tp_g_signal_connect_object (notification, "closed",
1546                                   G_CALLBACK (chat_window_notification_closed_cb), window, 0);
1547
1548                 if (has_x_canonical_append) {
1549                         /* We have to set a not empty string to keep libnotify happy */
1550                         notify_notification_set_hint_string (notification,
1551                                 EMPATHY_NOTIFY_MANAGER_CAP_X_CANONICAL_APPEND, "1");
1552                 }
1553
1554                 notify_notification_set_hint (notification,
1555                         EMPATHY_NOTIFY_MANAGER_CAP_CATEGORY,
1556                         g_variant_new_string ("im.received"));
1557         }
1558
1559         pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
1560                 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1561
1562         if (pixbuf != NULL) {
1563                 notify_notification_set_icon_from_pixbuf (notification, pixbuf);
1564                 g_object_unref (pixbuf);
1565         }
1566
1567         notify_notification_show (notification, NULL);
1568
1569         g_free (escaped);
1570 }
1571
1572 static gboolean
1573 empathy_chat_window_has_focus (EmpathyChatWindow *window)
1574 {
1575         EmpathyChatWindowPriv *priv;
1576         gboolean              has_focus;
1577
1578         g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
1579
1580         priv = GET_PRIV (window);
1581
1582         g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
1583
1584         return has_focus;
1585 }
1586
1587 static void
1588 chat_window_new_message_cb (EmpathyChat       *chat,
1589                             EmpathyMessage    *message,
1590                             gboolean pending,
1591                             gboolean should_highlight,
1592                             EmpathyChatWindow *window)
1593 {
1594         EmpathyChatWindowPriv *priv;
1595         gboolean              has_focus;
1596         gboolean              needs_urgency;
1597         EmpathyContact        *sender;
1598
1599         priv = GET_PRIV (window);
1600
1601         has_focus = empathy_chat_window_has_focus (window);
1602
1603         /* - if we're the sender, we play the sound if it's specified in the
1604          *   preferences and we're not away.
1605          * - if we receive a message, we play the sound if it's specified in the
1606          *   preferences and the window does not have focus on the chat receiving
1607          *   the message.
1608          */
1609
1610         sender = empathy_message_get_sender (message);
1611
1612         if (empathy_contact_is_user (sender)) {
1613                 empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1614                                     EMPATHY_SOUND_MESSAGE_OUTGOING);
1615         }
1616
1617         if (has_focus && priv->current_chat == chat) {
1618                 /* window and tab are focused so consider the message to be read */
1619
1620                 /* FIXME: see Bug#610994 and coments about it in EmpathyChatPriv */
1621                 empathy_chat_messages_read (chat);
1622                 return;
1623         }
1624
1625         /* Update the chat tab if this is the first unread message */
1626         if (empathy_chat_get_nb_unread_messages (chat) == 1) {
1627                 chat_window_update_chat_tab (chat);
1628         }
1629
1630         /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1631          * If empathy_chat_get_remote_contact () returns NULL, that means it's
1632          * an unamed MUC (msn-like).
1633          * In case of a MUC, we set urgency if either:
1634          *   a) the chatroom's always_urgent property is TRUE
1635          *   b) the message contains our alias
1636          */
1637         if (empathy_chat_is_room (chat)) {
1638                 TpAccount             *account;
1639                 const gchar           *room;
1640                 EmpathyChatroom       *chatroom;
1641
1642                 account = empathy_chat_get_account (chat);
1643                 room = empathy_chat_get_id (chat);
1644
1645                 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
1646                                                           account, room);
1647
1648                 if (chatroom != NULL && empathy_chatroom_is_always_urgent (chatroom)) {
1649                         needs_urgency = TRUE;
1650                 } else {
1651                         needs_urgency = should_highlight;
1652                 }
1653         } else {
1654                 needs_urgency = TRUE;
1655         }
1656
1657         if (needs_urgency) {
1658                 if (!has_focus) {
1659                         chat_window_set_urgency_hint (window, TRUE);
1660                 }
1661
1662                 /* Pending messages have already been displayed and notified in the
1663                 * approver, so we don't display a notification and play a sound for those */
1664                 if (!pending) {
1665                         empathy_sound_manager_play (priv->sound_mgr, GTK_WIDGET (priv->dialog),
1666                                     EMPATHY_SOUND_MESSAGE_INCOMING);
1667
1668                         chat_window_show_or_update_notification (window, message, chat);
1669                 }
1670         }
1671
1672         /* update the number of unread messages and the window icon */
1673         chat_window_title_update (priv);
1674         chat_window_icon_update (priv, TRUE);
1675 }
1676
1677 static void
1678 chat_window_command_part (EmpathyChat *chat,
1679                            GStrv        strv)
1680 {
1681         EmpathyChat *chat_to_be_parted;
1682         EmpathyTpChat *tp_chat = NULL;
1683
1684         if (strv[1] == NULL) {
1685                 /* No chatroom ID specified  */
1686                 tp_chat = empathy_chat_get_tp_chat (chat);
1687                 if (tp_chat)
1688                         empathy_tp_chat_leave (tp_chat, "");
1689                 return;
1690         }
1691         chat_to_be_parted = empathy_chat_window_find_chat (
1692                 empathy_chat_get_account (chat), strv[1], FALSE);
1693
1694         if (chat_to_be_parted != NULL) {
1695                 /* Found a chatroom matching the specified ID */
1696                 tp_chat = empathy_chat_get_tp_chat (chat_to_be_parted);
1697                 if (tp_chat)
1698                         empathy_tp_chat_leave (tp_chat, strv[2]);
1699         } else {
1700                 gchar *message;
1701
1702                 /* Going by the syntax of PART command:
1703                  *
1704                  * /PART [<chatroom-ID>] [<reason>]
1705                  *
1706                  * Chatroom-ID is not a must to specify a reason.
1707                  * If strv[1] (chatroom-ID) is not a valid identifier for a connected
1708                  * MUC then the current chatroom should be parted and srtv[1] should
1709                  * be treated as part of the optional part-message. */
1710                 message = g_strconcat (strv[1], " ", strv[2], NULL);
1711                 tp_chat = empathy_chat_get_tp_chat (chat);
1712                 if (tp_chat)
1713                         empathy_tp_chat_leave (tp_chat, message);
1714
1715                 g_free (message);
1716         }
1717 }
1718
1719 static GtkNotebook *
1720 notebook_create_window_cb (GtkNotebook *source,
1721                          GtkWidget   *page,
1722                          gint         x,
1723                          gint         y,
1724                          gpointer     user_data)
1725 {
1726         EmpathyChatWindowPriv *priv;
1727         EmpathyChatWindow     *window, *new_window;
1728         EmpathyChat           *chat;
1729
1730         chat = EMPATHY_CHAT (page);
1731         window = chat_window_find_chat (chat);
1732
1733         new_window = empathy_chat_window_new ();
1734         priv = GET_PRIV (new_window);
1735
1736         DEBUG ("Detach hook called");
1737
1738         empathy_chat_window_move_chat (window, new_window, chat);
1739
1740         gtk_widget_show (priv->dialog);
1741         gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1742
1743         return NULL;
1744 }
1745
1746 static void
1747 chat_window_page_switched_cb (GtkNotebook      *notebook,
1748                               GtkWidget         *child,
1749                               gint              page_num,
1750                               EmpathyChatWindow *window)
1751 {
1752         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1753         EmpathyChat           *chat = EMPATHY_CHAT (child);
1754
1755         DEBUG ("Page switched");
1756
1757         if (priv->page_added) {
1758                 priv->page_added = FALSE;
1759                 empathy_chat_scroll_down (chat);
1760         }
1761         else if (priv->current_chat == chat) {
1762                 return;
1763         }
1764
1765         priv->current_chat = chat;
1766         empathy_chat_messages_read (chat);
1767
1768         chat_window_update_chat_tab (chat);
1769 }
1770
1771 static void
1772 chat_window_page_added_cb (GtkNotebook      *notebook,
1773                            GtkWidget        *child,
1774                            guint             page_num,
1775                            EmpathyChatWindow *window)
1776 {
1777         EmpathyChatWindowPriv *priv;
1778         EmpathyChat           *chat;
1779
1780         priv = GET_PRIV (window);
1781
1782         /* If we just received DND to the same window, we don't want
1783          * to do anything here like removing the tab and then readding
1784          * it, so we return here and in "page-added".
1785          */
1786         if (priv->dnd_same_window) {
1787                 DEBUG ("Page added (back to the same window)");
1788                 priv->dnd_same_window = FALSE;
1789                 return;
1790         }
1791
1792         DEBUG ("Page added");
1793
1794         /* Get chat object */
1795         chat = EMPATHY_CHAT (child);
1796
1797         /* Connect chat signals for this window */
1798         g_signal_connect (chat, "composing",
1799                           G_CALLBACK (chat_window_composing_cb),
1800                           window);
1801         g_signal_connect (chat, "new-message",
1802                           G_CALLBACK (chat_window_new_message_cb),
1803                           window);
1804         g_signal_connect (chat, "part-command-entered",
1805                           G_CALLBACK (chat_window_command_part),
1806                           NULL);
1807         g_signal_connect (chat, "notify::tp-chat",
1808                           G_CALLBACK (chat_window_update_chat_tab),
1809                           window);
1810
1811         /* Set flag so we know to perform some special operations on
1812          * switch page due to the new page being added.
1813          */
1814         priv->page_added = TRUE;
1815
1816         /* Get list of chats up to date */
1817         priv->chats = g_list_append (priv->chats, chat);
1818
1819         chat_window_update_chat_tab (chat);
1820 }
1821
1822 static void
1823 chat_window_page_removed_cb (GtkNotebook      *notebook,
1824                              GtkWidget        *child,
1825                              guint             page_num,
1826                              EmpathyChatWindow *window)
1827 {
1828         EmpathyChatWindowPriv *priv;
1829         EmpathyChat           *chat;
1830
1831         priv = GET_PRIV (window);
1832
1833         /* If we just received DND to the same window, we don't want
1834          * to do anything here like removing the tab and then readding
1835          * it, so we return here and in "page-added".
1836          */
1837         if (priv->dnd_same_window) {
1838                 DEBUG ("Page removed (and will be readded to same window)");
1839                 return;
1840         }
1841
1842         DEBUG ("Page removed");
1843
1844         /* Get chat object */
1845         chat = EMPATHY_CHAT (child);
1846
1847         /* Disconnect all signal handlers for this chat and this window */
1848         g_signal_handlers_disconnect_by_func (chat,
1849                                               G_CALLBACK (chat_window_composing_cb),
1850                                               window);
1851         g_signal_handlers_disconnect_by_func (chat,
1852                                               G_CALLBACK (chat_window_new_message_cb),
1853                                               window);
1854         g_signal_handlers_disconnect_by_func (chat,
1855                                               G_CALLBACK (chat_window_update_chat_tab),
1856                                               window);
1857
1858         /* Keep list of chats up to date */
1859         priv->chats = g_list_remove (priv->chats, chat);
1860         empathy_chat_messages_read (chat);
1861
1862         if (priv->chats == NULL) {
1863                 g_object_unref (window);
1864         } else {
1865                 chat_window_update (window, TRUE);
1866         }
1867 }
1868
1869 static gboolean
1870 chat_window_focus_in_event_cb (GtkWidget        *widget,
1871                                GdkEvent         *event,
1872                                EmpathyChatWindow *window)
1873 {
1874         EmpathyChatWindowPriv *priv;
1875
1876         priv = GET_PRIV (window);
1877
1878         empathy_chat_messages_read (priv->current_chat);
1879
1880         chat_window_set_urgency_hint (window, FALSE);
1881
1882         /* Update the title, since we now mark all unread messages as read. */
1883         chat_window_update_chat_tab_full (priv->current_chat, FALSE);
1884
1885         return FALSE;
1886 }
1887
1888 static gboolean
1889 chat_window_drag_drop (GtkWidget        *widget,
1890                          GdkDragContext   *context,
1891                          int               x,
1892                          int               y,
1893                          guint             time_,
1894                          EmpathyChatWindow *window)
1895 {
1896         GdkAtom target;
1897         EmpathyChatWindowPriv *priv;
1898
1899         priv = GET_PRIV (window);
1900
1901         target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1902         if (target == GDK_NONE)
1903                 target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1904
1905         if (target != GDK_NONE) {
1906                 gtk_drag_get_data (widget, context, target, time_);
1907                 return TRUE;
1908         }
1909
1910         return FALSE;
1911 }
1912
1913 static gboolean
1914 chat_window_drag_motion (GtkWidget        *widget,
1915                          GdkDragContext   *context,
1916                          int               x,
1917                          int               y,
1918                          guint             time_,
1919                          EmpathyChatWindow *window)
1920 {
1921         GdkAtom target;
1922         EmpathyChatWindowPriv *priv;
1923
1924         priv = GET_PRIV (window);
1925
1926         target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
1927         if (target != GDK_NONE) {
1928                 /* This is a file drag.  Ensure the contact is online and set the
1929                    drag type to COPY.  Note that it's possible that the tab will
1930                    be switched by GTK+ after a timeout from drag_motion without
1931                    getting another drag_motion to disable the drop.  You have
1932                    to hold your mouse really still.
1933                  */
1934                 EmpathyContact *contact;
1935
1936                 priv = GET_PRIV (window);
1937                 contact = empathy_chat_get_remote_contact (priv->current_chat);
1938                 /* contact is NULL for multi-user chats.  We don't do
1939                  * file transfers to MUCs.  We also don't send files
1940                  * to offline contacts or contacts that don't support
1941                  * file transfer.
1942                  */
1943                 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1944                         gdk_drag_status (context, 0, time_);
1945                         return FALSE;
1946                 }
1947                 if (!(empathy_contact_get_capabilities (contact)
1948                            & EMPATHY_CAPABILITIES_FT)) {
1949                         gdk_drag_status (context, 0, time_);
1950                         return FALSE;
1951                 }
1952                 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1953                 return TRUE;
1954         }
1955
1956         target = gtk_drag_dest_find_target (widget, context, priv->contact_targets);
1957         if (target != GDK_NONE) {
1958                 /* This is a drag of a contact from a contact list.  Set to COPY.
1959                    FIXME: If this drag is to a MUC window, it invites the user.
1960                    Otherwise, it opens a chat.  Should we use a different drag
1961                    type for invites?  Should we allow ASK?
1962                  */
1963                 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1964                 return TRUE;
1965         }
1966
1967         return FALSE;
1968 }
1969
1970 static void
1971 chat_window_drag_data_received (GtkWidget        *widget,
1972                                 GdkDragContext   *context,
1973                                 int               x,
1974                                 int               y,
1975                                 GtkSelectionData *selection,
1976                                 guint             info,
1977                                 guint             time_,
1978                                 EmpathyChatWindow *window)
1979 {
1980         if (info == DND_DRAG_TYPE_CONTACT_ID) {
1981                 EmpathyChat           *chat = NULL;
1982                 EmpathyChatWindow     *old_window;
1983                 TpAccount             *account = NULL;
1984                 EmpathyClientFactory  *factory;
1985                 const gchar           *id;
1986                 gchar                **strv;
1987                 const gchar           *account_id;
1988                 const gchar           *contact_id;
1989
1990                 id = (const gchar*) gtk_selection_data_get_data (selection);
1991
1992                 factory = empathy_client_factory_dup ();
1993
1994                 DEBUG ("DND contact from roster with id:'%s'", id);
1995
1996                 strv = g_strsplit (id, ":", 2);
1997                 if (g_strv_length (strv) == 2) {
1998                         account_id = strv[0];
1999                         contact_id = strv[1];
2000                         account =
2001                                 tp_simple_client_factory_ensure_account (
2002                                 TP_SIMPLE_CLIENT_FACTORY (factory), account_id,
2003                                  NULL, NULL);
2004
2005                         g_object_unref (factory);
2006                         if (account != NULL)
2007                                 chat = empathy_chat_window_find_chat (account, contact_id, FALSE);
2008                 }
2009
2010                 if (account == NULL) {
2011                         g_strfreev (strv);
2012                         gtk_drag_finish (context, FALSE, FALSE, time_);
2013                         return;
2014                 }
2015
2016                 if (!chat) {
2017                         empathy_chat_with_contact_id (
2018                                 account, contact_id,
2019                                 empathy_get_current_action_time (),
2020                                 NULL, NULL);
2021
2022                         g_strfreev (strv);
2023                         return;
2024                 }
2025                 g_strfreev (strv);
2026
2027                 old_window = chat_window_find_chat (chat);
2028                 if (old_window) {
2029                         if (old_window == window) {
2030                                 gtk_drag_finish (context, TRUE, FALSE, time_);
2031                                 return;
2032                         }
2033
2034                         empathy_chat_window_move_chat (old_window, window, chat);
2035                 } else {
2036                         empathy_chat_window_add_chat (window, chat);
2037                 }
2038
2039                 /* Added to take care of any outstanding chat events */
2040                 empathy_chat_window_present_chat (chat,
2041                         TP_USER_ACTION_TIME_NOT_USER_ACTION);
2042
2043                 /* We should return TRUE to remove the data when doing
2044                  * GDK_ACTION_MOVE, but we don't here otherwise it has
2045                  * weird consequences, and we handle that internally
2046                  * anyway with add_chat () and remove_chat ().
2047                  */
2048                 gtk_drag_finish (context, TRUE, FALSE, time_);
2049         }
2050         else if (info == DND_DRAG_TYPE_URI_LIST) {
2051                 EmpathyChatWindowPriv *priv;
2052                 EmpathyContact *contact;
2053                 const gchar *data;
2054
2055                 priv = GET_PRIV (window);
2056                 contact = empathy_chat_get_remote_contact (priv->current_chat);
2057
2058                 /* contact is NULL when current_chat is a multi-user chat.
2059                  * We don't do file transfers to MUCs, so just cancel the drag.
2060                  */
2061                 if (contact == NULL) {
2062                         gtk_drag_finish (context, TRUE, FALSE, time_);
2063                         return;
2064                 }
2065
2066                 data = (const gchar *) gtk_selection_data_get_data (selection);
2067                 empathy_send_file_from_uri_list (contact, data);
2068
2069                 gtk_drag_finish (context, TRUE, FALSE, time_);
2070         }
2071         else if (info == DND_DRAG_TYPE_TAB) {
2072                 EmpathyChat        **chat;
2073                 EmpathyChatWindow   *old_window = NULL;
2074
2075                 DEBUG ("DND tab");
2076
2077                 chat = (void *) gtk_selection_data_get_data (selection);
2078                 old_window = chat_window_find_chat (*chat);
2079
2080                 if (old_window) {
2081                         EmpathyChatWindowPriv *priv;
2082
2083                         priv = GET_PRIV (window);
2084                         priv->dnd_same_window = (old_window == window);
2085                         DEBUG ("DND tab (within same window: %s)",
2086                                 priv->dnd_same_window ? "Yes" : "No");
2087                 }
2088         } else {
2089                 DEBUG ("DND from unknown source");
2090                 gtk_drag_finish (context, FALSE, FALSE, time_);
2091         }
2092 }
2093
2094 static void
2095 chat_window_chat_manager_chats_changed_cb (EmpathyChatManager *chat_manager,
2096                                            guint num_chats_in_manager,
2097                                            EmpathyChatWindow *window)
2098 {
2099         EmpathyChatWindowPriv *priv = GET_PRIV (window);
2100
2101         gtk_action_set_sensitive (priv->menu_tabs_undo_close_tab,
2102                                   num_chats_in_manager > 0);
2103 }
2104
2105 static void
2106 chat_window_finalize (GObject *object)
2107 {
2108         EmpathyChatWindow     *window;
2109         EmpathyChatWindowPriv *priv;
2110
2111         window = EMPATHY_CHAT_WINDOW (object);
2112         priv = GET_PRIV (window);
2113
2114         DEBUG ("Finalized: %p", object);
2115
2116         g_object_unref (priv->ui_manager);
2117         g_object_unref (priv->chatroom_manager);
2118         g_object_unref (priv->notify_mgr);
2119         g_object_unref (priv->gsettings_chat);
2120         g_object_unref (priv->gsettings_notif);
2121         g_object_unref (priv->gsettings_ui);
2122         g_object_unref (priv->sound_mgr);
2123
2124         if (priv->notification != NULL) {
2125                 notify_notification_close (priv->notification, NULL);
2126                 priv->notification = NULL;
2127         }
2128
2129         if (priv->contact_targets) {
2130                 gtk_target_list_unref (priv->contact_targets);
2131         }
2132         if (priv->file_targets) {
2133                 gtk_target_list_unref (priv->file_targets);
2134         }
2135
2136         if (priv->chat_manager) {
2137                 g_signal_handler_disconnect (priv->chat_manager,
2138                                              priv->chat_manager_chats_changed_id);
2139                 g_object_unref (priv->chat_manager);
2140                 priv->chat_manager = NULL;
2141         }
2142
2143         chat_windows = g_list_remove (chat_windows, window);
2144         gtk_widget_destroy (priv->dialog);
2145
2146         G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
2147 }
2148
2149 static void
2150 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
2151 {
2152         GObjectClass *object_class = G_OBJECT_CLASS (klass);
2153
2154         object_class->finalize = chat_window_finalize;
2155
2156         g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
2157 }
2158
2159 static void
2160 empathy_chat_window_init (EmpathyChatWindow *window)
2161 {
2162         GtkBuilder            *gui;
2163         GtkAccelGroup         *accel_group;
2164         GClosure              *closure;
2165         GtkWidget             *menu;
2166         GtkWidget             *submenu;
2167         guint                  i;
2168         GtkWidget             *chat_vbox;
2169         gchar                 *filename;
2170         EmpathySmileyManager  *smiley_manager;
2171         EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
2172                 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
2173
2174         window->priv = priv;
2175         filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
2176         gui = empathy_builder_get_file (filename,
2177                                        "chat_window", &priv->dialog,
2178                                        "chat_vbox", &chat_vbox,
2179                                        "ui_manager", &priv->ui_manager,
2180                                        "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
2181                                        "menu_conv_favorite", &priv->menu_conv_favorite,
2182                                        "menu_conv_always_urgent", &priv->menu_conv_always_urgent,
2183                                        "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
2184                                        "menu_edit_cut", &priv->menu_edit_cut,
2185                                        "menu_edit_copy", &priv->menu_edit_copy,
2186                                        "menu_edit_paste", &priv->menu_edit_paste,
2187                                        "menu_edit_find", &priv->menu_edit_find,
2188                                        "menu_tabs_next", &priv->menu_tabs_next,
2189                                        "menu_tabs_prev", &priv->menu_tabs_prev,
2190                                        "menu_tabs_undo_close_tab", &priv->menu_tabs_undo_close_tab,
2191                                        "menu_tabs_left", &priv->menu_tabs_left,
2192                                        "menu_tabs_right", &priv->menu_tabs_right,
2193                                        "menu_tabs_detach", &priv->menu_tabs_detach,
2194                                        NULL);
2195         g_free (filename);
2196
2197         empathy_builder_connect (gui, window,
2198                               "menu_conv", "activate", chat_window_conv_activate_cb,
2199                               "menu_conv_clear", "activate", chat_window_clear_activate_cb,
2200                               "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
2201                               "menu_conv_always_urgent", "toggled", chat_window_always_urgent_toggled_cb,
2202                               "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
2203                               "menu_conv_invite_participant", "activate", chat_window_invite_participant_activate_cb,
2204                               "menu_conv_close", "activate", chat_window_close_activate_cb,
2205                               "menu_edit", "activate", chat_window_edit_activate_cb,
2206                               "menu_edit_cut", "activate", chat_window_cut_activate_cb,
2207                               "menu_edit_copy", "activate", chat_window_copy_activate_cb,
2208                               "menu_edit_paste", "activate", chat_window_paste_activate_cb,
2209                               "menu_edit_find", "activate", chat_window_find_activate_cb,
2210                               "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
2211                               "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
2212                               "menu_tabs_undo_close_tab", "activate", chat_window_tabs_undo_close_tab_activate_cb,
2213                               "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
2214                               "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
2215                               "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
2216                               "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
2217                               "menu_help_about", "activate", chat_window_help_about_activate_cb,
2218                               NULL);
2219
2220         g_object_ref (priv->ui_manager);
2221         g_object_unref (gui);
2222
2223         priv->gsettings_chat = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
2224         priv->gsettings_notif = g_settings_new (EMPATHY_PREFS_NOTIFICATIONS_SCHEMA);
2225         priv->gsettings_ui = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2226         priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
2227
2228         priv->sound_mgr = empathy_sound_manager_dup_singleton ();
2229
2230         priv->notebook = gtk_notebook_new ();
2231
2232         g_signal_connect (priv->notebook, "create-window",
2233                 G_CALLBACK (notebook_create_window_cb), window);
2234
2235         gtk_notebook_set_group_name (GTK_NOTEBOOK (priv->notebook),
2236                 "EmpathyChatWindow");
2237         gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
2238         gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
2239         gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
2240         gtk_widget_show (priv->notebook);
2241
2242         /* Set up accels */
2243         accel_group = gtk_accel_group_new ();
2244         gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
2245
2246         for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
2247                 closure =  g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
2248                                            window,
2249                                            NULL);
2250                 gtk_accel_group_connect (accel_group,
2251                                          tab_accel_keys[i],
2252                                          GDK_MOD1_MASK,
2253                                          0,
2254                                          closure);
2255         }
2256
2257         g_object_unref (accel_group);
2258
2259         /* Set up drag target lists */
2260         priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
2261                                                      G_N_ELEMENTS (drag_types_dest_contact));
2262         priv->file_targets = gtk_target_list_new (drag_types_dest_file,
2263                                                   G_N_ELEMENTS (drag_types_dest_file));
2264
2265         /* Set up smiley menu */
2266         smiley_manager = empathy_smiley_manager_dup_singleton ();
2267         submenu = empathy_smiley_menu_new (smiley_manager,
2268                                            chat_window_insert_smiley_activate_cb,
2269                                            window);
2270         menu = gtk_ui_manager_get_widget (priv->ui_manager,
2271                 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
2272         gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
2273         g_object_unref (smiley_manager);
2274
2275         /* Set up signals we can't do with ui file since we may need to
2276          * block/unblock them at some later stage.
2277          */
2278
2279         g_signal_connect (priv->dialog,
2280                           "delete_event",
2281                           G_CALLBACK (chat_window_delete_event_cb),
2282                           window);
2283         g_signal_connect (priv->dialog,
2284                           "focus_in_event",
2285                           G_CALLBACK (chat_window_focus_in_event_cb),
2286                           window);
2287         g_signal_connect_after (priv->notebook,
2288                                 "switch_page",
2289                                 G_CALLBACK (chat_window_page_switched_cb),
2290                                 window);
2291         g_signal_connect (priv->notebook,
2292                           "page_added",
2293                           G_CALLBACK (chat_window_page_added_cb),
2294                           window);
2295         g_signal_connect (priv->notebook,
2296                           "page_removed",
2297                           G_CALLBACK (chat_window_page_removed_cb),
2298                           window);
2299
2300         /* Set up drag and drop */
2301         gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
2302                            GTK_DEST_DEFAULT_HIGHLIGHT,
2303                            drag_types_dest,
2304                            G_N_ELEMENTS (drag_types_dest),
2305                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
2306
2307         /* connect_after to allow GtkNotebook's built-in tab switching */
2308         g_signal_connect_after (priv->notebook,
2309                                 "drag-motion",
2310                                 G_CALLBACK (chat_window_drag_motion),
2311                                 window);
2312         g_signal_connect (priv->notebook,
2313                           "drag-data-received",
2314                           G_CALLBACK (chat_window_drag_data_received),
2315                           window);
2316         g_signal_connect (priv->notebook,
2317                           "drag-drop",
2318                           G_CALLBACK (chat_window_drag_drop),
2319                           window);
2320
2321         chat_windows = g_list_prepend (chat_windows, window);
2322
2323         /* Set up private details */
2324         priv->chats = NULL;
2325         priv->current_chat = NULL;
2326         priv->notification = NULL;
2327
2328         priv->notify_mgr = empathy_notify_manager_dup_singleton ();
2329
2330         priv->chat_manager = empathy_chat_manager_dup_singleton ();
2331         priv->chat_manager_chats_changed_id =
2332                 g_signal_connect (priv->chat_manager, "closed-chats-changed",
2333                                   G_CALLBACK (chat_window_chat_manager_chats_changed_cb),
2334                                   window);
2335
2336         chat_window_chat_manager_chats_changed_cb (priv->chat_manager,
2337                                                    empathy_chat_manager_get_num_closed_chats (priv->chat_manager),
2338                                                    window);
2339 }
2340
2341 /* Returns the window to open a new tab in if there is a suitable window,
2342  * otherwise, returns NULL indicating that a new window should be added.
2343  */
2344 static EmpathyChatWindow *
2345 empathy_chat_window_get_default (gboolean room)
2346 {
2347         GSettings *gsettings = g_settings_new (EMPATHY_PREFS_UI_SCHEMA);
2348         GList    *l;
2349         gboolean  separate_windows = TRUE;
2350
2351         separate_windows = g_settings_get_boolean (gsettings,
2352                         EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2353
2354         g_object_unref (gsettings);
2355
2356         if (separate_windows) {
2357                 /* Always create a new window */
2358                 return NULL;
2359         }
2360
2361         for (l = chat_windows; l; l = l->next) {
2362                 EmpathyChatWindow *chat_window;
2363                 guint nb_rooms, nb_private;
2364
2365                 chat_window = l->data;
2366
2367                 empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
2368
2369                 /* Skip the window if there aren't any rooms in it */
2370                 if (room && nb_rooms == 0)
2371                         continue;
2372
2373                 /* Skip the window if there aren't any 1-1 chats in it */
2374                 if (!room && nb_private == 0)
2375                         continue;
2376
2377                 return chat_window;
2378         }
2379
2380         return NULL;
2381 }
2382
2383 static void
2384 empathy_chat_window_add_chat (EmpathyChatWindow *window,
2385                               EmpathyChat       *chat)
2386 {
2387         EmpathyChatWindowPriv *priv;
2388         GtkWidget             *label;
2389         GtkWidget             *popup_label;
2390         GtkWidget             *child;
2391         GValue                value = { 0, };
2392
2393         g_return_if_fail (window != NULL);
2394         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2395
2396         priv = GET_PRIV (window);
2397
2398         /* Reference the chat object */
2399         g_object_ref (chat);
2400
2401         /* If this window has just been created, position it */
2402         if (priv->chats == NULL) {
2403                 const gchar *name = "chat-window";
2404                 gboolean     separate_windows;
2405
2406                 separate_windows = g_settings_get_boolean (priv->gsettings_ui,
2407                                 EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS);
2408
2409                 if (empathy_chat_is_room (chat))
2410                         name = "room-window";
2411
2412                 if (separate_windows) {
2413                         gint x, y;
2414
2415                         /* Save current position of the window */
2416                         gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y);
2417
2418                         /* First bind to the 'generic' name. So new window for which we didn't
2419                         * save a geometry yet will have the geometry of the last saved
2420                         * window (bgo #601191). */
2421                         empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2422
2423                         /* Restore previous position of the window so the newly created window
2424                         * won't be in the same position as the latest saved window and so
2425                         * completely hide it. */
2426                         gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
2427
2428                         /* Then bind it to the name of the contact/room so we'll save the
2429                         * geometry specific to this window */
2430                         name = empathy_chat_get_id (chat);
2431                 }
2432
2433                 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
2434         }
2435
2436         child = GTK_WIDGET (chat);
2437         label = chat_window_create_label (window, chat, TRUE);
2438         popup_label = chat_window_create_label (window, chat, FALSE);
2439         gtk_widget_show (child);
2440
2441         g_signal_connect (chat, "notify::name",
2442                           G_CALLBACK (chat_window_chat_notify_cb),
2443                           NULL);
2444         g_signal_connect (chat, "notify::subject",
2445                           G_CALLBACK (chat_window_chat_notify_cb),
2446                           NULL);
2447         g_signal_connect (chat, "notify::remote-contact",
2448                           G_CALLBACK (chat_window_chat_notify_cb),
2449                           NULL);
2450         g_signal_connect (chat, "notify::sms-channel",
2451                           G_CALLBACK (chat_window_chat_notify_cb),
2452                           NULL);
2453         g_signal_connect (chat, "notify::n-messages-sending",
2454                           G_CALLBACK (chat_window_chat_notify_cb),
2455                           NULL);
2456         g_signal_connect (chat, "notify::nb-unread-messages",
2457                           G_CALLBACK (chat_window_chat_notify_cb),
2458                           NULL);
2459         chat_window_chat_notify_cb (chat);
2460
2461         gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
2462         gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2463         gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
2464         g_value_init (&value, G_TYPE_BOOLEAN);
2465         g_value_set_boolean (&value, TRUE);
2466         gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2467                                           child, "tab-expand" , &value);
2468         gtk_container_child_set_property (GTK_CONTAINER (priv->notebook),
2469                                           child,  "tab-fill" , &value);
2470         g_value_unset (&value);
2471
2472         DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
2473 }
2474
2475 static void
2476 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
2477                                  EmpathyChat       *chat)
2478 {
2479         EmpathyChatWindowPriv *priv;
2480         gint                   position;
2481         EmpathyContact        *remote_contact;
2482         EmpathyChatManager    *chat_manager;
2483
2484         g_return_if_fail (window != NULL);
2485         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2486
2487         priv = GET_PRIV (window);
2488
2489         g_signal_handlers_disconnect_by_func (chat,
2490                                               chat_window_chat_notify_cb,
2491                                               NULL);
2492         remote_contact = g_object_get_data (G_OBJECT (chat),
2493                                             "chat-window-remote-contact");
2494         if (remote_contact) {
2495                 g_signal_handlers_disconnect_by_func (remote_contact,
2496                                                       chat_window_update_chat_tab,
2497                                                       chat);
2498         }
2499
2500         chat_manager = empathy_chat_manager_dup_singleton ();
2501         empathy_chat_manager_closed_chat (chat_manager, chat);
2502         g_object_unref (chat_manager);
2503
2504         position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2505                                           GTK_WIDGET (chat));
2506         gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
2507
2508         DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
2509
2510         g_object_unref (chat);
2511 }
2512
2513 static void
2514 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
2515                                EmpathyChatWindow *new_window,
2516                                EmpathyChat       *chat)
2517 {
2518         GtkWidget *widget;
2519
2520         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2521         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2522         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2523
2524         widget = GTK_WIDGET (chat);
2525
2526         DEBUG ("Chat moving with widget:%p (%d references)", widget,
2527                 G_OBJECT (widget)->ref_count);
2528
2529         /* We reference here to make sure we don't loose the widget
2530          * and the EmpathyChat object during the move.
2531          */
2532         g_object_ref (chat);
2533         g_object_ref (widget);
2534
2535         empathy_chat_window_remove_chat (old_window, chat);
2536         empathy_chat_window_add_chat (new_window, chat);
2537
2538         g_object_unref (widget);
2539         g_object_unref (chat);
2540 }
2541
2542 static void
2543 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2544                                     EmpathyChat       *chat)
2545 {
2546         EmpathyChatWindowPriv *priv;
2547         gint                  page_num;
2548
2549         g_return_if_fail (window != NULL);
2550         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2551
2552         priv = GET_PRIV (window);
2553
2554         page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2555                                           GTK_WIDGET (chat));
2556         gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2557                                        page_num);
2558 }
2559
2560 EmpathyChat *
2561 empathy_chat_window_find_chat (TpAccount   *account,
2562                                const gchar *id,
2563                                gboolean     sms_channel)
2564 {
2565         GList *l;
2566
2567         g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2568
2569         for (l = chat_windows; l; l = l->next) {
2570                 EmpathyChatWindowPriv *priv;
2571                 EmpathyChatWindow     *window;
2572                 GList                *ll;
2573
2574                 window = l->data;
2575                 priv = GET_PRIV (window);
2576
2577                 for (ll = priv->chats; ll; ll = ll->next) {
2578                         EmpathyChat *chat;
2579
2580                         chat = ll->data;
2581
2582                         if (account == empathy_chat_get_account (chat) &&
2583                             !tp_strdiff (id, empathy_chat_get_id (chat)) &&
2584                             sms_channel == empathy_chat_is_sms_channel (chat)) {
2585                                 return chat;
2586                         }
2587                 }
2588         }
2589
2590         return NULL;
2591 }
2592
2593 void
2594 empathy_chat_window_present_chat (EmpathyChat *chat,
2595                                   gint64 timestamp)
2596 {
2597         EmpathyChatWindow     *window;
2598         EmpathyChatWindowPriv *priv;
2599         guint32 x_timestamp;
2600
2601         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2602
2603         window = chat_window_find_chat (chat);
2604
2605         /* If the chat has no window, create one */
2606         if (window == NULL) {
2607                 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2608                 if (!window) {
2609                         window = empathy_chat_window_new ();
2610
2611                         /* we want to display the newly created window even if we don't present
2612                         * it */
2613                         priv = GET_PRIV (window);
2614                         gtk_widget_show (priv->dialog);
2615                 }
2616
2617                 empathy_chat_window_add_chat (window, chat);
2618         }
2619
2620         /* Don't force the window to show itself when it wasn't
2621          * an action by the user
2622          */
2623         if (!tp_user_action_time_should_present (timestamp, &x_timestamp))
2624                 return;
2625
2626         priv = GET_PRIV (window);
2627
2628         if (x_timestamp != GDK_CURRENT_TIME) {
2629                 /* Don't present or switch tab if the action was earlier than the
2630                  * last actions X time, accounting for overflow and the first ever
2631                 * presentation */
2632
2633                 if (priv->x_user_action_time != 0
2634                         && X_EARLIER_OR_EQL (x_timestamp, priv->x_user_action_time))
2635                         return;
2636
2637                 priv->x_user_action_time = x_timestamp;
2638         }
2639
2640         empathy_chat_window_switch_to_chat (window, chat);
2641
2642         /* Don't use empathy_window_present_with_time () which would move the window
2643          * to our current desktop but move to the window's desktop instead. This is
2644          * more coherent with Shell's 'app is ready' notication which moves the view
2645          * to the app desktop rather than moving the app itself. */
2646         empathy_move_to_window_desktop (GTK_WINDOW (priv->dialog), x_timestamp);
2647
2648         gtk_widget_grab_focus (chat->input_text_view);
2649 }
2650
2651 static void
2652 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2653                                guint *nb_rooms,
2654                                guint *nb_private)
2655 {
2656         EmpathyChatWindowPriv *priv = GET_PRIV (self);
2657         GList *l;
2658         guint _nb_rooms = 0, _nb_private = 0;
2659
2660         for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2661                 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2662                         _nb_rooms++;
2663                 else
2664                         _nb_private++;
2665         }
2666
2667         if (nb_rooms != NULL)
2668                 *nb_rooms = _nb_rooms;
2669         if (nb_private != NULL)
2670                 *nb_private = _nb_private;
2671 }