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