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