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