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