]> git.0d.be Git - empathy.git/blob - src/empathy-chat-window.c
change empathy_chat_window_get_nb_rooms to get_chats_type
[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-2008 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 <glib/gi18n.h>
36 #include <libnotify/notification.h>
37
38 #include <telepathy-glib/account-manager.h>
39 #include <telepathy-glib/util.h>
40
41 #include <libempathy/empathy-contact.h>
42 #include <libempathy/empathy-message.h>
43 #include <libempathy/empathy-dispatcher.h>
44 #include <libempathy/empathy-chatroom-manager.h>
45 #include <libempathy/empathy-utils.h>
46
47 #include <libempathy-gtk/empathy-images.h>
48 #include <libempathy-gtk/empathy-conf.h>
49 #include <libempathy-gtk/empathy-contact-dialogs.h>
50 #include <libempathy-gtk/empathy-log-window.h>
51 #include <libempathy-gtk/empathy-geometry.h>
52 #include <libempathy-gtk/empathy-smiley-manager.h>
53 #include <libempathy-gtk/empathy-sound.h>
54 #include <libempathy-gtk/empathy-ui-utils.h>
55 #include <libempathy-gtk/empathy-notify-manager.h>
56
57 #include "empathy-chat-window.h"
58 #include "empathy-about-dialog.h"
59
60 #define DEBUG_FLAG EMPATHY_DEBUG_CHAT
61 #include <libempathy/empathy-debug.h>
62
63 typedef struct {
64         EmpathyChatWindow *window;
65         EmpathyChat *chat;
66 } NotificationData;
67
68 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChatWindow)
69 typedef struct {
70         EmpathyChat *current_chat;
71         GList       *chats;
72         GList       *chats_new_msg;
73         GList       *chats_composing;
74         gboolean     page_added;
75         gboolean     dnd_same_window;
76         EmpathyChatroomManager *chatroom_manager;
77         EmpathyNotifyManager *notify_mgr;
78         GtkWidget   *dialog;
79         GtkWidget   *notebook;
80         NotifyNotification *notification;
81         NotificationData *notification_data;
82
83         GtkTargetList *contact_targets;
84         GtkTargetList *file_targets;
85
86         /* Menu items. */
87         GtkUIManager *ui_manager;
88         GtkAction   *menu_conv_insert_smiley;
89         GtkAction   *menu_conv_favorite;
90         GtkAction   *menu_conv_toggle_contacts;
91
92         GtkAction   *menu_edit_cut;
93         GtkAction   *menu_edit_copy;
94         GtkAction   *menu_edit_paste;
95
96         GtkAction   *menu_tabs_next;
97         GtkAction   *menu_tabs_prev;
98         GtkAction   *menu_tabs_left;
99         GtkAction   *menu_tabs_right;
100         GtkAction   *menu_tabs_detach;
101 } EmpathyChatWindowPriv;
102
103 static GList *chat_windows = NULL;
104
105 static const guint tab_accel_keys[] = {
106         GDK_1, GDK_2, GDK_3, GDK_4, GDK_5,
107         GDK_6, GDK_7, GDK_8, GDK_9, GDK_0
108 };
109
110 typedef enum {
111         DND_DRAG_TYPE_CONTACT_ID,
112         DND_DRAG_TYPE_URI_LIST,
113         DND_DRAG_TYPE_TAB
114 } DndDragType;
115
116 static const GtkTargetEntry drag_types_dest[] = {
117         { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
118         { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB },
119         { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
120 };
121
122 static const GtkTargetEntry drag_types_dest_contact[] = {
123         { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
124 };
125
126 static const GtkTargetEntry drag_types_dest_file[] = {
127         { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
128 };
129
130 static void chat_window_update (EmpathyChatWindow *window);
131
132 G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT);
133
134 static void
135 chat_window_accel_cb (GtkAccelGroup    *accelgroup,
136                       GObject          *object,
137                       guint             key,
138                       GdkModifierType   mod,
139                       EmpathyChatWindow *window)
140 {
141         EmpathyChatWindowPriv *priv;
142         gint                  num = -1;
143         guint                 i;
144
145         priv = GET_PRIV (window);
146
147         for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
148                 if (tab_accel_keys[i] == key) {
149                         num = i;
150                         break;
151                 }
152         }
153
154         if (num != -1) {
155                 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), num);
156         }
157 }
158
159 static EmpathyChatWindow *
160 chat_window_find_chat (EmpathyChat *chat)
161 {
162         EmpathyChatWindowPriv *priv;
163         GList                 *l, *ll;
164
165         for (l = chat_windows; l; l = l->next) {
166                 priv = GET_PRIV (l->data);
167                 ll = g_list_find (priv->chats, chat);
168                 if (ll) {
169                         return l->data;
170                 }
171         }
172
173         return NULL;
174 }
175
176 static void
177 chat_window_close_clicked_cb (GtkAction   *action,
178                               EmpathyChat *chat)
179 {
180         EmpathyChatWindow *window;
181
182         window = chat_window_find_chat (chat);
183         empathy_chat_window_remove_chat (window, chat);
184 }
185
186 static void
187 chat_tab_style_set_cb (GtkWidget *hbox,
188                                        GtkStyle  *previous_style,
189                                        gpointer   user_data)
190 {
191         GtkWidget *button;
192         int char_width, h, w;
193         PangoContext *context;
194         PangoFontMetrics *metrics;
195
196         button = g_object_get_data (G_OBJECT (user_data),
197                 "chat-window-tab-close-button");
198         context = gtk_widget_get_pango_context (hbox);
199
200         metrics = pango_context_get_metrics (context, gtk_widget_get_style (hbox)->font_desc,
201                 pango_context_get_language (context));
202         char_width = pango_font_metrics_get_approximate_char_width (metrics);
203         pango_font_metrics_unref (metrics);
204
205         gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button),
206                                            GTK_ICON_SIZE_MENU, &w, &h);
207
208         /* Request at least about 12 chars width plus at least space for the status
209          * image and the close button */
210         gtk_widget_set_size_request (hbox,
211                 12 * PANGO_PIXELS (char_width) + 2 * w, -1);
212
213         gtk_widget_set_size_request (button, w, h);
214 }
215
216 static GtkWidget *
217 chat_window_create_label (EmpathyChatWindow *window,
218                           EmpathyChat       *chat,
219                           gboolean           is_tab_label)
220 {
221         EmpathyChatWindowPriv *priv;
222         GtkWidget            *hbox;
223         GtkWidget            *name_label;
224         GtkWidget            *status_image;
225         GtkWidget            *close_button;
226         GtkWidget            *close_image;
227         GtkWidget            *event_box;
228         GtkWidget            *event_box_hbox;
229         PangoAttrList        *attr_list;
230         PangoAttribute       *attr;
231
232         priv = GET_PRIV (window);
233
234         /* The spacing between the button and the label. */
235         hbox = gtk_hbox_new (FALSE, 0);
236
237         event_box = gtk_event_box_new ();
238         gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
239
240         name_label = gtk_label_new (NULL);
241         if (is_tab_label)
242                 gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END);
243
244         attr_list = pango_attr_list_new ();
245         attr = pango_attr_scale_new (1/1.2);
246         attr->start_index = 0;
247         attr->end_index = -1;
248         pango_attr_list_insert (attr_list, attr);
249         gtk_label_set_attributes (GTK_LABEL (name_label), attr_list);
250         pango_attr_list_unref (attr_list);
251
252         gtk_misc_set_padding (GTK_MISC (name_label), 2, 0);
253         gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5);
254         g_object_set_data (G_OBJECT (chat),
255                 is_tab_label ? "chat-window-tab-label" : "chat-window-menu-label",
256                 name_label);
257
258         status_image = gtk_image_new ();
259
260         /* Spacing between the icon and label. */
261         event_box_hbox = gtk_hbox_new (FALSE, 0);
262
263         gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0);
264         gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0);
265
266         g_object_set_data (G_OBJECT (chat),
267                 is_tab_label ? "chat-window-tab-image" : "chat-window-menu-image",
268                 status_image);
269         g_object_set_data (G_OBJECT (chat),
270                 is_tab_label ? "chat-window-tab-tooltip-widget" : "chat-window-menu-tooltip-widget",
271                 event_box);
272
273         gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox);
274         gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0);
275
276         if (is_tab_label) {
277                 close_button = gtk_button_new ();
278                 gtk_button_set_relief (GTK_BUTTON (close_button), GTK_RELIEF_NONE);
279                 g_object_set_data (G_OBJECT (chat), "chat-window-tab-close-button", close_button);
280
281                 /* We don't want focus/keynav for the button to avoid clutter, and
282                  * Ctrl-W works anyway.
283                  */
284                 gtk_widget_set_can_focus (close_button, FALSE);
285                 gtk_widget_set_can_default (close_button, FALSE);
286
287                 /* Set the name to make the special rc style match. */
288                 gtk_widget_set_name (close_button, "empathy-close-button");
289
290                 close_image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
291
292                 gtk_container_add (GTK_CONTAINER (close_button), close_image);
293
294                 gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
295
296                 g_signal_connect (close_button,
297                                   "clicked",
298                                   G_CALLBACK (chat_window_close_clicked_cb),
299                                   chat);
300
301                 /* React to theme changes and also setup the size correctly.  */
302                 g_signal_connect (hbox,
303                                   "style-set",
304                                   G_CALLBACK (chat_tab_style_set_cb),
305                                   chat);
306         }
307
308         gtk_widget_show_all (hbox);
309
310         return hbox;
311 }
312
313 static void
314 _submenu_notify_visible_changed_cb (GObject    *object,
315                                     GParamSpec *pspec,
316                                     gpointer    userdata)
317 {
318         g_signal_handlers_disconnect_by_func (object,
319                                               _submenu_notify_visible_changed_cb,
320                                               userdata);
321         chat_window_update (EMPATHY_CHAT_WINDOW (userdata));
322 }
323
324 static void
325 chat_window_menu_context_update (EmpathyChatWindowPriv *priv,
326                               gint num_pages)
327 {
328         gboolean first_page;
329         gboolean last_page;
330         gboolean is_connected;
331         gint     page_num;
332
333         page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
334         first_page = (page_num == 0);
335         last_page = (page_num == (num_pages - 1));
336         is_connected = empathy_chat_get_tp_chat (priv->current_chat) != NULL;
337
338         DEBUG ("Update window : Menu Contexts (Tabs & Conv)");
339
340         gtk_action_set_sensitive (priv->menu_tabs_next, TRUE);
341         gtk_action_set_sensitive (priv->menu_tabs_prev, TRUE);
342         gtk_action_set_sensitive (priv->menu_tabs_detach, num_pages > 1);
343         gtk_action_set_sensitive (priv->menu_tabs_left, !first_page);
344         gtk_action_set_sensitive (priv->menu_tabs_right, !last_page);
345         gtk_action_set_sensitive (priv->menu_conv_insert_smiley, is_connected);
346 }
347
348 static void
349 chat_window_contact_menu_update (EmpathyChatWindowPriv *priv,
350                                  EmpathyChatWindow     *window)
351 {
352         GtkWidget *menu, *submenu, *orig_submenu;
353
354         menu = gtk_ui_manager_get_widget (priv->ui_manager,
355                 "/chats_menubar/menu_contact");
356         orig_submenu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
357
358         DEBUG ("Update window : Contact Menu");
359
360         if (orig_submenu == NULL || !GTK_WIDGET_VISIBLE (orig_submenu)) {
361                 submenu = empathy_chat_get_contact_menu (priv->current_chat);
362                 gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
363                 gtk_widget_show (menu);
364         } else {
365                 empathy_signal_connect_weak (orig_submenu,
366                                              "notify::visible",
367                                              (GCallback)_submenu_notify_visible_changed_cb,
368                                              G_OBJECT (window));
369         }
370 }
371
372 static guint
373 get_all_unread_messages (EmpathyChatWindowPriv *priv)
374 {
375         GList *l;
376         guint nb = 0;
377
378         for (l = priv->chats_new_msg; l != NULL; l = g_list_next (l)) {
379                 EmpathyChat *chat = l->data;
380
381                 nb += empathy_chat_get_nb_unread_messages (chat);
382         }
383
384         return nb;
385 }
386
387 static gchar *
388 get_window_title_name (EmpathyChatWindowPriv *priv)
389 {
390         const gchar *active_name;
391         guint nb_chats;
392         guint current_unread_msgs;
393
394         nb_chats = g_list_length (priv->chats);
395         g_assert (nb_chats > 0);
396
397         active_name = empathy_chat_get_name (priv->current_chat);
398
399         current_unread_msgs = empathy_chat_get_nb_unread_messages (
400                         priv->current_chat);
401
402         if (nb_chats == 1) {
403                 /* only one tab */
404                 if (current_unread_msgs == 0)
405                         return g_strdup (active_name);
406                 else
407                         return g_strdup_printf (ngettext (
408                                 "%s (%d unread)",
409                                 "%s (%d unread)", current_unread_msgs),
410                                 active_name, current_unread_msgs);
411         } else {
412                 guint nb_others = nb_chats - 1;
413                 guint all_unread_msgs;
414
415                 all_unread_msgs = get_all_unread_messages (priv);
416
417                 if (all_unread_msgs == 0) {
418                         /* no unread message */
419                         return g_strdup_printf (ngettext (
420                                 "%s (and %u other)",
421                                 "%s (and %u others)", nb_others),
422                                 active_name, nb_others);
423                 }
424
425                 else if (all_unread_msgs == current_unread_msgs) {
426                         /* unread messages are in the current tab */
427                         return g_strdup_printf (ngettext (
428                                 "%s (%d unread)",
429                                 "%s (%d unread)", current_unread_msgs),
430                                 active_name, current_unread_msgs);
431                 }
432
433                 else if (current_unread_msgs == 0) {
434                         /* unread messages are in other tabs */
435                         return g_strdup_printf (ngettext (
436                                 "%s (%d unread from others)",
437                                 "%s (%d unread from others)",
438                                 all_unread_msgs),
439                                 active_name, all_unread_msgs);
440                 }
441
442                 else {
443                         /* unread messages are in all the tabs */
444                         return g_strdup_printf (ngettext (
445                                 "%s (%d unread from all)",
446                                 "%s (%d unread from all)",
447                                 all_unread_msgs),
448                                 active_name, all_unread_msgs);
449                 }
450         }
451 }
452
453 static void
454 chat_window_title_update (EmpathyChatWindowPriv *priv)
455 {
456         gchar *name;
457
458         DEBUG ("Update window : Title");
459
460         name = get_window_title_name (priv);
461         gtk_window_set_title (GTK_WINDOW (priv->dialog), name);
462         g_free (name);
463 }
464
465 static void
466 chat_window_icon_update (EmpathyChatWindowPriv *priv)
467 {
468         GdkPixbuf      *icon;
469         EmpathyContact *remote_contact;
470         gboolean        avatar_in_icon;
471         guint           n_chats;
472
473         n_chats = g_list_length (priv->chats);
474
475         DEBUG ("Update window : Icon");
476
477         /* Update window icon */
478         if (priv->chats_new_msg) {
479                 gtk_window_set_icon_name (GTK_WINDOW (priv->dialog),
480                                           EMPATHY_IMAGE_MESSAGE);
481         } else {
482                 empathy_conf_get_bool (empathy_conf_get (),
483                                        EMPATHY_PREFS_CHAT_AVATAR_IN_ICON,
484                                        &avatar_in_icon);
485
486                 if (n_chats == 1 && avatar_in_icon) {
487                         remote_contact = empathy_chat_get_remote_contact (priv->current_chat);
488                         icon = empathy_pixbuf_avatar_from_contact_scaled (remote_contact, 0, 0);
489                         gtk_window_set_icon (GTK_WINDOW (priv->dialog), icon);
490
491                         if (icon != NULL) {
492                                 g_object_unref (icon);
493                         }
494                 } else {
495                         gtk_window_set_icon_name (GTK_WINDOW (priv->dialog), NULL);
496                 }
497         }
498 }
499
500 static void
501 chat_window_close_button_update (EmpathyChatWindowPriv *priv,
502                                  gint num_pages)
503 {
504         GtkWidget *chat;
505         GtkWidget *chat_close_button;
506         gint       i;
507
508         DEBUG ("Update window : Close Button");
509
510         if (num_pages == 1) {
511                 chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), 0);
512                 chat_close_button = g_object_get_data (G_OBJECT (chat),
513                                 "chat-window-tab-close-button");
514                 gtk_widget_hide (chat_close_button);
515         } else {
516                 for (i=0; i<num_pages; i++) {
517                         chat = gtk_notebook_get_nth_page (GTK_NOTEBOOK (priv->notebook), i);
518                         chat_close_button = g_object_get_data (G_OBJECT (chat),
519                                         "chat-window-tab-close-button");
520                         gtk_widget_show (chat_close_button);
521                 }
522         }
523 }
524
525 static void
526 chat_window_update (EmpathyChatWindow *window)
527 {
528         EmpathyChatWindowPriv *priv = GET_PRIV (window);
529         gint                   num_pages;
530
531         num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
532
533         DEBUG ("Update window");
534
535         /* Update Tab menu */
536         chat_window_menu_context_update (priv,
537                                          num_pages);
538
539         chat_window_contact_menu_update (priv,
540                                          window);
541
542         chat_window_title_update (priv);
543
544         chat_window_icon_update (priv);
545
546         chat_window_close_button_update (priv,
547                                          num_pages);
548 }
549
550 static void
551 append_markup_printf (GString    *string,
552                       const char *format,
553                       ...)
554 {
555         gchar *tmp;
556         va_list args;
557
558         va_start (args, format);
559
560         tmp = g_markup_vprintf_escaped (format, args);
561         g_string_append (string, tmp);
562         g_free (tmp);
563
564         va_end (args);
565 }
566
567 static void
568 chat_window_update_chat_tab (EmpathyChat *chat)
569 {
570         EmpathyChatWindow     *window;
571         EmpathyChatWindowPriv *priv;
572         EmpathyContact        *remote_contact;
573         const gchar           *name;
574         const gchar           *id;
575         TpAccount             *account;
576         const gchar           *subject;
577         const gchar           *status = NULL;
578         GtkWidget             *widget;
579         GString               *tooltip;
580         gchar                 *markup;
581         const gchar           *icon_name;
582         GtkWidget             *tab_image;
583         GtkWidget             *menu_image;
584
585         window = chat_window_find_chat (chat);
586         if (!window) {
587                 return;
588         }
589         priv = GET_PRIV (window);
590
591         /* Get information */
592         name = empathy_chat_get_name (chat);
593         account = empathy_chat_get_account (chat);
594         subject = empathy_chat_get_subject (chat);
595         remote_contact = empathy_chat_get_remote_contact (chat);
596
597         DEBUG ("Updating chat tab, name=%s, account=%s, subject=%s, remote_contact=%p",
598                 name, tp_proxy_get_object_path (account), subject, remote_contact);
599
600         /* Update tab image */
601         if (empathy_chat_get_tp_chat (chat) == NULL) {
602                 /* No TpChat, we are disconnected */
603                 icon_name = NULL;
604         }
605         else if (g_list_find (priv->chats_new_msg, chat)) {
606                 icon_name = EMPATHY_IMAGE_MESSAGE;
607         }
608         else if (g_list_find (priv->chats_composing, chat)) {
609                 icon_name = EMPATHY_IMAGE_TYPING;
610         }
611         else if (remote_contact) {
612                 icon_name = empathy_icon_name_for_contact (remote_contact);
613         } else {
614                 icon_name = EMPATHY_IMAGE_GROUP_MESSAGE;
615         }
616
617         tab_image = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image");
618         menu_image = g_object_get_data (G_OBJECT (chat), "chat-window-menu-image");
619         if (icon_name != NULL) {
620                 gtk_image_set_from_icon_name (GTK_IMAGE (tab_image), icon_name, GTK_ICON_SIZE_MENU);
621                 gtk_widget_show (tab_image);
622                 gtk_image_set_from_icon_name (GTK_IMAGE (menu_image), icon_name, GTK_ICON_SIZE_MENU);
623                 gtk_widget_show (menu_image);
624         } else {
625                 gtk_widget_hide (tab_image);
626                 gtk_widget_hide (menu_image);
627         }
628
629         /* Update tab tooltip */
630         tooltip = g_string_new (NULL);
631
632         if (remote_contact) {
633                 id = empathy_contact_get_id (remote_contact);
634                 status = empathy_contact_get_presence_message (remote_contact);
635         } else {
636                 id = name;
637         }
638
639         append_markup_printf (tooltip,
640                               "<b>%s</b><small> (%s)</small>",
641                               id,
642                               tp_account_get_display_name (account));
643
644         if (!EMP_STR_EMPTY (status)) {
645                 append_markup_printf (tooltip, "\n<i>%s</i>", status);
646         }
647
648         if (subject) {
649                 append_markup_printf (tooltip, "\n<b>%s</b> %s",
650                                       _("Topic:"), subject);
651         }
652
653         if (g_list_find (priv->chats_composing, chat)) {
654                 append_markup_printf (tooltip, "\n%s", _("Typing a message."));
655         }
656
657         markup = g_string_free (tooltip, FALSE);
658         widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget");
659         gtk_widget_set_tooltip_markup (widget, markup);
660         widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-tooltip-widget");
661         gtk_widget_set_tooltip_markup (widget, markup);
662         g_free (markup);
663
664         /* Update tab and menu label */
665         widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
666         gtk_label_set_text (GTK_LABEL (widget), name);
667         widget = g_object_get_data (G_OBJECT (chat), "chat-window-menu-label");
668         gtk_label_set_text (GTK_LABEL (widget), name);
669
670         /* Update the window if it's the current chat */
671         if (priv->current_chat == chat) {
672                 chat_window_update (window);
673         }
674 }
675
676 static void
677 chat_window_chat_notify_cb (EmpathyChat *chat)
678 {
679         EmpathyContact *old_remote_contact;
680         EmpathyContact *remote_contact = NULL;
681
682         old_remote_contact = g_object_get_data (G_OBJECT (chat), "chat-window-remote-contact");
683         remote_contact = empathy_chat_get_remote_contact (chat);
684
685         if (old_remote_contact != remote_contact) {
686                 /* The remote-contact associated with the chat changed, we need
687                  * to keep track of any change of that contact and update the
688                  * window each time. */
689                 if (remote_contact) {
690                         g_signal_connect_swapped (remote_contact, "notify",
691                                                   G_CALLBACK (chat_window_update_chat_tab),
692                                                   chat);
693                 }
694                 if (old_remote_contact) {
695                         g_signal_handlers_disconnect_by_func (old_remote_contact,
696                                                               chat_window_update_chat_tab,
697                                                               chat);
698                 }
699
700                 g_object_set_data (G_OBJECT (chat), "chat-window-remote-contact",
701                                    remote_contact);
702         }
703
704         chat_window_update_chat_tab (chat);
705 }
706
707 static void
708 chat_window_insert_smiley_activate_cb (EmpathySmileyManager *manager,
709                                        EmpathySmiley        *smiley,
710                                        gpointer              window)
711 {
712         EmpathyChatWindowPriv *priv = GET_PRIV (window);
713         EmpathyChat           *chat;
714         GtkTextBuffer         *buffer;
715         GtkTextIter            iter;
716
717         chat = priv->current_chat;
718
719         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view));
720         gtk_text_buffer_get_end_iter (buffer, &iter);
721         gtk_text_buffer_insert (buffer, &iter, smiley->str, -1);
722 }
723
724 static void
725 chat_window_conv_activate_cb (GtkAction         *action,
726                               EmpathyChatWindow *window)
727 {
728         EmpathyChatWindowPriv *priv = GET_PRIV (window);
729         gboolean               is_room;
730         gboolean               active;
731         EmpathyContact        *remote_contact = NULL;
732
733         /* Favorite room menu */
734         is_room = empathy_chat_is_room (priv->current_chat);
735         if (is_room) {
736                 const gchar *room;
737                 TpAccount   *account;
738                 gboolean     found = FALSE;
739                 EmpathyChatroom *chatroom;
740
741                 room = empathy_chat_get_id (priv->current_chat);
742                 account = empathy_chat_get_account (priv->current_chat);
743                 chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
744                                                        account, room);
745                 if (chatroom != NULL)
746                         found = empathy_chatroom_is_favorite (chatroom);
747
748                 DEBUG ("This room %s favorite", found ? "is" : "is not");
749                 gtk_toggle_action_set_active (
750                         GTK_TOGGLE_ACTION (priv->menu_conv_favorite), found);
751         }
752         gtk_action_set_visible (priv->menu_conv_favorite, is_room);
753
754         /* Show contacts menu */
755         g_object_get (priv->current_chat,
756                       "remote-contact", &remote_contact,
757                       "show-contacts", &active,
758                       NULL);
759         if (remote_contact == NULL) {
760                 gtk_toggle_action_set_active (
761                         GTK_TOGGLE_ACTION (priv->menu_conv_toggle_contacts),
762                                            active);
763         }
764         gtk_action_set_visible (priv->menu_conv_toggle_contacts,
765                                 (remote_contact == NULL));
766         if (remote_contact != NULL) {
767                 g_object_unref (remote_contact);
768         }
769 }
770
771 static void
772 chat_window_clear_activate_cb (GtkAction         *action,
773                                EmpathyChatWindow *window)
774 {
775         EmpathyChatWindowPriv *priv = GET_PRIV (window);
776
777         empathy_chat_clear (priv->current_chat);
778 }
779
780 static void
781 chat_window_favorite_toggled_cb (GtkToggleAction   *toggle_action,
782                                  EmpathyChatWindow *window)
783 {
784         EmpathyChatWindowPriv *priv = GET_PRIV (window);
785         gboolean               active;
786         TpAccount             *account;
787         const gchar           *room;
788         EmpathyChatroom       *chatroom;
789
790         active = gtk_toggle_action_get_active (toggle_action);
791         account = empathy_chat_get_account (priv->current_chat);
792         room = empathy_chat_get_id (priv->current_chat);
793
794         chatroom = empathy_chatroom_manager_find (priv->chatroom_manager,
795                                                   account, room);
796
797         if (chatroom == NULL) {
798                 const gchar *name;
799
800                 name = empathy_chat_get_name (priv->current_chat);
801                 chatroom = empathy_chatroom_new_full (account, room, name, FALSE);
802                 empathy_chatroom_manager_add (priv->chatroom_manager, chatroom);
803                 g_object_unref (chatroom);
804         }
805
806         empathy_chatroom_set_favorite (chatroom, active);
807 }
808
809 static void
810 chat_window_contacts_toggled_cb (GtkToggleAction   *toggle_action,
811                                  EmpathyChatWindow *window)
812 {
813         EmpathyChatWindowPriv *priv = GET_PRIV (window);
814         gboolean               active;
815
816         active = gtk_toggle_action_get_active (toggle_action);
817
818         empathy_chat_set_show_contacts (priv->current_chat, active);
819 }
820
821 static void
822 chat_window_close_activate_cb (GtkAction         *action,
823                                EmpathyChatWindow *window)
824 {
825         EmpathyChatWindowPriv *priv;
826
827         priv = GET_PRIV (window);
828
829         g_return_if_fail (priv->current_chat != NULL);
830
831         empathy_chat_window_remove_chat (window, priv->current_chat);
832 }
833
834 static void
835 chat_window_edit_activate_cb (GtkAction         *action,
836                               EmpathyChatWindow *window)
837 {
838         EmpathyChatWindowPriv *priv;
839         GtkClipboard         *clipboard;
840         GtkTextBuffer        *buffer;
841         gboolean              text_available;
842
843         priv = GET_PRIV (window);
844
845         g_return_if_fail (priv->current_chat != NULL);
846
847         if (!empathy_chat_get_tp_chat (priv->current_chat)) {
848                 gtk_action_set_sensitive (priv->menu_edit_copy, FALSE);
849                 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
850                 gtk_action_set_sensitive (priv->menu_edit_paste, FALSE);
851                 return;
852         }
853
854         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view));
855         if (gtk_text_buffer_get_has_selection (buffer)) {
856                 gtk_action_set_sensitive (priv->menu_edit_copy, TRUE);
857                 gtk_action_set_sensitive (priv->menu_edit_cut, TRUE);
858         } else {
859                 gboolean selection;
860
861                 selection = empathy_chat_view_get_has_selection (priv->current_chat->view);
862
863                 gtk_action_set_sensitive (priv->menu_edit_cut, FALSE);
864                 gtk_action_set_sensitive (priv->menu_edit_copy, selection);
865         }
866
867         clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
868         text_available = gtk_clipboard_wait_is_text_available (clipboard);
869         gtk_action_set_sensitive (priv->menu_edit_paste, text_available);
870 }
871
872 static void
873 chat_window_cut_activate_cb (GtkAction         *action,
874                              EmpathyChatWindow *window)
875 {
876         EmpathyChatWindowPriv *priv;
877
878         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
879
880         priv = GET_PRIV (window);
881
882         empathy_chat_cut (priv->current_chat);
883 }
884
885 static void
886 chat_window_copy_activate_cb (GtkAction         *action,
887                               EmpathyChatWindow *window)
888 {
889         EmpathyChatWindowPriv *priv;
890
891         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
892
893         priv = GET_PRIV (window);
894
895         empathy_chat_copy (priv->current_chat);
896 }
897
898 static void
899 chat_window_paste_activate_cb (GtkAction         *action,
900                                EmpathyChatWindow *window)
901 {
902         EmpathyChatWindowPriv *priv;
903
904         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window));
905
906         priv = GET_PRIV (window);
907
908         empathy_chat_paste (priv->current_chat);
909 }
910
911 static void
912 chat_window_tabs_next_activate_cb (GtkAction         *action,
913                                    EmpathyChatWindow *window)
914 {
915         EmpathyChatWindowPriv *priv;
916         EmpathyChat           *chat;
917         gint                  index_, numPages;
918
919         priv = GET_PRIV (window);
920
921         chat = priv->current_chat;
922         index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
923         numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
924
925         if (index_ == (numPages - 1)) {
926                 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), 0);
927                 return;
928         }
929
930         gtk_notebook_next_page (GTK_NOTEBOOK (priv->notebook));
931 }
932
933 static void
934 chat_window_tabs_previous_activate_cb (GtkAction         *action,
935                                    EmpathyChatWindow *window)
936 {
937         EmpathyChatWindowPriv *priv;
938         EmpathyChat           *chat;
939         gint                  index_, numPages;
940
941         priv = GET_PRIV (window);
942
943         chat = priv->current_chat;
944         index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
945         numPages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook));
946
947         if (index_ <= 0) {
948                 gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), numPages - 1);
949                 return;
950         }
951
952         gtk_notebook_prev_page (GTK_NOTEBOOK (priv->notebook));
953 }
954
955 static void
956 chat_window_tabs_left_activate_cb (GtkAction         *action,
957                                    EmpathyChatWindow *window)
958 {
959         EmpathyChatWindowPriv *priv;
960         EmpathyChat           *chat;
961         gint                  index_;
962
963         priv = GET_PRIV (window);
964
965         chat = priv->current_chat;
966         index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
967         if (index_ <= 0) {
968                 return;
969         }
970
971         gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
972                                     GTK_WIDGET (chat),
973                                     index_ - 1);
974 }
975
976 static void
977 chat_window_tabs_right_activate_cb (GtkAction         *action,
978                                     EmpathyChatWindow *window)
979 {
980         EmpathyChatWindowPriv *priv;
981         EmpathyChat           *chat;
982         gint                  index_;
983
984         priv = GET_PRIV (window);
985
986         chat = priv->current_chat;
987         index_ = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
988
989         gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook),
990                                     GTK_WIDGET (chat),
991                                     index_ + 1);
992 }
993
994 static void
995 chat_window_detach_activate_cb (GtkAction         *action,
996                                 EmpathyChatWindow *window)
997 {
998         EmpathyChatWindowPriv *priv;
999         EmpathyChatWindow     *new_window;
1000         EmpathyChat           *chat;
1001
1002         priv = GET_PRIV (window);
1003
1004         chat = priv->current_chat;
1005         new_window = empathy_chat_window_new ();
1006
1007         empathy_chat_window_move_chat (window, new_window, chat);
1008
1009         priv = GET_PRIV (new_window);
1010         gtk_widget_show (priv->dialog);
1011 }
1012
1013 static void
1014 chat_window_help_contents_activate_cb (GtkAction         *action,
1015                                        EmpathyChatWindow *window)
1016 {
1017         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1018
1019         empathy_url_show (priv->dialog, "ghelp:empathy");
1020 }
1021
1022 static void
1023 chat_window_help_about_activate_cb (GtkAction         *action,
1024                                     EmpathyChatWindow *window)
1025 {
1026         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1027
1028         empathy_about_dialog_new (GTK_WINDOW (priv->dialog));
1029 }
1030
1031 static gboolean
1032 chat_window_delete_event_cb (GtkWidget        *dialog,
1033                              GdkEvent         *event,
1034                              EmpathyChatWindow *window)
1035 {
1036         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1037
1038         DEBUG ("Delete event received");
1039
1040         g_object_ref (window);
1041         while (priv->chats) {
1042                 empathy_chat_window_remove_chat (window, priv->chats->data);
1043         }
1044         g_object_unref (window);
1045
1046         return TRUE;
1047 }
1048
1049 static void
1050 chat_window_composing_cb (EmpathyChat       *chat,
1051                           gboolean          is_composing,
1052                           EmpathyChatWindow *window)
1053 {
1054         EmpathyChatWindowPriv *priv;
1055
1056         priv = GET_PRIV (window);
1057
1058         if (is_composing && !g_list_find (priv->chats_composing, chat)) {
1059                 priv->chats_composing = g_list_prepend (priv->chats_composing, chat);
1060         } else {
1061                 priv->chats_composing = g_list_remove (priv->chats_composing, chat);
1062         }
1063
1064         chat_window_update_chat_tab (chat);
1065 }
1066
1067 static void
1068 chat_window_set_urgency_hint (EmpathyChatWindow *window,
1069                               gboolean          urgent)
1070 {
1071         EmpathyChatWindowPriv *priv;
1072
1073         priv = GET_PRIV (window);
1074
1075         DEBUG ("Turning %s urgency hint", urgent ? "on" : "off");
1076         gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent);
1077 }
1078
1079 static void
1080 free_notification_data (NotificationData *data)
1081 {
1082         g_object_unref (data->chat);
1083         g_slice_free (NotificationData, data);
1084 }
1085
1086 static void
1087 chat_window_notification_closed_cb (NotifyNotification *notify,
1088                                     NotificationData *cb_data)
1089 {
1090         EmpathyNotificationClosedReason reason = 0;
1091         EmpathyChatWindowPriv *priv = GET_PRIV (cb_data->window);
1092
1093 #ifdef notify_notification_get_closed_reason
1094         reason = notify_notification_get_closed_reason (notify);
1095 #endif
1096         if (reason == EMPATHY_NOTIFICATION_CLOSED_DISMISSED) {
1097                 empathy_chat_window_present_chat (cb_data->chat);
1098         }
1099
1100         g_object_unref (notify);
1101         priv->notification = NULL;
1102         free_notification_data (cb_data);
1103         priv->notification_data = NULL;
1104 }
1105
1106 static void
1107 chat_window_show_or_update_notification (EmpathyChatWindow *window,
1108                                          EmpathyMessage *message,
1109                                          EmpathyChat *chat)
1110 {
1111         EmpathyContact *sender;
1112         const gchar *header;
1113         char *escaped;
1114         const char *body;
1115         GdkPixbuf *pixbuf;
1116         EmpathyChatWindowPriv *priv = GET_PRIV (window);
1117         gboolean res;
1118
1119         if (!empathy_notify_manager_notification_is_enabled (priv->notify_mgr)) {
1120                 return;
1121         } else {
1122                 empathy_conf_get_bool (empathy_conf_get (),
1123                                        EMPATHY_PREFS_NOTIFICATIONS_FOCUS, &res);
1124                 if (!res) {
1125                         return;
1126                 }
1127         }
1128
1129         sender = empathy_message_get_sender (message);
1130         header = empathy_contact_get_name (sender);
1131         body = empathy_message_get_body (message);
1132         escaped = g_markup_escape_text (body, -1);
1133
1134         if (priv->notification != NULL) {
1135                 notify_notification_update (priv->notification,
1136                                             header, escaped, NULL);
1137         } else {
1138                 NotificationData *cb_data = cb_data = g_slice_new0 (NotificationData);
1139
1140                 cb_data->chat = g_object_ref (chat);
1141                 cb_data->window = window;
1142
1143                 priv->notification_data = cb_data;
1144                 priv->notification = notify_notification_new (header, escaped, NULL, NULL);
1145                 notify_notification_set_timeout (priv->notification, NOTIFY_EXPIRES_DEFAULT);
1146
1147                 g_signal_connect (priv->notification, "closed",
1148                                   G_CALLBACK (chat_window_notification_closed_cb), cb_data);
1149         }
1150
1151         pixbuf = empathy_notify_manager_get_pixbuf_for_notification (priv->notify_mgr,
1152                 sender, EMPATHY_IMAGE_NEW_MESSAGE);
1153
1154         if (pixbuf != NULL) {
1155                 notify_notification_set_icon_from_pixbuf (priv->notification, pixbuf);
1156                 g_object_unref (pixbuf);
1157         }
1158
1159         notify_notification_show (priv->notification, NULL);
1160
1161         g_free (escaped);
1162 }
1163
1164 static void
1165 chat_window_set_highlight_room_tab_label (EmpathyChat *chat)
1166 {
1167         gchar *markup;
1168         GtkWidget *widget;
1169
1170         if (!empathy_chat_is_room (chat))
1171                 return;
1172
1173         markup = g_markup_printf_escaped (
1174                 "<span color=\"red\" weight=\"bold\">%s</span>",
1175                 empathy_chat_get_name (chat));
1176
1177         widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label");
1178         gtk_label_set_markup (GTK_LABEL (widget), markup);
1179         g_free (markup);
1180 }
1181
1182 static void
1183 chat_window_new_message_cb (EmpathyChat       *chat,
1184                             EmpathyMessage    *message,
1185                             EmpathyChatWindow *window)
1186 {
1187         EmpathyChatWindowPriv *priv;
1188         gboolean              has_focus;
1189         gboolean              needs_urgency;
1190         EmpathyContact        *sender;
1191
1192         priv = GET_PRIV (window);
1193
1194         has_focus = empathy_chat_window_has_focus (window);
1195
1196         /* - if we're the sender, we play the sound if it's specified in the
1197          *   preferences and we're not away.
1198          * - if we receive a message, we play the sound if it's specified in the
1199          *   preferences and the window does not have focus on the chat receiving
1200          *   the message.
1201          */
1202
1203         sender = empathy_message_get_sender (message);
1204
1205         if (empathy_contact_is_user (sender)) {
1206                 empathy_sound_play (GTK_WIDGET (priv->dialog),
1207                                     EMPATHY_SOUND_MESSAGE_OUTGOING);
1208         }
1209
1210         if (has_focus && priv->current_chat == chat) {
1211                 /* window and tab are focused so consider the message to be read */
1212                 empathy_chat_messages_read (chat);
1213                 return;
1214         }
1215
1216         if (!g_list_find (priv->chats_new_msg, chat)) {
1217                 priv->chats_new_msg = g_list_prepend (priv->chats_new_msg, chat);
1218                 chat_window_update_chat_tab (chat);
1219         }
1220
1221         /* If empathy_chat_is_room () returns TRUE, that means it's a named MUC.
1222          * If empathy_chat_get_remote_contact () returns NULL, that means it's
1223          * an unamed MUC (msn-like).
1224          * In case of a MUC, we set urgency only if the message contains our
1225          * alias. */
1226         if (empathy_chat_is_room (chat) ||
1227             empathy_chat_get_remote_contact (chat) == NULL) {
1228                 needs_urgency = empathy_message_should_highlight (message);
1229         } else {
1230                 needs_urgency = TRUE;
1231         }
1232
1233         if (needs_urgency) {
1234                 if (!has_focus) {
1235                         chat_window_set_urgency_hint (window, TRUE);
1236                         chat_window_set_highlight_room_tab_label (chat);
1237                 }
1238
1239                 empathy_sound_play (GTK_WIDGET (priv->dialog),
1240                     EMPATHY_SOUND_MESSAGE_INCOMING);
1241                 chat_window_show_or_update_notification (window, message, chat);
1242         }
1243
1244         /* update the number of unread messages */
1245         chat_window_title_update (priv);
1246 }
1247
1248 static GtkNotebook *
1249 chat_window_detach_hook (GtkNotebook *source,
1250                          GtkWidget   *page,
1251                          gint         x,
1252                          gint         y,
1253                          gpointer     user_data)
1254 {
1255         EmpathyChatWindowPriv *priv;
1256         EmpathyChatWindow     *window, *new_window;
1257         EmpathyChat           *chat;
1258
1259         chat = EMPATHY_CHAT (page);
1260         window = chat_window_find_chat (chat);
1261
1262         new_window = empathy_chat_window_new ();
1263         priv = GET_PRIV (new_window);
1264
1265         DEBUG ("Detach hook called");
1266
1267         empathy_chat_window_move_chat (window, new_window, chat);
1268
1269         gtk_window_move (GTK_WINDOW (priv->dialog), x, y);
1270         gtk_widget_show (priv->dialog);
1271
1272         return NULL;
1273 }
1274
1275 static void
1276 chat_window_page_switched_cb (GtkNotebook      *notebook,
1277                               GtkNotebookPage  *page,
1278                               gint              page_num,
1279                               EmpathyChatWindow *window)
1280 {
1281         EmpathyChatWindowPriv *priv;
1282         EmpathyChat           *chat;
1283         GtkWidget            *child;
1284
1285         DEBUG ("Page switched");
1286
1287         priv = GET_PRIV (window);
1288
1289         child = gtk_notebook_get_nth_page (notebook, page_num);
1290         chat = EMPATHY_CHAT (child);
1291
1292         if (priv->page_added) {
1293                 priv->page_added = FALSE;
1294                 empathy_chat_scroll_down (chat);
1295         }
1296         else if (priv->current_chat == chat) {
1297                 return;
1298         }
1299
1300         priv->current_chat = chat;
1301         priv->chats_new_msg = g_list_remove (priv->chats_new_msg, chat);
1302         empathy_chat_messages_read (chat);
1303
1304         chat_window_update_chat_tab (chat);
1305 }
1306
1307 static void
1308 chat_window_page_added_cb (GtkNotebook      *notebook,
1309                            GtkWidget        *child,
1310                            guint             page_num,
1311                            EmpathyChatWindow *window)
1312 {
1313         EmpathyChatWindowPriv *priv;
1314         EmpathyChat           *chat;
1315
1316         priv = GET_PRIV (window);
1317
1318         /* If we just received DND to the same window, we don't want
1319          * to do anything here like removing the tab and then readding
1320          * it, so we return here and in "page-added".
1321          */
1322         if (priv->dnd_same_window) {
1323                 DEBUG ("Page added (back to the same window)");
1324                 priv->dnd_same_window = FALSE;
1325                 return;
1326         }
1327
1328         DEBUG ("Page added");
1329
1330         /* Get chat object */
1331         chat = EMPATHY_CHAT (child);
1332
1333         /* Connect chat signals for this window */
1334         g_signal_connect (chat, "composing",
1335                           G_CALLBACK (chat_window_composing_cb),
1336                           window);
1337         g_signal_connect (chat, "new-message",
1338                           G_CALLBACK (chat_window_new_message_cb),
1339                           window);
1340         g_signal_connect (chat, "notify::tp-chat",
1341                           G_CALLBACK (chat_window_update_chat_tab),
1342                           window);
1343
1344         /* Set flag so we know to perform some special operations on
1345          * switch page due to the new page being added.
1346          */
1347         priv->page_added = TRUE;
1348
1349         /* Get list of chats up to date */
1350         priv->chats = g_list_append (priv->chats, chat);
1351
1352         chat_window_update_chat_tab (chat);
1353 }
1354
1355 static void
1356 chat_window_page_removed_cb (GtkNotebook      *notebook,
1357                              GtkWidget        *child,
1358                              guint             page_num,
1359                              EmpathyChatWindow *window)
1360 {
1361         EmpathyChatWindowPriv *priv;
1362         EmpathyChat           *chat;
1363
1364         priv = GET_PRIV (window);
1365
1366         /* If we just received DND to the same window, we don't want
1367          * to do anything here like removing the tab and then readding
1368          * it, so we return here and in "page-added".
1369          */
1370         if (priv->dnd_same_window) {
1371                 DEBUG ("Page removed (and will be readded to same window)");
1372                 return;
1373         }
1374
1375         DEBUG ("Page removed");
1376
1377         /* Get chat object */
1378         chat = EMPATHY_CHAT (child);
1379
1380         /* Disconnect all signal handlers for this chat and this window */
1381         g_signal_handlers_disconnect_by_func (chat,
1382                                               G_CALLBACK (chat_window_composing_cb),
1383                                               window);
1384         g_signal_handlers_disconnect_by_func (chat,
1385                                               G_CALLBACK (chat_window_new_message_cb),
1386                                               window);
1387         g_signal_handlers_disconnect_by_func (chat,
1388                                               G_CALLBACK (chat_window_update_chat_tab),
1389                                               window);
1390
1391         /* Keep list of chats up to date */
1392         priv->chats = g_list_remove (priv->chats, chat);
1393         priv->chats_new_msg = g_list_remove (priv->chats_new_msg, chat);
1394         empathy_chat_messages_read (chat);
1395         priv->chats_composing = g_list_remove (priv->chats_composing, chat);
1396
1397         if (priv->chats == NULL) {
1398                 g_object_unref (window);
1399         } else {
1400                 chat_window_update (window);
1401         }
1402 }
1403
1404 static gboolean
1405 chat_window_focus_in_event_cb (GtkWidget        *widget,
1406                                GdkEvent         *event,
1407                                EmpathyChatWindow *window)
1408 {
1409         EmpathyChatWindowPriv *priv;
1410
1411         DEBUG ("Focus in event, updating title");
1412
1413         priv = GET_PRIV (window);
1414
1415         priv->chats_new_msg = g_list_remove (priv->chats_new_msg, priv->current_chat);
1416         empathy_chat_messages_read (priv->current_chat);
1417
1418         chat_window_set_urgency_hint (window, FALSE);
1419
1420         /* Update the title, since we now mark all unread messages as read. */
1421         chat_window_update_chat_tab (priv->current_chat);
1422
1423         return FALSE;
1424 }
1425
1426 static gboolean
1427 chat_window_drag_motion (GtkWidget        *widget,
1428                          GdkDragContext   *context,
1429                          int               x,
1430                          int               y,
1431                          guint             time_,
1432                          EmpathyChatWindow *window)
1433 {
1434         GdkAtom target;
1435         EmpathyChatWindowPriv *priv;
1436         GdkAtom dest_target;
1437
1438         priv = GET_PRIV (window);
1439
1440         target = gtk_drag_dest_find_target (widget, context, NULL);
1441
1442         dest_target = gdk_atom_intern_static_string ("text/uri-list");
1443         if (target == dest_target) {
1444                 /* This is a file drag.  Ensure the contact is online and set the
1445                    drag type to COPY.  Note that it's possible that the tab will
1446                    be switched by GTK+ after a timeout from drag_motion without
1447                    getting another drag_motion to disable the drop.  You have
1448                    to hold your mouse really still.
1449                  */
1450                 EmpathyContact *contact;
1451
1452                 priv = GET_PRIV (window);
1453                 contact = empathy_chat_get_remote_contact (priv->current_chat);
1454                 /* contact is NULL for multi-user chats.  We don't do
1455                  * file transfers to MUCs.  We also don't send files
1456                  * to offline contacts or contacts that don't support
1457                  * file transfer.
1458                  */
1459                 if ((contact == NULL) || !empathy_contact_is_online (contact)) {
1460                         gdk_drag_status (context, 0, time_);
1461                         return FALSE;
1462                 }
1463                 if (!(empathy_contact_get_capabilities (contact)
1464                            & EMPATHY_CAPABILITIES_FT)) {
1465                         gdk_drag_status (context, 0, time_);
1466                         return FALSE;
1467                 }
1468                 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1469                 return TRUE;
1470         }
1471
1472         dest_target = gdk_atom_intern_static_string ("text/contact-id");
1473         if (target == dest_target) {
1474                 /* This is a drag of a contact from a contact list.  Set to COPY.
1475                    FIXME: If this drag is to a MUC window, it invites the user.
1476                    Otherwise, it opens a chat.  Should we use a different drag
1477                    type for invites?  Should we allow ASK?
1478                  */
1479                 gdk_drag_status (context, GDK_ACTION_COPY, time_);
1480                 return TRUE;
1481         }
1482
1483         /* Otherwise, it must be a notebook tab drag.  Set to MOVE. */
1484         gdk_drag_status (context, GDK_ACTION_MOVE, time_);
1485         return TRUE;
1486 }
1487
1488 static void
1489 chat_window_drag_data_received (GtkWidget        *widget,
1490                                 GdkDragContext   *context,
1491                                 int               x,
1492                                 int               y,
1493                                 GtkSelectionData *selection,
1494                                 guint             info,
1495                                 guint             time_,
1496                                 EmpathyChatWindow *window)
1497 {
1498         if (info == DND_DRAG_TYPE_CONTACT_ID) {
1499                 EmpathyChat           *chat = NULL;
1500                 EmpathyChatWindow     *old_window;
1501                 TpAccount             *account = NULL;
1502                 TpAccountManager      *account_manager;
1503                 const gchar           *id;
1504                 gchar                **strv;
1505                 const gchar           *account_id;
1506                 const gchar           *contact_id;
1507
1508                 id = (const gchar*) gtk_selection_data_get_data (selection);
1509
1510                 /* FIXME: Perhaps should be sure that the account manager is
1511                  * prepared before calling _ensure_account on it. */
1512                 account_manager = tp_account_manager_dup ();
1513
1514                 DEBUG ("DND contact from roster with id:'%s'", id);
1515
1516                 strv = g_strsplit (id, ":", 2);
1517                 if (g_strv_length (strv) == 2) {
1518                         account_id = strv[0];
1519                         contact_id = strv[1];
1520                         account =
1521                                 tp_account_manager_ensure_account (account_manager, account_id);
1522                         if (account != NULL)
1523                                 chat = empathy_chat_window_find_chat (account, contact_id);
1524                 }
1525
1526                 if (account == NULL) {
1527                         g_strfreev (strv);
1528                         gtk_drag_finish (context, FALSE, FALSE, time_);
1529                         return;
1530                 }
1531
1532                 if (!chat) {
1533                         TpConnection *connection;
1534
1535                         connection = tp_account_get_connection (account);
1536
1537                         if (connection) {
1538                                 empathy_dispatcher_chat_with_contact_id (
1539                                         connection, contact_id, NULL, NULL);
1540                         }
1541
1542                         g_strfreev (strv);
1543                         return;
1544                 }
1545                 g_object_unref (account_manager);
1546                 g_strfreev (strv);
1547
1548                 old_window = chat_window_find_chat (chat);
1549                 if (old_window) {
1550                         if (old_window == window) {
1551                                 gtk_drag_finish (context, TRUE, FALSE, time_);
1552                                 return;
1553                         }
1554
1555                         empathy_chat_window_move_chat (old_window, window, chat);
1556                 } else {
1557                         empathy_chat_window_add_chat (window, chat);
1558                 }
1559
1560                 /* Added to take care of any outstanding chat events */
1561                 empathy_chat_window_present_chat (chat);
1562
1563                 /* We should return TRUE to remove the data when doing
1564                  * GDK_ACTION_MOVE, but we don't here otherwise it has
1565                  * weird consequences, and we handle that internally
1566                  * anyway with add_chat () and remove_chat ().
1567                  */
1568                 gtk_drag_finish (context, TRUE, FALSE, time_);
1569         }
1570         else if (info == DND_DRAG_TYPE_URI_LIST) {
1571                 EmpathyChatWindowPriv *priv;
1572                 EmpathyContact *contact;
1573                 const gchar *data;
1574
1575                 priv = GET_PRIV (window);
1576                 contact = empathy_chat_get_remote_contact (priv->current_chat);
1577
1578                 /* contact is NULL when current_chat is a multi-user chat.
1579                  * We don't do file transfers to MUCs, so just cancel the drag.
1580                  */
1581                 if (contact == NULL) {
1582                         gtk_drag_finish (context, TRUE, FALSE, time_);
1583                         return;
1584                 }
1585
1586                 data = (const gchar *) gtk_selection_data_get_data (selection);
1587                 empathy_send_file_from_uri_list (contact, data);
1588
1589                 gtk_drag_finish (context, TRUE, FALSE, time_);
1590         }
1591         else if (info == DND_DRAG_TYPE_TAB) {
1592                 EmpathyChat        **chat;
1593                 EmpathyChatWindow   *old_window = NULL;
1594
1595                 DEBUG ("DND tab");
1596
1597                 chat = (void *) gtk_selection_data_get_data (selection);
1598                 old_window = chat_window_find_chat (*chat);
1599
1600                 if (old_window) {
1601                         EmpathyChatWindowPriv *priv;
1602
1603                         priv = GET_PRIV (window);
1604
1605                         if (old_window == window) {
1606                                 DEBUG ("DND tab (within same window)");
1607                                 priv->dnd_same_window = TRUE;
1608                                 gtk_drag_finish (context, TRUE, FALSE, time_);
1609                                 return;
1610                         }
1611
1612                         priv->dnd_same_window = FALSE;
1613                 }
1614
1615                 /* We should return TRUE to remove the data when doing
1616                  * GDK_ACTION_MOVE, but we don't here otherwise it has
1617                  * weird consequences, and we handle that internally
1618                  * anyway with add_chat () and remove_chat ().
1619                  */
1620                 gtk_drag_finish (context, TRUE, FALSE, time_);
1621         } else {
1622                 DEBUG ("DND from unknown source");
1623                 gtk_drag_finish (context, FALSE, FALSE, time_);
1624         }
1625 }
1626
1627 static void
1628 chat_window_finalize (GObject *object)
1629 {
1630         EmpathyChatWindow     *window;
1631         EmpathyChatWindowPriv *priv;
1632
1633         window = EMPATHY_CHAT_WINDOW (object);
1634         priv = GET_PRIV (window);
1635
1636         DEBUG ("Finalized: %p", object);
1637
1638         g_object_unref (priv->ui_manager);
1639         g_object_unref (priv->chatroom_manager);
1640         g_object_unref (priv->notify_mgr);
1641
1642         if (priv->notification != NULL) {
1643                 notify_notification_close (priv->notification, NULL);
1644                 g_object_unref (priv->notification);
1645                 priv->notification = NULL;
1646                 if (priv->notification_data != NULL)
1647                         {
1648                                 free_notification_data (priv->notification_data);
1649                                 priv->notification_data = NULL;
1650                         }
1651         }
1652
1653         if (priv->contact_targets) {
1654                 gtk_target_list_unref (priv->contact_targets);
1655         }
1656         if (priv->file_targets) {
1657                 gtk_target_list_unref (priv->file_targets);
1658         }
1659
1660         chat_windows = g_list_remove (chat_windows, window);
1661         gtk_widget_destroy (priv->dialog);
1662
1663         G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object);
1664 }
1665
1666 static void
1667 empathy_chat_window_class_init (EmpathyChatWindowClass *klass)
1668 {
1669         GObjectClass *object_class = G_OBJECT_CLASS (klass);
1670
1671         object_class->finalize = chat_window_finalize;
1672
1673         g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv));
1674
1675         /* Set up a style for the close button with no focus padding. */
1676         gtk_rc_parse_string (
1677                 "style \"empathy-close-button-style\"\n"
1678                 "{\n"
1679                 "  GtkWidget::focus-padding = 0\n"
1680                 "  xthickness = 0\n"
1681                 "  ythickness = 0\n"
1682                 "}\n"
1683                 "widget \"*.empathy-close-button\" style \"empathy-close-button-style\"");
1684
1685         gtk_notebook_set_window_creation_hook (chat_window_detach_hook, NULL, NULL);
1686 }
1687
1688 static void
1689 empathy_chat_window_init (EmpathyChatWindow *window)
1690 {
1691         GtkBuilder            *gui;
1692         GtkAccelGroup         *accel_group;
1693         GClosure              *closure;
1694         GtkWidget             *menu;
1695         GtkWidget             *submenu;
1696         guint                  i;
1697         GtkWidget             *chat_vbox;
1698         gchar                 *filename;
1699         EmpathySmileyManager  *smiley_manager;
1700         EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window,
1701                 EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv);
1702
1703         window->priv = priv;
1704         filename = empathy_file_lookup ("empathy-chat-window.ui", "src");
1705         gui = empathy_builder_get_file (filename,
1706                                        "chat_window", &priv->dialog,
1707                                        "chat_vbox", &chat_vbox,
1708                                        "ui_manager", &priv->ui_manager,
1709                                        "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley,
1710                                        "menu_conv_favorite", &priv->menu_conv_favorite,
1711                                        "menu_conv_toggle_contacts", &priv->menu_conv_toggle_contacts,
1712                                        "menu_edit_cut", &priv->menu_edit_cut,
1713                                        "menu_edit_copy", &priv->menu_edit_copy,
1714                                        "menu_edit_paste", &priv->menu_edit_paste,
1715                                        "menu_tabs_next", &priv->menu_tabs_next,
1716                                        "menu_tabs_prev", &priv->menu_tabs_prev,
1717                                        "menu_tabs_left", &priv->menu_tabs_left,
1718                                        "menu_tabs_right", &priv->menu_tabs_right,
1719                                        "menu_tabs_detach", &priv->menu_tabs_detach,
1720                                        NULL);
1721         g_free (filename);
1722
1723         empathy_builder_connect (gui, window,
1724                               "menu_conv", "activate", chat_window_conv_activate_cb,
1725                               "menu_conv_clear", "activate", chat_window_clear_activate_cb,
1726                               "menu_conv_favorite", "toggled", chat_window_favorite_toggled_cb,
1727                               "menu_conv_toggle_contacts", "toggled", chat_window_contacts_toggled_cb,
1728                               "menu_conv_close", "activate", chat_window_close_activate_cb,
1729                               "menu_edit", "activate", chat_window_edit_activate_cb,
1730                               "menu_edit_cut", "activate", chat_window_cut_activate_cb,
1731                               "menu_edit_copy", "activate", chat_window_copy_activate_cb,
1732                               "menu_edit_paste", "activate", chat_window_paste_activate_cb,
1733                               "menu_tabs_next", "activate", chat_window_tabs_next_activate_cb,
1734                               "menu_tabs_prev", "activate", chat_window_tabs_previous_activate_cb,
1735                               "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb,
1736                               "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb,
1737                               "menu_tabs_detach", "activate", chat_window_detach_activate_cb,
1738                               "menu_help_contents", "activate", chat_window_help_contents_activate_cb,
1739                               "menu_help_about", "activate", chat_window_help_about_activate_cb,
1740                               NULL);
1741
1742         g_object_ref (priv->ui_manager);
1743         g_object_unref (gui);
1744
1745         priv->chatroom_manager = empathy_chatroom_manager_dup_singleton (NULL);
1746
1747         priv->notebook = gtk_notebook_new ();
1748         gtk_notebook_set_group (GTK_NOTEBOOK (priv->notebook), "EmpathyChatWindow");
1749         gtk_notebook_set_scrollable (GTK_NOTEBOOK (priv->notebook), TRUE);
1750         gtk_notebook_popup_enable (GTK_NOTEBOOK (priv->notebook));
1751         gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0);
1752         gtk_widget_show (priv->notebook);
1753
1754         /* Set up accels */
1755         accel_group = gtk_accel_group_new ();
1756         gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group);
1757
1758         for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) {
1759                 closure =  g_cclosure_new (G_CALLBACK (chat_window_accel_cb),
1760                                            window,
1761                                            NULL);
1762                 gtk_accel_group_connect (accel_group,
1763                                          tab_accel_keys[i],
1764                                          GDK_MOD1_MASK,
1765                                          0,
1766                                          closure);
1767         }
1768
1769         g_object_unref (accel_group);
1770
1771         /* Set up drag target lists */
1772         priv->contact_targets = gtk_target_list_new (drag_types_dest_contact,
1773                                                      G_N_ELEMENTS (drag_types_dest_contact));
1774         priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1775                                                   G_N_ELEMENTS (drag_types_dest_file));
1776
1777         /* Set up smiley menu */
1778         smiley_manager = empathy_smiley_manager_dup_singleton ();
1779         submenu = empathy_smiley_menu_new (smiley_manager,
1780                                            chat_window_insert_smiley_activate_cb,
1781                                            window);
1782         menu = gtk_ui_manager_get_widget (priv->ui_manager,
1783                 "/chats_menubar/menu_conv/menu_conv_insert_smiley");
1784         gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
1785         g_object_unref (smiley_manager);
1786
1787         /* Set up signals we can't do with ui file since we may need to
1788          * block/unblock them at some later stage.
1789          */
1790
1791         g_signal_connect (priv->dialog,
1792                           "delete_event",
1793                           G_CALLBACK (chat_window_delete_event_cb),
1794                           window);
1795         g_signal_connect (priv->dialog,
1796                           "focus_in_event",
1797                           G_CALLBACK (chat_window_focus_in_event_cb),
1798                           window);
1799         g_signal_connect_after (priv->notebook,
1800                                 "switch_page",
1801                                 G_CALLBACK (chat_window_page_switched_cb),
1802                                 window);
1803         g_signal_connect (priv->notebook,
1804                           "page_added",
1805                           G_CALLBACK (chat_window_page_added_cb),
1806                           window);
1807         g_signal_connect (priv->notebook,
1808                           "page_removed",
1809                           G_CALLBACK (chat_window_page_removed_cb),
1810                           window);
1811
1812         /* Set up drag and drop */
1813         gtk_drag_dest_set (GTK_WIDGET (priv->notebook),
1814                            GTK_DEST_DEFAULT_ALL,
1815                            drag_types_dest,
1816                            G_N_ELEMENTS (drag_types_dest),
1817                            GDK_ACTION_MOVE | GDK_ACTION_COPY);
1818
1819         /* connect_after to allow GtkNotebook's built-in tab switching */
1820         g_signal_connect_after (priv->notebook,
1821                                 "drag-motion",
1822                                 G_CALLBACK (chat_window_drag_motion),
1823                                 window);
1824         g_signal_connect (priv->notebook,
1825                           "drag-data-received",
1826                           G_CALLBACK (chat_window_drag_data_received),
1827                           window);
1828
1829         chat_windows = g_list_prepend (chat_windows, window);
1830
1831         /* Set up private details */
1832         priv->chats = NULL;
1833         priv->chats_new_msg = NULL;
1834         priv->chats_composing = NULL;
1835         priv->current_chat = NULL;
1836
1837         priv->notify_mgr = empathy_notify_manager_dup_singleton ();
1838 }
1839
1840 EmpathyChatWindow *
1841 empathy_chat_window_new (void)
1842 {
1843         return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL));
1844 }
1845
1846 /* Returns the window to open a new tab in if there is only one window
1847  * visble, otherwise, returns NULL indicating that a new window should
1848  * be added.
1849  */
1850 EmpathyChatWindow *
1851 empathy_chat_window_get_default (gboolean room)
1852 {
1853         GList    *l;
1854         gboolean  separate_windows = TRUE;
1855
1856         empathy_conf_get_bool (empathy_conf_get (),
1857                               EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS,
1858                               &separate_windows);
1859
1860         if (separate_windows) {
1861                 /* Always create a new window */
1862                 return NULL;
1863         }
1864
1865         for (l = chat_windows; l; l = l->next) {
1866                 EmpathyChatWindowPriv *priv;
1867                 EmpathyChatWindow *chat_window;
1868                 GtkWidget         *dialog;
1869
1870                 chat_window = l->data;
1871                 priv = GET_PRIV (chat_window);
1872
1873                 dialog = empathy_chat_window_get_dialog (chat_window);
1874                 if (empathy_window_get_is_visible (GTK_WINDOW (dialog))) {
1875                         guint nb_rooms, nb_private;
1876                         empathy_chat_window_get_nb_chats (chat_window, &nb_rooms, &nb_private);
1877
1878                         /* Skip the window if there aren't any rooms in it */
1879                         if (room && nb_rooms == 0)
1880                                 continue;
1881
1882                         /* Skip the window if there aren't any 1-1 chats in it */
1883                         if (!room && nb_private == 0)
1884                                 continue;
1885
1886                         /* Found a visible window on this desktop */
1887                         return chat_window;
1888                 }
1889         }
1890
1891         return NULL;
1892 }
1893
1894 GtkWidget *
1895 empathy_chat_window_get_dialog (EmpathyChatWindow *window)
1896 {
1897         EmpathyChatWindowPriv *priv;
1898
1899         g_return_val_if_fail (window != NULL, NULL);
1900
1901         priv = GET_PRIV (window);
1902
1903         return priv->dialog;
1904 }
1905
1906 void
1907 empathy_chat_window_add_chat (EmpathyChatWindow *window,
1908                               EmpathyChat       *chat)
1909 {
1910         EmpathyChatWindowPriv *priv;
1911         GtkWidget             *label;
1912         GtkWidget             *popup_label;
1913         GtkWidget             *child;
1914
1915         g_return_if_fail (window != NULL);
1916         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1917
1918         priv = GET_PRIV (window);
1919
1920         /* Reference the chat object */
1921         g_object_ref (chat);
1922
1923         /* If this window has just been created, position it */
1924         if (priv->chats == NULL) {
1925                 const gchar *name = "chat-window";
1926                 gboolean     separate_windows;
1927
1928                 empathy_conf_get_bool (empathy_conf_get (),
1929                                        EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS,
1930                                        &separate_windows);
1931
1932                 if (separate_windows) {
1933                         name = empathy_chat_get_id (chat);
1934                 }
1935
1936                 empathy_geometry_bind (GTK_WINDOW (priv->dialog), name);
1937         }
1938
1939         child = GTK_WIDGET (chat);
1940         label = chat_window_create_label (window, chat, TRUE);
1941         popup_label = chat_window_create_label (window, chat, FALSE);
1942         gtk_widget_show (child);
1943
1944         g_signal_connect (chat, "notify::name",
1945                           G_CALLBACK (chat_window_chat_notify_cb),
1946                           NULL);
1947         g_signal_connect (chat, "notify::subject",
1948                           G_CALLBACK (chat_window_chat_notify_cb),
1949                           NULL);
1950         g_signal_connect (chat, "notify::remote-contact",
1951                           G_CALLBACK (chat_window_chat_notify_cb),
1952                           NULL);
1953         chat_window_chat_notify_cb (chat);
1954
1955         gtk_notebook_append_page_menu (GTK_NOTEBOOK (priv->notebook), child, label, popup_label);
1956         gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
1957         gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE);
1958         gtk_notebook_set_tab_label_packing (GTK_NOTEBOOK (priv->notebook), child,
1959                                             TRUE, TRUE, GTK_PACK_START);
1960
1961         DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count);
1962 }
1963
1964 void
1965 empathy_chat_window_remove_chat (EmpathyChatWindow *window,
1966                                  EmpathyChat       *chat)
1967 {
1968         EmpathyChatWindowPriv *priv;
1969         gint                   position;
1970         EmpathyContact        *remote_contact;
1971
1972         g_return_if_fail (window != NULL);
1973         g_return_if_fail (EMPATHY_IS_CHAT (chat));
1974
1975         priv = GET_PRIV (window);
1976
1977         g_signal_handlers_disconnect_by_func (chat,
1978                                               chat_window_chat_notify_cb,
1979                                               NULL);
1980         remote_contact = g_object_get_data (G_OBJECT (chat),
1981                                             "chat-window-remote-contact");
1982         if (remote_contact) {
1983                 g_signal_handlers_disconnect_by_func (remote_contact,
1984                                                       chat_window_update_chat_tab,
1985                                                       chat);
1986         }
1987
1988         position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
1989                                           GTK_WIDGET (chat));
1990         gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position);
1991
1992         DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1);
1993
1994         g_object_unref (chat);
1995 }
1996
1997 void
1998 empathy_chat_window_move_chat (EmpathyChatWindow *old_window,
1999                                EmpathyChatWindow *new_window,
2000                                EmpathyChat       *chat)
2001 {
2002         GtkWidget *widget;
2003
2004         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window));
2005         g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window));
2006         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2007
2008         widget = GTK_WIDGET (chat);
2009
2010         DEBUG ("Chat moving with widget:%p (%d references)", widget,
2011                 G_OBJECT (widget)->ref_count);
2012
2013         /* We reference here to make sure we don't loose the widget
2014          * and the EmpathyChat object during the move.
2015          */
2016         g_object_ref (chat);
2017         g_object_ref (widget);
2018
2019         empathy_chat_window_remove_chat (old_window, chat);
2020         empathy_chat_window_add_chat (new_window, chat);
2021
2022         g_object_unref (widget);
2023         g_object_unref (chat);
2024 }
2025
2026 void
2027 empathy_chat_window_switch_to_chat (EmpathyChatWindow *window,
2028                                     EmpathyChat       *chat)
2029 {
2030         EmpathyChatWindowPriv *priv;
2031         gint                  page_num;
2032
2033         g_return_if_fail (window != NULL);
2034         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2035
2036         priv = GET_PRIV (window);
2037
2038         page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook),
2039                                           GTK_WIDGET (chat));
2040         gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook),
2041                                        page_num);
2042 }
2043
2044 gboolean
2045 empathy_chat_window_has_focus (EmpathyChatWindow *window)
2046 {
2047         EmpathyChatWindowPriv *priv;
2048         gboolean              has_focus;
2049
2050         g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE);
2051
2052         priv = GET_PRIV (window);
2053
2054         g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL);
2055
2056         return has_focus;
2057 }
2058
2059 EmpathyChat *
2060 empathy_chat_window_find_chat (TpAccount   *account,
2061                                const gchar *id)
2062 {
2063         GList *l;
2064
2065         g_return_val_if_fail (!EMP_STR_EMPTY (id), NULL);
2066
2067         for (l = chat_windows; l; l = l->next) {
2068                 EmpathyChatWindowPriv *priv;
2069                 EmpathyChatWindow     *window;
2070                 GList                *ll;
2071
2072                 window = l->data;
2073                 priv = GET_PRIV (window);
2074
2075                 for (ll = priv->chats; ll; ll = ll->next) {
2076                         EmpathyChat *chat;
2077
2078                         chat = ll->data;
2079
2080                         if (account == empathy_chat_get_account (chat) &&
2081                             !tp_strdiff (id, empathy_chat_get_id (chat))) {
2082                                 return chat;
2083                         }
2084                 }
2085         }
2086
2087         return NULL;
2088 }
2089
2090 void
2091 empathy_chat_window_present_chat (EmpathyChat *chat)
2092 {
2093         EmpathyChatWindow     *window;
2094         EmpathyChatWindowPriv *priv;
2095
2096         g_return_if_fail (EMPATHY_IS_CHAT (chat));
2097
2098         window = chat_window_find_chat (chat);
2099
2100         /* If the chat has no window, create one */
2101         if (window == NULL) {
2102                 window = empathy_chat_window_get_default (empathy_chat_is_room (chat));
2103                 if (!window) {
2104                         window = empathy_chat_window_new ();
2105                 }
2106
2107                 empathy_chat_window_add_chat (window, chat);
2108         }
2109
2110         priv = GET_PRIV (window);
2111         empathy_chat_window_switch_to_chat (window, chat);
2112         empathy_window_present (GTK_WINDOW (priv->dialog), TRUE);
2113
2114         gtk_widget_grab_focus (chat->input_text_view);
2115 }
2116
2117 void
2118 empathy_chat_window_get_nb_chats (EmpathyChatWindow *self,
2119                                guint *nb_rooms,
2120                                guint *nb_private)
2121 {
2122         EmpathyChatWindowPriv *priv = GET_PRIV (self);
2123         GList *l;
2124         guint _nb_rooms = 0, _nb_private = 0;
2125
2126         for (l = priv->chats; l != NULL; l = g_list_next (l)) {
2127                 if (empathy_chat_is_room (EMPATHY_CHAT (l->data)))
2128                         _nb_rooms++;
2129                 else
2130                         _nb_private++;
2131         }
2132
2133         if (nb_rooms != NULL)
2134                 *nb_rooms = _nb_rooms;
2135         if (nb_private != NULL)
2136                 *nb_private = _nb_private;
2137 }