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