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