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