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