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